Next.js Redirects: next.config.js, Middleware, and App Router
Next.js gives you four places to put a redirect, and picking the wrong one is a surprisingly common source of bugs. This guide covers all of them — next.config.js, middleware, the App Router's redirect(), and the Pages Router's getServerSideProps — and when to reach for each.
The four places redirects can live
next.config.jsredirects array — static configuration, evaluated at the edge/server before rendering.- Middleware (
middleware.ts) — runs on every request at the edge; use when you need runtime logic. - App Router
redirect()/permanentRedirect()— called inside Server Components, Server Actions, or route handlers. - Pages Router
getServerSideProps/getStaticProps— legacy pattern, but still widely deployed.
next.config.js: the first place to try
If the redirect rule doesn't depend on request state (cookies, headers, A/B flags), put it in next.config.js. It's the fastest path and requires no runtime code.
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: true,
},
{
source: '/docs/:path*',
destination: '/documentation/:path*',
permanent: true,
},
{
source: '/promo',
destination: '/sale',
permanent: false, // -> 307 temporary
},
]
},
}Key things:
permanent: trueemits 308 (not 301).permanent: falseemits 307 (not 302). Both preserve the HTTP method.- Use
:paramfor single segments and:path*for catch-all. - Conditional matching is supported via
hasandmissing— useful for host, cookie, or header-based routing without dropping to middleware.
{
source: '/',
has: [{ type: 'host', value: 'old.example.com' }],
destination: 'https://example.com/',
permanent: true,
},Middleware: runtime logic at the edge
When the redirect depends on something only known at request time — auth cookies, geolocation, feature flags, A/B test buckets — use middleware.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US'
if (request.nextUrl.pathname === '/' && country === 'DE') {
return NextResponse.redirect(new URL('/de', request.url))
}
const token = request.cookies.get('auth')
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('redirect', request.nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next()
}
export const config = {
matcher: ['/', '/dashboard/:path*'],
}NextResponse.redirect() defaults to 307. To send a 308 (permanent):
return NextResponse.redirect(new URL('/new', request.url), 308)App Router: redirect() and permanentRedirect()
Inside Server Components, Server Actions, and route handlers, use the helpers from next/navigation:
// app/profile/page.tsx
import { redirect } from 'next/navigation'
import { getCurrentUser } from '@/lib/auth'
export default async function ProfilePage() {
const user = await getCurrentUser()
if (!user) redirect('/login')
return Welcome, {user.name}
}redirect() throws a special error that Next.js catches, so it must be called outside of try/catch blocks (or re-thrown). Use permanentRedirect() for 308 semantics.
Pages Router: getServerSideProps
Still running on the Pages Router? Redirects go in the return value:
export async function getServerSideProps(context) {
const user = await getUser(context.req)
if (!user) {
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
return { props: { user } }
}Which one should I use?
| If the redirect... | Use this |
|---|---|
| Is static and doesn't depend on request state | next.config.js redirects() |
| Depends on cookie / header / geo at request time | middleware.ts |
| Depends on data fetched inside the component | App Router redirect() |
| Is a legacy Pages Router app | getServerSideProps |
| Is a permanent URL migration | next.config.js with permanent: true |
Gotchas that trip people up
- 308, not 301.
permanent: trueemits 308. Most SEO tooling handles both, but if you're migrating from a system that used 301 and you need byte-for-byte parity, know this up front. - Middleware runs on everything. Always set
matcherin the config to scope it, or you'll slow down static assets. - trailingSlash inconsistencies. If you have
trailingSlash: truein config, Next.js auto-redirects/footo/foo/. Stacking a custom redirect on top can cause a double-hop. - redirect() in Client Components.
redirect()fromnext/navigationonly works in Server Components/Actions. On the client, userouter.push(). - Redirect chains. Config + middleware + component can compound. After any change, verify with a redirect checker to make sure you haven't created a 3-hop chain.
TL;DR
Start with next.config.js. Move to middleware only when the rule needs request state. Use redirect() inside Server Components for data-driven redirects. And after every change, run your URLs through Redirect Check to catch chains and loops before they hit production.
今すぐリダイレクトをチェック
不適切なリダイレクトでSEOを損なわないでください。無料ツールで即座にリンクを監査しましょう。
How to Fix ERR_TOO_MANY_REDIRECTS: A No-BS Troubleshooting Guide
Stuck in a redirect loop? Learn how to diagnose and fix the ERR_TOO_MANY_REDIRECTS error with practical solutions for WordPress, Cloudflare, Apache, and Nginx.
Setting Up Redirects in Cloudflare: A No-Nonsense Guide for 2025
Master Cloudflare redirects with this practical guide. Learn Single Redirects, Bulk Redirects, common pitfalls, and real troubleshooting tips that actually work.
Mobile-First Redirects: How to Optimize for Core Web Vitals in 2025
Learn how redirects impact Core Web Vitals and mobile performance. Practical strategies to maintain LCP, INP, and CLS scores during redirects.