Accepting new 2025 CodeWheel AI engagements for AI web, security, and commerce programs.
Back to Blog

WordPress to Astro Migration: Static Speed with Modern Block Components

How to migrate WordPress sites to Astro—performance-focused architecture, modern tooling, and safer content pipelines.

WordPress to Astro Migration: Static Speed with Modern Block Components
Matt Owens
Matt Owens
12 May 2025 - 9 min read

WordPress to Astro Migration: Static Speed with Modern Block Components

WordPress powers a massive slice of the internet, but teams hitting scale run into Core Web Vitals alerts, plugin debt, and slow authoring workflows. Astro gives you static-first performance, component islands, and the freedom to rebuild page sections with modern block components instead of patching legacy PHP. This guide covers the straightforward migration path I use when we need clean shadcn blocks, fast re-builds, and predictable deployment on Vercel—without bolting on extra AI or MCP systems you don’t want.

Migration at a glance

  1. Audit & content freeze: inventory plugins, custom post types, shortcodes, and REST endpoints. Decide what stays, what gets rewritten, and what dies.
  2. Data extraction: export the entire site to JSON (WPGraphQL, REST API, or WP-CLI) so you have every post, page, menu, and custom field in one portable snapshot.
  3. Astro scaffolding: initialize Astro + Tailwind/shadcn, configure Vercel deployment, and map layouts/partials to WordPress templates using MDX or JSON-fed components.
  4. Feature parity: recreate marketing pages, landing flows, blog, and any gated content. Replace jQuery/carousel plugins with modern components.
  5. Enhancements: add Shopify for e-commerce, Algolia/Pagefind for search, Formspree for forms, or Plausible for analytics—only what your content site actually needs.
  6. Component pass: build reusable shadcn primitives (hero, feature rows, testimonials) so editors can assemble new sections without touching legacy shortcodes.
  7. Cutover: run both stacks, hydrate Astro with production data, then flip DNS once analytics look steady.

What Astro is NOT for

Before migrating, understand Astro’s strengths and limitations:

✅ Astro is great for:

  • Marketing sites with blog/docs
  • Content-heavy sites needing Core Web Vitals 100
  • Static landing pages with minimal JavaScript
  • Sites where editors rarely need WYSIWYG (or can use MDX/headless CMS)

❌ Astro is NOT for:

  • SaaS platforms with user auth (use Next.js + Clerk/Auth0)
  • Applications with billing/subscriptions (use Next.js + Stripe)
  • Dynamic dashboards or real-time data (use Next.js + server components)
  • Heavy client-side interactions or app-like workflows

When to choose Next.js over Astro:

If your WordPress site has:

  • WooCommerce with complex checkout flows → Next.js + Shopify/Stripe
  • User accounts and login → Next.js + Clerk
  • AI features (RAG, chat, agents) → Next.js + RAG pipelines
  • Real-time dashboards → Next.js + server components

For those cases, see our WordPress modernization services and choose the Next.js migration path—or jump directly to the WordPress to Next.js guide.

Should you keep WordPress or go fully static?

You have two migration paths, and the choice depends on your content team’s workflow:

Option 1: Keep WordPress Headless (Best for Active Content Teams)

When to choose this:

  • Editors are comfortable with WordPress and don’t want to learn Git/MDX
  • You publish 3+ posts per week and need a visual editor
  • You have custom fields, taxonomies, or complex content types
  • Multiple non-technical stakeholders contribute content

How it works:

  • WordPress stays as your CMS backend (headless mode)
  • Install WPGraphQL or use WordPress REST API
  • Astro fetches content at build time via API
  • Content editors use familiar WordPress interface
  • Astro rebuilds automatically on publish (Vercel webhooks)

Tech stack:

// astro.config.mjs (excerpt)
export default defineConfig({
  integrations: [
    wordpress({
      endpoint: "https://your-wp-site.com/graphql",
      auth: process.env.WP_AUTH_TOKEN,
      // map routes, handle images, and trigger rebuild webhooks here
    }),
  ],
});

