June 29, 2026 · 9 min read · Updated June 29, 2026
The WordPress to Next.js Redirect Map, In Full
A pattern-by-pattern guide to mapping every WordPress permalink to a Next.js App Router route, keeping canonicals and titles, and proving the redirects work.
By Tal Gerafi, Founder & Website Engineer
A WordPress to Next.js redirect map is a one-to-one list that matches every old WordPress URL to its new App Router route and sends each one a 301 redirect. Build it in three passes: list every live URL, map each WordPress permalink pattern to a Next.js route, then carry the old canonical and title tag onto the new page. Get this list right and rankings survive the move. Get it wrong and you lose the authority those pages spent years earning.
This is the detailed companion to our WordPress to Next.js migration playbook. The playbook gives you the order of operations for the whole move; this article goes deep on the single artifact that decides whether you keep your traffic — the redirect map itself.
How do WordPress permalinks map to Next.js App Router routes?
WordPress URLs come from a permalink setting, and most live sites use "Post name" (/sample-post/) or a dated structure (/2023/05/sample-post/). Next.js App Router builds routes from folders: a file at app/blog/[slug]/page.tsx serves /blog/sample-post. So the mapping job is matching each WordPress pattern to the folder route that replaces it, and writing a redirect for every old URL that doesn't already match.
Two details cause most broken links. First, trailing slashes: WordPress adds them (/sample-post/), Next.js by default does not. Pick one style and redirect the other, or you create duplicate URLs that split your signals. Second, dated permalinks: if old posts live at /2023/05/sample-post/ and new ones live at /blog/sample-post, every post needs a redirect — the path changed, not just the domain.
Here is how the common WordPress permalink patterns line up with App Router routes.
| WordPress permalink (old) | Next.js App Router route (new) | Route file |
|---|---|---|
/sample-post/ | /blog/sample-post | app/blog/[slug]/page.tsx |
/2023/05/sample-post/ | /blog/sample-post | app/blog/[slug]/page.tsx |
/?p=412 | /blog/sample-post | app/blog/[slug]/page.tsx |
/category/news/ | /blog | app/blog/page.tsx |
/tag/pricing/ | /blog (or 410) | app/blog/page.tsx |
/author/tal/ | /about (or 410) | app/about/page.tsx |
/page/2/ | /blog?page=2 | app/blog/page.tsx |
/wp-content/uploads/img.png | /assets/blog/img.png | static file in public/ |
How do I write 301 redirects in Next.js for the App Router?
Next.js gives you two clean places to declare redirects, and the right one depends on how many you have. For a short, fixed list, use the redirects array in next.config.js. For thousands of mapped URLs, generate them from a data file so you're not hand-editing config. Either way, set permanent: true so Next.js returns a 308 (the modern permanent redirect, which preserves the method and passes authority the same way a 301 does for search engines).
A few next.config.js redirects look like this:
async redirects() {
return [
{ source: '/2023/05/sample-post/', destination: '/blog/sample-post', permanent: true },
{ source: '/category/news/', destination: '/blog', permanent: true },
]
}
For dated permalinks across a whole blog, use a wildcard so you don't write one line per post: source: '/:year(\\d{4})/:month(\\d{2})/:slug/' mapping to destination: '/blog/:slug'. When your list runs into the thousands with exact one-off targets, generate the array from a CSV or JSON file at build time, or move the lookups to middleware.ts. The rule that saves you from a slow site: never chain redirects. Point each old URL straight at its final destination so you don't waste crawl budget or make users wait through two hops.
How do I preserve canonical URLs and title tags during the migration?
A redirect moves a visitor to the new page; the new page still has to send search engines the same signals the old one sent, or the redirect alone won't hold your ranking. Two signals matter most: the canonical URL and the title tag. The canonical should be self-referencing — point to the new page's own URL, not the old WordPress one — so engines treat the new page as the real version. The title tag should match the old page word for word unless you have a deliberate reason to change it.
In the App Router you set both through the metadata export, which keeps them in code next to the page instead of scattered in a plugin:
export const metadata = {
title: 'Your exact old title tag',
alternates: { canonical: 'https://yoursite.com/blog/sample-post' },
}
Carry the rest of the parity set too: meta descriptions per page, Open Graph tags, and structured data rebuilt as JSON-LD (Article, FAQ, Breadcrumb). A migration is the wrong moment to "improve" titles or trim copy — change the platform and the content in the same week and you'll never know which one moved your traffic. Keep parity first; redesign later. The full checklist lives in the migration playbook.
What WordPress URLs do people forget to redirect?
The URLs that bite you are the ones no human ever typed but Google still has indexed. WordPress generates a lot of them automatically, and a sitemap-only crawl misses most. Pull your full list from three sources together — a Screaming Frog crawl, your XML sitemap, and the "Pages" report in Google Search Console — then check it against the usual suspects:
/?p=123post-ID links — WordPress's raw permalink before pretty URLs; old backlinks often use them./category/and/tag/archives — thin pages that usually fold into/blogor get a 410./author/name/archives — rarely have value; redirect to/aboutor let them 410.- Paginated archives (
/page/2/,/blog/page/3/) — map to your new pagination or/blog. - Feed URLs (
/feed/,/comments/feed/) — point to your new RSS feed or remove. /wp-content/uploads/media — every image and PDF you hotlinked needs a new home.- Attachment pages (
/sample-post/image-name/) — WordPress makes a page per image; almost always 410.
Decide each one on purpose. A URL with traffic, backlinks, or rankings gets a 301. A genuinely dead URL — spam tags, empty archives, attachment pages — should return a 410 or be allowed to 404, not be swept to the homepage. Redirecting everything to the homepage looks tidy but Google treats it as a soft 404 and drops the signal anyway. Being deliberate here also prevents index bloat — hundreds of low-value URLs cluttering your index after launch.
How do I verify the redirects actually work after launch?
You verify a redirect map by checking real HTTP responses, not by trusting the config. For every important old URL, confirm three things: it returns a 301 or 308 (not 302, not 200), it lands on the right new URL in one hop, and the destination returns 200. The fastest check on one URL is curl: curl -sI https://yoursite.com/old-path/ and read the HTTP status and location header. For the whole list, re-crawl your old URLs with Screaming Frog in list mode and sort by status code so any 404 or redirect chain jumps out.
| What you check | Good result | Bad result |
|---|---|---|
| Status of old URL | 301 / 308 | 302, 404, or 200 |
| Number of hops | 1 (straight to final) | 2+ (a chain) |
| Destination status | 200 | 404 or another redirect |
| Canonical on new page | points to itself | points to old URL |
Then watch the data for two to four weeks. In Search Console, the "Pages" and "Crawl stats" reports show Google discovering your redirects; the "Not indexed" bucket flags any old URL that's 404ing by mistake. As Google's John Mueller has repeatedly noted, a site move with proper redirects recovers, but recrawling a large site takes time — so a short ranking wobble is normal, and the fix for nerves is checking the logs rather than re-editing redirects in a panic. At Greeto we treat this verification as a gate: an AI agent crawls and diffs every mapped URL, and a senior engineer signs off before the old site is retired. That human-in-the-loop review is what catches a redirect quietly pointing at the wrong post.
FAQ
Should redirects in Next.js be 301 or 308?
Use a permanent redirect, which Next.js implements as a 308 when you set permanent: true. For SEO this is equivalent to a 301 — both are permanent, both pass authority to the new URL, and Google follows both the same way. The difference is technical: a 308 preserves the HTTP method, while a 301 historically allowed it to change. For a content migration either is fine; just never use a 302 (temporary) for a page that has moved for good.
Do I need a redirect for every WordPress URL?
For every URL that has traffic, backlinks, or rankings, yes — give it a 301. For genuinely dead URLs like spam tags, empty author archives, and WordPress attachment pages, return a 410 or let them 404 on purpose. The goal of the redirect map is to make that call deliberately for each URL, not to redirect everything blindly to the homepage, which Google reads as a soft 404 and ignores.
How do I handle WordPress dated permalinks like /2023/05/post/?
Map them with a single wildcard redirect instead of one line per post. In next.config.js, a source like /:year(\\d{4})/:month(\\d{2})/:slug/ redirecting to /blog/:slug catches every dated post at once. This is far less error-prone than listing thousands of individual posts, and it still produces a clean one-hop 301 to each new slug.
Where do redirects go in the Next.js App Router?
For a fixed list, put them in the redirects array in next.config.js. For large or dynamic maps, generate that array from a data file at build time, or handle the lookups in middleware.ts, which runs before the request reaches a page and can read an external map. The App Router itself doesn't change this — redirects live in config or middleware, not in your page files.
How do I keep my title tags and canonicals after migrating?
Set them in each page's metadata export in the App Router. Copy the old title tag word for word, and make the canonical self-referencing — pointing to the new page's own URL, not the old WordPress one. Combined with the 301 redirect, matching titles and a correct canonical are what tell search engines the new page is the same page that earned the ranking.
What's the fastest way to test redirects after going live?
Use curl for spot checks and Screaming Frog's list mode for the full set. curl -sI on an old URL shows you the status code and the location header in one line, so you can confirm a single 301 in seconds. To check the whole map, feed your old URL list into Screaming Frog and sort by status code — any 404, 302, or redirect chain stands out immediately, which is how you prove the migration is clean before retiring the old site.