SEO
Redirect Anti-Patterns and Best Practices for SEO
301 vs 302, redirect chains, the PRG pattern, when not to redirect, and how to consolidate www-vs-non-www without leaking link equity.
Redirects are a five-line topic that becomes a five-hour topic the moment you actually try to fix one. The 301-vs-302 distinction every SEO blog explains is the easy part. The hard part is what to do about a 6-hop redirect chain that has accumulated over a decade of platform migrations, when to use a 308 instead of a 301, why your apex-to-www redirect is silently losing link equity to a query-string parameter, and how to roll out a domain change without tanking organic traffic for two months.
This post is about the redirect patterns that actually matter for SEO and user experience in 2026, the anti-patterns we see most often when auditing client sites, and the right way to handle the canonical decisions every site eventually faces (www-vs-not, trailing slash, lowercase vs case-sensitive). We will lean on practical Nginx, Caddy, Cloudflare Workers, and Next.js examples because that is where redirects actually live in production.
If you are doing a domain migration or a major URL restructure, this is the post we wish we had read before our first one. If your site is stable and you just want to verify your existing redirects are clean, the audit checklist near the end is the part to skip to.
301 vs 302 vs 307 vs 308: when each is correct
301 (Moved Permanently) is the redirect for permanent URL changes. Search engines transfer link equity to the new URL, browsers cache the redirect aggressively, and the original URL is effectively retired. This is what you want for a site move, a URL restructure, or any change you do not plan to roll back.
302 (Found) is a temporary redirect. Search engines do not transfer link equity (they keep the original URL in the index and just follow the redirect on each crawl). Browsers do not cache as aggressively. Use this for A/B tests, geographic redirects, login-required pages that bounce to a /signin URL, or anything where the redirect is content-dependent and might disappear.
307 (Temporary Redirect) is the strict-method-preserving version of 302. The HTTP spec requires that 307 not change the request method (a POST stays a POST), while 302 historically allowed clients to convert POST to GET. For modern API redirects, 307 is the correct status. For browser navigation, the difference is rarely visible.
308 (Permanent Redirect) is the strict-method-preserving version of 301. Same SEO semantics as 301, but POST stays POST. Use 308 for permanent API endpoint moves; use 301 for permanent page moves. Modern browsers and crawlers handle 308 cleanly; older versions of some bots may not, so 301 remains the default safe choice for HTML pages.
The single most common mistake in 2026 production code is using 302 for a permanent move. The framework defaults of Next.js and Astro both used to redirect with 307 or 302 by default — newer versions default to 308 for known-permanent moves. Audit your redirects with curl and explicitly set 301 or 308 for anything intended to be permanent.
# Check what status code your redirect is actually returning
$ curl -sIL -o /dev/null -w "%{http_code} %{url_effective}\n" https://example.com/old-page
301 https://example.com/old-page
200 https://example.com/new-page
# Trace the full chain with redirect-meter
$ curl -sI -L -w "%{http_code} -> %{redirect_url}\n" https://example.com/old-page
# Look for: minimum hops, no temporary redirects in a permanent chainRedirect chains and the depth budget
Each redirect hop costs latency (one extra round trip per hop) and crawler effort (search engines have a redirect-depth limit beyond which they stop following). Google's documented limit is 10 hops; in practice, anything over 3 hops degrades crawl budget meaningfully and anything over 5 risks not being followed at all.
Real production sites accumulate chains over time. A typical pattern: original URL → redirect to www → redirect to HTTPS → redirect to canonical-path → redirect to lowercase. Each step was added separately by a different team, each is reasonable in isolation, and the result is a 5-hop chain that should have been a single redirect.
The fix is consolidation. Audit your top 100 redirected URLs by traffic, measure the chain length, and rewrite to single-hop redirects. Modern web servers (Nginx, Caddy, Apache) can match multiple conditions and redirect once — there is no excuse for a chain when single-hop is possible.
For sites with many redirects, store the source-to-final mapping in a flat lookup (a Cloudflare KV namespace, a Redis hash, a static JSON file deployed with the site). At the edge, look up the source URL and redirect directly to the final destination. This is the only sane approach above ~1000 redirects — maintaining redirect rules in Nginx config breaks down at scale.
The www-vs-not decision (still a real decision)
Pick one canonical hostname — example.com or www.example.com — and 301 the other to it. Pick before you launch; switching after launch is expensive in lost link equity. There is no SEO advantage to either choice in 2026; pick based on operational considerations.
The case for www: you can use a CNAME at the apex, which most non-www DNS setups cannot (apex-CNAME is a flat-file DNS limitation; some providers like Cloudflare and Route 53 support "CNAME flattening" but the standard does not). CNAME flexibility helps with multi-region deployments and CDN failovers.
The case for non-www: shorter, more memorable, the modern aesthetic. Most launch-week brands choose non-www. The DNS limitation is mostly solved at major DNS providers via flattening, so the apex-CNAME problem is less of a blocker than it once was.
Whichever you choose, redirect the other variant with a 301 at the edge. Single hop. Preserve the path and query string. Verify with curl -sIL https://www.example.com/ and curl -sIL https://example.com/ that exactly one of them serves a 200 and the other 301s to the canonical.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Single 301 to apex, preserve URI and query string
return 301 https://example.com$request_uri;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# HTTP to HTTPS
return 301 https://example.com$request_uri;
}CheckFast Redirect Checker traces every hop, flags chains over 3 deep, and identifies wrong status codes.
Audit your redirectsTrailing slashes: pick a side and enforce it
/about and /about/ are different URLs to search engines. Pick one canonical form and 301 the other. The choice does not matter for SEO as long as you are consistent — the cost is in inconsistent linking that splits link equity across two URLs.
Frameworks have defaults: Next.js without trailing slashes by default; Astro and Hugo with trailing slashes; SvelteKit either way (configurable). Static-site generators that produce index.html files often imply trailing slashes (because /about/ maps to /about/index.html. Choose based on framework convention if you have one; choose based on aesthetics otherwise.
Whichever you choose, enforce at the edge. Nginx, Caddy, and Cloudflare can all rewrite trailing slashes with a single rule. Internal linking should match the canonical form to avoid one-redirect-per-internal-link penalties. Tools like the linkinator npm package and CheckFast's broken-links checker flag inconsistent internal trailing slashes.
Verify monthly. New developers and new pages introduce new variations. A site that started with /about canonical can drift to mixed /about and /about/ over time as different teams write different links.
The PRG (Post-Redirect-Get) pattern
PRG is the canonical pattern for handling form submissions in HTTP. The flow: user POSTs the form; server processes it, returns a 303 (See Other) redirect to a GET URL; user's browser follows the redirect with a GET. The result: the GET URL appears in browser history, refresh does not re-submit the form, and back-button does not show the "resubmit form?" dialog.
Without PRG, a user who refreshes after a successful checkout gets a duplicate-charge prompt. With PRG, refresh just loads the confirmation page (which is now a clean GET URL). This is the right pattern for any state-changing form submission.
Modern frameworks implement PRG by default. Next.js Server Actions, Remix actions, SvelteKit actions, and Rails redirect_to all PRG. Hand-written Express or Flask routes need explicit res.redirect(303, '/confirmation') calls.
303 is the right status for PRG specifically — it forces the next request to be GET regardless of the original method. 302 also works in practice (most browsers convert POST to GET on 302) but 303 is the spec-compliant choice and what frameworks emit when given the choice.
When not to redirect
Redirects are not free. Every redirect adds latency, can leak link equity if mishandled, and complicates analytics attribution. Default to not redirecting; redirect only when there is a specific reason.
Do not redirect for tracking purposes. A redirect through /click?dest=... adds latency and looks like a link-equity-bleeding link to crawlers. Use beacon-style tracking (navigator.sendBeacon on click instead — the user goes directly to the destination, the analytics fire asynchronously.
Do not redirect language variants without consideration. Auto-redirecting /page to /en/page based on Accept-Language breaks crawlers (which announce a default language) and confuses users with VPNs. Use hreflang tags to indicate language variants and let users choose.
Do not redirect logged-in users from public pages. If /pricing redirects to /dashboard for logged-in users, search engines crawling without auth see the public page, but users who signed in cannot ever revisit it. Show a different navigation; do not redirect.
Do not redirect from old URLs that should remain live. A common pattern in CMS migrations: every old URL is redirected to a new structure, regardless of whether the old URL had unique content. The right move is to keep old URLs live for any with meaningful traffic, and redirect only the deeply-buried or genuinely-removed ones.
Cloudflare Workers and edge redirects
Edge redirects (handled at the CDN, not the origin) are 50-200ms faster than origin redirects, depending on geography. Cloudflare Workers, Vercel Edge Middleware, and Netlify Edge Functions all let you redirect at the edge with low latency.
The pattern: parse the request URL, look up the canonical destination (in a KV store or a static map), and emit a redirect response without touching the origin. This is especially valuable for high-traffic redirect endpoints (URL shorteners, campaign-tracking links) where every saved millisecond is real revenue.
Cloudflare's Bulk Redirects feature handles up to 100,000 single-hop redirects without writing a Worker — just a CSV upload of source-to-destination mappings. For static redirect lists, this is the simplest path. For dynamic patterns or transformation logic, use a Worker.
Edge redirects also help with regional canonicalization. A user in Germany requesting /products can be redirected to /de/products at the edge based on Cloudflare's geo-IP data, with a 302 (so search engines do not pick up the regional URL as canonical for the original).
// Cloudflare Worker — redirect old paths from a JSON map
const REDIRECTS = {
"/blog/old-slug": "/blog/new-slug",
"/docs/v1/getting-started": "/docs/getting-started",
// ... could be 10k entries from a KV namespace lookup instead
};
export default {
async fetch(request) {
const url = new URL(request.url);
const target = REDIRECTS[url.pathname];
if (target) {
return Response.redirect(`${url.origin}${target}${url.search}`, 301);
}
return fetch(request); // pass through to origin
},
};Domain migrations: the playbook that does not tank traffic
Moving from oldco.com to newco.com is one of the riskier SEO operations. Done wrong, you lose 30-60% of organic traffic for 3-6 months. Done right, you lose less than 10% and recover within 4-6 weeks.
Step 1: Crawl oldco.com thoroughly. Identify every URL with traffic, every URL with backlinks, and every URL referenced from internal navigation. Tools: Screaming Frog, Sitebulb, or Ahrefs's site audit. Export to a spreadsheet.
Step 2: Map every old URL to a new URL. Most should map 1:1. Some merge (consolidating redundant pages). Some retire entirely (404 — only do this for genuinely-dead content). The mapping is the single most important deliverable; spend more time on it than on the redirect implementation.
Step 3: Set up 301 redirects from every old URL to its new mapped URL. Single hop. Preserve query strings. Test 50 sample URLs by hand before launch.
Step 4: Update internal links to point at new URLs directly (no redirects). Update canonical tags. Update sitemap. Update robots.txt. Update Search Console property.
Step 5: Submit the new sitemap to Search Console. Submit a Change of Address request in Search Console (the official mechanism for telling Google about a domain migration). Monitor Coverage report daily for the first two weeks.
Step 6: Keep the redirects active for at least 12 months — search engines need time to fully transfer signals, and old links from external sites continue to come in for years. Do not remove the old domain redirects to save hosting costs.
Frequently asked
Not directly but indirectly. Google's crawl budget is finite — chains consume more of it per page leaving less for new content discovery. Chains also fail more often than single redirects (one broken link in the middle breaks the whole chain). Penalty is the wrong word; degradation is the right one. Aim for single-hop redirects always.
No. Generic 404-to-homepage redirects look like soft 404s to search engines lose link equity from any backlinks pointing at the missing page and confuse users who clicked an external link. Serve a real 404 page with a search box and links to popular content. Implement targeted 301s only for missing pages with measurable traffic or backlinks.
Yes and you should. Every modern web server CDN and load balancer supports this. The pattern is a server block that listens on port 80 and 301-redirects to the same path on HTTPS. Cloudflare's Always Use HTTPS setting does this with one click for sites behind Cloudflare.
301s do almost completely. 302s do not (or do partially after extended duration). 307 and 308 behave like 302 and 301 respectively for SEO purposes. The myth that 301s pass only 90% of link equity ( PageRank evaporation ) was retracted by Google in 2016 — modern 301s pass full equity.
At least 12 months ideally permanently. External backlinks to your old domain continue to send traffic for years. The cost of keeping old domain DNS plus a redirect rule is trivial; the cost of letting the redirects expire is permanent loss of any traffic and equity those backlinks would have sent.
No hard limit but practical limits exist. Above ~10 000 redirects Nginx config files become unwieldy and you should move to an edge KV lookup. Above ~100 000 even KV becomes expensive and you may want to compress patterns into regex rules. CheckFast's redirect checker can audit thousands of URLs in parallel and surface chains and 4xx-after-redirect failures.
Related reading
Performance
The Core Web Vitals 2026 Guide: How to Hit Green on LCP, INP, and CLS
12 min read
SEO
Schema.org Markup That Actually Helps SaaS Products Rank
12 min read
Security
Setting Security Headers in 2026: CSP, HSTS, COEP, and What Actually Matters
12 min read
Business & strategy
TLD Strategy for Startups: .com vs .io vs .ai vs .dev
11 min read