Skip to content

How to migrate from WordPress to Astro

How do you migrate a WordPress site to Astro?

HardEstimated time: 2-5 daysLow data-loss riskFrom: wordpress To: astro

Why migrate from WordPress to Astro?

The Astro path is a hybrid: WordPress stays as the CMS (editors keep the admin UI they know), and Astro replaces the publicly-facing front-end. Astro builds static HTML at deploy time by calling the WordPress REST API, then serves that HTML from a CDN. Public requests never hit WordPress -- the WP install can be locked down to admin-IP-only or behind a VPN.

Compared to the full WordPress-to-Hugo path:

  • Editorial workflow is unchanged. Writers see Gutenberg, not Markdown files in Git.
  • Astro's component model (Astro/React/Vue/Svelte islands) gives you a real frontend toolchain -- TypeScript, modern CSS, partial hydration -- in a way Hugo's Go templates don't.
  • The performance + security wins are similar (static HTML at the edge, no public PHP).

The trade vs Hugo: more moving parts. You're now running WordPress

  • a Node build + a static deploy, all of which need to stay in sync. Worth it when the editorial team is non-technical and a single static-Markdown workflow won't fly.

Before you start

  • Astro 4+ -- earlier versions had a less-mature WordPress integration.
  • WordPress REST API enabled. Default in WP 5+. Verify by visiting https://your-site.com/wp-json/wp/v2/posts.
  • Decide where WordPress lives post-migration. Lock it to admin-IP-only via firewall, or move it to a private subdomain (cms.your-site.com) with Disallow: / in robots.txt.

Step-by-step migration

1. Initialise an Astro project

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

Pick the blog starter template -- it has the page structure (/, /blog, /blog/[slug]) you'll wire to WordPress.

2. Wire WordPress as a content source

npm install @astrojs/wordpress

In astro.config.mjs:

import { defineConfig } from 'astro/config';
import wordpress from '@astrojs/wordpress';

export default defineConfig({
  integrations: [
    wordpress({
      url: 'https://cms.your-site.com',
    }),
  ],
});

In your blog page, replace the local-file fetch with a WordPress fetch:

---
const posts = await fetch('https://cms.your-site.com/wp-json/wp/v2/posts?_embed&per_page=100')
  .then((r) => r.json());
---
<ul>
  {posts.map((p) => <li><a href={`/blog/${p.slug}`} set:html={p.title.rendered} /></li>)}
</ul>

The dynamic per-post page (src/pages/blog/[slug].astro) follows the same pattern using getStaticPaths().

WordPress media URLs in REST responses point at the WordPress origin. Either:

  • Keep WordPress public (defeats the security point), or
  • Mirror media to a CDN bucket as part of your Astro build, or
  • Use Astro's image-CDN integration (@astrojs/image) to proxy on-demand.

Most teams pick option 3 once and never look back.

4. Rebuild your theme as Astro components

Astro's component model is HTML-shaped with Vue-like script blocks. A typical post page becomes:

---
const { post } = Astro.props;
---
<article>
  <h1 set:html={post.title.rendered} />
  <time datetime={post.date}>{new Date(post.date).toLocaleDateString()}</time>
  <div set:html={post.content.rendered} />
</article>

If you used Tailwind, the same classes work in Astro. If you used WordPress block styles, those need to be re-implemented or preserved by including WordPress's emitted block CSS as a static asset.

5. Set up redirects

Same WordPress permalink problem as every other migration. WordPress: /2024/04/my-post/, Astro: /blog/my-post/. Configure your CDN / host to redirect /year/month/slug/ -> /blog/slug/ with a 301.

6. Deploy + connect WordPress webhooks

Astro static builds run on push or on schedule. To trigger a rebuild when a WordPress editor publishes a new post, install a WordPress webhook plugin (e.g., WP Webhooks) and POST to your deploy host's build webhook (Netlify/Vercel/Cloudflare all expose one).

What doesn't migrate automatically

  • WordPress comments. Astro is static; comments need a third-party (Giscus, Cusdis, Disqus).
  • Search. Add Pagefind at build time or wire Algolia.
  • WooCommerce frontend. WooCommerce data is exposed via REST, but the storefront UI doesn't transfer -- you'd be rebuilding the cart + checkout in Astro, which usually isn't worth it.
  • Form submissions. Use Formspree / Netlify Forms / Cloudflare Workers; WordPress form plugins don't translate.

Common pitfalls

  • Build-time fetching scales with post count. A WordPress site with 2000 posts means Astro fetches all 2000 at build. Pagination the REST calls + cache aggressively in CI.
  • HTML-in-content escapes catch you. WordPress posts include raw HTML in content.rendered. Use set:html= not {post.content.rendered} -- otherwise you'll see literal <p> tags as text.
  • Deploy-on-publish webhook lag. A WordPress publish triggers a build (1-3 min) before the new post appears. Set editor expectations or use Astro's incremental adapter.
  • Dropping WordPress-managed redirects. If you used a redirects plugin, export those rules first -- Astro won't know about them.

How BeaverCheck measures the difference

Avg performance: wordpress vs astroAvg performance: wordpress vs astro0255075100wordpressastro

We've audited 713 WordPress sites and 47 Astro sites. Average Lighthouse performance: WordPress 45 / 100, Astro 51 / 100, a difference of +6 points.

The headless-WordPress + Astro pattern is one of the more predictable performance wins in modern web architecture. Run a free audit on your current WordPress site before you decide.

Further reading

Related migrations

Send Feedback