Blog  /  Migration
WordPress → Astro

How to Migrate WordPress to Astro Without Losing Your SEO

By Hossam Hamdy 12 min read Updated July 2026

Astro is one of the best homes a content site can move to: near-zero JavaScript, blazing builds, and a content model that treats your posts as typed data. But like any migration, doing it carelessly can torch your rankings. Here’s the ranking-safe path from WordPress to Astro, end to end.

Astro earns its hype for content sites: it ships almost no JavaScript by default, builds to static HTML that ranks and loads fast, and models your content as typed collections you query like a database. For a blog, docs site, or marketing site coming off WordPress, it’s a natural fit.

The migration itself has two halves. The first is mechanical, get your content into Astro’s content collections. The second is the one that decides whether your organic traffic survives, preserving every URL, redirect, and meta tag Google already trusts. This guide covers both, in the order you should actually do them.

Step 1: Export your content to Markdown

Astro’s content collections read Markdown and MDX files, so the first job is getting your WordPress content out as clean Markdown, with the rendered body, YAML front matter, and the metadata you’ll need for URLs and SEO. This is the step that breaks migrations: WordPress’s own export gives you raw builder shortcodes, not rendered content.

Start with a clean Markdown export

Migratik renders every page (Elementor, WPBakery, Divi, Bricks, Oxygen, or Gutenberg), converts it to clean Markdown, and writes one file per item with full front matter, title, permalink, description, dates, and SEO fields. Point Astro’s loader straight at the result. It’s read-only and safe on a live site.

Drop the exported folder into src/content/ (for example src/content/blog/) and you’re ready to define a collection.

Step 2: Set up content collections

Astro 5 reads content through the Content Layer: you define a collection with a glob loader pointed at your files and a schema (built with Zod) that matches your front matter. Create src/content.config.ts:

// 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(),
    permalink: z.string(),
    date: z.coerce.date(),
  }),
});

export const collections = { blog };

The schema is your safety net: if an exported file is missing a field, the build fails loudly instead of shipping a broken page. That’s exactly what you want during a migration.

Step 3: Keep your URLs

The golden rule of any migration: if a page ranks, its URL should not change. Astro’s file-based routing makes this straightforward. Create a dynamic route and generate one page per entry with getStaticPaths(), using each post’s existing slug so the paths match WordPress exactly:

// src/pages/blog/[...slug].astro
---
import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.id },   // matches your WordPress slug
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await render(post);
---
<article><Content /></article>

One thing to settle early: trailing slashes. WordPress serves /blog/post/; Astro’s default is no slash. Pick one and set trailingSlash in astro.config.mjs so it matches your old URLs, and keep your canonical tags consistent with the choice.

Step 4: 301 anything that moves

For URLs that change, you need real 301 redirects so the old page’s ranking authority transfers. Astro lets you declare redirects in config:

// astro.config.mjs
export default defineConfig({
  site: 'https://yoursite.com',
  redirects: {
    '/category/[slug]': '/topics/[slug]',
    '/old-about-us': '/about',
  },
});
Static vs real 301

On a purely static build, Astro implements these as HTML meta refresh pages, fine for users, but not a true HTTP 301. To emit real 301s, either add an SSR adapter, or (simplest) declare the redirects at your host: a _redirects file on Netlify, vercel.json on Vercel, or the Cloudflare Pages rules. Because your Markdown front matter holds every old permalink, generating that redirect list is mechanical.

Step 5: Carry over metadata & canonical

Port your titles and descriptions verbatim from the front matter, this is what earns the click in search results, and it’s already in your exported files. Build a small <head> from each page’s data, and set an explicit canonical from your live site URL:

// in your layout's <head>
---
const { title, description } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site);
---
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<meta property="og:title" content={title} />

If your WordPress posts carried Article or FAQ structured data, reproduce the same JSON-LD in your Astro layout, losing it can shrink your rich results in search.

Step 6: Sitemap & robots

Astro generates a sitemap for you through an official integration. Add it and set your site URL:

// astro.config.mjs
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://yoursite.com',
  integrations: [sitemap()],
});

That emits a sitemap-index.xml at build time. Add a public/robots.txt that points to it and allows crawling:

# public/robots.txt
User-agent: *
Allow: /
Sitemap: https://yoursite.com/sitemap-index.xml
The launch-day classic

Make sure no staging config ships a site-wide noindex or a Disallow: /. If that reaches production, Google will deindex the whole site. Confirm production allows crawling before you point DNS.

Step 7: Images

Your exported Markdown references images by URL, and by default those still point at the old /wp-content/uploads/ path, which dies the moment WordPress goes down. Bundle the images with your content and rewrite the URLs to local files (Migratik Pro’s media bundle does this in one click), then place them under src/ so Astro’s built-in <Image /> component from astro:assets can optimize them, automatic sizing, modern formats, and lazy loading, all of which help Core Web Vitals.

Step 8: Launch, then verify

Ship, then run this in the first 48 hours:

  1. Submit the sitemap in Google Search Console and request indexing for your top pages.
  2. Spot-check redirects: hit old URLs and confirm a single 301 hop to the right page (real 301s if you used host-level redirects).
  3. Crawl the new site and diff the URL list against your export, anything missing is a gap.
  4. Verify metadata: titles, descriptions, canonicals, and JSON-LD present on live pages.
  5. Watch Coverage & 404s in Search Console daily for two weeks.

That’s the whole path: clean export, typed collections, and disciplined preservation of URLs, redirects, metadata, and images. The mechanical half is a joy in Astro; the SEO half is just refusing to change what already ranks. Do both and you get a site that’s dramatically faster and keeps every position it earned.

Moving to Next.js instead? The same principles, framework-specific, are in our WordPress → Next.js guide. Want the export details? See exporting WordPress to Markdown.

Frequently asked questions

How do I migrate WordPress to Astro?

Export your WordPress content to Markdown, drop it into src/content, define a content collection with the glob loader, and generate pages with getStaticPaths(). Then preserve SEO by keeping your URLs, adding 301 redirects, carrying over metadata, and generating a sitemap.

Does moving from WordPress to Astro affect SEO?

It only hurts SEO if you change what Google indexed. Keep the same URLs, 301-redirect anything that moves, port your titles and descriptions verbatim, set canonicals, and submit a fresh sitemap. Done right, Astro’s speed can improve rankings.

How do I import WordPress content into Astro content collections?

Export the content as Markdown with front matter, place the files under src/content, and define a collection in src/content.config.ts using the glob loader with a Zod schema that matches your front matter. Query it with getCollection().

Can I keep my WordPress URLs in Astro?

Yes. Use a dynamic route like src/pages/blog/[...slug].astro, map each entry’s existing slug in getStaticPaths(), and set trailingSlash to match WordPress. Keeping URLs identical preserves most of your ranking signal.

Export once. Rank the same.

Migratik turns your WordPress site into clean Markdown with front matter, metadata, and bundled images, the perfect input for Astro content collections. Free to install, read-only, safe on a live site.