Update: 25 November 2020
I have now removed Netlify CMS from my site because I realised it was overkill for a site I only update a few times a year. The information below still stands as a semi-useful tutorial.
As part of rebuilding my website to use Next.js and Netlify CMS, I wanted to reuse components I made for Next.js as the editor preview in Netlify CMS. Unfortunately for me, I couldn't find any resources about how to do so.
After lots of trial and error, I found a way to initialize the CMS as part of a Next.js page, so I was able to import other components to use as the preview. Here's how I did it.
Disclaimer
I am not claiming that this is the absolute best way to do this. It is simply the method I discovered. One particular thing to note is that the CMS can take a few seconds to load, since it only starts loading after the first render.
I'm going to assume that you have a Next.js site set up and that the CMS is set
up to be available at /admin
using the <script>
tag method.
First of all, you'll want to remove the page that's currently hosting the CMS
(/public/admin/index.html
). Then, move the Netlify CMS config.yml
to the top
level of the public
directory.
Next, install the
netlify-cms-app
package.
We're going to use this to initialise and customise the CMS.
yarn add netlify-cms-app
Now create a new admin page in Next.js by creating a /pages/admin.js
file. To
start with, just put a blank component in there so that the page loads but
doesn't display anything.
JSXconst Admin = () => <div />; export default Admin;
To actually load and display the CMS, we need to import the netlify-cms-app
module. But here's the issue: as soon as the module is imported, it'll try to
access the global window
object. That won't work because Next.js will try to
prerender or server-side render the page, so window
will be undefined and the
page will crash.
Instead, we'll use a dynamic import to only import the module once we know
window
is defined. We'll do that using the React useEffect
hook and an
inline anonymous async
function (since dynamic imports are asynchronous).
JSXimport { useEffect } from 'react'; const Admin = () => { useEffect(() => { (async () => { const CMS = (await import('netlify-cms-app')).default; CMS.init(); })(); }, []); return <div />; };
To break this down a bit:
- We use
useEffect
since it will run the code after the first render, whenwindow
is defined. - We define an anonymous
async
function and then call it immediately. This is becauseuseEffect
doesn't allow the callback itself to be asynchronous. - We
await
the import, and then access the default export of the module; that's the CMS object we need. - Once the module has been imported, we initialise the CMS by calling
init()
. This loads the CMS and causes it to replace the current page (which was blank). - The dependencies of
useEffect
are an empty array[]
because this should only be run once, after the first render.
At this point opening /admin
in the browser should display the Netlify CMS. If
you get an error about not being able to find the config, make sure you moved
/public/admin/config.yml
up to /public/config.yml
.
So far all we have achieved is replicating the existing setup. Let's move on to actually creating an editor preview.
The important function we need is
CMS.registerPreviewTemplate
.
This allows us to register a React component for rendering the preview for a
given collection in Netlify CMS.
The CMS will pass a number of props to this component, but we're only interested
one for now: entry
. This is an
Immutable.js Map
containing all the data about the entry. It will contain a key/value pair for
each field defined for the collection.
Since I assume you already know how you want to render a preview, I'm going to
assume that you have a collection called article
and you've already created a
component called ArticlePreview
at /components/ArticlePreview.js
. You'll
need to update this component to accept the entry
prop and extract the metdata
from it.
Assuming an article
has three fields – title
, date
, and body
– here's
how the component would extract the data to render it:
JSXconst ArticlePreview = ({ entry }) => { const title = entry.getIn(['data', 'title']); const date = entry.getIn(['data', 'date']); const body = entry.getIn(['data', 'body']); return <div>{/* Render the preview here... */}</div>; };
Here's how to register that component as the article preview:
JSXimport { useEffect } from 'react'; import ArticlePreview from '../components/ArticlePreview'; const Admin = () => { useEffect(() => { (async () => { const CMS = (await import('netlify-cms-app')).default; CMS.init(); CMS.registerPreviewTemplate('article', ArticlePreview); })(); }, []); return <div />; };
All interaction with the CMS
object has to be done in the async
function,
otherwise it will try to execute before the CMS is initialised.
At this point, opening an entry in the CMS should display using the preview
component you defined and registered! I'd recommend reading up on
registerPreviewTemplate
to see what other props it passes, if you need to make more complex previews.
For further reference, my admin page is
here, and
my preview component is
here.
Note my preview component is just a wrapper around the actual
PostPage
that I use to render posts on the real site (I like this approach because the
preview will always match the final page).