Feeding your WordPress Blog into Astro using WPGraphQL

Development / Drew Hagni

Prerequisites: WordPress (basic knowledge)
Required tools: WPGraphQL plugin (free)

Introduction

Like many other front-end devs, I’ve been experimenting with Astro recently to see what all of the buzz is about. Long story short, I think it lives up to the hype.

Coming from a LAMP stack and building most client sites hand-in-hand with WordPress, Node-based frameworks are always a bit of a paradigm shift for me. Astro seems to bridge that gap nicely though. It brings many of the modern conveniences of other JS frameworks, without the SPA baggage that typically comes with them.

Furthermore, it seems to be a good option for clients who are familiar with WordPress, and who prefer its interface for managing their site’s content. Building with Astro doesn’t mean you have to abandon WordPress altogether (or at all).

Alright, enough preamble. How can you get your WordPress blog posts into your Astro site? Let’s dig in.

Step 1: Install Astro

If you hop over to astro.build you’ll find some very simple steps to get up and running locally. Assuming you’ve got NodeJS installed on your machine, all you’ll really need to do is run npm create astro@latest in your favorite terminal app and follow the (admittedly adorable) instructions. There are just a few questions that will be asked of you, but the only one of real consequence for what we’re doing is the second question: “How would you like to start your new project?”

You can either start with the blog template and strip out what you don’t need, or start blank and build from the ground up. For the purposes of this walk-through, I’d suggest starting with the blog template as it will give us some basic styling and structure right off the bat, but starting blank is perfectly fine too.

Once set up, run npm run dev in the terminal and visit the provided URL to see your newly birthed website.

Step 2: Install WPGraphQL

If you have an existing WordPress site with a blog, you’ll be able to hit the ground running. If not, I won’t cover WordPress installation in this tutorial, but you can get everything set up in a matter of minutes and grab some sample content to get up to speed.

Note: In case it isn’t clear, this needs to be a separate website, hosted live on the Internet.

Next you’ll just want to install the WPGraphQL plugin on your site and navigate to the GraphQL IDE panel via the WP Dashboard. Here you can set up your query. Click “Query Composer” and “Add New Query” to start.

Note: If you’re using Advanced Custom Fields on your WordPress site, you’ll likely also want to install the WPGraphQL for ACF plugin.

I won’t cover all of the details of the GraphQL syntax, but the authors of the plugin have some great documentation specific to WPGraphQL. More likely than not, you’ll be able to drill down with the toggles in the IDE, hit “Execute Query”, and see the output to get a sense of how it all works.

Play around to your heart’s content until you’re returning the data you need for your project. Your query will certainly differ, but here’s ours as an example:

query GET_PAGINATED_POSTS($first: Int, $last: Int, $after: String, $before: String, $categoryName: String = "") {
  posts(
    first: $first
    last: $last
    after: $after
    before: $before
    where: {categoryName: $categoryName}
  ) {
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
    edges {
      cursor
      node {
        id
        postId
        title
        slug
        excerpt
        featuredImage {
          node {
            mediaItemUrl
            srcSet
            sizes
            altText
          }
        }
        content(format: RENDERED)
        author {
          node {
            firstName
            lastName
          }
        }
        postImages {
          headerImages {
            desktopImage {
              node {
                altText
                mediaItemUrl
                sizes
                srcSet
              }
            }
            mobileImage {
              node {
                altText
                mediaItemUrl
                sizes
                srcSet
              }
            }
            tabletImage {
              node {
                altText
                mediaItemUrl
                sizes
                sourceUrl
              }
            }
          }
        }
        blogPosts {
          headerImage {
            node {
              altText
              mediaItemUrl
              sizes
              srcSet
            }
          }
        }
        categories {
          nodes {
            name
          }
        }
      }
    }
  }
}

Step 3: Building our fetch utility

Alright, now that we’ve got our query, let’s hop back into our Astro project. We’re going to build a quick asynchronous function to fetch our data using the query we built in WPGraphQL. First, create a data subdirectory, if you don’t already have one in your src folder, and then create a new wordpress.ts file inside. Here’s the structure for that file:

interface WPGraphQLParams {
 query: string;
 variables?: object;
}

export async function wpquery({ query, variables = {}}: WPGraphQLParams) {
 const res = await fetch('https://[yourdomain.com]/graphql', {
   method: 'post',
   headers: {
     'Content-Type': 'application/json',
   },
   body: JSON.stringify({
     query,
     variables,
   })
 })

 if (!res.ok) {
   console.error(res)
   return {}
 }

 const { data } = await res.json()

 return data
}

export const getAllPostsStr =
 `[your query here]`

This is mostly pretty standard syntax for asynchronous functions. You’ll need to replace yourdomain.com with your actual WordPress site URL. Then at the bottom, is where we’ll paste our query we constructed in WPGraphQL.

Step 4: Displaying our feed

Now that we have our fetch utility ready to go, we can start populating our main Blog page with posts. If you don’t already have a blog subdirectory, go ahead and create one inside the pages directory. Then create an index.astro file inside of that folder, if you don’t already have one.

