Headless WordPress: How to Set Up and Deploy a Gatsby Site

With the growing popularity of JavaScript frameworks and tools for web development, it pays to look at technology that makes building sites easier. Gatsby is an open-source framework built using React, which is a great way to create a static site. What’s more, a killer Gatsby feature is that it can pull in content from many different sources – including your WordPress site.

In this post, we’ll go over what Gatsby is, why you should think about it for your next Headless WordPress project, and how to set it up.

What is Gatsby?

In the simplest terms, Gatsby is a static site generator. If you want to create a bunch of fully-styled HTML pages without having to duplicate and manage all the repeating bits of HTML yourself, a static site generator is the tool for you.

For the unaware, this type of generator takes your dynamic PHP template files and data and turns them into static HTML pages. It’s a similar premise to publishing platforms from the early 2000s such as Movable Type, in that the static pages can be regenerated once the dynamic content changes. Back then it was known as the “baked” (static) vs “fried” (dynamic) debate. “Fried” ultimately won out, but “baked” is making a comeback, with Gatsby being at the forefront of this comeback. However, Gatsby approaches “baking” a site in a slightly different way.

Gatsby is part of a modern web development architecture pattern known as Jamstack, which stands for Javascript, API, and Markup, the Markup section being the generated static HTML pages.

There are three key reasons why Gatsby should be on your ‘watch list’:

The benefit of static sites is that they’re fast (because the backend code isn’t processed at every page request), generally easier to maintain (even if something goes wrong with the source of the data, the static pages will still work), and because there aren’t any server-side requests that require input data to process, more secure. You generate the static files, deploy them to the server, and your site updates are live.

Static site generators such as Jekyll are longstanding tools for static web development. The difference between these and tools like Gatsby is that the Jamstack generators let you specify content ‘sources’, such as a WordPress installation.

This is almost the literal definition of ‘headless or decoupled WordPress‘. While the Content Management System (CMS) powers the content and data, the static site generator handles the frontend.

As we’ve discussed, Gatsby uses React as the JavaScript layer for templating but uses GraphQL as the API layer in the Jamstack. GraphQL is a newer, more flexible method to query data, and is often used as a replacement for a REST API. Finally, Instead of rendering React components at runtime, Gatsby creates HTML templates (the Markup) ahead of time using the Gatsby CLI.

If you’re already familiar with React, setting up and using Gatsby is fairly straightforward. Gatsby has a fairly robust plugin community, and there is also no shortage of tutorials and documentation on how to use WordPress as your content provider.

Getting Set Up With Gatsby and WordPress

Throughout much of this article, we’re going to develop a static site. Though, there are a few things you need to set up Gatsby to use WordPress as a data source. The Gatsby documentation has a good overview of what you’ll need.

For clarity, here’s the list:

If you need to install Git, Mac users can do this using Homebrew and the brew install git command. For Windows users, there’s a dedicated installer. Ubuntu and Debian Linux users can run sudo apt-get install git, while Fedora users can run sudo dnf install git.

It’s likely that you already have Node installed, but there are installers available for all platforms if you don’t. Mac users can also do this using brew install node. The only prerequisite here is that you install Node first, as everything else is dependent on it.

To install the gatsby-cli, you can use the Node Package Manager (npm), with the following shell command:

npm install -g gatsby-cli

You might find that the install process will throw up some deprecated dependencies, but this isn’t something to worry about here. Your goal is to see an analytics consent screen, as this indicates Gatsby and the Command Line Interface (CLI) is installed:

The Gatsby CLI success screen

Note that we’ve had some success with the install process on Apple Silicon machines, although in some cases there seems to be an issue with Node and its related packages rather than Gatsby itself. If you receive errors such as gatsby: command not found, you may need to delete your node_modules directory, and reinstall gatsby-cli.

At this point, you’re ready to rock and roll! There are many ‘starters’ for Gatsby – in other words, boilerplate themes and predefined settings for your Gatsby site.

You can see a full list in the Starter Library. There are even a couple pre-built for WordPress. The “gatsby-wordpress-starter” theme is perfect for our needs here, and to install it run the following:

gatsby new /path/to/my-wordpress-gatsby-site https://github.com/gatsbyjs/gatsby-starter-wordpress-blog

