Go Headless with Netlify CMS
Jhey Tompkins
Posted on March 31, 2021
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
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
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>
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()
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
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" }
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.
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.
Now we need to enable "Identity" for our site. Navigate to the "Identity" tab and hit "Enable Identity".
Go into "Settings and usage" and hit "Enable Git Gateway". It's a bit of scrolling down.
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.
Once you've done that, you'll see the user in the 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/'
})
}
})
}
And this script included.
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
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>
)
}
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
Once installed, login with
netlify login
And then from within your repo directory, link your repo to your site with
netlify link
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
When you're happy with what you're deploying.
netlify build
netlify deploy --prod
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.
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".
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>
)
}
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"
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",
}
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.
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()
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"
}
}
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! ʕ •ᴥ•ʔ
Posted on March 31, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.