First, we need to import our function and query in the frontmatter. Frontmatter is Astro-specific syntax where we can import files/data and set up variables to use in our page content.

After our imports, we’ll call our wpquery function, injecting the getAllPostsStr query.

import { wpquery, getAllPostsStr } from "../../data/wordpress"

const data = await wpquery({
 query: getAllPostsStr,
 variables: { first: 1000, after: null }
})

And then we can loop through those posts and output the data like so:

{data.posts.edges.map((post) => {
  return (
    <div class="post">
      <a href={`/blog/${(post.node.slug).replaceAll('//', '/')}`}>
        <div class="post__image ">
          <figure class="post__figure">
            <img class="post__img" src={post.node.postImages.headerImages.desktopImage ? post.node.postImages.headerImages.desktopImage.node.mediaItemUrl : post.node.featuredImage ? post.node.featuredImage.node.mediaItemUrl : ''} srcset={post.node.postImages.headerImages.desktopImage ? post.node.postImages.headerImages.desktopImage.node.srcSet : post.node.featuredImage ? post.node.featuredImage.node.srcSet : ''} alt={post.node.postImages.headerImages.desktopImage ? post.node.postImages.headerImages.desktopImage.node.altText : post.node.featuredImage ? post.node.featuredImage.node.altText : ''} />
          </figure>
        </div>
        <div class="post__content">
          <h3 class="post__title">{post.node.title}</h3>
          <div class="post__author">
            <span>{`${post.node.author.node.firstName} ${post.node.author.node.lastName}`}</span>
          </div>
        </div>
      </a>
    </div>
  )
})}

Note: Ours looks a little more complex due to the variety of featured image fields available for our blog posts.

Once you’re satisfied with your setup, go ahead and hit save and review the results in your browser. You should see your posts as a list on the Blog page. What you see here depends greatly on what data you’re querying and how you’re formatting/styling said data. If it all looks good, you can move on to the final step (if not, adjust your query and/or apply additional styles until you’re satisfied).

 

Step 5: Handling single posts

We’ll do something similar in the [...slug.astro] file. The bracket and ellipse notation indicates a special kind of Astro file that will be generated dynamically based on the getStaticPaths function. We’ll use our fetch utility and query once more in the frontmatter, this time nested inside the getStaticPaths function:

import { getPostsForSlug, wpquery } from '../../data/wordpress';

export async function getStaticPaths() {
  const data = await wpquery({
   query: getPostsForSlug,
 })

 return data.posts.nodes.map((post: any) => {
   return {
     params: { slug: post.slug },
     props: { post },
   }
 })
}

const { post } = Astro.props

And then we can simply output our data in the markup for our post template:

<!-- Featured Image -->
<figure class="featured-image">
  {post['featuredImage'] &&
    <img
      src={post['featuredImage']['node']['mediaItemUrl']}
      srcset={post['featuredImage']['node']['srcSet']}
      alt={post['featuredImage']['node']['alt']}
    />
  }
</figure>
<!-- Content -->
<div class="post">
  <Fragment set:html={post['content']} />
</div>

 Note: Fragment set:html={post['content']} is some special Astro syntax that will render our HTML content correctly.

If you’ve done everything correctly up to this point, you should be able to click into your posts and see them in full—complete with HTML markup and images. Woohoo! 🎉

 

Caveats

There are a few drawbacks that I’ve encountered with this approach that I wanted to note in conclusion.

Styling
While testing with our actual blog I saw a few instances where styling wasn’t translated correctly when rendered in my Astro site. In just about every case though, I found some funky markup in the original WordPress post and once cleaned up things started working as expected once again.

A similar thing happened with some embedded content—notably a Codepen embed, but I found an alternative embed method from Codepen that seemed to work for both WordPress and Astro.

Of course, if you have any plugins that apply to your post like Prismatic or similar, you’ll also need to install equivalent plugins on your Astro site and apply them accordingly.

Fetching at build time
It’s important to note that the way we’ve set up Astro it is fully static, meaning that our GraphQL fetch will only run at build time, so publishing new posts to WordPress will not automatically update our Astro site. We’ll have to re-run our build to pull new posts.

Astro brings a lot of flexibility to the table in terms of SSG vs. SSR modes and a myriad of adapters, so if this is a deal-breaker for your project, there’s likely a way to pivot to something more reactive within Astro—but that’s beyond the scope of this tutorial.

Post limit
I’m certain there’s a better way to handle this, but you may have noticed in our wpquery call on our main blog feed we have: variables: { first: 1000, after: null }. This means we’re limiting our query to 1,000 posts, which gives us plenty of wiggle room for our particular blog. In our case, we’re hiding the majority of posts and revealing more via JS on the Astro side of things with a button click.

That approach may not suit your project, however. You may have more than 1,000 posts to query and/or loading that many posts all at once may not be performant. In that case, a paginated approach is likely a better option. Again, out of scope for this walk-through, but very likely achievable.

All in all, I was pleasantly surprised by the process of pulling posts from WordPress and integrating them into Astro. It gives us the best of both worlds for our clients, and for us as developers, without sacrificing too much on either side.

Good luck with your next Astro project!

Recent Posts