A WordPress to Astro migration is two projects wearing one name. The first is moving content, which is mostly mechanical. The second is keeping the rankings that content earned, which is where sites get hurt. This guide does both, in the order that protects the second.
There is no magic converter at the end of it. The exporters below do the bulk work, an AI editor is useful for the frontmatter clean-up, and the rest is redirects and checking.
Before You Touch Anything
Inventory the site. You cannot redirect a URL you forgot existed.
- Every URL. Crawl the site with Screaming Frog or pull the sitemap at
/sitemap_index.xml(Yoast) or/wp-sitemap.xml(core) - Content types. Pages, posts, custom post types, categories, tags, authors
- Features. Contact forms, search, comments, e-commerce, gated content
- Plugins. List every active one and what it does. Most either have a static equivalent or were never needed
- Traffic. From Search Console or Analytics, the top 50 pages by organic clicks. These are the pages a broken redirect will cost you real money on
Grade the site before you start. Run it through the free Website Grader and keep the report. The point of the migration is a better second report, and you want the first one on record.
Size the job honestly. A twenty-page business site is a day or two of work. A two-hundred-post blog with custom post types is closer to a couple of weeks once redirect testing is included.
Step 1: Export the Content
Posts and Pages to Markdown
WordPress keeps content in a database; Astro wants Markdown files with frontmatter.
Option A: the XML export plus a converter. In WordPress go to Tools, then Export, and download all content as XML. Then:
npx wordpress-export-to-markdown --input=export.xml --output=src/content/blog
It asks a few questions (folder layout, date prefixes, whether to download images) and writes one Markdown file per post with the title, date, categories and tags in frontmatter. Expect to fix formatting by hand where page builders and complex blocks were involved.
Option B: an exporter plugin. The Jekyll Exporter plugin writes posts and pages as Markdown with YAML frontmatter straight from the admin. The output is aimed at Jekyll, so a few frontmatter keys will need renaming, but it is often cleaner than the XML route for plain posts.
Images
Pull wp-content/uploads/ and flatten it under public/images/ in the Astro project, then update the paths in your Markdown:
rsync -avz user@server:/var/www/html/wp-content/uploads/ ./public/images/uploads/
Images that should get Astro’s optimization (hero and post images) belong in src/assets/ instead, imported from frontmatter so the <Image> component can resize and convert them. Everything in public/ is served as-is.
Step 2: Set Up the Destination
Start from a template that already has the pages a business site needs, so the migration is content in, not site out. The free Amrify Starter is a complete site (home, services, about, blog with tags, contact, thank-you, privacy, 404) with structured data, sitemap, RSS, llms.txt and deploy configs for Netlify, Vercel and Cloudflare Pages. Amrify Pro adds trade templates and the lead engine on the same base.
# the public Starter repo opens with the product launch
git clone https://github.com/amrify/starter.git my-sites
cd my-sites && pnpm install
Site Configuration
In the Starter a site is one validated site.json plus a folder of Markdown. Set the domain, business name, logo and colors there; the layouts derive canonicals, the sitemap, JSON-LD and the social cards from it.
Content Collections
Define schemas that match what you exported. The Content Collections guide goes deeper; the shape is:
// src/content.config.ts
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
categories: z.array(z.string()).default([]),
image: z.string().optional(),
draft: z.boolean().default(false),
}),
});
Run the build after every schema change. Each failure names the file and field, which turns the clean-up into a checklist.
Step 3: Move the Content In
Posts
Copy the exported Markdown into src/content/blog/ and make the frontmatter match the schema:
---
title: "Your Post Title"
description: "A one-sentence summary for search results"
date: 2025-06-15
categories: ["tutorial"]
image: "/images/uploads/post-image.jpg"
---
The usual clean-up:
- Strip WordPress shortcodes (
[gallery],[contact-form-7 ...]) - Rewrite image paths from
/wp-content/uploads/to/images/uploads/ - Convert leftover HTML blocks to Markdown
- Write descriptions; WordPress rarely exports them, and Yoast keeps its own in a separate table
This is the step an AI editor earns its keep on. With the schema in the repo and a CLAUDE.md describing it, you can hand Claude Code or Cursor fifty posts and ask for the frontmatter fixed to spec, then review the diff.
Pages
About, Contact and Privacy go into the Starter’s page collections. Pages that were built from sections (hero, features, testimonials) map to frontmatter blocks the layouts already render.
Categories and Tags
If WordPress had thirty categories and five carried traffic, keep five. A handful of well-populated categories beats a long tail of thin archive pages, which you will otherwise have to redirect or noindex anyway.
Step 4: Protect the Rankings
This is the half of the project that decides whether the migration was a success.
Map Every Old URL
Build a complete table from the inventory in the first section:
WordPress URL -> Astro URL
/2024/06/15/my-post-title/ -> /blog/my-post-title/
/category/tutorials/ -> /blog/
/about-us/ -> /about/
/services/web-design/ -> /services/web-design/
/feed/ -> /rss.xml
Date-based post URLs are the most common change. Keeping the old structure is allowed, but flat /blog/slug/ URLs are easier to maintain, and a redirect handles the difference.
Add the 301s
For Netlify and Cloudflare Pages, a public/_redirects file:
/2024/06/15/my-post-title/ /blog/my-post-title/ 301
/category/tutorials/ /blog/ 301
/about-us/ /about/ 301
/feed/ /rss.xml 301
For Vercel, vercel.json:
{
"redirects": [
{ "source": "/2024/06/15/my-post-title/", "destination": "/blog/my-post-title/", "statusCode": 301 }
]
}
Astro’s own redirects config option also works, but on a static build it emits meta-refresh pages rather than real 301 responses, so use the host’s redirect layer for anything Google needs to follow.
Every indexed URL that returns a 404 after launch throws away whatever authority it had. The 301 carries it to the new address.
Keep the Meta Data
For each migrated page, confirm the title is the same or better, the description exists, the Open Graph image resolves, and the canonical points at the new URL rather than the old domain.
Resubmit
After launch, submit the new sitemap in Search Console and watch the Pages report for 404s from URLs you missed in the map.
Step 5: Replace the Plugins
Contact Forms
Contact Form 7 and Gravity Forms need PHP. On a static site the form posts somewhere else. The Starter’s form works on static hosting out of the box, posting to Formspree, Basin or an endpoint of your own, with a honeypot field for bots. Pro replaces that with its lead engine: a serverless API that stores every lead, emails it, and pushes it to Telegram or SMS, with spam filtering in front.
Search
WordPress searched its database. For a static site, Pagefind indexes the built HTML at build time and searches it in the browser, which covers most business sites. Larger content libraries can use a hosted service such as Algolia.
Comments
Giscus (backed by GitHub Discussions) suits developer audiences; Disqus is the general-purpose option. Most business sites remove comments during the move and never miss them.
RSS
WordPress served the feed at /feed/. The Starter already generates /rss.xml; on a fresh Astro project, add @astrojs/rss and an rss.xml.ts route. Either way, redirect /feed/ so existing subscribers keep receiving posts.
Step 6: Test on a Staging URL
Deploy to a preview URL first and check:
- The top 20 pages by traffic render correctly
- Every row in the redirect table returns a 301 to the right place (a shell loop with
curl -Idoes this in a minute) - Titles, descriptions and canonicals are right on a sample of pages
- Structured data validates in Google’s Rich Results Test
- Lighthouse scores meet the bar you set; the Starter’s CI gate expects 100 in all four categories
- The form delivers a test submission
- No broken images from the path rewrite
Step 7: Go Live
- Point DNS at the new host
- Confirm HTTPS and the www or apex redirect
- Submit the sitemap in Search Console
- Check Search Console daily for the first two weeks
- Watch for crawl errors, indexing drops and ranking changes on the top 50 pages
A short dip while Google processes the redirects is normal. With a complete redirect map it is small and it recovers; without one it is neither.
The Mistakes That Cost Traffic
- Missing redirects. The number one cause of post-migration losses, every time
- “Small” URL changes without 301s. A trailing slash is a different URL to Google
- Lost descriptions. Yoast stores them outside the post content and the export does not include them
- Stale internal links. Old-structure links inside migrated posts create redirect chains; rewrite them
- A dead
/feed/. Subscribers silently stop receiving posts - Nobody watching. Search Console for two weeks, or you learn about the 404s from the traffic graph
What Amrify Gives You Here
The destination, not a converter. The Starter is the finished site the content lands in, with the SEO plumbing (canonicals, sitemap, JSON-LD, RSS, llms.txt) already wired. Pro’s new-site scaffolder creates that destination in one command inside a multi-site repo, which matters when you are moving a portfolio of client sites one at a time. The export and clean-up are the steps above; the four bundled Claude Code skills in Pro can take the frontmatter and content work off your hands.
For the case for moving at all, read Astro vs WordPress for agencies. For running the migrated sites afterwards, read one codebase, a whole fleet of sites.
Keep a before-and-after you can show the client. Grade the WordPress site with the free Website Grader today, then add the new domain to the free Website Monitor once it is live: it keeps the last ten Lighthouse results per site, so the old score and the new one sit on the same chart. Both are free; the monitor needs a free account and no card.