Guide for Enterprise

How to Use Astro With WordPress

Step-by-step: pair Astro with WordPress using WPGraphQL, an Astro content loader, builds on publish, editor previews, and deployment to Spacefast.

9 min read Updated October 10, 2026 Recommends Spacefast

Astro and WordPress are a natural pair: WordPress gives your editors the admin they already know, and Astro turns that content into fast, mostly static pages with almost no client JavaScript. This is the setup we use at Seahawk for content-heavy enterprise sites, written as the steps you would actually follow, with Spacefast as the deploy target at the end.

Before you start

You need a WordPress install you can add plugins to, a Node environment on a version current Astro supports (check the Astro docs), and a Git host. Keep WordPress on its own domain or subdomain such as cms.example.com; the Astro site will own the public domain.

Step 1: Expose content with WPGraphQL

  1. Install and activate the WPGraphQL plugin. Your endpoint is now https://cms.example.com/graphql.
  2. If you use Advanced Custom Fields, install WPGraphQL for ACF and enable “Show in GraphQL” on each field group.
  3. Open the GraphiQL IDE in the WordPress admin and run a test query:
query Posts {
  posts(first: 10) {
    nodes {
      id
      slug
      title
      date
      excerpt
      content
      featuredImage { node { sourceUrl altText } }
    }
  }
}
  1. Harden the endpoint: in WPGraphQL settings, disable public introspection in production, enable query depth limits, and restrict the endpoint to GET and POST from your build and preview origins at the CDN or WAF.

Step 2: Create the Astro project

npm create astro@latest my-site
cd my-site
npm install

Pick the minimal template. Add an .env file with the endpoint and a token for authenticated preview requests:

WP_GRAPHQL_URL=https://cms.example.com/graphql
WP_PREVIEW_TOKEN=replace-me

Never commit the token. Set it in your host’s environment variables.

Step 3: Write a content loader

Astro’s Content Layer lets you define a collection backed by any source. Create src/content.config.ts:

import { defineCollection, z } from "astro:content";

const posts = defineCollection({
  loader: async () => {
    const res = await fetch(import.meta.env.WP_GRAPHQL_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        query: `{ posts(first: 1000) { nodes {
          id slug title date excerpt content
          featuredImage { node { sourceUrl altText } }
        } } }`,
      }),
    });
    const { data } = await res.json();
    return data.posts.nodes.map((p) => ({ ...p, id: p.slug }));
  },
  schema: z.object({
    slug: z.string(),
    title: z.string(),
    date: z.string(),
    excerpt: z.string().nullable(),
    content: z.string().nullable(),
    featuredImage: z
      .object({ node: z.object({ sourceUrl: z.string(), altText: z.string().nullable() }) })
      .nullable(),
  }),
});

export const collections = { posts };

For sites over a few thousand posts, paginate with after and pageInfo.endCursor instead of one large query, and consider persisting the loader result so unchanged entries are skipped on rebuild.

Then render the collection in src/pages/[slug].astro:

---
import { getCollection, getEntry } from "astro:content";
export async function getStaticPaths() {
  const posts = await getCollection("posts");
  return posts.map((p) => ({ params: { slug: p.data.slug } }));
}
const post = await getEntry("posts", Astro.params.slug);
---
<article>
  <h1>{post.data.title}</h1>
  <Fragment set:html={post.data.content} />
</article>

WordPress block HTML renders cleanly with set:html. Sanitize it at build time if contributors can embed arbitrary markup, and rewrite internal cms.example.com links to the public domain.

Step 4: Rebuild on publish

Editors expect publish to mean live. Wire WordPress to your host’s build hook:

  1. Create a deploy hook URL in your hosting dashboard.
  2. In WordPress, add a small plugin or mu-plugin that calls the hook on transition_post_status when a post becomes published, is updated while published, or is unpublished:
add_action('transition_post_status', function ($new, $old, $post) {
  if ($new === 'publish' || $old === 'publish') {
    wp_remote_post(getenv('ASTRO_DEPLOY_HOOK'), ['blocking' => false]);
  }
}, 10, 3);
  1. Debounce on the host side if your team publishes in bursts; most platforms queue and collapse concurrent builds.

Full builds of a few thousand pages typically finish in a minute or two. If your site is much larger, split the long tail into server-rendered routes with edge caching and keep only the top pages static.

Step 5: Editor previews

Point WordPress’s Preview button at Astro:

  1. Filter preview_post_link in WordPress to return https://www.example.com/preview?id={post_id}&token={signed token}. Sign the token with a shared secret and a short expiry.
  2. Add src/pages/preview.astro as a server-rendered route (set export const prerender = false). It verifies the token, then queries WPGraphQL with asPreview: true using an application password or JWT in the Authorization header to fetch the latest revision.
  3. Render with the same layout and components as the published route.
  4. Send Cache-Control: no-store and X-Robots-Tag: noindex from the preview route.

Ship preview in the first sprint. It is the feature that earns editor trust.

Step 6: Images and media

Keep media in WordPress, but serve it well. Either proxy sourceUrl through Astro’s <Image> component with the CMS domain allow-listed in astro.config.mjs, or put the WordPress uploads folder behind your CDN with image resizing on the edge. Always carry altText through; accessibility audits will find the gap otherwise.

Step 7: Deploy

Push the repository and connect it to Spacefast. Spacefast detects Astro, sets the build command, and gives you a deploy hook URL to drop into the WordPress plugin from Step 4. Add your environment variables, map the production domain, and enable the server adapter if you used a preview route or on-demand rendering. The first deploy runs a full build; later publishes hit the hook and the site refreshes within minutes.

Before launch, confirm:

  • The CMS domain is not indexed and only serves the API and admin.
  • Redirects from legacy WordPress URLs are handled at the edge.
  • The build fails loudly if WPGraphQL is unreachable, instead of shipping an empty site.

Next step

Spacefast, a Seahawk partner, is Astro-native hosting, so the deploy hooks, environment handling, and edge caching this guide relies on are first-class rather than bolted on. If your WordPress side needs architecture help, a hardened WPGraphQL setup, or an enterprise host such as WordPress VIP behind the API, Seahawk VIP handles that half of the stack.

Next step

Deploy your Astro site on Spacefast

Spacefast, a Seahawk partner, is Astro-native hosting built for exactly this stack: WordPress as the content API, Astro at the edge.