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) withDisallow: /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().
3. Pull in featured images + media
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. Useset: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
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
- Astro WordPress integration
- Astro components reference
- Compare both stacks: WordPress vs Astro