Server Side Next.js Redirect: The Complete SEO Implementation Guide
Use permanentRedirect() for 308 permanent redirects or redirect() for 307 temporary redirects from next/navigation in App Router Server Components and Route Handlers, or NextResponse.redirect() with explicit status codes in Middleware, to ensure search engines receive proper HTTP status headers without executing client-side JavaScript.
Implementing a server side Next.js redirect is critical for preserving search rankings when migrating URLs or restructuring content. According to the vercel/next.js source code, the framework provides optimized helpers that issue HTTP-level redirects with correct status codes, eliminating client-side round trips that can confuse search engine crawlers.
App Router: Server Components and Route Handlers
The App Router provides dedicated navigation utilities in packages/next/src/client/components/redirect.ts that throw RedirectError instances caught by the runtime to generate HTTP responses.
Permanent Redirects with permanentRedirect()
For SEO-friendly permanent moves, use permanentRedirect() which returns a 308 Permanent Redirect status. In packages/next/src/client/components/redirect-status-code.ts, this maps to RedirectStatusCode.PermanentRedirect.
// app/old-page/page.tsx
import { permanentRedirect } from 'next/navigation'
export default function OldPage() {
// Throws RedirectError, caught by runtime, returns 308 with Location header
permanentRedirect('/new-page')
}
Temporary Redirects with redirect()
For temporary moves, redirect() issues a 307 Temporary Redirect by default, preserving the HTTP method used in the original request.
// app/api/redirect/route.ts
import { redirect } from 'next/navigation'
export async function GET() {
// 307 temporary redirect
redirect('/temporary-destination')
}
Advanced: Using getRedirectError Directly
For custom logic requiring manual error throwing, use the internal getRedirectError helper. This achieves the same result as redirect() but allows dynamic status code configuration.
// lib/redirect-utils.ts
import { getRedirectError } from 'next/navigation'
export function redirectTo(url: string) {
// Manually throw the internal error – same effect as `redirect()`
throw getRedirectError(url, 'replace', 307)
}
Middleware and Edge Runtime
For redirects that must execute before hitting application logic, Next.js Middleware running in the Edge Runtime uses NextResponse.redirect() as shown in packages/next/src/server/web/error.ts.
// middleware.ts
import { NextResponse } from 'next/server'
export function middleware(request) {
const url = new URL(request.url)
if (url.pathname === '/old-blog') {
// Explicit 301 permanent redirect
return NextResponse.redirect(new URL('/new-blog', request.url), { status: 301 })
}
return NextResponse.next()
}
This approach allows precise control over status codes (301 or 302) while executing at the edge, enabling instant response caching without invoking Node.js server processes.
Pages Router API Routes
For the legacy Pages Router, implement redirects using the Node.js response object directly when running outside the Edge Runtime.
// pages/api/old.ts
export default function handler(req, res) {
res.writeHead(301, { Location: '/new' })
res.end()
}
Why Server-Side Redirects Matter for SEO
Using the built-in server side Next.js redirect APIs provides specific search engine optimization advantages over client-side navigation:
- No Client-Side Execution: Crawlers receive the
Locationheader immediately without executing JavaScript, ensuring the redirect is followed correctly and indexation targets the destination URL. - Correct Status Codes: The
308permanent redirect (used bypermanentRedirect()) preserves POST request methods during the redirect, while301remains available in Middleware for classic permanent redirects that method-shift to GET. - Edge Caching: Next.js caches redirect responses in the Edge Runtime, reducing latency for subsequent crawler requests and minimizing server load.
Summary
- Use
permanentRedirect()fromnext/navigationfor 308 permanent redirects in App Router Server Components, as implemented inpackages/next/src/client/components/redirect.ts. - Use
redirect()fromnext/navigationfor 307 temporary redirects in Route Handlers. - Use
NextResponse.redirect()in Middleware for edge-level redirects with customizable 301/302 status codes. - Reference
packages/next/src/client/components/redirect-status-code.tsfor the enum definitions of supported status codes. - Always prefer server-side redirects over
useRouter()client-side navigation for SEO-critical URL changes to ensure search engines properly transfer ranking signals.
Frequently Asked Questions
What is the difference between 301 and 308 redirects in Next.js?
Next.js supports both status codes: 301 (classic permanent redirect) available in Middleware via NextResponse.redirect(), and 308 (permanent redirect) used by permanentRedirect() in the App Router. While both indicate permanent moves, 308 preserves the original HTTP method (POST stays POST), whereas 301 historically causes clients to switch POST requests to GET.
Can I use redirect() in a Client Component?
No. The redirect() and permanentRedirect() functions from next/navigation are designed for Server Components and Route Handlers only. They function by throwing a RedirectError that the server runtime catches and converts into an HTTP response. For client-side navigation, use useRouter(), though this lacks the SEO benefits of server-side redirects.
How do I handle dynamic URL redirects in Next.js Middleware?
Inspect the request.nextUrl pathname in your middleware.ts file, then construct the target URL dynamically before calling NextResponse.redirect(). This executes at the edge before the request reaches your application code, making it efficient for high-volume redirect rules and A/B testing scenarios.
Do server-side redirects in Next.js affect page performance?
Server-side redirects improve perceived performance by eliminating the client-side JavaScript execution required for client-side navigation. When implemented in Middleware, the Edge Runtime caches redirect responses, meaning subsequent requests receive the Location header instantly without invoking Node.js server processes or database queries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →