Page builder with Netlify CMS and Next.js

lokman musliu
Lokman Musliu

February 21, 2021 · 6 min read · 5,486 views

NetlifyCMS and Next.js Blogpost Image

Building Dynamic Websites with Netlify CMS and Next.js

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.

Why Next.js for website development?

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.

Getting Started with Next.js and Netlify CMS

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

Setting Up Netlify CMS Configuration

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.

Creating an Admin Page

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:

Create Pages Image

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,
                },
              ],
            },
          ],
        },
      ],
    },
  ],

Leveraging Netlify CMS's Variable List Types

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.

Creating and Saving Your Page

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.

Builder Image

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.

Mapping CMS Types to React Components

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.

Integrating the Builder Component with Your Index.js File

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.

Viewing Your Dynamic Page

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.

Extending Your Page Builder

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.


Bring Your Ideas to Life 🚀

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

lokman musliu
Lokman Musliu

Founder and CEO of Lucky Media

Technologies:

Next.js
React
Heading Pattern

Related Posts

Stay up to date

Be updated with all news, products and tips we share!