Live Previews with WordPress and Gatsby
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. - 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) andnonce
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.