This initializes a new Gatsby instance (using gatsby new), and specifies a target folder and starter theme. For the target folder, the path you give here will be the location where you want to build the local site on your computer. If the directory doesn’t exist, Gatsby will create it for you.

Once Gatsby is done creating the local site, you’ll want to install two plugins on your live WordPress site, to turn it into a Gatsby data source:

You can install these plugins in the standard way within WordPress. Together, these turn your site into a Gatsby data source, but you still need to configure your local starter site.

The only change is in the gatsby-config.js file within your new local Gatsby site directory. You can open this up with whatever your favorite code editor is, and change the GraphQL URL for your live WordPress website:

Changing the URL within Gatsby

Once you’re ready, you can switch to the local Gatsby site directory in the command line, and run the Gatsby develop command

cd /path/to/my-wordpress-gatsby-site
gatsby develop

Running the gatsby develop command

You’ll see Gatsby run through its process of generating the static site files. You may see an error message noting that you need to install the Mitt package.

This is a tiny event emitter and observer that Gatsby requires, so once you run npm install mitt, try gatsby develop again, and the process should complete. If everything works, you can browse to http://localhost:8000. This should show the local copy of the Gatsby site, generated using the source data from WordPress, which needs deployment in order to make it live:

The initial local Gatsby site

Exciting! Next, let’s look at the data sources in some more detail.

Data Sources

Previous versions of Gatsby had more convoluted workflows to set WordPress up as a data source. Now you just install a couple of plugins and much of this work is done for you.

WPGatsby and WPGraphQL do all the heavy lifting here to get data for your site. This is one of the key selling points of using Gatsby over another static site generator. Here are a couple of points to remember:

In this case, we’re looking to make data from our WordPress site available to the static site generator process, which in turn generates your static site. This is what both plugins do in part. They will let WordPress work as a data source, and set up the relevant GraphQL resources that Gatsby requires.

The Gatsby documentation has further details on the exact changes, but given that the plugins work on this under the hood, we don’t need to cover the full details here.

GraphQL for the masses

For the unaware, GraphQL is an alternative to using a REST API, but with much more control over the flow of data. With Gatsby, we don’t really need to know everything about GraphQL as Gatsby abstracts a lot of it away. Even so, it’s useful to understand the basics of GraphQL.

GraphQL is fairly straightforward: you ask for the data you want and get it in return.

