Skip to content

Redirect rules cheat sheet for docs sites

Every format below is generated by the migration redirect checker from an old sitemap or URL list, so you rarely need to write these by hand. Use this page to check what a rule means before you deploy it. Items marked as needing verification should be checked against the vendor's current documentation.

General rules

  • Use 301 for permanent moves. 302 and 307 tell search engines the move is temporary and keep the old URL in the index. 308 is a permanent redirect that also preserves the request method; for docs pages 301 and 308 behave the same.
  • One hop. Old → new, never old → interim → new. Chains lose ranking signal and slow readers down. PathIntact reports chains of two or more hops.
  • Match the trailing-slash convention of the target. Redirecting /old/ to /new on a site that then redirects /new to /new/ creates a chain.
  • Exact matches beat patterns until you have hundreds of rules. Patterns are easy to get wrong and hard to audit.
  • Redirects do not carry fragments reliably. /old#section redirects to /new, and whether #section survives depends on the browser. Keep heading ids stable instead.

Vercel (vercel.json)

{
  "redirects": [
    { "source": "/docs/old-page", "destination": "/docs/new-page", "statusCode": 301 },
    { "source": "/docs/legacy/:slug", "destination": "/docs/:slug", "permanent": true }
  ]
}

Verified 2026-09-17: permanent: true (the default) yields 308 and permanent: false yields 307; statusCode sets any other code and cannot be combined with permanent.

Netlify (_redirects)

# from  to  status
/docs/old-page   /docs/new-page   301
/docs/legacy/*   /docs/:splat     301

One rule per line, whitespace separated, first match wins. Put the file in the publish directory.

Cloudflare Pages (_redirects)

/docs/old-page   /docs/new-page   301
/docs/legacy/*   /docs/:splat     301

Same syntax as Netlify; the status defaults to 302 when omitted, so always write it. Limits: 2,000 static and 100 dynamic redirects per file, 1,000 characters per line (verified 2026-09-17).

nginx

location = /docs/old-page { return 301 /docs/new-page; }
location ~ ^/docs/legacy/(.*)$ { return 301 /docs/$1; }

location = is an exact match and is the safest form. Reload nginx after editing.

Apache (.htaccess, mod_alias)

RedirectMatch 301 ^/docs/old-page$ /docs/new-page
RedirectMatch 301 ^/docs/legacy/(.*)$ /docs/$1

Redirect (without Match) matches path prefixes, which is why the exact-match RedirectMatch form is used here. TODO(verify): the host has mod_alias enabled; mod_rewrite syntax differs.

Docusaurus (@docusaurus/plugin-client-redirects)

// docusaurus.config.js
plugins: [
  [
    '@docusaurus/plugin-client-redirects',
    {
      redirects: [{ from: '/docs/old-page', to: '/docs/new-page' }],
    },
  ],
],

Generates HTML pages that redirect with JavaScript at build time rather than HTTP redirects; from accepts an array. TODO(verify): behavior with trailingSlash. Prefer host redirects where you control the host.

Mintlify (docs.json)

{
  "redirects": [{ "source": "/docs/old-page", "destination": "/docs/new-page" }]
}

Top-level redirects in docs.json; permanent defaults to true (308), false gives 307; :slug* wildcards are supported (verified 2026-09-17).

MkDocs (mkdocs-redirects plugin)

plugins:
  - redirects:
      redirect_maps:
        old/page.md: new/page.md

Creates HTML redirect pages, not HTTP redirects. Keys are old Markdown paths relative to docs_dir; values are new Markdown paths or external URLs (verified 2026-09-17).

CSV (for import into other tools)

from,to,status
/docs/old-page,/docs/new-page,301

After deploying

Run the migration redirect checker again with the same old URL list: every row should be OK or redirected, none broken. Then upload the list as a baseline in PathIntact so any URL that regresses to 404 is reported as a missing redirect.

Last updated 2026-09-16.