FrontAid CMS - Agile Content Management with JSON & Git.

» FrontAid CMS / Blog

Using Next.js with a JSON File as a CMS

This post will guide you how to use Next.js to generate pages from a JSON file. The example is using content from JSON to enrich an existing static page and to create new pages dynamically.

(We also have a similar post about Nuxt.js and Nuxt Content.)

Let’s begin with a quick introduction in what tools and libraries we will be using for that. Next.js is a framework for server-side rendered or statically generated applications. It combines the Node.js backend with a frontend written in React to a single universal application. FrontAid CMS is a Content Management System (CMS) built on Git that generates a content file in the JSON format. While we will explain how Next.js works with a JSON file that is managed with FrontAid CMS, this whole post is also applicable to other use cases. For example, you can use any other headless CMS or whatever tool you like to emit JSON. As long as you are dealing with Next.js and JSON files, then this post is for you.

What we will do in this post is the following: We are going to load a JSON file to be used with Next.js. Based on its content, we will manually extend static pages and create pages completely dynamically. Thus, we will create a dynamic route handler and navigation. The static page and the dynamic pages will be filled with their respective content. Note that you can find the complete example code on GitHub.

File Structure

The main files and directories that we will use are the following. We have a pages directory that contains the definitions for the web app structure, the index page, and the dynamic pages. The file named frontaid.content.json contains the actual content that we will be using to enrich our pages with. And there is a file called frontaid.model.json which includes the data model that is used with FrontAid CMS. This file is not needed if you don’t use FrontAid (yet). And finally, there is also a package.json file that contains the dependencies and some scripts.

Setup

Now let’s start setting all this up. If you use FrontAid CMS, create a Git repository or use an existing one. Then read Setup & Git Integration to learn how to set it up properly. For the optional FrontAid model, create a file called frontaid.model.json and add the following model content. We put both the JSON content and model files into the root directory, but you can place it wherever you want. The actual content of the frontaid.content.json file will be covered in the next section. For now, just make sure the file exists.

We are using npm and thus create a package.json file with the content below. That defines some scripts that are used with Next.js, and also adds the necessary dependencies. Please note that this specifies the exact dependency versions that we used, but you should probably install the latest versions instead. After you create that file, execute npm install on the command line.

{
  "name": "frontaid-next",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "9.5.3",
    "react": "16.13.1",
    "react-dom": "16.13.1"
  }
}

We are going to load a single JSON file in different places in our example project. For that, we use the standard ES module system like this import content from '../frontaid.content.json'. If you use a different name or path, make sure to change it accordingly for all the following code examples.

Adding Content

Now it gets a little more interesting as we are finally adding actual content. If you are using FrontAid CMS, you can add your own content using the input masks that are generated from the model. You can even extend the model however you like, then commit and push that file to your Git repository. When you are ready, just fill in some example content and create pages using FrontAid, then hit “save” and pull your changes from Git. Your updated content will be available in the frontaid.content.json file (unless you use a different name).

If you don’t use FrontAid CMS (yet), you can update the JSON content file manually. And if you are not creative, you can just add the following example content.

{
  "title": "FrontAid CMS with Next.js",
  "index": {
    "title": "Index Page",
    "content": "<p>Some <strong>index</strong> content...</p>"
  },
  "pages": [
    {
      "title": "Foo Page",
      "path": "/foo",
      "content": "<p>Foo content...</p><h2>Heading</h2><p>Some other content...</p>"
    },
    {
      "title": "Bar Page",
      "path": "/bar",
      "content": "<p><strong>Bar</strong> content...</p>"
    },
    {
      "title": "Baz Page",
      "path": "/baz",
      "content": "<p><strong>Baz</strong> content...</p>"
    }
  ]
}

Static Pages

Now let’s build our first page. Next.js creates pages automatically from the files that are placed in the pages directory. In our example, we have the landing page defined as a static page. Its file name is index.jsx and it contains the code below.

import Head from 'next/head';
import content from '../frontaid.content';

export default function Index() {
  return (
      <>
        <Head>
          <title>{content.title}</title>
        </Head>
        <h1>{content.index.title}</h1>
        <div dangerouslySetInnerHTML={{__html: content.index.content}}></div>
      </>
  );
}

