February 21, 2021 · 6 min read · 5,535 views
Static websites, a combination of HTML, CSS, and JavaScript, are known for their speed and efficiency. They don't require a backend or database, as all files are pre-deployed and ready for the user without runtime manipulation. If you're interested in learning more about static websites, check out our article explaining the difference between static sites and database-powered ones.
However, when working with clients, you may need to provide a level of customizability that allows them to easily make changes to a dynamic site. For simple websites, we often use Netlify CMS to let the client control the website content. In this tutorial, we'll guide you on how to build a dynamic page builder using Netlify CMS and Next.js.
We chose Next.js for this tutorial because of the numerous advantages it offers, such as file-based routing and SEO benefits when deploying to production. If you're new to Next.js, you can learn more about it on our Next.js development page.
First, let's create a new Next.js project using the following command:
npx create-next-app demo-netlify
After the installation has been completed we also need the following packages for Netlify CMS:
npm install netlify-cms-app
After this we can run our dev server with the following command npm run dev
In the root folder, create a new folder called cms
and within it, create a config.js
file to store the Netlify CMS configuration. Although Netlify CMS offers the ability to make a YAML config file, we find JavaScript more convenient for our development.
Here's what your config file should contain for now:
module.exports = {
// We want to manually init the config file
cms_manual_init: true,
// Backend configuration, in this case with git
backend: {
name: "git-gateway",
branch: "master",
squash_merges: true,
},
// Local backend is used during development
local_backend: true,
// Where to store the images
media_folder: "public/images/",
// Where to link the images
public_folder: "public/images/",
// The Pages collection
collections: [
{
name: "Pages",
label: "Page",
editor: { preview: false },
label_singular: "Page",
folder: "content/pages",
create: true,
slug: "{{slug}}",
extension: "md",
format: "yaml-frontmatter",
fields: [
{
label: "Title",
name: "title",
widget: "string",
required: true
},
],
},
],
};
The comments in the code explain the default config values needed for development. For our setup, we only need one collection called pages
. We specify where we want to save the Markdown files, in this case, the content/pages
folder. For now, we only have one field, named Title
. We'll return to this file later.
Next, go to your pages
folder and create an admin.js
file. Here, we make use of the Next.js dynamic component to load this page (JavaScript) on demand and only when it's needed. This helps us optimize our app.
import dynamic from "next/dynamic";
import Head from "next/head";
import config from "../cms/config";
const CMS = dynamic(
() =>
import("netlify-cms-app").then((cms) => {
cms.init({ config });
}),
{
ssr: false,
loading: () => <h1>Loading</h1>,
}
);
const AdminPage = () => {
return (
<>
<Head>
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</Head>
<CMS />
</>
);
};
export default AdminPage;
Inside the closure, we import the netlify-cms-app
that we installed earlier and then we initialize the config that we also created earlier.
To test if everything is working, we need to use a beta feature from Netlify CMS to spin up a proxy server for our backend. This allows us to test locally without having to test directly with a live repository. Run this command in a new tab/window in your command line:
npx netlify-cms-proxy-server
If everything is right you should visit http://localhost:3000/admin
and you should be able to view the Netlify CMS
page which looks something like this:
Now that our setup is ready we need to get back to the config
file so we can add our dynamic builder to the pages collection
.
In order to save some space I will only be showing the content of the collection key, which should be in your config.js
file.
collections: [
{
name: "Pages",
label: "Page",
editor: { preview: false },
label_singular: "Page",
folder: "content/pages",
create: true,
slug: "{{slug}}",
extension: "md",
format: "yaml-frontmatter",
fields: [
{
label: "Title",
name: "title",
widget: "string",
required: true,
},
{
label: "Builder",
name: "builder",
widget: "list",
types: [
{
label: "Header Image",
name: "header",
widget: "object",
fields: [
{
label: "Title",
name: "title",
widget: "string",
required: true,
},
{
label: "Background Image",
name: "photo",
widget: "image",
required: true,
media_library: { config: { multiple: false } },
},
],
},
{
label: "CTA Section",
name: "cta",
widget: "object",
fields: [
{
label: "Title",
name: "title",
widget: "string",
required: true,
},
{
label: "Link",
name: "link",
widget: "string",
},
],
},
{
label: "Content",
name: "content",
widget: "object",
fields: [
{
name: "Content",
widget: "markdown",
required: true,
},
],
},
],
},
],
},
],
To make our dynamic content work, we'll use a beta feature of Netlify CMS called Variable List Types. This feature allows us to define a list of types to build our page builder. In our case, we have three types: Header Image, CTA Section, and Content. You can add as many of these as you want, or order them in any way you like, just like with any other traditional page builder. For this example we went with minimal configuration, but you can add more complex widget types which you can check on the Netlify CMS documentation.
Once you've set everything up correctly, adding a new Page should present you with a Builder section where you can add different sections to your home page. After saving, this will trigger Netlify CMS to create a new file called home.md
under content/pages/home.md
as defined in our configuration file.
Now that we have the content, we need to parse the MD file so we can use it in our frontend. To parse our markdown, we will use gray-matter
, which can be installed with the following command:
npm install --save gray-matter
To load content to our index.js
, we will use the Next.js function called getStaticProps which will pass down as a prop to the MD file, and we will parse that with gray-matter
.
Before that, we will create a new component under components/Builder.js
so we can map each type from the CMS to a React Component. Here's a sample of how to create functional components for each of our types that we defined in Netlify CMS:
function BackgroundImage({ item }) {
return (
<div style={{ backgroundImage: `url('${item.photo}')`, height: "200px" }}>
<h1 style={{ color: "white" }}>{item.title}</h1>
</div>
);
}
function Content({ item }) {
return <div>{item.content}</div>;
}
function Cta({ item }) {
return <a href={item.link}>{item.title}</a>;
}
const components = {
header: BackgroundImage,
content: Content,
cta: Cta,
};
export default function Builder(props) {
const Component = components[props.type];
return <Component item={props.item} />;
}
What we have here is three functional components in our Builder component for each of our types that we did previously in Netlify CMS. We have a component for the Header Image which I have called BackgroundImage, one for Content and the other one for Cta.
After setting up our Builder component, we can now call this in our index.js
file. Here's how:
import fs from "fs";
import { join } from "path";
import matter from "gray-matter";
import Builder from "../components/Builder";
export default function Home({ home }) {
return home.builder.map((item, index) => {
return <Builder key={index} type={item.type} item={item} />;
});
}
function getBySlug(dir, slug) {
const realSlug = slug.replace(/\.md$/, "");
const fullPath = join(dir, `${realSlug}.md`);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { data } = matter(fileContents);
return data;
}
export async function getStaticProps() {
const home = getBySlug("content/pages", "home");
return {
props: {
home,
},
};
}
As mentioned earlier, we used the Next.js function called getStaticProps
to load the data of home
as a prop to the component. We also made a small helper function called getBySlug
so we can fetch only the file needed for the homepage.
After passing it down to the component as a prop, we need to map over our builder, which is an array of sets that we defined in our CMS, and pass it down to our Builder component. If everything is set up correctly, you should see the homepage populated with the data from the CMS.
This is just the basic version of what can be archived with Netlify CMS as a backend, and Next.js for the frontend. From this you can extend to adding more features which we will list below:
Add dynamic routing for the other pages other than home
Add more components to the builder
Refactor Builder to be used with Next.js dynamic import
The repo for this small demo can be found on our Github repo.
If you need help with a Laravel project let's get in touch.
Lucky Media is proud to be recognized as a Top Next.js Development Agency
Technologies:
Related Posts
Stay up to date
Be updated with all news, products and tips we share!