How to Redirect in Next.js
Every method for handling redirects in Next.js: next.config.js, middleware, API routes, App Router redirect(), permanent vs temporary, and dynamic redirects.
Next.js gives you at least five different ways to redirect users, and each one serves a different purpose. Picking the wrong method leads to slow page loads, broken SEO, or redirects that simply do not fire when you expect them to. This guide covers every approach with practical examples so you can match the right tool to the situation. For background on how redirects work at the HTTP level, see the HTTP Redirect Status Codes reference.
Redirects in next.config.js
The simplest method is declaring redirects in your next.config.js file. These redirects are evaluated at the server level before any page rendering happens.
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/articles/:slug',
permanent: true,
},
{
source: '/about-us',
destination: '/about',
permanent: true,
},
]
},
}
The permanent property controls the HTTP status code. When set to true, Next.js sends a 308 Permanent Redirect. When set to false, it sends a 307 Temporary Redirect. Note that Next.js uses 308 and 307 rather than 301 and 302. The 308 and 307 variants preserve the HTTP method of the original request, which prevents issues where a POST request gets converted to a GET during the redirect. For the differences between these codes, see 301 vs 302 Redirects.
Path matching and parameters
Next.js supports path parameters with the :param syntax. You can also use wildcards:
{
source: '/docs/:path*',
destination: 'https://docs.example.com/:path*',
permanent: false,
}
The :path* wildcard captures everything after /docs/, including nested paths like /docs/api/v2/users. A single :path without the asterisk captures only one segment.
You can also match based on headers, cookies, and query strings:
{
source: '/feature',
has: [
{
type: 'cookie',
key: 'beta_user',
value: 'true',
},
],
destination: '/feature-v2',
permanent: false,
}
Limitations
Config-based redirects require a server restart (or redeployment) to update. They do not support runtime logic. If you need to redirect based on database lookups, authentication state, or any other dynamic condition, you need middleware or server-side redirects.
The redirect list is also evaluated sequentially. On sites with thousands of redirect rules, this can add measurable latency. Vercel recommends keeping the list under 1,024 entries and moving larger sets to middleware.
Middleware Redirects
Next.js Middleware runs before every request and gives you full control over the response. This is the most flexible redirect method and the one you should reach for when config-based redirects are not enough.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
if (pathname.startsWith('/legacy-app')) {
const newUrl = pathname.replace('/legacy-app', '/app')
return NextResponse.redirect(
new URL(newUrl, request.url),
301
)
}
}
export const config = {
matcher: '/legacy-app/:path*',
}
Middleware runs at the edge on Vercel and at the Node.js server level in self-hosted deployments. The matcher config limits which routes trigger the middleware, which is important for performance. Without a matcher, the middleware runs on every single request, including static assets.
When to use middleware redirects
Middleware is the right choice when you need to redirect based on authentication status, geolocation, A/B test assignments, feature flags, or any condition that requires reading request headers or cookies at runtime. It is also the right approach for large redirect maps, since you can load the map from a database or key-value store rather than hardcoding it.
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US'
if (country === 'DE' && !request.nextUrl.pathname.startsWith('/de')) {
return NextResponse.redirect(new URL('/de' + request.nextUrl.pathname, request.url))
}
}
Status codes in middleware
When calling NextResponse.redirect(), you can pass the status code as the second argument. If omitted, it defaults to 307 (Temporary Redirect). Always specify 301 explicitly for permanent redirects, otherwise search engines will not treat the redirect as permanent and will keep crawling the old URL.
App Router redirect() Function
In the Next.js App Router (the app directory), you can call redirect() inside Server Components, Server Actions, and Route Handlers.
// app/old-page/page.tsx
import { redirect } from 'next/navigation'
export default function OldPage() {
redirect('/new-page')
}
This function throws an internal Next.js error that short-circuits rendering and sends the redirect response. It works in both Server Components and Server Actions. By default, redirect() sends a 307 in Server Actions and a 308 in other contexts. You can override this:
redirect('/new-page', RedirectType.replace)
redirect() in Server Actions
Server Actions are form handlers that run on the server. After processing a form submission, you often want to redirect the user to a confirmation page:
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const post = await saveToDatabase(formData)
redirect(`/posts/${post.id}`)
}
The redirect happens after the action completes. If you need to redirect conditionally (for example, back to the form with errors), use try/catch carefully. The redirect() function works by throwing, so catching it will prevent the redirect from happening. Next.js provides isRedirectError() to distinguish redirect throws from real errors.
permanentRedirect()
Next.js 14 introduced permanentRedirect() as a companion to redirect(). It sends a 308 status code:
import { permanentRedirect } from 'next/navigation'
export default function OldPage() {
permanentRedirect('/new-page')
}
Use this when the old URL should never be visited again and you want search engines to update their index.
API Route Redirects
In the Pages Router, API routes can redirect using the res.redirect() method:
// pages/api/go.ts
export default function handler(req, res) {
const { target } = req.query
res.redirect(301, `/pages/${target}`)
}
In the App Router, Route Handlers use NextResponse.redirect():
// app/api/go/route.ts
import { NextResponse } from 'next/server'
export function GET(request: Request) {
const url = new URL(request.url)
const target = url.searchParams.get('target')
return NextResponse.redirect(
new URL(`/pages/${target}`, url.origin),
301
)
}
API route redirects are useful for short URLs, tracking links, or any scenario where a programmatic endpoint needs to send the user somewhere else.
Pages Router getServerSideProps Redirects
In the Pages Router, getServerSideProps can return a redirect object instead of props:
export async function getServerSideProps(context) {
const session = await getSession(context.req)
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
return { props: { user: session.user } }
}
This is evaluated on every request. The permanent field controls the status code (308 for true, 307 for false), just like in next.config.js.
This approach is useful when the redirect decision depends on server-side data: authentication, authorization, feature flags, or anything that requires a database query before you know whether to render the page or redirect.
Permanent vs Temporary Redirects
Choosing between permanent and temporary redirects matters for both SEO and browser behavior. A permanent redirect (301/308) tells browsers to cache the redirect and tells search engines to transfer ranking signals to the new URL. A temporary redirect (302/307) tells both that the original URL may come back.
Use permanent redirects when the old URL is gone for good: after a URL restructure, domain migration, or content consolidation. Use temporary redirects for A/B tests, maintenance pages, geolocation routing, or any situation where the original URL might serve content again in the future.
For a deeper comparison, see 301 vs 302 Redirects and 308 Permanent Redirect.
Dynamic Redirects with Path Rewriting
Sometimes you do not want a visible redirect at all. Next.js rewrites let you serve content from a different path without changing the browser URL:
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/blog/:slug',
destination: '/posts/:slug',
},
]
},
}
Rewrites are not redirects. The browser URL stays the same, and no HTTP redirect response is sent. This is useful for API proxying, vanity URLs, or migrating URL structures incrementally. For the full comparison, see Redirect vs Rewrite.
Test your redirects after deployment
Redirects that work in development can behave differently in production, especially on platforms with edge caching. Always verify your redirect chains after deploying. Middleware redirects in particular can be cached at the CDN layer, causing stale redirect rules to persist.
Common Mistakes
Forgetting to specify permanent: true. Next.js defaults to temporary redirects in most contexts. If you intend a permanent redirect, always set it explicitly. Temporary redirects do not transfer SEO signals the way permanent ones do.
Redirect loops between middleware and config. If you have a redirect in next.config.js and a conflicting rule in middleware, you can create a loop. Config redirects run first. Make sure your middleware matcher excludes paths already handled by config redirects.
Not using the matcher in middleware. Without a matcher, middleware runs on every request, including static files, images, and API routes. This adds unnecessary latency and can cause unexpected redirects on asset URLs.
Redirecting in client components. Calling router.push() or router.replace() in a Client Component is not a server-side redirect. The user's browser loads the page first, then JavaScript executes and navigates. Search engines may not follow client-side redirects. For SEO-critical redirects, always use a server-side method.
Choosing the Right Method
For static, known URL mappings that rarely change, use next.config.js. For dynamic, conditional redirects based on request properties, use middleware. For redirects that depend on data fetched during rendering, use redirect() in Server Components or getServerSideProps. For programmatic endpoints, use API routes.
If you manage more than a handful of redirects, consider storing your redirect map in a database or CMS and loading it in middleware. This lets non-developers manage redirects without code deployments.
References
- Next.js Documentation, "Redirecting," 2024. https://nextjs.org/docs/app/building-your-application/routing/redirecting
- Next.js Documentation, "next.config.js redirects," 2024. https://nextjs.org/docs/app/api-reference/next-config-js/redirects
- Next.js Documentation, "Middleware," 2024. https://nextjs.org/docs/app/building-your-application/routing/middleware
- Vercel, "Edge Middleware," 2024. https://vercel.com/docs/functions/edge-middleware
Verify your Next.js redirects are working
Trace every redirect hop, check status codes, and confirm your permanent redirects are returning the right response.
Try Redirect Tracer