Migrating Off WordPress Without Losing Rankings
Rankings do not leak out during a move. Specific URLs stop resolving, and specific pages stop answering the query that earned them. Both are decided long before cutover day.
On This Page

The new site launched on a Tuesday. It is faster, the editors are happier, and three weeks later the organic traffic has not come back. Nobody can say which pages lost what, because nobody wrote down which pages existed. That is the ordinary shape of a move that went wrong, and remarkably little of it has anything to do with the platform the team moved to.
A WordPress migration breaks in two places: the URLs, and the content extraction. Everything else is recoverable in an afternoon. A URL that resolved for six years and now returns a 404 is a page that stopped existing, and search treats it exactly that way. Content pulled out of the old database as a wall of rendered HTML is content the new site can display and never restructure.
So the sequence is not "build the new site, then deal with SEO". It is inventory, extraction, redirect map, parity, cutover, watch. A planned migration that runs in that order rarely surprises anyone. One that treats redirects as a launch-week task surprises everyone, always in the same direction.
What a WordPress migration actually loses
Rankings are not a substance that spills when you tip the site over. They are per-URL, per-query judgements, and they go in three distinct ways. Naming which one you are looking at is the difference between a fix and a fortnight of guessing.
The first is disappearance. A URL that collected links for years now returns nothing, or returns the homepage, which is worse because it looks intentional. The second is dilution: the page resolves, but the rebuild cut nine hundred words down to a hero line and three cards, and it no longer answers the question that earned it the position. The third is structural. The old site pointed its internal links at the pages that mattered; the new navigation, designed by someone looking at eleven templates rather than four hundred pages, points them somewhere else.
Only the first is solved by redirects. Teams who plan the whole project as a redirect exercise fix a third of the problem and then spend a quarter puzzled by the rest. It helps to hold the sequence in mind: a crawler has to crawl, render and index in that order, and a migration can break any of the three independently.
Start from the inventory, not the sitemap
The XML sitemap on a WordPress site is generated by a plugin, and it lists what the plugin was configured to include. That is not the same as what exists. It is nowhere near the same as what has value. Starting from it means everything the plugin was told to exclude vanishes silently, which is how sites lose their oldest and best-linked pages without anybody noticing for a month.
Where the real list comes from
Build one deduplicated list from six sources. Each of them knows about URLs the others have forgotten.
- A full crawl of the live site. Follow internal links to exhaustion, with JavaScript rendering on, and keep the response code, canonical and title for every result.
- Search Console indexing data. It knows about pages nothing links to any more, including ones a crawler will never reach on its own.
- Twelve months of server access logs. The only honest record of what people and bots actually requested, and the one place the undocumented URLs show up.
- The analytics landing-page report. Entry pages across a full year rather than sessions, long tail included.
- Backlink data. Any URL somebody else has linked to deserves a destination, even if it stopped being useful in 2019.
- The database itself. Every published row in the posts table, every taxonomy term, and everything sitting in the uploads directory. The last of those is an inventory in its own right.
What counts as a page
Anything that returned a 200 and could be requested. That includes the PDFs somebody uploaded in 2018 and linked from a newsletter, the feed a partner still polls, and the JSON endpoints a plugin exposed without telling anyone. A file in the uploads directory is a URL with a status code, and moving hosts without moving those files produces a very quiet kind of breakage: the pages look fine, and every third image is missing.
Run the whole URL list through a status checker while the old site is still live, and store the status, canonical, title, description and word count for every row. That file is the only evidence you will have after cutover. Without it, every argument about whether something changed becomes a memory test, and memory loses.
The URLs WordPress made for you
Every CMS generates routes nobody asked for. WordPress is unusually generous about it, and the generated ones are exactly the ones that never appear in a hand-written spreadsheet, because nobody remembers creating them.
| URL type | What WordPress generates | What the new site should do |
|---|---|---|
| Attachment pages | A page per uploaded file, at its own permalink | Redirect to the parent post, or to the file itself |
| Feeds | A feed for the site, plus one per category, tag, author and post | Keep the main feed at its old path; retire the rest deliberately |
| Author archives | An archive for every user who ever published | Redirect to the author page you actually have, or to the index |
| Pagination | Numbered pages on the blog, every category, every tag, every search | Recreate the sequence, or send each one to page one on purpose |
| Date archives | A URL for every year and every month that has a post in it | Usually retire, with a redirect to the year or the blog index |
| Taxonomy terms | A page per category and tag, including terms used exactly once | Keep the ones with links or traffic; fold the rest into a parent |
| Slash handling | Trailing-slash canonicals, with the other form redirecting | Pick one form, enforce it in one place, and never mix the two |
Attachment pages catch the most people. Unless somebody disabled them, every image, PDF and video ever uploaded has an indexable URL with the file title as its heading. A few of them rank, usually for queries nobody targeted. They are also the largest single source of thin pages on an old site, so the temptation is to let the whole family 404 and call it housekeeping. Do not. Redirect them to the parent post and let the map record the relationship, because a 404 that used to hold a link is a link you have thrown away for nothing.
Pagination is the second one. A blog that ran for a decade has numbered pages deep into double figures, and each is a crawl path into the archive underneath. Replace them with infinite scroll and those URLs die, the deep archive loses its route in, and the posts on page nine become reachable only through a sitemap. If the new page size differs from the old, the mapping is not one-to-one either, so somebody has to decide whether page two of the old site becomes page two of the new one or the index.
Feeds are a small trap with a long tail: a partner aggregator, somebody’s reader, an internal notification job, a syndication arrangement agreed four years ago by a person who has left. Author archives matter more than they used to, because attribution is part of how a page gets judged, and a broken author URL breaks the one place the site said who wrote things.
Getting the content out in a shape you can use
Export is not extraction. The built-in WordPress export produces an XML file in which the entire body of every post is one field holding whatever the editor left behind: shortcodes, block comments, inline styles, and the occasional table pasted out of a spreadsheet in 2016. That file will get your words onto the new site. It will not get them into fields.
What the REST API gives you
The WordPress REST API is the better extraction route for most sites: posts with their taxonomies, media with their metadata, and both a rendered and a raw form of the content, page by page, in JSON you can transform. What it will not hand you without extra work is custom field data, page-builder payloads stored in post meta, and anything a plugin invented its own storage for. Check those three before assuming the export is complete.
This is where a site that was modelled properly pays for itself. If the old build used custom post types and real fields, extraction is mechanical: each type maps to a type on the new side, each field to a field, and the work is a script rather than a project. If everything is a page with one rich-text blob, extraction is a person reading four hundred pages and deciding what each paragraph was for.
Shortcodes, blocks and page builders
A shortcode is a function call rendered by a theme or plugin you are about to delete. Extract the rendered output and you inherit the markup of the thing you are leaving. Extract the raw content and you inherit a token that means nothing anywhere else. Neither is wrong. The point is to decide per shortcode, and to notice that the accordion, the pricing table and the call-to-action band are three separate decisions wearing one syntax.
Page builders are harder, because the layout lives as serialised data in post meta and is genuinely meaningless outside the builder that wrote it. For those templates, treat the live page as the source of truth: read it, decide which fields it actually needs, and rebuild it against the new content model. Slower than a script, and considerably faster than shipping a site nobody can edit.
The shape the content lands in is a separate decision from the platform it lands on, and it deserves its own argument about where the content should live afterwards. If the answer is a repository, a workspace such as Acrosite generates the files, commits them to GitHub and triggers the configured deployment, which puts the migrated archive into the same history as the code that renders it.
The surrounding question, and the half of this that outlives the migration by years, is who drafts and who can stop a page shipping. Our parent company has written about that directly in where drafts live and what stops them.
Writing the redirect map
The redirect map is a two-column artefact: every old URL, and either exactly one new URL or a recorded decision to let it go. It is not a plugin setting, and it is not generated at the end. It gets written while the new site is being built, because it is the thing the new site is being built to satisfy.
- Freeze the old list. Pick a date, take the inventory, and stop adding to it. Anything published afterwards is handled as an exception rather than as a moving target.
- Classify every row: keep, merge, retire. Old sites tend to split roughly into thirds. The retire column needs a named person to approve it, because that is the column where things get thrown away.
- Match on intent, not on string similarity. Two URLs that look alike often answered different questions. An automated fuzzy match is a useful first draft and wrong often enough that every row needs an eye on it.
- Write a rule where a pattern exists, a row where it does not. One expression handles ten thousand date archives. Four hundred hand-mapped rows handle the pages that matter. Confusing the two, in either direction, is the usual source of pain.
- Resolve the chains. Old sites already contain redirects, and your map will point at some of them. Flatten every chain to a single hop before launch rather than after.
- Decide what a retired URL returns. A 410 says gone on purpose. A redirect to a barely-related page says something false and gets treated as a soft 404 anyway. Choose, then write the choice down next to the row.
A redirect map is not a file you generate at the end of a build. It is the specification the build is held to.
Use permanent redirects for anything that genuinely moved, and keep them for at least a year, longer where the old URLs carry links you cannot get updated. Query strings need their own thought. Campaign parameters can be stripped safely; a filtered or paginated URL that used a parameter is a real page and needs a real destination.
The parity check before cutover
Parity is not "does it look right". It is a list of properties the old page had that the new page must also have, checked mechanically on a staging domain, row by row, against the before-picture you captured earlier. Running it after launch is not a check. It is an incident review with better lighting.
- Status and canonical. Every mapped URL reaches a 200 in one hop, and the page it lands on points its canonical at itself.
- Title, description and H1. Changed on purpose or not changed at all. A template that silently appends the site name to four hundred titles is a real regression, not a detail.
- Body content, counted. Compare word counts old against new. Any page that lost a third of its text lost it to a bug, a missing shortcode, or a design decision nobody flagged.
- Structured data. Article, breadcrumb and FAQ markup present wherever it was present before, and valid rather than merely present.
- Internal links. The pages that used to receive the most internal links still do. This is the check that catches a navigation redesign quietly demoting the pages that earn money.
- Images and alternative text. Files present at their new paths, alt text carried across rather than regenerated from filenames.
- Robots directives and the robots file. Read both character by character. This is where the worst launch-day mistakes live, and they are invisible in a browser.
The thing most often shipped by accident on cutover day is the staging noindex tag, closely followed by a staging robots file that disallows everything. Neither is visible to anyone reviewing the design. Put "remove the staging block, then verify it on production" into the cutover script as its own numbered step, with a named owner.
The first thirty days after cutover
Watch in this order: server logs, then coverage reports, then rankings. Logs tell you within the hour what is returning 404 and what is being crawled, which is the only signal fast enough to act on during launch week. Coverage reports lag by days and tell you what search made of it. Rankings lag by weeks and tell you almost nothing useful until the rest has settled.
Expect movement. A move of any size produces a visible wobble while the index catches up, and the honest concession is that nobody can tell you in week one whether a dip is recovery lag or damage. What you can do is remove the ambiguity in advance. With the before-picture, the map and the parity run in hand, "did this page change" is a question you answer rather than debate. Then ordinary technical SEO work resumes, and a surprising amount of it is the same work the old platform needed anyway.
Keep the old installation alive and read-only for a quarter, on a different host name, with the database intact. It costs very little and it is the only way to answer questions about what a page used to say. Take a full copy on the night of the move as well, because a replatform is precisely the moment when the backup nobody valued becomes the most important file you own.
When we would not migrate at all
Here is the part that costs us work. Most sites we are asked to move off WordPress do not have a WordPress problem. They have twenty-six plugins, an abandoned theme, a host chosen on price, and nobody whose job it is to apply updates. Rebuilding on a different stack fixes all four for about eighteen months, after which the same forces reassert themselves in a new syntax.
So the test we apply before agreeing to a replatform is narrow: name the constraint the new platform removes, in one sentence, without using the words modern or clean. "Editors cannot publish anything without a developer" is a constraint. "Rendering a product page takes four queries we cannot cache" is a constraint. "It feels dated" is a design brief, and a design brief is far cheaper to satisfy than a rebuild.
If the constraint is real, move, and move deliberately, with the inventory written before the first template. If it is not, spend a tenth of the budget on hosting, plugin removal and somebody owning maintenance, then revisit in a year. If you are genuinely unsure which of the two you are holding, describe what keeps breaking, or start from what a rebuild would actually involve. The answer usually shows up in the first conversation, and it is much cheaper to hear it then than on the Tuesday after launch.
Common questions.
Will moving off WordPress hurt my search rankings?
Not by itself. Traffic falls when URLs stop resolving, when pages lose the content that answered the query, or when a new navigation sends internal link equity somewhere else. A move that preserves the URL set, keeps the substance of each page, and maps every retired address to a deliberate destination usually settles within weeks. Expect a visible wobble first, while the index catches up.
What should I do with WordPress attachment pages when I replatform?
Redirect each attachment page to its parent post, or to the media file itself where no sensible parent exists. WordPress creates one indexable page per uploaded file unless somebody disabled that behaviour, so an old site can carry thousands. Letting them all return 404 throws away whatever inbound links they collected, which is a permanent loss in exchange for a temporary tidiness gain.
Do old pagination URLs need redirects?
Yes, and they need a decision rather than a blanket rule. Numbered archive pages are crawl paths into the deep archive, so if the new site drops them in favour of infinite scroll, older posts lose their route in. Where the new page size matches the old, map page for page. Where it does not, send the old numbered URLs to the archive index and make sure a sitemap still lists everything.
How long should redirects stay in place after a migration?
Keep them for at least a year, and indefinitely for any URL carrying external links you cannot get updated. Redirects cost almost nothing to serve once they are flattened to a single hop, and removing them early converts a solved problem back into 404 responses. Review the list annually, retire rules with no traffic behind them, and keep the mapping file in version control rather than in a plugin interface.
What is the best way to export content out of WordPress?
Use the REST API rather than the built-in XML export for anything beyond a small site. The API returns posts, taxonomies and media as structured JSON you can transform field by field, whereas the XML export packs each body into a single blob. Check custom fields, page-builder data and plugin-specific storage separately, because none of those reliably appear in either output.
Should the old WordPress site be kept running after cutover?
Keep it alive and read-only on a different host name for roughly a quarter. It is the only dependable way to answer questions about what a page used to contain, and it costs very little to run. Take a full file and database backup on the night of the move as well. Block the old copy from search engines, and confirm the robots directives before pointing anything at it.
Facing this in your
own business?
Tell us where you’re headed — we’ll map the shortest honest route.