CraftCMS - Enabling Live Preview With Gatsby.js

Tanveer Karim
Feb 18, 2020

Craft CMS and Gatsby.js complement each other wonderfully. Together they offer the speedy performance of a static site generator with the power and flexibility of a headless CMS. Unfortunately, when using these two products together, we lose out on the useful Preview feature. But fret not! There is an easy way to re-enable live preview.

Link

How live preview works underneath

The preview is simply an iframe that points to a preview page. As you edit your content, the iframe reloads and displays the changes. Out of the box, only pages created within Craft CMS will show changes as you edit them. Fortunately, Craft provides an easy to use mechanism that allows third party service to hook into the preview mechanism.

When editing an entry, Craft CMS will generate a token to associate with the draft content. This token can be used to query for the draft content data. When using the preview feature, the token is automatically added to the iframe URL as a query parameter name token. Gatsby.js can use this token to fetch the draft data and re-render the preview page.

<iframe src="https://localhost:8000?token=abcdExampleTokenZxy"></iframe>
Link

Before we begin...

This guide will assume that Gatsby.js has been connected to a Craft CMS instance via the gatsby-source-graphql plugin. And, pages in Gatsby should be created using the same entry URI provided by Craft CMS.

// If Craft entry URI is /blog/{slug} 
// Gatsby pages should be created using the same path
action.createPages({
  path: `/blog/${slug}`,
  ...
});

And finally, we are going to assume you are using the standard page queries to fetch your data. This guide will probably not work if you are using a different querying method. For example: you perform your queries in gatsby-node.js and pass the data via page context.

// Page should be using standard page queries
export const HomePage = () => {
  ...
};
export const query = graphql`
  query HomePageQuery {...}
`;
Link

Configuring Craft CMS for live preview

First, edit a section in Craft by going to Settings > Sections > SECTION_NAME. On this edit screen, find the settings for Preview Targets and change the URL Format to point to your local Gatsby development server - https://localhost:8000/{uri}. If you are using a custom hostname and port for the dev server, don't forget to add the wildcard path to the end of your URL: https://localhost:1234/{uri}

Screenshot of Craft CMS preview options.
Preview options

Finally, un-check the Refresh option. This will prevent Craft from reloading the preview pages while making changes. Instead, Gatsby will automatically refresh the page via hot-reloading.

These steps must be completed for all sections that will be using the live preview feature.

Link

Secure URLs

If you are hosting Craft on a secure URL, then you must also use a secure URL for the Gatsby development server. Otherwise security settings in the browser may prevent the preview from loading. Instructions on how to setup HTTPS for the Gatsby development server can be found here: https://www.gatsbyjs.org/docs/...

Make sure the protocols for the Craft and Gatsby URLs match!
Link

Setup Gatsby.js for live preview

All of our configuration will be done inside gatsby-config.js. You can learn more here: https://www.gatsbyjs.org/docs/api-files-gatsby-config/

Link

Retrieving the preview token in Gatsby

In order for Gatsby to query the preview content, it will need access to the preview token. We can do this by using an Express.js middleware which will perform two important tasks:

  • Check every page request for the Craft CMS preview token.
  • If the token is found, store it to be used later.

I recommend using Store.js for storing the token. Store.js provides an easy to use API that works well within the Gatsby (Node.js) development environment.

Here is what our implementation will look like:

All configuration will be done inside gatsby-config.js
// gatsby-config.js

// Store.js plugin for store the token
const store = require('store');

// Create developMiddleware function.
// Receives the express app as the first argument
const developMiddleware = app => {
  
  // Create a middleware
  // Will apply to all page request - "*"
  app.use('*', (req, res, next) => {
    // Check if request contains a query parameter named token
    if (req.query.token) {
      // Store token for use later
      store.set('Craft-Preview-Token', req.query.token);
    }
    next();
  })
};

Notice that the middleware is wrapped in another function called developMiddleware? This function receives the Express app object, allowing us to configure a route and build the middleware. We will eventually pass the developMiddleware function to the Gatsby configuration, but more on that later.

In our developMiddleware function, the route is using a wildcard path '*', to catch any URL from Craft's preview iframe. And we are using the Store.js library to store the token.

For more info about development middlewares see https://www.gatsbyjs.org/docs/api-proxy/#advanced-proxying

Link

Adding the middleware to Gatsby

Gatsby provides a developMiddleware option that accepts a callback function. This is where we will add our developMiddleware function. But before we do that, we want to make a few modifications to the existing configuration.

By default, Gatsby exports the configuration object as a module. Reassign this object to a variable and export the variable instead.

// gatsby-config.js

// By default, Gatsby exports the configuration object
module.exports = {...}

// First, reassing the configuration to a variable
const config = {...}

