5 min read

WordPress as a CMS for Astro.js Sites: The Best of Both Worlds

Fetching content from WordPress into Astro static sites using WPGraphQL or REST API for the best of both worlds: familiar CMS and modern frontend

WordPress as a CMS for Astro.js Sites: The Best of Both Worlds

If you’ve ever wished you could pair WordPress’s unbeatable content editing experience with the blistering speed of a modern static site, I’ve got good news: you absolutely can. Welcome to the world of headless WordPress, where your favourite CMS becomes a powerful content engine behind an Astro.js frontend.

I’ve been running this exact setup for several projects, and honestly, it’s a game-changer. Your content editors get the familiar WordPress dashboard they know and love, while your visitors get a lightning-fast static site built with Astro. Let’s walk through how to wire it all up.

Why Go Headless with WordPress?

WordPress powers over 40% of the web for a reason — it’s an incredible content management system. The editing experience, the plugin ecosystem, the media library, custom post types, ACF fields… it’s a mature platform that’s hard to beat for content sourcing.

But let’s be honest: WordPress on the frontend can be sluggish. Theme rendering, database queries on every page load, and plugin bloat all take their toll. That’s where the headless approach comes in.

In a headless setup, WordPress handles only the content. There’s no theme rendering the frontend. Instead, WordPress exposes your content through an API — either the built-in REST API or the more flexible WPGraphQL plugin — and Astro fetches that content at build time to generate blazing-fast static HTML.

Here’s what you gain:

  • Performance: Astro generates static HTML with zero JavaScript by default. Your pages load instantly.
  • Security: Your WordPress instance doesn’t need to be publicly accessible. No theme vulnerabilities, no brute-force login attacks on your frontend.
  • Flexibility: You’re not locked into PHP templates. You can use Astro components, React, Vue, Svelte — whatever you want.
  • Familiar editing: Your content team doesn’t need to learn Markdown or Git. They just use WordPress.

Setting Up WPGraphQL for Content Sourcing

While the WordPress REST API works fine, I strongly recommend WPGraphQL for this kind of setup. It lets you request exactly the data you need in a single query — no over-fetching, no chaining multiple REST calls together.

Step 1: Install WPGraphQL

Install the WPGraphQL plugin on your WordPress site. Once activated, you’ll have a GraphQL endpoint at https://your-wordpress-site.com/graphql. The plugin also adds a GraphiQL IDE right in your WordPress admin panel, which is incredibly handy for testing queries.

Step 2: Query Your Content from Astro

Here’s a utility function you can use in your Astro project to fetch posts from WordPress:

// src/lib/wordpress.ts
const WP_GRAPHQL_URL = import.meta.env.WP_GRAPHQL_URL;

export async function getAllPosts() {
  const response = await fetch(WP_GRAPHQL_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: `
        query GetAllPosts {
          posts(first: 100, where: { status: PUBLISH }) {
            nodes {
              id
              title
              slug
              date
              excerpt
              content
              featuredImage {
                node {
                  sourceUrl
                  altText
                }
              }
              categories {
                nodes {
                  name
                  slug
                }
              }
            }
          }
        }
      `,
    }),
  });

  const json = await response.json();
  return json.data.posts.nodes;
}

export async function getPostBySlug(slug: string) {
  const response = await fetch(WP_GRAPHQL_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: `
        query GetPostBySlug($slug: ID!) {
          post(id: $slug, idType: SLUG) {
            title
            content
            date
            featuredImage {
              node {
                sourceUrl
                altText
              }
            }
            author {
              node {
                name
              }
            }
          }
        }
      `,
      variables: { slug },
    }),
  });

  const json = await response.json();
  return json.data.post;
}

Notice how with WPGraphQL, you ask for exactly the fields you need. No wasted bandwidth, no unnecessary data processing. This is especially important at build time when Astro is generating potentially hundreds of pages.

Step 3: Generate Static Pages

Now let’s use those functions in an Astro page to generate individual blog post routes:

