How to Redirect a URL (301, 302, Meta, JS)
Complete guide to implementing redirects: server-side (Apache .htaccess, Nginx, Cloudflare), HTML meta refresh, JavaScript, and framework-specific methods. When to use each type.
There are many ways to redirect a URL. Some are correct. Some work but hurt your SEO. Some are hacks that will break eventually. Here is how to implement each type of redirect, when to use it, and when to avoid it.
The Right Way: Server-Side Redirects
Server-side redirects return a 3xx HTTP status code before any page content loads. Search engines understand them. Browsers follow them instantly. This is the correct default.
Apache (.htaccess)
The most common method for shared hosting and WordPress sites. Add rules to the .htaccess file in your site root.
Single URL redirect:
# 301 — Permanent redirect
Redirect 301 /old-page https://example.com/new-page
# 302 — Temporary redirect
Redirect 302 /promo https://example.com/current-sale
Pattern-based redirects with mod_rewrite:
RewriteEngine On
# Redirect entire directory
RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# Force www
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
# Redirect with query string
RewriteCond %{QUERY_STRING} ^id=123$
RewriteRule ^product\.php$ /products/widget? [R=301,L]
The [L] flag matters
The [L] flag means "last rule" — stop processing after this match. Without it, subsequent rules may trigger and create unexpected chains or loops.
Nginx
Nginx handles redirects in the server configuration file, not in a per-directory file like Apache.
Single URL redirect:
server {
listen 80;
server_name example.com;
# Single redirect
location = /old-page {
return 301 https://example.com/new-page;
}
# Redirect entire directory
location /blog/ {
rewrite ^/blog/(.*)$ /articles/$1 permanent;
}
# Force HTTPS (entire server block)
return 301 https://$host$request_uri;
}
Regex-based redirects:
# Redirect old product URLs to new format
location ~ ^/product\.php {
if ($arg_id = "123") {
return 301 /products/widget;
}
}
Nginx requires reload
After editing Nginx config, run nginx -t to test, then systemctl reload nginx to apply. Unlike .htaccess, changes do not take effect automatically.
Cloudflare Page Rules and Redirect Rules
If you use Cloudflare, you can add redirects without touching your server at all.
Page Rules (legacy):
- Go to Rules > Page Rules
- Enter the URL pattern:
example.com/old-page* - Add setting: Forwarding URL
- Choose 301 or 302
- Enter the destination:
https://example.com/new-page
Redirect Rules (newer):
- Go to Rules > Redirect Rules
- Create a rule with a matching expression
- Set the URL redirect action with status code and target
Cloudflare redirects execute at the edge — before the request reaches your server. They are fast and reduce origin load.
Node.js / Express
const express = require('express');
const app = express();
// Single redirect
app.get('/old-page', (req, res) => {
res.redirect(301, '/new-page');
});
// Pattern redirect
app.get('/blog/:slug', (req, res) => {
res.redirect(301, `/articles/${req.params.slug}`);
});
Next.js
In next.config.js:
module.exports = {
async redirects() {
return [
{
source: '/old-page',
destination: '/new-page',
permanent: true, // 301
},
{
source: '/blog/:slug',
destination: '/articles/:slug',
permanent: true,
},
];
},
};
Trace your redirect chains
Find redirect loops, broken chains, and unnecessary hops instantly.
The Acceptable Way: Meta Refresh
HTML meta refresh redirects work without server access. They are not ideal, but they work in a pinch.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0;url=https://example.com/new-page">
<title>Page Moved</title>
</head>
<body>
<p>This page has moved. <a href="https://example.com/new-page">Click here</a> if not redirected.</p>
</body>
</html>
The content="0" means redirect immediately (0 seconds delay).
SEO implications
Google processes meta refresh redirects, but they do not pass link equity as reliably as 301 server-side redirects. Use them only when you cannot modify server configuration — on free hosting, GitHub Pages (without custom routing), or platforms that limit server access.
The Last Resort: JavaScript Redirect
JavaScript redirects execute in the browser after the page loads. They are the weakest form of redirect.
<script>
window.location.replace("https://example.com/new-page");
</script>
Use window.location.replace() instead of window.location.href = — replace() does not add an entry to the browser history, which prevents the back button from trapping users in a redirect loop.
When JavaScript redirects are acceptable:
- Client-side routing in SPAs (React Router, Vue Router)
- Conditional redirects based on client-side state (locale detection, A/B test assignment)
- Platforms where you have zero server control and cannot use meta refresh
When JavaScript redirects are wrong:
- Permanent page moves (use 301)
- SEO-important pages (search engines may not execute JS reliably)
- Anything that a server-side redirect can handle
When to Use Each Status Code
| Code | Name | When to Use | SEO |
|---|---|---|---|
| 301 | Moved Permanently | Page has permanently moved to a new URL | Passes link equity |
| 302 | Found (Temporary) | Page temporarily at a different URL | May not pass link equity |
| 307 | Temporary Redirect | Same as 302 but preserves HTTP method (POST stays POST) | Same as 302 |
| 308 | Permanent Redirect | Same as 301 but preserves HTTP method | Same as 301 |
Rule of thumb: If the old URL will never come back, use 301. If it will come back (maintenance, seasonal content, A/B testing), use 302.
Common Redirect Mistakes
Using 302 when you mean 301
This is the most common mistake. If a page has permanently moved, use 301. Using 302 tells search engines the old URL might come back, so they may not transfer ranking signals to the new URL.
Forgetting trailing slashes
/about and /about/ are different URLs. If your server treats them differently, you need explicit rules for both. Many redirect loops come from trailing slash inconsistencies.
Creating redirect chains
Redirect A to B. Later, redirect B to C. Now A goes to B goes to C — a chain. Always redirect directly to the final destination.
Redirecting to a page that also redirects
Always verify your redirect destination is a real, live page that returns 200. Not another redirect. Not a 404.
Not updating internal links
After setting up a redirect, update all internal links to point to the new URL directly. Redirects are for external links and bookmarks you cannot control.
Verify Your Redirect
After implementing any redirect, verify it:
# Check the redirect chain
curl -sIL "https://example.com/old-page" | grep -iE "^(HTTP/|location:)"
# Expected output for a clean 301:
# HTTP/2 301
# location: https://example.com/new-page
# HTTP/2 200
One redirect, one 200. Clean.
Related Articles
Server-side 301 for permanent moves. Everything else is a compromise.
Never miss a broken redirect
Trace redirect chains and detect issues before they affect your users and SEO. Free instant tracing.