Pros:

  • ✅ Editors keep familiar WordPress UI
  • ✅ No retraining on Git/MDX workflows
  • ✅ Preview functionality built-in
  • ✅ Complex custom fields work out of the box

Cons:

  • ❌ WordPress hosting cost remains
  • ❌ WP security patches still required
  • ❌ Another dependency to maintain

Option 2: Export to Static (Best for Low-Volume Sites)

When to choose this:

  • You publish <5 posts per month
  • Technical team comfortable with Git/MDX
  • Want to eliminate WordPress entirely
  • Simple content structure (posts, pages, basic metadata)

How it works:

  • Export WordPress content to JSON/MDX once
  • Store content in src/content/blog/ as MDX files
  • Editors commit content via Git (or use Netlify CMS / Decap CMS)
  • No WordPress backend required post-migration

Pros:

  • ✅ Zero WordPress hosting/maintenance
  • ✅ Content versioned in Git
  • ✅ Maximum performance (no API calls)
  • ✅ Eliminate security surface

Cons:

  • ❌ Non-technical editors need training
  • ❌ No visual preview without extra tooling
  • ❌ Complex content structures harder to manage

For most teams, I recommend starting with headless WordPress (Option 1) to avoid disrupting editorial workflows during the frontend migration. Once editors see the new Astro site’s performance and your team stabilizes the new stack, you can export to static and decommission WordPress.

This staged approach:

  1. Week 1-4: Build Astro frontend pulling from WordPress GraphQL
  2. Week 5-6: Parallel-run both stacks, train editors on Astro previews
  3. Week 7-8: Switch DNS to Astro, keep WordPress headless
  4. Month 3+: Evaluate static export if content velocity drops

If you’re handling 10+ stakeholder edits per week, headless WordPress indefinitely might be your best option. If you’re a lean team publishing occasionally, skip to static from day one.

Content strategy: Choosing your workflow

1. Install WPGraphQL plugin

# In WordPress admin
Plugins → Add New → Search "WPGraphQL"
Install + Activate

2. Configure Astro to fetch WordPress data

// src/lib/wordpress.ts
import { GraphQLClient } from "graphql-request";

const client = new GraphQLClient(process.env.WORDPRESS_GRAPHQL_ENDPOINT, {
  headers: {
    Authorization: `Bearer ${process.env.WORDPRESS_AUTH_TOKEN}`,
  },
});

export async function getPosts() {
  // Shape the query to fetch the minimal fields you need (ids, slugs, dates, etc.)
  const query = /* GraphQL */ `
    query GetPosts {
      posts {
        nodes {
          id
          slug
          title
        }
      }
    }
  `;

  return client.request(query);
}

3. Create Astro content collections

// src/pages/blog/[slug].astro
---
import { getPosts } from '@/lib/wordpress'

