Skip to content
Numan Zahid
Get in touch
All work

Moving a WordPress site to Astro without losing rankings

Professional services · Migration and rebuild

Astro 6Content collectionsSvelte 5TypeScriptNetlify
40Legacy URLs redirected, generated rather than hand-written
34Articles migrated with their SEO metadata intact
0Server runtime: every route is a file on a CDN
Starting point
  • A long-running WordPress site carried years of articles and a set of per-location landing pages, all of which held rankings worth keeping.
  • The page structure had accumulated to the point that a layout change meant editing each page individually.
  • Article metadata lived in an SEO plugin's database fields, which do not survive an export unless something deliberately extracts them.
What I did
  • Wrote migration scripts rather than migrating by hand: one to export the WordPress content and assets, another to convert posts to Markdown with their SEO fields preserved in frontmatter.
  • Modelled content as a typed collection, so an article's shape is validated at build time instead of discovered when a page renders wrong.
  • Generated the redirect map from the old permalink structure, so the mapping is reproducible and cannot drift from the content it describes.
  • Consolidated the per-location landing pages into anchors on a single contact page, with the old URLs redirecting to their section.
Measured outcome
  • Every historical URL resolves, so nothing that had accumulated ranking returns a 404.
  • The site has no PHP, no database and no plugin surface to maintain or patch.
  • Content is Markdown in version control, so an article change is a reviewable diff.
Technical detail
  • Redirects are generated by a script that runs as part of the build, reading the old slug set and emitting the redirect file. Hand-written redirect lists go stale the moment content moves; a generated one cannot.
  • The collection schema includes the migrated SEO fields as a nested object, which means a post that lost its metadata in the export fails the build rather than quietly rendering without a description.
  • Interactive pieces -- the header, an accordion, two carousels -- are Svelte islands; everything else ships as static HTML with no JavaScript at all.
  • The deploy adapter is enabled only for builds, not for the dev server, because leaving it on made the dev server serve stale output from the previous build.
  • Page copy lives in typed modules rather than in the templates, so the same content can be restructured without touching markup -- which is the problem that made the original site hard to change.

Got a project?

Email me with where things are now and what you'd like to change. If I'm not the right person for it, I'll tell you, and I'll usually know someone who is.

hello@zahid.dev