---
title: "How to Migrate WordPress Sites to Jamstack: Tutorial + Video Guide"
description: "See how you can keep the benefits of WordPress while avoiding its biggest flaws. In this step-by-step guide, learn to migrate to the Jamstack using Gatsby and Netlify."
source: "https://www.netlify.com/blog/2020/03/23/migrate-your-wordpress-site-to-the-jamstack/"
last_updated: "2026-07-10T21:01:09.000Z"
---
WordPress is the most popular content management system on the planet, powering about a third of the websites online today.

If you’re working on one of the roughly 1 in 3 websites powered by WordPress and wish you could migrate your development workflow to the Jamstack, I have good news! **It’s possible to move your WordPress websites to the Jamstack _today._**

And what’s even more exciting is that **your content creators don’t need to change their current workflow!** They can continue to use the WordPress admin dashboard to manage content and their changes will trigger a rebuild of your new, blazing fast Jamstack site.

In this post, we’ll walk through migrating a WordPress site to Gatsby, a popular Jamstack framework powered by React and GraphQL.

## If you prefer video, we’ve got you covered!

This post is an expanded version of a project I built with Zac Gordon on _Learn With Jason_. In about 90 minutes, Zac and I migrated a WordPress site to Gatsby. **Watch Zac Gordon teach us [how to migrate WordPress sites to the Jamstack](https://www.learnwithjason.dev/use-gatsby-wordpress-for-dynamic-sites) on _Learn With Jason_.**

If you prefer short videos that only focus on code and don’t waste any time, I also created a 30-minute video tutorial that covers all the steps in this project. **You can [learn how to move your WordPress site to Gatsby](https://jason.af/egghead/wp-jamstack) on egghead.**

> NOTE: The lessons from [the egghead course](https://jason.af/egghead/wp-jamstack) are also embedded in this tutorial so it’s easy to watch and reference the code.

## Set up WordPress

The first things we need to migrate a WordPress site to the Jamstack is a WordPress site. In this tutorial, we’re going to use [https://wordpressjamstack.lwj.dev/](https://wordpressjamstack.lwj.dev/), but you can follow along using your own site if you prefer.

### Install WPGraphQL and WPGraphiQL

{% renderFile ”./src/\_includes/pages/blog/egghead-embed.vue”, { eggheadVideoTitle: “Install WPGraphQL and WPGraphiQL plugins in WordPress using the command line”, eggheadVideoUrl: “[https://egghead.io/lessons/gatsby-install-wpgraphql-and-wpgraphiql-plugins-in-wordpress-using-the-command-line](https://egghead.io/lessons/gatsby-install-wpgraphql-and-wpgraphiql-plugins-in-wordpress-using-the-command-line)” } %}

The heart of a Jamstack-friendly WordPress site is pulling WordPress data from an API instead of using the built-in template system. One of the most approachable options for accessing WordPress data via API is [WPGraphQL](https://wpgraphql.com/).

Because these plugins are developer-focused, they’re not available through the standard WordPress plugins search. Instead, we need to install them from GitHub.

We need two plugins:

1.  [WP GraphQL](https://github.com/wp-graphql/wp-graphql) — this enables a GraphQL API that allows access to all public WordPress data through an unauthenticated GraphQL API. (There’s also an authenticated API for privileged access, but we won’t go into details on that in this post.)
2.  [WP GraphiQL](https://github.com/wp-graphql/wp-graphiql) — this is technically optional, but it adds a new tab inside the WordPress admin that allows us to quickly try out GraphQL queries and see data coming back.

To install the plugins, log into the server where your site is hosted and clone the plugins into the `wp-content/plugins` directory:

```
ssh <user>@<domain>cd /path/to/your/wp-content/plugins/git clone  --depth=1 --single-branch https://github.com/wp-graphql/wp-graphql.gitgit clone  --depth=1 --single-branch https://github.com/wp-graphql/wp-graphiql.git
```

This will add the latest files to your site’s plugins directory without including unnecessary Git metadata.

### Activate the GraphQL plugins

Once the plugins are installed, we need to activate them. Head to your WordPress admin dashboard, then click the “Plugins” option from the left-hand menu.

We should see both WP GraphQL and WP GraphiQL as installed, but not activated.

![WordPress plugins page](/v3/img/blog/01-wp-plugins-inactive.png)

Click the “Activate” link for both WP GraphQL and WP GraphiQL.

### Write our first GraphQL query in WP GraphiQL

Click the new “GraphiQL” menu option at the left-hand side. This brings up the GraphiQL interface inside our WordPress dashboard.

![WPGraphiQL UI](/v3/img/blog/02-wp-graphiql.png)

Choose fields in the explorer at the left-hand side to build out a query. For example, if we want to load our site’s pages, we can run this query:

```
query MyQuery {  pages {    nodes {      title      uri      content      isFrontPage    }  }}
```

![GraphQL query result.](/v3/img/blog/03-wp-graphiql-result.png)

Great! We’ve now got a functioning GraphQL API for WordPress that we can use to power our Jamstack frontend!

## Create a new Gatsby site

{% renderFile ”./src/\_includes/pages/blog/egghead-embed.vue”, { eggheadVideoTitle: “Create a new Gatsby site using npx and the hello world starter”, eggheadVideoUrl: “[https://egghead.io/lessons/gatsby-create-a-new-gatsby-site-using-npx-and-the-hello-world-starter](https://egghead.io/lessons/gatsby-create-a-new-gatsby-site-using-npx-and-the-hello-world-starter)” } %}

Before we can use our WordPress data, we need to create a new Jamstack site that will display it.

In this example we’ll use [Gatsby](https://gatsbyjs.org), an open source, React-based framework that specializes in pulling data from third-party sources.

To create a new site, run the following commands:

```
# create a new Gatsby site in a directory called `wordpress-jamstack`# using the Hello World Gatsby starternpx gatsby new wordpress-jamstack gatsbyjs/gatsby-starter-hello-world
# move into the foldercd wordpress-jamstack/
```

This generates a new site in a directory called `wordpress-jamstack` with a bare-bones Gatsby site.

> NOTE: There are lots of additional options for starting a new Gatsby site with WordPress that come with batteries included. One great example is Alexandra Spalato’s [`gatsby-theme-wordpress-blog`](https://www.gatsbyjs.org/packages/@alexadark/gatsby-theme-wordpress-blog/). We’re intentionally building this site from scratch to make sure we understand how Gatsby and WordPress work together under the hood.

### Install and configure `gatsby-source-graphql`

{% renderFile ”./src/\_includes/pages/blog/egghead-embed.vue”, { eggheadVideoTitle: “Install and configure `gatsby-source-graphql` to read WordPress data from WPGraphQL”, eggheadVideoUrl: “[https://egghead.io/lessons/gatsby-install-and-configure-gatsby-source-graphql-to-read-wordpress-data-from-wpgraphql](https://egghead.io/lessons/gatsby-install-and-configure-gatsby-source-graphql-to-read-wordpress-data-from-wpgraphql)” } %}

Gatsby uses [source plugins](https://www.gatsbyjs.org/tutorial/part-five/) to load data. One of the most powerful source plugins is `gatsby-source-graphql`, which allows us to use _any_ GraphQL API as a data source in Gatsby.

Since we just created a GraphQL API for our WordPress site, this is a perfect option for loading our WordPress data in Gatsby!

Install the plugin with the following command:

```
npm install gatsby-source-graphql
```

After installing the plugin, we need to load it by modifying `gatsby-config.js`:

```
/**   * Configure your Gatsby site with this file.   *   * See: https://www.gatsbyjs.org/docs/gatsby-config/   */
  module.exports = {-   /* Your site config here */+   plugins: [+     {+       resolve: 'gatsby-source-graphql',+       options: {+         typeName: 'WPGraphQL',+         fieldName: 'wpgraphql',+         url: 'https://wordpress-jamstack.lwj.dev/graphql',+       }+     }+   ]  }
```

Save this, then start the Gatsby development server by running:

```
npm run develop
```

Once the site finishes starting up, open `http://localhost:8000/___graphql` in your browser to see Gatsby’s version of GraphiQL.

In Gatsby, writing a query to load WordPress data is _almost_ exactly the same as the one we used in WP GraphiQL, except Gatsby wraps all WordPress queries in `wpgraphql` — the `fieldName` we set in our config — to avoid naming collisions with other data sources.

Add the following query in GraphiQL:

```
{  wpgraphql {    pages {      nodes {        title        uri        content        isFrontPage      }    }  }}
```

After executing the query by pressing the play button, we’ll see our WordPress data loaded in Gatsby!

![WordPress data loaded from GraphQL in Gatsby](/v3/img/blog/04-gatsby-page-data.png)

## Create pages from WordPress content

{% renderFile ”./src/\_includes/pages/blog/egghead-embed.vue”, { eggheadVideoTitle: “Create pages in Gatsby from WordPress pages”, eggheadVideoUrl: “[https://egghead.io/lessons/gatsby-create-pages-in-gatsby-from-wordpress-pages](https://egghead.io/lessons/gatsby-create-pages-in-gatsby-from-wordpress-pages)” } %}

Now that we have a Gatsby site that has access to our WordPress data, we can start creating pages.

To create pages in Gatsby, we need three things:

1.  Data to display on the page
2.  A template component to define the page layout
3.  A call to the [`createPages` API](https://www.gatsbyjs.org/docs/node-apis/#createPages) exported from `gatsby-node.js` to combine the data and template together into pages

We have the data from WordPress now, so we can create our template component, then create pages.

### Create a template component for pages

A template component in Gatsby is a standard React component. Gatsby passes in several props to the component when it creates pages, so it’s probably a good idea to take a look at what those are.

Create a new files called `src/templates/page-template.js` and put this inside:

```
import React from "react"
const PageTemplate = props => {  return <pre>{JSON.stringify(props, null, 2)}</pre>}
export default PageTemplate
```

Once we’ve saved this file, we’re ready to actually create pages.

### Create pages in `gatsby-node.js`

To create pages, create a new file in the root directory (next to `gatsby-config.js`) called `gatsby-node.js`. Inside, let’s add a `createPages` API call:

```
exports.createPages = async ({ actions, graphql }) => {  // query for WordPress page data  const result = await graphql(`    {      wpgraphql {        pages {          nodes {            id            uri          }        }      }    }  `)
  // pull the page data out of the query response  const pages = result.data.wpgraphql.pages.nodes
  // loop through WordPress pages and create a Gatsby page for each one  pages.forEach(page => {    actions.createPage({      path: page.uri,      component: require.resolve("./src/templates/page-template.js"),      context: {        id: page.id,      },    })  })}
```

After saving this file, we can stop the server (press `control + C`), then run `npm run develop` again.

Once the site has started, visit `http://localhost:8000`. We can see everything that Gatsby passes to page components, including the `id` value we passed in `context`:

![Debugging data printed from a Gatsby page React component.](/v3/img/blog/05-page-debug.png)

This doesn’t look like much right now, but it gives us the page ID, which will let us load page-specific data in our template component.

### Write a GraphQL query to load page content from WordPress

{% renderFile ”./src/\_includes/pages/blog/egghead-embed.vue”, { eggheadVideoTitle: “Display WordPress page content in Gatsby page template components”, eggheadVideoUrl: “[https://egghead.io/lessons/gatsby-display-wordpress-page-content-in-gatsby-page-template-components](https://egghead.io/lessons/gatsby-display-wordpress-page-content-in-gatsby-page-template-components)” } %}

Collocating GraphQL queries with the components that use them is a great way to keep your codebase understandable. Because of this, we’re going to query for individual page data using the page ID in the template component itself.

Anything passed in the `context` object is also available as a GraphQL variable, so we can use the `id` to load content for each page by adding the following query:

```
  import React from "react"+ import { graphql } from "gatsby"++ export const query = graphql`+   query($id: ID!) {+     wpgraphql {+       page(id: $id) {+         title+         content+       }+     }+   }+ `
  const PageTemplate = props => {    return <pre>{JSON.stringify(props, null, 2)}</pre>  }
  export default PageTemplate
```

Once we save this, the page at `http://localhost:8000` will update to include a new `data` prop that contains the result of this query.

![Debugging output with the data prop highlighted.](/v3/img/blog/06-data-prop.png)

Alright! Now that we have content, we need to write some markup to actually display it in a reader-friendly way.

### Display the content in the page template

WordPress returns markup and HTML-encoded entities, so we need to use `dangerouslySetInnerHTML` to make sure our content displays properly.

To use our page data, we can grab just the `data` prop in our component, then drill down to the page content and display those values:

```
  {% raw %}  import React from "react"  import { graphql } from "gatsby"
  export const query = graphql`    query($id: ID!) {      wpgraphql {        page(id: $id) {          title          content        }      }    }  `
- const PageTemplate = (...args) => {-   return <pre>{JSON.stringify(args, null, 2)}</pre>+ const PageTemplate = ({ data }) => {+   const page = data.wpgraphql.page+   return (+     <>+       <h1 dangerouslySetInnerHTML={{ __html: page.title }} />+       <div dangerouslySetInnerHTML={{ __html: page.content }} />+     </>+   )  }
  export default PageTemplate  {% endraw %}
```

Save and check out `http://localhost:8000` — it’s working!

![WordPress content displaying on a simple page in Gatsby.](/v3/img/blog/07-simple-page-display.png)

## Add a shared layout and styles

{% renderFile ”./src/\_includes/pages/blog/egghead-embed.vue”, { eggheadVideoTitle: “Add a shared layout component and global styles to a Gatsby site”, eggheadVideoUrl: “[https://egghead.io/lessons/gatsby-add-a-shared-layout-component-and-global-styles-to-a-gatsby-site](https://egghead.io/lessons/gatsby-add-a-shared-layout-component-and-global-styles-to-a-gatsby-site)” } %}

To make our Gatsby site look more like a real website, we need to add a layout — a shared header in this case — and styles.

### Create a shared `Layout` component

Creating a Layout component requires a standard React component that wraps whatever content is passed to it (as the `children` prop) with markup to give the page semantic structure.

Create `src/components/layout.js`, then add the following code:

```
import React from "react"import { Link } from "gatsby"
const Layout = ({ children }) => {  return (    <>      <header>        <Link to="/" className="home">          Migrate WordPress to the Jamstack        </Link>      </header>      <main>{children}</main>    </>  )}
export default Layout
```