10 Lessons We Learned Migrating WordPress to Code (the SEO Edition)
We migrated a real, multi-language WordPress site to code. The cutover itself went smoothly. Every page was there, the site was faster, hosting was cheaper. It looked like a clean win.
Then the long tail arrived. Over the following six weeks we spent roughly ten follow-up rounds fixing damage that only showed up after launch, in Google Search Console: pages returning 404, chains of redirects pointing at redirects, split ranking signals, broken language tags, and traffic that quietly slid. None of it was visible on day one.
This article is the playbook we wish we had started with. It is written for site owners and the people who help them, in plain language, but it does not skip the specifics. If you are about to move off WordPress, read this before you flip the switch, not after.
The one theme that ties all ten together: Google does not recheck your whole site the moment you go live. It revisits pages gradually over weeks. So mistakes do not announce themselves on launch day. They show up later, when they are harder to connect to the cause. Get the details right before cutover.
1. Trailing slashes can quietly tank your rankings
What broke: WordPress had indexed almost all of our traffic on URLs ending in a slash, like
/blog/post/. The new site redirected every one of those to the slashless version,
/blog/post. That sounds harmless. It was not. Redirecting your entire indexed URL base at once tells
Google "every page just moved", which triggers a full reconsolidation of the index. Rankings dropped for months.
The fix: serve both forms as a normal page (a 200 response), and pick one as the canonical. Google then consolidates them gently through the canonical hint instead of a hard redirect. Weeks of recovery instead of months.
Avoid it: decide your trailing-slash strategy before cutover and match whatever WordPress was serving, which is almost always the trailing slash. Never 301 your whole URL base on day one.
2. WordPress is forgiving about URLs; code is strict
What broke: WordPress happily served the same content under slightly wrong addresses and variants. The new site did not. It returned a clean 404 for every variant that did not match exactly. Search Console reported 247 of these broken URLs that we did not know existed. A hand-written list of about 80 redirects only patched a fraction of them.
The fix: generate the redirects automatically from your content instead of typing them out. We read the information already stored with each article and produced a redirect for every wrong-address combination, which came to over 700 rules.
Avoid it: never hand-maintain a large redirect map. If it can be derived from your content, derive it. Humans miss cases; a generator does not.
3. The order your rules run in actually matters
What broke: a redirect rule written for the slashless URL never fired for the slashed version, because the platform evaluated redirect rules before the step that strips the slash. The slashed URL slipped straight through to a 404.
The fix: create both the slashed and the slashless version of every single redirect rule, as a pair. Do not assume the system will normalise them for you.
Avoid it: treat "with slash" and "without slash" as two URLs that both need handling, every time, for every rule.
4. Do not redirect a missing translation, show a fallback instead
What broke: when a French page did not exist yet, our first approach redirected the visitor to the English version. That technically worked, but it broke the experience we wanted. A link to the French page should still open in French, with the English content shown inside the French layout, so the link is never dead.
The fix: when no translation exists, do not redirect. Serve the English content wrapped in the
requested language's shell, tell Google the English page is the canonical, and mark the fallback as
noindex so it does not compete in search. Only redirect when a real translation actually exists at a
different address.
Avoid it: separate the two cases up front. Translation exists means redirect to it. Translation does not exist means serve a fallback, mark it noindex, and point the canonical at the original.
5. Every page needs its own canonical tag
What broke: a shared site-wide template hardcoded the canonical tag to the homepage. So every page on the site told Google "the real version of me is the homepage". That is a recipe for mass deindexing, where Google drops your inner pages because they all claim to be something else.
The fix: generate a correct, page-specific canonical for each page, and remove the hardcoded one from the shared layout.
Avoid it: audit the canonical tag on every template before launch. A single wrong canonical in a shared layout poisons the entire site at once.
6. Language tags must point both ways and be complete
What broke: on a multi-language site, the tags that tell Google which pages are translations of each other (called hreflang) were incomplete and one-directional. The English page pointed at the German one, but not the reverse, and the language switcher sent visitors to pages that 404'd.
The fix: give every article a stable translation key in its content, then drive everything from that one key: the redirects, the language tags, the sitemap, the language switcher, even the social preview images. One source of truth keeps them all in sync.
Avoid it: establish that per-article key from the very start. It is the backbone the whole multi-language setup hangs off.
7. Your sitemap must describe the new site, not the old one
What broke: the first sitemap we shipped still carried the old URL structure, missed some language pages, and had no language tags. A sitemap is the map you hand Google. Ours described a site that no longer existed.
The fix: generate the sitemap from the same content source as the pages themselves, so it can never drift out of sync. Include one entry per language per page, real last-modified dates, and the language tags. Then submit it in Search Console the moment you go live.
Avoid it: never maintain the sitemap by hand or let a plugin's old export linger. Generate, then submit at cutover.
8. Kill the WordPress-specific junk URLs on purpose
What broke: WordPress quietly creates whole families of URLs you never think about, and Google has them all indexed. After launch, Search Console kept surfacing them for weeks. The full kill-list we eventually needed included:
- Taxonomy pages:
/category/,/tag/and/author/archives. - Feeds: the main RSS feed and, easy to forget, a separate feed for every single post.
- WordPress system files: anything under
/wp-content/and/wp-includes/. - Old localized slugs: the translated page names WordPress used, which differ from your new clean paths.
- Pricing and date variants: things like monthly and yearly pricing pages, and slugs with a year stamped on the end.
The fix: redirect each family to a sensible destination, for example all taxonomy pages to your blog index.
Avoid it: export the full WordPress URL inventory before you switch it off, from your sitemap, your SEO plugin and your server logs. Build the kill-list from that real data, not from memory.
9. The migration tooling has its own hidden traps
What broke: some of the worst bugs came not from our content but from a default setting in the framework. One default made the site quietly send visitors back to whichever language they last viewed, stored in a cookie, so pages "stuck" on the wrong language no matter what link you clicked. Another wiring mistake meant the language switcher itself lived outside the part of the page that knew about translations, so it 404'd.
The fix: keep the configuration simple and make the URL the single source of truth for which language a visitor sees, never a cookie. Then test the switcher and the menus, not just the article pages.
Avoid it: assume the defaults are tuned for a generic case, not your migration. Read what each one does before you trust it.
10. There are hard limits, so verify with real checks
What broke: generating redirects automatically is powerful, but it can explode the rule count. Ours reached 770 rules against a hosting limit of 1024, close enough to be a real constraint. Separately, the build ran out of memory partway through because the content set was large.
The fix: watch the limits your host imposes, and give the build enough resources for a big site. Most importantly, verify the result with real checks. For a list of actual URLs, confirm each one returns the status you expect: a clean page with no redirect chain, or a redirect that lands on the right destination in one hop.
Avoid it: do not trust "it looks fine in the browser". Check the status codes, and keep Search Console's coverage and redirect reports open for four to six weeks after launch to catch the long tail.
Want all ten handled for you?
Every lesson here is something that can be generated and verified automatically rather than learned the hard way in Search Console. That is exactly what ShiftPress does when it moves you off WordPress. Join the waitlist and skip the six weeks of cleanup.
Join the waitlist ↗The pattern behind all ten
Look back over the list and one shape repeats. The mistakes are almost never about the content itself. They are about the thousand small URL and metadata details that WordPress handled invisibly and that a new site has to reproduce on purpose: trailing slashes, redirects, canonical tags, language tags, the sitemap, and the junk URLs Google still remembers.
The good news is that nearly all of it can be generated from your content rather than maintained by hand, and checked automatically before launch. When that happens, the migration that looked clean on day one actually stays clean in week six. That is the difference between learning these lessons yourself and having them already built in.
Frequently asked questions
Why do rankings drop weeks after launch, not on day one? +
What is the single biggest SEO mistake when migrating? +
How do I know which old URLs to redirect? +
Can I avoid all this without doing it by hand? +
The bottom line
Migrating WordPress to code gives you a faster, safer, cheaper site. But the win is only real if your rankings survive the move, and that comes down to details Google checks slowly, over weeks. Plan the trailing slashes, redirects, canonical tags, language tags and sitemap before you launch, verify them with real checks, and keep an eye on Search Console afterward. Or use a platform that does all of it for you, which is exactly what ShiftPress is built to do.