Content Preview for your Contentful-Gatsby site with Next.js

Blog preview gatsby next js 01

We build a number of our Contentful sites with Gatsby, and as a result most of our sites have a build time (30s to 4m); we’ve implemented some work arounds that allow clients to preview content in the production build. This however has a wait time, as originally pointed out by Work and Co. We opted for another route that would allow for instant previews, and not require the client to publish articles before they were ready.

This was even more important for Supercluster. We built them a very modular article templates that allowed for a diverse editorial experience. We also created an image module that could be rendered in various unique ways on the screen. This required the client to essentially build/test the article and make sure images/text never overlapped depending on the content modules. We also used the Contentful Rich Text editor to inline modules and make the editing experience even smoother.

Enter Next.js

We needed a quick way to replicate the current production code in a preview environment. Since we were using Gatsby the frontend language was built in React. As a result it was a sort of no brainer to pick Next.js as the framework for rendering our dynamic previews.

Gotchas —

  • Next.js doesn’t do shared components from other projects very well
  • We’re using PostCSS in our Gatsby build so importing and compiling that in Next isn’t something supported out of the box
  • Because we’re copy and pasting the Gatsby build into Next.js we’ll also need to install all the dependencies in the Gatsby app (at least for the template we’re previewing)
  • We’ll want to run a server so we can serve a robots.txt disallow
  • Gatsby { Link } is trouble… more on that later

Fixing the shared components… I spent the better part of ~5 hours troubleshooting this, and eventually caved on a simpler solution that wouldn’t require things like symlinking/transpiling external modules with complex configurations. The solution? Copy your Gatsby directory into your next app before you build it so you can always access the most up-to-date shared components from the parent project.

Getting our styles out of Gatsby also proved relatively difficult. Getting PostCSS to work in Next.js is its own can of worms. As a result I opted to handle all the PostCSS work in the 'package.json' file. Depending on the architecture of your project you may not run into this issue. I’ll share my 'package.json' scripts for this project below regardless:

1
2
3
4
5
6
7
8
"scripts": {
    "dev": "next",
    "copy": "npm run build:css; rm -rf gatsby/; cp -R ../src gatsby",
    "copy-fonts": "cp -R ../static static",
    "build": "next build",
    "build:css": "postcss gatsby/styles/main.css -o static/main.css",
    "start": "next start"
  },

Keep in mind, while developing you could run a prestart script as well, but you’ll not want that in the now production because it will run into trouble finding the parent directory!

Now we can finally build our preview page component. I’m going to show you the whole component and then explain what’s going on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React from 'react'
import Head from 'next/head'
import contentfulAPI from '../api/contentful'

import Article from '../gatsby/templates/article.js'

export default class extends React.Component {
  static async getInitialProps ({ query }) {
    const response = await contentfulAPI.getEntries({
      content_type: 'article',
      'sys.id': query.id,
      include: 8
    })
    return {
      article: response.items[0]
    }
  }

  render () {
    const {
      title,
      tags
    } = this.props.article.fields
    const context = {
      data: this.props.article.fields,
      title: title,
      tags: tags
    }
    return (
      <div>
        <Head>
          <link href='/static/main.css' rel='stylesheet' />
        </Head>
        <Article pageContext={context} />
      </div>
    )
  }
}

Those of you familiar with Next.js shouldn’t see anything that unusual here. We’re importing our article from our copied Gatsby template file. We’re also including the compiled CSS from our build task above in the header.

The only real work going on here is the query to get the article based on the id. We’re not including things like layout/header/footer modules as we don’t need them for the preview.

To get this hooked up you have 2 options, the first being ngrok for local development, and the second being a now deployment. Once you have an endpoint set up we’ll want to go into Contentful. 'Settings->Content Preview' from there you can specify the content type you want to be able to preview, in this case 'Articles' and set the url you’ll want to handle the preview.

e.g. http://site-preview.now.sh/preview?id={entry.sys.id}

Once that’s hooked up you should now be able to preview articles from Contentful to your local/deployed environment.

Additional Gotchas — Data Structure

There’s always something else right? So in my case I actually don’t use the gatsby-contentful-source plugin. I ran into a lot of problems with modular nested content and it would just continuously cause build fails depending on the initial article that was queried to build the schema. So I rolled my own source plugin for Contentful. This allowed me to fetch the Contentful data on my own, and as a result I simply handled the data in a large JSON response. This actually benefited me in the long run because the above app has the same data structure. If you end up using the default source plugin you will not be able to simply reference the article.js template like I have. This is because the GraphQL data structure is so different from the Contentful Preview API response.

Robots.txt

Because this is a preview server, we’ll want to add a 'robots.txt' file, so the crawlers don’t ever index this content unnecessarily. Since we’re using Next.js this is pretty easy. We’ll have to set up a 'server.js' file and populate it as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const path = require('path')
const express = require('express')
const next = require('next')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

const options = {
  root: path.join(__dirname, '/raw'),
  headers: {
    'Content-Type': 'text/plain;charset=UTF-8'
  }
}

app.prepare().then(() => {
  const server = express()

  server.get('/robots.txt', (req, res) => (
    res.status(200).sendFile('robots.txt', options)
  ))

  server.get('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(port, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${port}`)
  })
})

You just need to create a robots.txt file in a directory within your app, in my case I made one in a folder called 'raw'.

If you’re using this in any of your components that you are previewing you’ll encounter errors. Gatsby Link mounts a static query that Next.js isn’t a big fan of. So we’re gonna use replace to update all the instances of Link in our components after we copy them over…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const replace = require('replace')

replace({
  regex: "import { Link } from 'gatsby'",
  replacement: '',
  paths: ['./gatsby/'],
  recursive: true,
  silent: true
})

replace({
  regex: "<Link to=",
  replacement: '<a href=',
  paths: ['./gatsby/'],
  recursive: true,
  silent: false
})

replace({
  regex: "</Link>",
  replacement: '</a>',
  paths: ['./gatsby/'],
  recursive: true,
  silent: false
})

replace({
  regex: "<Link ",
  replacement: '<a ',
  paths: ['./gatsby/'],
  recursive: true,
  silent: false
})

replace({
  regex: "to=",
  replacement: 'href=',
  paths: ['./gatsby/'],
  recursive: true,
  silent: false
})

It’s just one final gotcha. If you have any other variations feel free to add them, and then just add another script to package.json like 'node replace.js'.

Last Thoughts

The hardest part of this was importing the components and styles out of the Gatsby app into Next.js. I also tried to do this with react-create-app but that also doesn’t like importing from parent directories. If you have a larger organization you could potentially leverage npm link and host private repos for your shared components. I however didn’t want to add any additional levels of technical debt to this project, so I kept everything in the build process documented in the 'package.json'.

Also choosing Next.js meant that we could quickly deploy to services like now and create alias now domains without needing to set up additional subdomains for the client.

This article was originally posted on medium. Read it here.

Updates in your inbox

Subscribe to receive the most important updates. We send eNewsletters once a month that cover trending topics and feature updates.