export async function getStaticPaths() {
  const { posts } = await getPosts()

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

const { post } = Astro.props
---

<Layout>
  <h1>{post.title}</h1>
  <article set:html={post.content}></article>
</Layout>

4. Set up Vercel webhooks for auto-rebuild

// In WordPress: Settings → WPGraphQL → Webhooks
// Add webhook: https://api.vercel.com/v1/integrations/deploy/[YOUR_HOOK]

// Triggers rebuild on:
// - Post publish
// - Post update
// - Post delete

Static Export Setup (For Low-Volume Sites)

If you’re exporting WordPress to static MDX files:

1. Export WordPress data

# Use WP-CLI to export all posts
wp post list --post_type=post --format=json > posts.json

# Or use WordPress REST API
curl https://your-site.com/wp-json/wp/v2/posts > posts.json

2. Convert to MDX

// scripts/convert-wp-to-mdx.ts
import fs from "fs";
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeRemark from "rehype-remark";
import remarkStringify from "remark-stringify";

async function convertPosts() {
  const posts = JSON.parse(fs.readFileSync("posts.json", "utf-8"));

  for (const post of posts) {
    const markdown = await unified()
      .use(rehypeParse)
      .use(rehypeRemark)
      .use(remarkStringify)
      .process(post.content.rendered);

    const frontmatter = `---
title: "${post.title.rendered}"
date: ${post.date}
slug: ${post.slug}
categories: ${JSON.stringify(post.categories)}
---

${markdown.value}`;

    fs.writeFileSync(`src/content/blog/${post.slug}.mdx`, frontmatter);
  }
}

convertPosts();

3. Run conversion

tsx scripts/convert-wp-to-mdx.ts

Which approach should you use?

Choose Headless WordPress if:

  • Content team publishes 3+ times per week
  • Multiple non-technical editors need access
  • You use ACF (Advanced Custom Fields) or custom taxonomies
  • Editors need preview functionality

Choose Static Export if:

  • Publishing <5 posts per month
  • Comfortable with Git workflows
  • Want to eliminate WordPress entirely
  • Simple content structure (title, body, date, tags)

Pro tip: You can start headless and migrate to static later. This lets editors adjust to the new frontend without learning Git immediately, then transition when ready.

  • Astro maintains an up-to-date guide on supported headless CMS options—Sanity, Contentful, Hygraph, Payload, and more—at docs.astro.build/en/guides/cms/.
  • Keep WordPress in read-only mode until editors confirm parity; once Astro is live, decommission WP or keep it offline as an archive.
  • If you need search, use Algolia or Vercel Postgres rather than custom plugin code. No need for vector databases unless you truly plan to add semantic search later.
  • After you choose a path, review the Rails modernization case study for an example of staged modernization that balances frontend and backend updates.

Performance + hosting

  • Astro builds static files plus island components that hydrate only where needed.
  • Deploy to Vercel for edge caching, ISR, and preview branches.
  • Use Cloudflare or Fastly in front of Vercel if you need additional WAF/zero-trust access.

Build structure without the bloat

  • shadcn + block components: Define a base Block component (hero, feature list, testimonial, CTA) and store page layouts as JSON arrays. Each block maps to a typed component so Astro can render entire pages from data exported out of WordPress.
  • Shared layout primitives: Recreate headers/footers using Astro layout slots and tie them to site-wide settings stored in JSON or src/content/config.ts.
  • Client-side sprinkles only where needed: Use Astro Islands for calculators, carousels, or forms—everything else stays static HTML.

Realistic Astro integrations

Astro sites stay lean. Only add integrations your content site truly needs.

E-commerce (if migrating from WooCommerce)

Option 1: Shopify headless

  • Keep products in Shopify
  • Astro fetches product data at build time
  • Checkout redirects to Shopify hosted checkout
  • Best for <100 products, infrequent updates

Option 2: Snipcart

  • Add Snipcart script to Astro
  • Products defined in Astro content collections
  • Cart overlay handles checkout
  • Best for simple catalogs
// src/content/products/example.mdx
---
id: 'product-1'
name: 'Example Product'
price: 29.99
image: '/images/product.jpg'
---

Option 1: Pagefind (recommended)

  • Static search index generated at build time
  • Zero external dependencies
  • Fast, client-side search
npx pagefind --site dist

Option 2: Algolia

  • For large content sites (1000+ pages)
  • Requires API key management
  • Monthly costs after free tier

Forms

Option 1: Formspree

<form action="https://formspree.io/f/your-id" method="POST">
  <input type="email" name="email" placeholder="you@example.com" required />
  <button type="submit">Subscribe</button>
</form>

Option 2: Netlify Forms

  • If deploying to Netlify
  • Zero config, just add netlify attribute

Option 3: API Route

// src/pages/api/contact.ts
import type { APIRoute } from "astro";

export const POST: APIRoute = async ({ request }) => {
  const data = await request.formData();
  // send to email service, CRM, etc.
  return new Response(JSON.stringify({ success: true }), {
    headers: { "Content-Type": "application/json" },
  });
};

Analytics

Plausible (recommended)

  • Privacy-focused, GDPR compliant
  • Lightweight script (~1KB)
  • No cookie consent needed
<script
  defer
  data-domain="your-site.com"
  src="https://plausible.io/js/script.js"
></script>

PostHog (only if needed)

  • Feature flags, session recording, experimentation
  • Adds ~50KB JavaScript
  • Use only when you need these features

Headless WordPress: Technical implementation

Authentication & security

// src/middleware/auth.ts
export function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.replace("Bearer ", "");

  if (!token || token !== process.env.WORDPRESS_PREVIEW_SECRET) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  next();
}