The second import statement loads the (JSON.parsed) content from the JSON file into the content variable. The Head component then picks it up and sets the document title (i.e. the <title> tag) to whatever is defined in the global title property of the content file. In addition to that, the index component also uses index.title and index.content to define its title and main content, respectively.

The heading is defined using the standard JSX syntax with curly braces {}. The content, on the other hand, is using the dangerouslySetInnerHTML attribute. This slightly awkward naming is just React’s way to tell you that you should be sure what you are doing with it as it could lead to cross-site scripting (XSS) attacks. But if you control the content that you pass to this attribute like we do here, it is not dangerous at all. We use it like that, because content.index.content might contain HTML that is supposed to be used and not just escaped in our index page.

Dynamic Pages

While by now we have a static page that is enhanced with dynamic content from a JSON file, we also want to create whole pages dynamically. To do just that, we define a Catch all Route as can be seen below. We name the file […slug].jsx as this is Next.js’ way to make such a generic route. That slug component again imports the content from the JSON file and uses it very similarly as the index page. So every page that we define in the content pages array will be formatted according to this template.

import Head from 'next/head';
import content from '../frontaid.content.json';

export default function Page({page}) {
  return (
      <>
        <Head>
          <title>{page.title} | {content.title}</title>
        </Head>
        <h1>{page.title}</h1>
        <div dangerouslySetInnerHTML={{__html: page.content}}></div>
      </>
  );
}

export async function getStaticPaths() {
  const paths = content.pages.map(page => {
    const slug = page.path.split('/').slice(1);
    return {params: {slug}};
  });
  return {paths, fallback: true};
}

export async function getStaticProps({params}) {
  const currentPath = `/${params.slug.join('/')}`;
  const page = content.pages.find(page => page.path === currentPath) || {notfound: true};
  return {props: {page}};
}

In addition to the component itself, that file also defines two functions:

Note that the version of Next.js that we used seems to have a bug that shows up during the build. So if you run into “Error occurred prerendering page “/[…slug]" or similar, it is likely because of that. See the […slug].jsx file on GitHub how we solved that. We omitted it in our code example above as we hope this problem will have disappeared by the time you read this.

With both the dynamic routes and the static page set up, Next.js already creates all the necessary files. But what is still missing is a navigation that lists all our pages.

PS: If you want to implement a more elaborate page structure, for example a blog, you can have a look at Next.js’ Dynamic Routes section.

Page Setup and Navigation

In this section we are going to add a global navigation to all the pages that we create. Think of it as a sitemap that lists and links to all the pages. To do that, we will add a Custom App component. Create the file _app.jsx in the pages directory and add the code below. This component is used as to inizialize pages and to add the navigation to them.

import Link from 'next/link';
import React from 'react';
import content from '../frontaid.content';

export default function MyApp({Component, pageProps}) {
  return (
      <>
        <nav>
          <ul>
            <li><Link href="/"><a>{content.index.title}</a></Link></li>
            {content.pages.map(page =>
                <li key={page.path}>
                  <Link href="[...slug]" as={page.path}><a>{page.title}</a></Link>
                </li>,
            )}
          </ul>
        </nav>
        <main>
          <Component {...pageProps} />
        </main>
      </>
  );
}

The content from our JSON is imported again. And as you can see, a custom navigation is added. The first list item is just the static index page for which we use the value of content.index.title. Then we add a further list item for every dynamic page that is found in content.pages. Each one of them uses it’s path and title. This component as a whole is used as the template for all our pages. Thus it also shows the navigation on each of them.

Note that we used <Link href="[...slug]" as={page.path}> instead of just <Link href={page.path}>. Usually, you do not need to do that. But in this case, Next.js complained about “Unknown key passed via urlObject into url.format: searchParams” and thus we used the more verbose version of it. See the corresponding bug report for more details.

And now that we have everything set up, you can update the JSON content file however you want. For example, you can update the project title or add new dynamic pages. And thanks to Next.js, everything will be adapted accordingly. This is a very simple way to create dynamic applications based on JSON as a data source. We are essentially using a JSON file as a CMS. And if you are using FrontAid CMS, you or your copy texters can manage that file easily.