query WpPosts {
  allWpPost(sort: { fields: [date], order: DESC }) {
    edges {
    post: node {

This will return JSON-like data that’s very specific and exact:

  "data": {
    "allWpPost": {
    "edges": [
        "post": {
            "id": "cG9zdDozODU0",
            "uri": "/how-to-get-your-team-on-board-with-your-brand-strategy/",
            "title": "How to Get Your Team on Board with Your Brand Strategy",
            "excerpt": "<p>“What a curious feeling!” said Alice; “I must be shutting up like a telescope.” And so it was indeed: she was now only ten inches high, and her face brightened up at the thought that she was now the right size for going through the little door into that lovely garden. First, however, she waited [&hellip;]</p>\n"
        "post": {
            "id": "cG9zdDozODUx",
            "uri": "/7-reasons-to-host-wordpress-yourself-in-2021/",
            "title": "7 Reasons to Host WordPress Yourself in 2021",
            "excerpt": "<p>Alice opened the door and found that it led into a small passage, not much larger than a rat-hole: she knelt down and looked along the passage into the loveliest garden you ever saw. How she longed to get out of that dark hall, and wander about among those beds of bright flowers and those [&hellip;]</p>\n"
        "post": {
            "id": "cG9zdDozODQ4",
            "uri": "/why-a-billboard-is-probably-the-last-thing-you-need/",
            "title": "Why a Billboard Is Probably the Last Thing You Need",
            "excerpt": "<p>Down, down, down. There was nothing else to do, so Alice soon began talking again. “Dinah’ll miss me very much to-night, I should think!” (Dinah was the cat.) “I hope they’ll remember her saucer of milk at tea-time. Dinah my dear! I wish you were down here with me! There are no mice in the [&hellip;]</p>\n"
        "post": {
            "id": "cG9zdDozODQ1",
            "uri": "/how-to-pick-a-name-thats-truely-memorable/",
            "title": "How to Pick a Name That’s Truely Memorable",
            "excerpt": "<p>Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, “and what is the use of a book,” thought Alice “without pictures [&hellip;]</p>\n"
  "extensions": {}

With a REST API, the return values are whatever the API endpoints contain:

curl https://example.com/wp-json/wp/v2/posts

You’ll get a lot more data, of which only some will be relevant to your needs:

    "id": 3854,
    "date": "2021-08-04T20:09:58",
    "date_gmt": "2021-08-04T20:09:58",
    "guid": {
      "rendered": "https://source.hellfish.media/?p=3854"
    "modified": "2021-08-04T20:13:47",
    "modified_gmt": "2021-08-04T20:13:47",
    "slug": "how-to-get-your-team-on-board-with-your-brand-strategy",
    "status": "publish",
    "type": "post",
    "link": "https://source.hellfish.media/how-to-get-your-team-on-board-with-your-brand-strategy/",
    "title": {
      "rendered": "How to Get Your Team on Board with Your Brand Strategy"
    "content": {
      "rendered": "<p>“What a curious feeling!” said Alice; “I must be shutting up like a telescope.”</p>\n<p>And so it was indeed: she was now only ten inches high, and her face brightened up at the thought that she was now the right size for going through the little door into that lovely garden. First, however, she waited for a few minutes to see if she was going to shrink any further: she felt a little nervous about this; “for it might end, you know,” said Alice to herself, “in my going out altogether, like a candle. I wonder what I should be like then?” And she tried to fancy what the flame of a candle is like after the candle is blown out, for she could not remember ever having seen such a thing.</p>\n<p>After a while, finding that nothing more happened, she decided on going into the garden at once; but, alas for poor Alice! when she got to the door, she found she had forgotten the little golden key, and when she went back to the table for it, she found she could not possibly reach it: she could see it quite plainly through the glass, and she tried her best to climb up one of the legs of the table, but it was too slippery; and when she had tired herself out with trying, the poor little thing sat down and cried.</p>\n<p>“Come, there’s no use in crying like that!” said Alice to herself, rather sharply; “I advise you to leave off this minute!” She generally gave herself very good advice, (though she very seldom followed it), and sometimes she scolded herself so severely as to bring tears into her eyes; and once she remembered trying to box her own ears for having cheated herself in a game of croquet she was playing against herself, for this curious child was very fond of pretending to be two people. “But it’s no use now,” thought poor Alice, “to pretend to be two people! Why, there’s hardly enough of me left to make one respectable person!”</p>\n<p>Soon her eye fell on a little glass box that was lying under the table: she opened it, and found in it a very small cake, on which the words “EAT ME” were beautifully marked in currants. “Well, I’ll eat it,” said Alice, “and if it makes me grow larger, I can reach the key; and if it makes me grow smaller, I can creep under the door; so either way I’ll get into the garden, and I don’t care which happens!”</p>\n",
      "protected": false
    "excerpt": {
      "rendered": "<p>“What a curious feeling!” said Alice; “I must be shutting up like a telescope.” And so it was indeed: she was now only ten inches high, and her face brightened up at the thought that she was now the right size for going through the little door into that lovely garden. First, however, she waited&hellip; <a class=\"more-link\" href=\"https://source.hellfish.media/how-to-get-your-team-on-board-with-your-brand-strategy/\">Continue reading <span class=\"screen-reader-text\">How to Get Your Team on Board with Your Brand Strategy</span></a></p>\n",
      "protected": false
    "author": 2,
    "featured_media": 3856,
    "comment_status": "open",
    "ping_status": "open",
    "sticky": false,
    "template": "",
    "format": "standard",
    "meta": [],
    "categories": [
    "tags": [],
    "_links": {
      "self": [
          "href": "https://source.hellfish.media/wp-json/wp/v2/posts/3854"
      "collection": [
          "href": "https://source.hellfish.media/wp-json/wp/v2/posts"
      "about": [
          "href": "https://source.hellfish.media/wp-json/wp/v2/types/post"
      "author": [
          "embeddable": true,
          "href": "https://source.hellfish.media/wp-json/wp/v2/users/2"
      "replies": [
          "embeddable": true,
          "href": "https://source.hellfish.media/wp-json/wp/v2/comments?post=3854"
      "version-history": [
          "count": 1,
          "href": "https://source.hellfish.media/wp-json/wp/v2/posts/3854/revisions"
      "predecessor-version": [
          "id": 3855,
          "href": "https://source.hellfish.media/wp-json/wp/v2/posts/3854/revisions/3855"
      "wp:featuredmedia": [
          "embeddable": true,
          "href": "https://source.hellfish.media/wp-json/wp/v2/media/3856"
      "wp:attachment": [
          "href": "https://source.hellfish.media/wp-json/wp/v2/media?parent=3854"
      "wp:term": [
          "taxonomy": "category",
          "embeddable": true,
          "href": "https://source.hellfish.media/wp-json/wp/v2/categories?post=3854"
          "taxonomy": "post_tag",
          "embeddable": true,
          "href": "https://source.hellfish.media/wp-json/wp/v2/tags?post=3854"
      "curies": [
          "name": "wp",
          "href": "https://api.w.org/{rel}",
          "templated": true

That’s just one post!

This can lead the REST API to over and/or under fetch data.

Also with a REST API, you’ll often need to make multiple requests to different endpoints to get all the data you need. With GraphQL you ask the server for the specific data you’re looking for in one request.

Also, the format of the request and the response are the same, because of a standard schema and type system shared between the server and client.

Gatsby includes a tool called GraphiQL running on http://localhost:8000/___graphql. By the way, that is a triple underscore (we’re going to coin it a “tunder”). This is like phpMyAdmin for GraphQL, allowing you to test out queries.

The GraphiQL tool

While you’re able to type straight into the code area, you can also choose different query functions from the left-hand “Explorer”. If you do this, the query area will auto populate with the updated query and carry out some initial query formatting.

You can also choose the data types from the “Explorer”, and this will also come with pre-applied formatting:

Auto formatting in GraphiQL

From here, it’s not a stretch to put together a full query. As an example, here’s the query in the gatsby-node.js file (we’ll talk about this more in the next section), used to query the WordPress posts from the source site:

query WpPosts {
  # Query all WordPress blog posts sorted by date
  allWpPost(sort: { fields: [date], order: DESC }) {
    edges {
    previous {

    # note: this is a GraphQL alias. It renames "node" to "post" for this query
    # We're doing this because this "node" is a post! It makes our code more readable further down the line.
    post: node {

    next {

If you run this query in your local GraphiQL tool, it will return a JSON response from your WordPress website:

A query in GraphiQL

You’ll notice that the id in the response doesn’t correspond to the WordPress post ID, but rather a unique identifier that the WP GraphQL plugin assigns to posts. When using Gatsby to generate static pages from your source site, you’ll want to run a query like this (or let Gatsby do it) to grab the right data.

The GraphiQL tool is fun to use, and can help you test out the queries you need to return the right data. However, this highlights another advantage of using Gatsby with a WordPress starter theme, Gatbsy does everything you need under the hood anyway.

How to Convert WordPress data to Gatsby pages

At this point, let’s look at the gatsby-node.js file we were using in the last section. This file is where we transform our WordPress data into Gatsby pages.

In past versions of Gatsby, you’d need to add queries in order to convert data into pages. The current setup process is slicker than before, so lots of this work is done for you.

The file itself is structured with code to create the site’s blog posts, then gets into the GraphQL queries. As we mentioned, the query from the last section is verbatim from the gatsby-node.js file.

The code preceding it – specifically the createPages method will take the results of these queries and turn them into site blog posts.

exports.createPages = async gatsbyUtilities => {
// Query our posts from the GraphQL server
const posts = await getPosts(gatsbyUtilities)

// If there are no posts in WordPress, don't do anything
if (!posts.length) {

// If there are posts, create pages for them
await createIndividualBlogPostPages({ posts, gatsbyUtilities })

// And a paginated archive
await createBlogPostArchive({ posts, gatsbyUtilities })

You’ll find that Gatsby already has a blog post template in place – you can find them all in /src/templates/ – and it turns the named template into a dedicated component to work with further:

component: path.resolve(`./src/templates/blog-post.js`),

Again, because Gatsby does a good job of setting up templates and layouts, we won’t get too far into the process (although there are good tutorials elsewhere).

The output of the GraphQL query is injected as a property (here using context) so we can use it in our component.

Whenever you make changes to your installation, you’ll have to run gatsby develop again to see them. With this we have the basics of our site going. Though, there are some caveats to note, especially with styling – let’s look at this now.


If we were betting people, we’d put a wager on the dynamic aspects of WordPress being a big draw for development. This brings about a few caveats when it comes to your new Gatsby site that will require you to get stuck into the code.

For example, you’ll notice early that Gatsby doesn’t replicate (or even pull in) the layout and style of the parent site. While the source blog isn’t full of bleeding-edge styling…

The Hellfish Media source blog

…it doesn’t get implemented into the Gatsby site at all.

In these cases, you’ll want to use something like React Style Components to straighten out the look of your new site. This should be familiar to React users, although it’s worth summing up the process:

We’re going to use CSS Modules here, as this is a recommendation within the official documentation. However, Gatsby gives you flexibility to use the system you’re most comfortable with.

To use CSS Modules, create a file in your components directory with the suffix module.css in order for Gatsby to recognize them (e.g. src/components/layout.module.css). Inside the module file, you can create your style class.

.container {
  margin: auto;
  max-width: 500px;
  font-family: sans-serif;

Once you have your style classes set in your module, you then import them back into any layout component (the default layout for the “gatsby-wordpress-starter” theme is src/components/layout.js) and apply them using the className property on the element you want the style applied to:

import React from "react"
import { Link, useStaticQuery, graphql } from "gatsby"
import parse from "html-react-parser"
import { container } from './layout.module.css'

const Layout = ({ isHomePage, children }) => {
  const {
    wp: {
    generalSettings: { title },
  } = useStaticQuery(graphql`
    query LayoutQuery {
    wp {
        generalSettings {

  return (
    <div className="global-wrapper" data-is-root-path={isHomePage}>
    <header className="global-header">
        {isHomePage ? (
        <h1 className="main-heading">
            <Link to="/">{parse(title)}</Link>
        ) : (
        <Link className="header-link-home" to="/">
<div className={container}>
         © {new Date().getFullYear()}, Built with
        {` `}
        <a href="https://www.gatsbyjs.com">Gatsby</a>
        {` `}
        And <a href="https://wordpress.org/">WordPress</a>

export default Layout

On the face of it, Gatsby is a pure static site generator, but it does have a trick up its sleeve for dynamic-like content. Although it uses server-side APIs to generate static content, you can use ‘React hydration’ to bridge the gap.

Detailing how the hydrate() method operates is beyond the scope of this article. It’s just worth noting that Gatsby supports it and it’s the ‘official’ way to hook in dynamic content if you need to.

Also, it’s easy to forget that lots of WordPress plugins don’t support GraphQL. Of course, you have to rely on this to be in place, and if it isn’t, you won’t be able to pull in all of the data from your site. This limits Gatsby’s scope and application in some circumstances, and it’s something you’ll have to weigh up.

Deploying a Gatsby Site

At some point, you’ll want to deploy your site to a live server. There are a few ways to do this, and because your site will be static, all of them are straightforward to implement.

For a total integration and almost near-perfect deployment, Gatsby Cloud is the tool for you. It’s not open source like its parent framework, but it does offer a host of great features specifically tailored to deploying a Gatsby site using WordPress as the data source. First and foremost, it offers the ability to configure build webhooks. These allow you to automatically deploy your site when the WordPress data is updated by adding something like the following code to a plugin on your WordPress site:

add_action( 'publish_post', function () {
    wp_remote_post( 'https://webhook.gatsbyjs.com/hooks/data_source/publish/{gatsby-cloud-site-id}', [] );
} );

It also has live previews, support for all the major Git hosts, and automated checks to fix errors before they’re published.

Netlify is another popular static site host. The main benefit of Netlify is that it’s been around longer and is therefore more feature rich, and it supports all major modern static site generators, not just Gatsby.

Netlify also supports build webhooks, and you can use the same plugin code, the only difference being the webhook URL:

add_action( 'publish_post', function () {
    wp_remote_post( 'https://api.netlify.com/build_hooks//{netlify-site-id}, []);

You don’t need to use either of these though. You can also use regular web hosting, especially if your web host supports Git repositories, and Git push-to-deploy. For example, SpinupWP is more than capable of hosting your Gatsby powered static site.

To start, you can change the Git origin of your development project to a newly created Git repository, and push the code to the new repository (we’re using a fresh GitHub repo as an example):

git remote set-url origin git@github.com:githubusername/githubreponame.git
git push origin main

Before you deploy the site, the SpinupWP server needs Node.js and the Gatsby CLI, much like your development server. The next part of the process requires that you have sudo SSH access to the server. First, install Node.js on the server:

curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs

Then run the familiar Gatsby command to install the CLI:

sudo npm install -g gatsby-cli

At this point, you can set up a new SpinupWP site. Once you get to the installation step, we can Clone a Git Repository, and enter the repo URL and branch.

SpinupWP New Setup Wizard using Git Repository

You’ll also want to add the deploy script:

npm install
gatsby build

Once the code has been fetched from the Git repository, these commands will install any required packages, and build the static site from the WordPress data source.

While SpinupWP will check whether it can access the Git repository (and you may need to configure it), we don’t need to provide anything else. Of course, we don’t need a database, so this is one less moving part.

From here, SpinupWP takes over with its Push to Deploy feature. Before you’re done and dusted, make sure to update the “Public Folder” in the site’s settings within SpinupWP. You’ll want to change it to /public, as the build process creates the static files in this directory.

Setting site Public Folder

The disadvantage of this approach is when you add data to your WordPress site, which is acting as your Gatsby data source. You’ll have to manually trigger a deployment from the SpinupWP dashboard, which would get any new code changes from the Git repository, and run the deploy script to get the updated data and rebuild the site.

One way you could automate this is to configure your WordPress site to update a file in your Git repository, whenever a new post is published, using the publish_post action hook and the GitHub REST API. Alternatively, you could just update that same file manually, by committing and pushing the change from your local Gatsby development site.

This is where Gatsby Cloud and Netlify might suit you better, although using SpinupWP in this way is a solid alternative, especially if you don’t publish new content regularly.

Final Thoughts

A post like this can only cover the bare minimum of what Gatsby can do. It’s a full web application framework for creating websites, much like a full-stack framework like Laravel or Ruby on Rails.

Of course, Gatsby is perfect for headless CMS applications, as you can pull multiple data sources into one site. What’s more, you can use your React knowledge from WordPress and use it alongside a dedicated theme, bridging both platforms.

We also think Gatsby is super-fast, especially if you self-host with a solution such as SpinupWP. In fact, with a Content Delivery Network (CDN), you could host a lot more sites than we’d normally recommend on one server.

Though, if you need dynamic functionality for aspects such as forms and e-commerce, Gatsby is going to be more work for less benefit. On the flip side, you can pull in content from multiple sources to build a ‘dynamically-backed’ static site. It’s a powerful combination.

On the whole, if you or your developers don’t want to build a WordPress theme, and you also want a fast site without having to set up caching, Gatsby could fit the bill.

Do you think Gatsby is a good solution for headless WordPress applications? Share your thoughts with us in the comments section below!

Avatar photo

Brad Touesnard, Founder & CEO

As founder of SpinupWP, Brad has worn many hats. Although he has a background in development and system administration, he spends most of his time helping the SpinupWP team with product management, UX, roadmap, and marketing.

Want your face and bio here? ☝

Write an article like this and get paid well. Check out our writers program

Start Your 7-Day Free Trial

Begin your SpinupWP journey today and spin up your first server within minutes.

Subscribe to get the latest news, updates and optimizations in performance and security.

You are already logged in

It looks like you are already logged in to SpinupWP.

Please log out of this account to continue.

Registration Successful

Thanks for registering for a new SpinupWP account.

Before getting started, could you verify your email address by clicking on the link we just emailed to you?

Start Your 7-Day Free Trial

No credit card required. All features included.

By signing up to SpinupWP, you agree to our Terms and Conditions.
For privacy related information, view our Privacy Policy.