Preview mode for editors

// src/pages/api/preview.ts
import type { APIRoute } from "astro";
import { getPostBySlug } from "@/lib/wordpress";

export const GET: APIRoute = async ({ request, cookies, redirect }) => {
  const url = new URL(request.url);
  const secret = url.searchParams.get("secret");
  const slug = url.searchParams.get("slug");

  if (secret !== process.env.WORDPRESS_PREVIEW_SECRET) {
    return new Response("Invalid secret", { status: 401 });
  }

  const post = await getPostBySlug(slug, { status: "draft" });

  if (!post) {
    return new Response("Post not found", { status: 404 });
  }

  cookies.set("preview-mode", "1", {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
  });

  return redirect(`/blog/${slug}`);
};

Incremental static regeneration (ISR)

// astro.config.mjs (ISR excerpt)
export default defineConfig({
  output: "hybrid",
  adapter: vercel({
    isr: {
      expiration: 60 * 60 * 24,
      exclude: ["/admin/*"],
    },
  }),
});

Caching strategy

// src/lib/wordpress.ts (caching excerpt)
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_URL,
  token: process.env.UPSTASH_REDIS_TOKEN,
});

export async function getPosts() {
  const cached = await redis.get("posts");
  if (cached) return JSON.parse(cached as string);
  // fetch via GraphQL/REST and cache
}

Testing & launch

  • Replace WordPress plugin auth with a simple API route or Clerk if you truly need login. Otherwise, static pages mean far fewer attack surfaces.
  • Run Playwright smoke tests for primary conversion flows (landing pages, forms, contact CTAs).
  • Automate redirects using the WordPress JSON export so SEO equity follows to the Astro routes.
  • Back up the original WordPress DB/files before decomissioning; I keep a wp-export.json + uploads archive in object storage for safety.

WordPress to Astro migration FAQ

Can I keep using WordPress as my CMS?

Yes—this is called “headless WordPress.” Install WPGraphQL, configure Astro to fetch content via GraphQL, and editors keep using the familiar WordPress interface. Astro rebuilds automatically on publish via Vercel webhooks.

How long does a typical migration take?

  • Headless setup: 2-3 weeks (WordPress stays, Astro frontend built around it)
  • Full static export: 4-6 weeks (includes content conversion, editor training, WordPress decommission)

Will I lose SEO rankings?

Not if you handle redirects properly. Export your WordPress URL structure, create 301 redirects in Astro, and submit the new sitemap to Google Search Console. If done correctly, rankings transfer within 2-4 weeks.

What about WordPress plugins?

Most plugins become unnecessary:

  • Yoast SEO → Astro has built-in SEO support
  • WP Rocket/caching → Not needed (static sites are cached by default)
  • Contact Form 7 → Replace with custom Astro form + API route
  • WooCommerce → Migrate to Stripe Checkout or Shopify

For custom functionality, rewrite as Astro components or API routes.

