Go Headless with Netlify CMS

jh3y

Jhey Tompkins

Posted on March 31, 2021

Go Headless with Netlify CMS

Got a site? How easy is it to make content changes? Are you using a CMS? What's a CMS? How do I set up a CMS?

After this article, you'll know how to get up and running with Netlify CMS. And you'll know some tricks to make your life easier.

  • Set up Netlify CMS with Next.js
  • Use netlify-cli
  • Make use of Netlify CMS Beta features!

What's a CMS?

A content management system(CMS) is a convenient way for you to manage content. Think of it as a special place you visit that allows you to update your website. Update images, copy, content! It can live on the same domain or a completely different one.

What's a "headless" CMS? This means that our CMS is not coupled to a front end. It has no notion of how the content will get displayed. This is ideal. It gives portability to our content. Want to switch front-end frameworks? Cool! Drop all your content into something new. The underlying structure of what you're presenting doesn't have to change.

Why CMS?

We mentioned it there. But, "Portability". Decoupling your content from the presentation layer has the benefit of "Create once, display anywhere!". Also, "Convenience". Realized you made a typo on your site? But, you're not at your desk? Log into your CMS, make the change, hit "Publish", done! It also opens your content up for a more collaborative experience. Want to support different authors or guest posting? Create separate logins for those users.

Netlify CMS

Netlify CMS is a great option if you're new to CMS and even if you're not. It's an interesting offering that is "Git Based". That means it creates and updates the content in your Git repo. This is great if you're new to CMSs as you have the ability to see the content changes on your machine as you develop. It's very customizable too. Think custom widgets and previews.

Setting Up Netlify CMS

For today's demo, we're going to set up Netlify CMS with a Next.js site. The set up is the same though for whatever you use. If you don't have an app to use, follow along and create a Next.js site.

The prerequisites?

  • Github/Gitlab/Bitbucket account
  • Netlify account
  • Optional – An app to integrate. Or create a demo app from the steps below.

For those in camp TL;DR , you can grab the demo app and starter kit from this repo.

Create App

Let's get started by creating an app.

yarn create next-app netlify-cms-next-starter
Enter fullscreen mode Exit fullscreen mode

And for this app, I've gone ahead and pulled Tailwind in with the new shiny "JIT" compiler.

Install Netlify CMS

Then we need netlify-cms-app.

yarn add netlify-cms-app
Enter fullscreen mode Exit fullscreen mode

Set Up Admin Page

Next, we need an "admin" page. This needs to be accessible via /admin. Different frameworks have different ways of doing this. We're using Next.js. We could drop an HTML file into public at public/admin/index.html or create pages/admin/index.js.

The straight HTML would use the CDN links for Netlify CMS and looks like this.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Admin: Netlify CMS && Next.js Starter</title>
    <link rel="shortcut icon" href="/assets/icons/favicon-32x32.png" />
    <link rel="apple-touch-icon" href="/assets/icons/logo-192x192.png" />
    <link rel="manifest" href="/manifest.webmanifest" />
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
  </head>
  <body>
    <!-- Include the script that builds the page and powers Netlify CMS -->
    <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

But, we've installed netlify-cms-app. That means we can start up the CMS from a module.

import CMS from 'netlify-cms-app'
// Initialize the CMS object
CMS.init()
Enter fullscreen mode Exit fullscreen mode

We're using Next.js though. And that means we can do something a little different with our setup.

import dynamic from 'next/dynamic'

const CMS_CONFIG = {}
const Loading = () => (
  <div className="min-h-screen flex items-center justify-center">
    <p className="text-gray-500 font-semibold text-xl">Loading...</p>
  </div>
)

const CMS = dynamic(
  () =>
    import('netlify-cms-app').then((CMS) => {
      CMS.init({ CMS_CONFIG })
    }),
  { ssr: false, loading: Loading }
)

const Admin = () => <CMS />

export default Admin
Enter fullscreen mode Exit fullscreen mode

Here, we load the netlify-cms-app dynamically into the browser with next/dynamic and then initialize the CMS.

Set Up CMS Config

The CMS is config-driven. We create a configuration file and this tells Netlify CMS what fields to show, what to update, etc. We need a config.yml file that our "Admin" page has access to. There are a variety of options for configuring the CMS. Here's a basic starting point. In this config, we're creating two collections. One handles global data such as site copy for example. The other is for standalone pages. This could be the same structure for creating blog posts, etc.

backend:
  name: git-gateway
  branch: main