---
// src/pages/blog/[slug].astro
import { getAllPosts, getPostBySlug } from "../../lib/wordpress";
import BaseLayout from "../../layouts/BaseLayout.astro";

export async function getStaticPaths() {
  const posts = await getAllPosts();
  return posts.map((post) => ({
    params: { slug: post.slug },
  }));
}

const { slug } = Astro.params;
const post = await getPostBySlug(slug);
const formattedDate = new Date(post.date).toLocaleDateString("en-GB", {
  year: "numeric",
  month: "long",
  day: "numeric",
});
---

<BaseLayout title={post.title}>
  <article class="post">
    {post.featuredImage && (
      <img
        src={post.featuredImage.node.sourceUrl}
        alt={post.featuredImage.node.altText}
        class="featured-image"
      />
    )}
    <h1 set:html={post.title} />
    <time datetime={post.date}>{formattedDate}</time>
    <p class="author">By {post.author.node.name}</p>
    <div class="content" set:html={post.content} />
  </article>
</BaseLayout>

That’s it. Astro’s getStaticPaths function fetches all your posts at build time, generates a route for each slug, and outputs pure static HTML. No client-side JavaScript, no hydration overhead — just fast pages.

What About the REST API?

If you’d rather not install WPGraphQL — maybe you don’t have control over the WordPress plugins, or you prefer working with REST — the built-in WordPress REST API works perfectly well too. Here’s the equivalent approach:

// src/lib/wordpress-rest.ts
const WP_API_URL = import.meta.env.WP_API_URL; // e.g., https://your-site.com/wp-json/wp/v2

export async function getAllPosts() {
  const response = await fetch(
    `${WP_API_URL}/posts?per_page=100&_embed`
  );
  const posts = await response.json();

  return posts.map((post: any) => ({
    id: post.id,
    title: post.title.rendered,
    slug: post.slug,
    date: post.date,
    excerpt: post.excerpt.rendered,
    content: post.content.rendered,
    featuredImage: post._embedded?.["wp:featuredmedia"]?.[0]?.source_url || null,
    author: post._embedded?.author?.[0]?.name || "Unknown",
  }));
}

The _embed parameter is the key here — it tells WordPress to include related resources (featured images, author info, categories) in a single response. Without it, you’d need to make separate requests for each resource, which gets messy fast.

That said, the REST API does return a lot of data you probably don’t need. This is exactly the problem that WPGraphQL solves with its query-based approach. For most headless setups, I’d recommend WPGraphQL as the primary content sourcing method.

Tips for a Production Setup

Before you ship, a few things worth considering:

  • Webhooks for rebuilds: Use a plugin like WP Webhooks to trigger a rebuild on your hosting platform (Netlify, Vercel, Cloudflare Pages) whenever content is published or updated. This keeps your static site in sync with WordPress.
  • Image optimisation: WordPress images can be heavy. Consider using Astro’s built-in <Image /> component or a service like Cloudinary to optimise images at build time.
  • Preview mode: One downside of static sites is that editors can’t instantly preview changes. Look into Astro’s server-side rendering (SSR) mode for a preview route that fetches draft content on demand.
  • Lock down WordPress: Since your WordPress instance is only serving as an API, you can restrict public access, disable the default theme, and even put it behind a VPN or IP allowlist.

Conclusion

Pairing WordPress as a headless CMS with Astro.js gives you an incredibly powerful stack. Your content team gets the editing experience they’re already comfortable with, and your users get a fast, modern website that scores perfect Lighthouse scores without breaking a sweat.

If you’re already running a WordPress site and want to modernise the frontend without migrating all your content, this is the path of least resistance. Start by installing WPGraphQL, spin up an Astro project, and fetch a few posts. You’ll be amazed at how quickly it all comes together.

Next steps to try:

  1. Set up a local WordPress instance with WPGraphQL and experiment with the GraphiQL IDE
  2. Create an Astro project and build out the content fetching layer
  3. Add webhook-triggered rebuilds for automatic deployments
  4. Explore Advanced Custom Fields (ACF) with WPGraphQL for structured content beyond standard posts

Happy building — and if you have questions, drop them in the comments below!