// Then export the variable
module.exports = config;

This allows us to more easily modify the configuration based on the current environment. Since the preview will only be used during development, we can wrap our code inside a check for the development environment.

Then to add the token middleware, simple assign the developMiddleware function to the developMiddleware property in the config object:

// gatsby-config.js

const store = require('store');

// Re-assigned configuration object
const config = {...}

// If development environment...
if (process.env.NODE_ENV === 'development') {
  // Token middleware
  const developMiddleware = app => {
    app.use('*', (req, res, next) => {
      if (req.query.token) {
        store.set('Craft-Preview-Token', req.query.token);
      }
      next();
    })
  };
  
  // Add token middeware to the config
  config.developMiddleware = developMiddleware;
}

module.exports = config;
Link

Using the preview token in GraphQL

Using the preview token is as simple as adding it as a query parameter to the GraphQL endpoint. If a token is detected, Craft will use it to query and return the preview data.

http://www.examplecraftcmswebsite.com/api?token=ABCD123

Inside Gatsby, we can configuring the gatsby-source-graphql plugin to fetch the preview data. The configuration option will be responsible for:

  • Retrieve the stored token
  • Append it to the API endpoint as a query string
  • Making a fetch request that will query the Craft CMS GraphQL API
Remember, all code should be wrapped in a check for development environment.

First step is to use a fetch API compatible library like node-fetch. This will avoid any compatibility issues with the gatsby-source-graphql plugin.

const fetch = require('node-fetch');

Next we need a way to retrive the stored token and convert it to a query string. We can encapsulate this inside a function.

const getToken = () => {
  // Get stored preview token
  const token = store.get('Craft-Preview-Token');
  // URLSearchParams makes it easy to convert an object to a query string
  const param = new URLSearchParams({ token });
  return token
    ? `?${param.toString()}`
    : '';
}

Then construct the custom fetch option where we will query the API endpoint using the token

const customFetcher = (uri, options) => {
  // Get the token and append it to the endpoint as a query string
  const tokenEndpoint = `${uri}${getToken()}`;

  // Make a fetch request to query Craft CMS
  return fetch(tokenEndpoint, options);
};

Finally, add the custom fetch function to the gatsby-source-graphql plugin and set how often it should fetch the preview data via the refetchInterval option.

// Loop through the array of plugin...
config.plugins = config.plugins.map(plugin => {
  const { resolve } = plugin;
  
  // Only modify the gatsby-source-graphql plugin
  if (resolve && resolve === 'gatsby-source-graphql') {
    // Add custom fetch callback to the plugin option
    plugin.options.fetch = customFetcher;
    // How often to query the api in seconds
    plugin.options.refetchInterval = 3;
  }
  
  return plugin;
})

The refetchInterval option specifies how often to query the api in seconds. This option will allow Gatsby to automatically refresh the preview page with the preview data.

// Query and refresh content every three seconds
plugin.options.refetchInterval = 3;

With everything brought together...

// Use a fetch API compatible library
const fetch = require('node-fetch');
// Storage api to help store and retrieve token
const store = require('store');

// Re-assigned configuration object
const config = {...}

// Only add custom options in development environment
if (process.env.NODE_ENV === 'development') {
  // Express middleware that retrieves preview token
  const craftTokenMiddleware = app => {
    app.use('*', (req, res, next) => {
      if (req.query.token) {
        store.set('Craft-Preview-Token', req.query.token);
      }
      next();
    });
  };
  // Add the developMiddleware to the config object
  config.developMiddleware = craftTokenMiddleware;

  // Getter to retrieve token and convert it to a query string
  const getToken = () => {
    const token = store.get('Craft-Preview-Token');
    const param = new URLSearchParams({ token });
    return token
      ? `?${param.toString()}`
      : '';
  }
  
  // Custom fetch callback that uses token to query the GraphQL API
  const customFetcher = (uri, options) => {
    const tokenEndpoint = `${uri}${getToken()}`;
    return fetch(tokenEndpoint, options);
  };

  config.plugins = config.plugins.map(plugin => {
    const { resolve } = plugin;
    
    // In our loop, only modify the plugin we need
    if (resolve && resolve === 'gatsby-source-graphql') {
      // Adding custom fetch callback to the plugin option
      plugin.options.fetch = customFetcher;
      
      // Dictating how often plugin should query API and refresh page data, in seconds
      plugin.options.refetchInterval = 3;
    }

    return plugin;
  })
}

// Then export the configuration
module.exports = config;
Link

Live preview is back!

Be sure to start up the Gatsby development server. Then edit an entry in Craft and click the Preview button. If everything was setup correctly, you should now see a live preview of the Gatsby site as you edit the content.

Happy editing!