publish_mode: editorial_workflow
media_folder: "public/images"
public_folder: "/images"
collections:
  - label: "Globals"
    name: "globals"
    files:
      - label: "Site Copy"
        name: "site_copy"
        delete: false
        create: true
        file: "_data/copy.json"
        fields:
          - { label: "Tagline", name: "tagline", widget: "string" }
          - { label: "Headline", name: "headline", widget: "string" }
  - name: "standalone pages"
    label: "Standalone Pages"
    folder: "standalone-pages"
    slug: ""
    file: "standalone-pages/.mdx"
    create: true
    extension: mdx
    format: frontmatter
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - {
          label: "SEO Meta Title",
          name: "metaTitle",
          widget: "string",
          required: false,
        }
      - {
          label: "SEO Meta Description",
          name: "metaDesc",
          widget: "string",
          required: false,
        }
      - {
          label: "Social Image",
          name: "socialImage",
          widget: "image",
          required: false,
        }
      - { label: "Body", name: "body", widget: "markdown" }
Enter fullscreen mode Exit fullscreen mode

We've got so many options for setting up Netlify CMS. The main things here are the "backend", "media_folder", and how our collections work.

When it comes to collections, we can define the types of files, where they live, how we create the content. For example, our standalone page collection. We are stating that the files will have the MDX extension and live under standalone-pages/. Each field under fields makes up the managed content for a page. In our example, we're using the string widget for text fields. But, the body of the file will be markdown. Note the format field on our collection? This tells Netlify CMS that we want to store the data in frontmatter and follow it with the content for body. It's worth checking out the "Configuration" docs and "Fields" docs.

Creating An Identity

If we start up our app and visit /admin we're hit with a login screen. The CMS is working! But, we don't have any credentials to log in with.

Blank Netlify CMS login screen

We need an identity for authentication. You can use different auth options. But, we're going to use Netlify Identity for auth. It's the quickest to set up and will give you exposure to using Netlify Identity if you've not used it before.

Open up your Netlify dashboard and you'll want to create a "New site from Git". That's assuming you've pushed your app to a Git repository.

This will take you through your site setup. Follow the prompts and Netlify will detect your build settings for you. Note how in our case, we get the neat "Essential Next.js" build plugin installed for us.

New site set up on Netlify

Now we need to enable "Identity" for our site. Navigate to the "Identity" tab and hit "Enable Identity".

Enabling Identity on Netlify dashboard

Go into "Settings and usage" and hit "Enable Git Gateway". It's a bit of scrolling down.

Enabling Git Gateway

We are almost there! Now we need to invite a user to use our CMS. Hit "Invite users" and send an invite to an email address you want to use.

Inviting users

Once you've done that, you'll see the user in the list.

Updated users list

Check your email and you should see one inviting you to join the app. But, if you hit the link, you'll get directed to your hosted site and nothing will happen. That's because we need to drop the identity widget into the page and run a piece of code for it.

This only needs to doing on our root page. We need the following code to run.