Can non-technical editors still publish content?

  • Headless WordPress: Yes, they use WordPress exactly as before
  • Static export: No, they’ll need to learn Git/MDX (or you can add Netlify CMS / Decap CMS for a visual editor)

What if I have 1,000+ posts?

Headless WordPress handles this easily via GraphQL pagination. Static export works too, but build times increase—you may need ISR for large sites.

Will my WordPress theme work in Astro?

No—Astro doesn’t run PHP. You’ll rebuild layouts as Astro components using shadcn/Tailwind. Most teams use this as an opportunity to modernize design and remove cruft.

When to use Next.js instead of Astro

If your WordPress site needs any of these, skip Astro and head straight to Next.js:

  • User authentication: membership sites, gated content, customer dashboards → Next.js + Clerk + Supabase RLS
  • Complex e-commerce: carts, subscriptions, order history → Next.js + Shopify Hydrogen or Stripe Billing
  • AI features: RAG-powered search, AI chat assistants, recommendations → Next.js + pgvector + Anthropic/OpenAI
  • Real-time features: live dashboards, collaboration, chat → Next.js + WebSockets or Supabase Realtime
  • Heavy client-side interactions: drag-and-drop builders, complex forms → Next.js + React state management

See Next.js AI Platform Development or the Next.js path on the WordPress modernization page.

Ready to migrate from WordPress to Astro?

Astro is ideal for content-focused sites prioritizing performance, SEO, and developer experience—without the complexity of user auth, billing, or AI-heavy roadmaps.

Option 1: WordPress Migration Assessment

2-hour deep-dive reviewing your WordPress site, content strategy, and recommending the best migration path (Astro, Next.js, or headless WordPress).

What’s included:

  • Current WordPress architecture review
  • Content inventory and migration complexity analysis
  • Performance and SEO baseline assessment
  • Technology recommendation (Astro vs Next.js vs headless)
  • Migration roadmap with timeline estimates
  • Cost estimate for full migration
  • Plugin replacement strategy

Timeline: 1 week Investment: $800

Request Assessment →


Option 2: WordPress to Astro Migration

Complete migration from WordPress to Astro with modern design, optimal performance, and SEO preservation.

What’s included:

  • Content export and MDX conversion (posts, pages, media)
  • Custom Astro component development with shadcn/Tailwind
  • Design modernization and responsive layouts
  • SEO optimization (meta tags, redirects, sitemap)
  • Contact forms and API routes
  • Vercel deployment and CDN configuration
  • Analytics setup (PostHog, Plausible, or Google Analytics)
  • Post-launch support (2 weeks)

Timeline: 3-6 weeks depending on content volume Investment:

  • Small site (≤50 pages): $8,000-$12,000
  • Medium site (50-200 pages): $12,000-$20,000
  • Large site (200+ pages): $20,000-$35,000

Discuss Your Migration →


Option 3: Headless WordPress Implementation

Keep WordPress as your CMS but replace the frontend with modern Astro or Next.js.

What’s included:

  • WordPress GraphQL API setup (WPGraphQL)
  • Astro or Next.js frontend development
  • Content preview and editing workflow
  • Image optimization and lazy loading
  • Incremental static regeneration (if Next.js)
  • Editor training and documentation
  • Deployment and hosting setup

Timeline: 4-8 weeks Investment: $15,000-$30,000

Explore Headless WordPress →


Option 4: Next.js Migration (For Complex Sites)

For WordPress sites needing auth, e-commerce, or AI features—migrate to Next.js instead of Astro.

Best for:

  • Membership sites with user authentication
  • E-commerce with Stripe or Shopify
  • AI-powered search or chat
  • Real-time features or complex interactivity

Timeline: 8-16 weeks Investment: $30,000-$80,000

View Next.js Platform Development →


Not sure which option fits?

Book a free 30-minute consultation to review your WordPress site and get personalized recommendations.

Related Articles

Dig deeper into adjacent topics across RAG, AI security, and platform architecture.