Want to hop right to the code? GitHub Repository is here.

There are so many headless WordPress GitHub examples now, whether they be tutorials, GitHub repos or actual production sites. Astounding considering where the space was just a few years ago. This is, yet another example, although perhaps more comprehensive in what it includes.

Previews! Where are my previews?

Batteries included 🔋. This repository includes the following:

A Gatsby Theme that provides a Higher Order Component for live Previews.

A WordPress Docker Container.

A “Headless” WordPress Theme.

WPGraphQL plugin

A Gatsby Starter that demonstrates how to use the theme’s higher order component to facilitate previews for WordPress posts and custom post types.

Quick start

Clone the repo

Rename theme/sample.env > .env & enter creds. Hint: current creds will work.

> & enter creds. Hint: current creds will work. In the root of the repo run yarn

docker-compose up

Run through WordPress install @ http://localhost:3030

Activate WPGraphQL plugin

Activate WP Headless theme

Enable Permalinks (any will do)

Fire up the Gatsby Starter Site: In the root of the project run yarn workspace site develop

Navigate to the “Hello World” post, change something & click the preview button

Using the preview higher order component

You can see an example implementation in the demo site here.

If you’re familiar with Gatsby, this component should look pretty familiar. If you’re familiar with WPGraphQL line 41 should look familiar as well.

const PREVIEW_QUERY = gql` query getPreview($id: Int!) { postBy(postId: $id) { title revisions { nodes { id title content } } } } `;

To support previews, a GraphQL preview query must be written and passed to the withPreview higher order component as the HOC makes no assumptions on the shape of your query.

export default withPreview({ preview: PREVIEW_QUERY })(PostTemplate);

Gatsby preview URLs

Are the same URLs Gatsby Builds + three parameters; preview=true , post=theWordPressPostID` and nonce=wordPressGeneratedNonce .

An example:

// Live URL built by Gatsby. https://justinwhall.com/blog/hello-world/ // Preview URL. https://justinwhall.com/blog/hello-world/?preview=true&post=1&nonce=ajd5end6

Writing the preview query

You may have noticed we passed a preview arg to the withPreview higher order component above. For any given post, page or customer post type, your query and subsequently preview query could be different as such the withPreview makes no assumptions on the shape your query so you must provide one and pass as an argument.

You can see an example of this in the post template in the demo site.

const PREVIEW_QUERY = gql` query getPreview($id: Int!) { postBy(postId: $id) { title revisions { nodes { id title content } } } } `;

This is subsequently passed as argument to the withPreview higher order component.

export default withPreview({ preview: PREVIEW_QUERY })(PostTemplate);

Fetching live data with Apollo & WPGraphQL

There is some set up here. We need to wrap the ApolloProvider around our app. You can see that in gatsby-browser.js and gatsby-ssr.js here and here respectively.

We also need to initialize the ApolloClient so we can import and use Apollo’s <Query> component in the withPreview component.

import { ApolloClient } from 'apollo-boost'; import { ApolloLink } from 'apollo-link'; import { setContext } from "apollo-link-context"; import { InMemoryCache } from 'apollo-cache-inmemory'; import { HttpLink } from 'apollo-link-http'; import fetch from 'isomorphic-fetch'; const cache = new InMemoryCache(); export const client = new ApolloClient({ cache, fetch, link: ApolloLink.from([ setContext((req, prev) => { return ({ headers: { ...prev.headers, // As long as we are on the same domain with or WP install and Gatsby front end, we can use the x-wp-nonce header to authenticate and fetch previews. "X-WP-Nonce": req.variables.nonce ? req.variables.nonce : '', }, }) } ), new HttpLink({ uri: 'http://localhost:3030/graphql', credentials: 'include', }), ]) });

The withPreview higher order component is simple in principle. The logic here is as follows:

Determine if the request is a preview.

If it isn’t, return the component and we’re done.

If it is…

Pass the post (WordPress Post ID) and nonce to Apollo & execute a client side query to the WPGraphQL endpoint.

(WordPress Post ID) and to Apollo & execute a client side query to the WPGraphQL endpoint. Return the preview data back to the component.

Render the preview data in the component.

So let’s do it.

Determine if the request is a preview and If it isn’t, return the component and we’re done.

const withPreview = (args = { preview: false }) => Component => { const preview = (props) => { /** * Parse the URL. Using the example above ( https://justinwhall.com/blog/hello-world/?preview=true&post=1&nonce=ajd5end6 ) */ const parsed = queryString.parse(props.location.search); const { nonce, // ajd5end6 preview, // true post, // 1 } = parsed; // Id needs to be an int for preview query. const id = parseInt(post, 10); /** * If no preview param, return the component with the preview props as false. */ if (!preview) { return ( <Component preview={false} {...props} /> ); } } };

Pass the post (WordPress Post ID) and nonce to Apollo & execute a client side query to the WPGraphQL endpoint and return the preview data back to the component.

import React from 'react'; import { Query } from 'react-apollo'; import queryString from 'query-string'; const withPreview = (args = { preview: false }) => Component => { const preview = (props) => { const parsed = queryString.parse(props.location.search); const { nonce, preview, post, } = parsed; // Id needs to be an int for preview query. const id = parseInt(post, 10); /** * If no preview param, return the component with the preview props as false. */ if (!preview) { return ( <Component preview={false} {...props} /> ); } /** * Otherwise, run our Apollo query. */ return ( <Query query={args.preview} variables={{ id, nonce, }} > {({ data, loading, error }) => { if (loading) return <p>Loading preview...</p>; if (error) return <p>Error: ${error.message}</p>; return ( <Component preview={data} {...props} /> ) }} </Query> ) }; return preview; }; export default withPreview;

Lastly, render the preview data in the component.

import React from "react"; import { graphql } from "gatsby"; import gql from 'graphql-tag'; import Layout from "../components/layout"; import withPreview from '../../../theme/src/components/withPreview'; const PostTemplate = (props) => { /** * Determine if we're looking at a preview or live page. */ const postData = props.preview ? props.preview.postBy.revisions.nodes[0] : // grab the first revision props.data.wpgraphql.post const { title, content, } = postData; return ( <Layout location={props.location}> <h1>{title}</h1> <div className="post-content" dangerouslySetInnerHTML={{ __html: content }} /> </Layout> ) } export const pageQuery = graphql` query GET_POST($id: ID!) { wpgraphql { post(id: $id) { title content uri } } } ` const PREVIEW_QUERY = gql` query getPreview($id: Int!) { postBy(postId: $id) { title revisions { nodes { id title content } } } } `; export default withPreview({ preview: PREVIEW_QUERY })(PostTemplate);

Full repository is located here.