if (window.netlifyIdentity) {
  window.netlifyIdentity.on('init', (user) => {
    if (!user) {
      window.netlifyIdentity.on('login', () => {
        document.location.href = '/admin/'
      })
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

And this script included.

<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
Enter fullscreen mode Exit fullscreen mode

For our Next.js app, we could update our "Home" page to run the code in an effect and use next/head to include the script.

import { useEffect, Fragment } from 'react'
import Head from 'next/head'

const Home = () => {
  useEffect(() => {
    if (window.netlifyIdentity) {
      window.netlifyIdentity.on('init', (user) => {
        if (!user) {
          window.netlifyIdentity.on('login', () => {
            document.location.href = '/admin/'
          })
        }
      })
    }
  }, [])
  return (
    <Fragment>
      <Head>
        <title>Netlify CMS && Next.js Starter</title>
        <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
      </Head>
      <main className="min-h-screen flex items-center justify-center mx-auto flex-col prose">
        <h1 className="text-center">Netlify CMS && Next.js Starter</h1>
      </main>
    </Fragment>
  )
}
Enter fullscreen mode Exit fullscreen mode

Re-Deploy

For the changes to take effect, we need to re-deploy our site. We can do this by pushing our changes up to our git repo. Netlify will detect the changes and re-deploy for us.

Or, we could use netlify-cli to re-deploy our site. I recommend making use of netlify-cli. It can improve your workflow a lot. Especially if you've already got your terminal open. To use netlify-cli, install it first.

npm i -g netlify-cli
Enter fullscreen mode Exit fullscreen mode

Once installed, login with

netlify login
Enter fullscreen mode Exit fullscreen mode

And then from within your repo directory, link your repo to your site with

netlify link
Enter fullscreen mode Exit fullscreen mode

Now you can build and deploy from the command line. Your build settings including plugins get pulled down for you too. It's a great way to get a deploy preview without opening a pull request too.

netlify build
netlify deploy
Enter fullscreen mode Exit fullscreen mode

When you're happy with what you're deploying.

netlify build
netlify deploy --prod
Enter fullscreen mode Exit fullscreen mode

Access the CMS

Once that's deployed, use your invite link from the email and you'll see a form to "Complete your signup". Create a password and you'll get logged in.

Complete sign up form

Now visit /admin and you'll get prompted for your CMS login details. Log in and we're in! We've set up a CMS for your site with Netlify CMS.

Update Content

Now we're in the CMS, have a poke around and explore what you can do. For example, upload an image or change the content in one of your collections. Here I've changed the content under a "Tagline" field in our "Globals" collection. When we're ready to push the changes, we hit "Save", update the status to "Ready", and then hit "Publish".

Publishing copy changes

Next, it's a waiting game. Wait for the changes to get pushed. Pull them down to your repo in your terminal and you'll see that in our case _data/copy.json has updated. This was the path we defined in our config.yml file.

Now you can integrate your content any way you like with your frontend. For example, in our demo app, I've added an import path for _data. And I've updated the "Home" page to display the headline and tagline.

import { useEffect, Fragment } from 'react'
import Head from 'next/head'
import copy from '@data/copy.json'

const Home = () => {
  useEffect(() => {...}, [])
  return (
    <Fragment>
      <Head>
        <title>Netlify CMS && Next.js</title>
        <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
      </Head>
      <main className="min-h-screen flex items-center justify-center mx-auto flex-col prose">
        <h1 className="text-center">{copy.headline}</h1>
        <h2>{copy.tagline}</h2>
      </main>
    </Fragment>
  )
}
Enter fullscreen mode Exit fullscreen mode

At this stage, you've got a working CMS for your app. Explore the docs and manage your content any way you like. But, we're not quite done yet. Let's improve this setup.

Local Backend

We've got a working CMS. But, the workflow could be slow. Especially if we're feeling out what we're going to put in our CMS. We haven't got time to keep deploying, making changes in the CMS, and waiting for things to get pushed to Git. Builds take time and they'll use up your build minutes on Netlify.

To get around this we're going to use a Beta feature, local_backend. In fact, if you've been using Netlify CMS already, this trick might help you out a bunch if you didn't know about it.

To use local_backend, add it to our config.yml file.

local_backend: true
backend:
  name: git-gateway
  branch: main
publish_mode: editorial_workflow
media_folder: "public/images"
public_folder: "/images"
Enter fullscreen mode Exit fullscreen mode

And then we have one more step. We need to use netlify-cms-proxy-server when we are developing. We can update our dev script to accommodate this.

"scripts": {
  "dev": "npx netlify-cms-proxy-server & next dev",
}
Enter fullscreen mode Exit fullscreen mode

Run our dev server and visit /admin. We're in! No log-in screen required. Make some content changes, hit publish, and the content gets updated in your directory. Awesome. This improves our workflow tenfold. No more waiting.

Netlify CMS locally

This is all possible because of netlify-cms-proxy-server which creates a local unauthenticated server. This is what the CMS runs on when we use local_backend. You can configure local_backend in different ways. And you don't have to worry if you deploy a config.yml file with local_backend on.

But, if that doesn't sit right with you, you can write a node script that you run when running your dev server.

Here's a script you can use that copies your config.yml to the public directory on change. It uses chokidar to watch the file changes and logs any activity with pino.

const chokidar = require('chokidar')
const logger = require('pino')({ prettyPrint: true })
const fs = require('fs')

// Write file to public/config.yml
const writeConfig = () => {
  logger.info('Updating Netlify CMS Config')
  const CONFIG = fs.readFileSync('./admin/config.yml', 'utf-8')
  fs.writeFileSync(
    `${process.cwd()}/public/config.yml`,
    `local_backend: true\n${CONFIG}`,
    'utf-8'
  )
}
// Set up Netlify CMS Config Watching
logger.info('Setting up Netlify CMS config watch')
chokidar.watch(`${process.cwd()}/admin/config.yml`).on('change', writeConfig)
// Write on script run so it's there
writeConfig()
Enter fullscreen mode Exit fullscreen mode

In this script, we are copying the config and appending the local_backend option. This means we no longer need a committed version of config.yml inside public. In the demo app, I've moved it into an admin directory along with the watch script.

To have that run at dev time, we can update our package.json script to run this alongside netlify-cms-proxy-server.

{
  "scripts": {
    "dev": "npx netlify-cms-proxy-server & next dev & node admin/config-watcher.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

That's It!

That's how to get up and running with a "Headless" CMS. In this case, Netlify CMS. It's a neat solution and a great entry point if you've not set up a CMS for yourself before.

I'd recommend setting up a CMS for your sites. Especially your personal portfolio sites. How about trying Netlify CMS? Get set up, have a look around the docs, customize it to your needs!

Want to try it out with Next.js? Check out the demo repository that's set up with Tailwind and use that as a starting point.

As always, thanks for reading. Wanna know more? Wanna see something else? Come find me on Twitter!

Stay awesome! ʕ •ᴥ•ʔ

💖 💪 🙅 🚩
jh3y
Jhey Tompkins

Posted on March 31, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related