Weiterleitungen in Next.js
In Next.js kannst du an vier Stellen weiterleiten: in der Konfiguration, in der Middleware, im Servercode des App Routers und über die Option trailingSlash. Jede davon sendet standardmäßig andere Statuscodes – es lohnt sich also, die Unterschiede zu kennen.
Überblick
| Methode | Standard-Status | Einsatzzweck |
|---|---|---|
redirects() in der next.config.js | 308 (permanent: true) oder 307 | Feste, bekannte URL-Zuordnungen |
Middleware / Proxy mit NextResponse.redirect() | 307 | Bedingungen zur Laufzeit: Cookies, Standort, große Redirect-Listen |
redirect() im App Router | 307 (303 in Server Actions) | Weiterleitung nach Logik in Server Components, Route Handlern, Server Actions |
permanentRedirect() | 308 | Dauerhaft umgezogene Inhalte, im Servercode entschieden |
trailingSlash | 308 | URLs mit oder ohne Slash am Ende vereinheitlichen |
Next.js setzt lieber auf 307 und 308 als auf 302 und 301, weil diese Codes die Anfragemethode garantiert beibehalten: Aus einem POST wird kein GET. Für Suchmaschinen ist 308 gleichwertig zu 301 und 307 zu 302. Die Hintergründe erklärt 307 vs. 308.
redirects() in der next.config.js
Die Funktion redirects in deiner Konfiguration gibt ein Array von Regeln zurück. Next.js prüft sie noch vor dem Dateisystem – sie greifen also auch für URLs, zu denen es keine Seite mehr gibt:
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
// 308 Permanent Redirect
{
source: '/ueber-uns',
destination: '/about',
permanent: true,
},
// Pfadparameter
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
// Wildcard: passt auch auf /docs/a/b/c
{
source: '/docs/:path*',
destination: 'https://docs.example.com/:path*',
permanent: false,
},
// Expliziter Statuscode statt permanent
{
source: '/alter-shop',
destination: '/shop',
statusCode: 301,
},
]
},
}
module.exports = nextConfig
Ein paar Regeln solltest du dir merken:
permanent: truesendet308,permanent: falsesendet307. Brauchst du für einen alten Client einen klassischen301, nimmstatusCodestattpermanent– nicht beides.- Query-Strings werden automatisch ans Ziel weitergereicht.
- Mit
hasundmissingprüfst du Header, Cookies, Query-Parameter oder den Host. So leitest du zum Beispiel innerhalb von Next.js von www auf die Domain ohne www um:
{
source: '/:path*',
has: [{ type: 'host', value: 'www.example.com' }],
destination: 'https://example.com/:path*',
permanent: true,
}
Hostest du bei Vercel, ist dafür eine Domain-Weiterleitung im Dashboard einfacher, siehe Weiterleitungen bei Vercel und Netlify. Mit next.config.ts bleibt die Syntax gleich, du exportierst nur per export default nextConfig. Wichtig: Mit output: 'export' funktioniert redirects() nicht, denn bei einem statischen Export gibt es keinen Server, der einen Statuscode senden könnte.
Middleware: NextResponse.redirect()
Die Middleware läuft, bevor eine Route gerendert wird, und kann für jede Anfrage einzeln entscheiden. Seit Next.js 16 heißt die Datei proxy.ts und die exportierte Funktion proxy; in älteren Versionen ist es middleware.ts mit der Funktion middleware. Die API ist dieselbe:
// proxy.ts (Next.js 16) bzw. middleware.ts (Next.js 15 und älter)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
if (!request.cookies.has('session')) {
// standardmäßig 307
return NextResponse.redirect(new URL('/login', request.url))
}
if (request.nextUrl.pathname === '/alte-preise') {
// ausdrücklich dauerhaft
return NextResponse.redirect(new URL('/preise', request.url), 308)
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/alte-preise'],
}
NextResponse.redirect() erwartet eine absolute URL, deshalb baut das Beispiel sie mit new URL(pfad, request.url) zusammen. Schränk die Middleware per matcher ein, damit sie nicht bei jedem Bild und jeder statischen Datei anspringt.
Die Middleware ist auch der empfohlene Ort für große Redirect-Listen mit Tausenden Einträgen: Du lädst eine Zuordnung aus einer JSON-Datei oder einem Edge-Speicher, schlägst den Pfad nach und leitest bei einem Treffer weiter. Für riesige Listen ist die Konfigurationsdatei nicht gedacht.
redirect() und permanentRedirect() im App Router
In Server Components, Route Handlern und Server Actions importierst du die Funktionen aus next/navigation:
// app/produkte/[id]/page.tsx
import { redirect, permanentRedirect, notFound } from 'next/navigation'
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id)
if (!product) {
notFound()
}
if (product.slug !== id) {
// dauerhaft umgezogen: 308
permanentRedirect(`/produkte/${product.slug}`)
}
if (!product.published) {
// vorübergehend: 307
redirect('/produkte')
}
return <h1>{product.name}</h1>
}
redirect()sendet307; in einer Server Action dagegen303 See Other, damit der Browser anschließend perGETlädt.permanentRedirect()sendet308.- Beide werfen intern einen Fehler, ein
returnbrauchst du also nicht. Ruf sie aber nicht innerhalb einestry-Blocks auf, sonst verschluckt deincatchdie Weiterleitung. - Wird die Antwort bereits gestreamt, lässt sich der Statuscode nicht mehr ändern. Next.js fügt dann ein Meta-Tag ein, das im Browser weiterleitet. Crawler behandeln das anders als einen echten HTTP-Redirect – löse Weiterleitungen daher aus, bevor eine Suspense-Grenze zu streamen beginnt. Ob du einen
308oder einen Meta Refresh bekommst, zeigt dir der Redirect-Checker.
Im Pages Router gibst du stattdessen aus getServerSideProps oder getStaticProps das Objekt { redirect: { destination: '/login', permanent: false } } zurück.
trailingSlash
Standardmäßig leitet Next.js /about/ per 308 auf /about um. Mit trailingSlash: true passiert das Gegenteil:
module.exports = {
trailingSlash: true, // /about -> /about/ (308)
}
Entscheide dich für eine Schreibweise und halte deine internen Links daran – sonst kostet jeder interne Link einen zusätzlichen Sprung. Zeigt eine Regel in redirects() auf die „falsche“ Schreibweise, kommt der Slash-Redirect noch obendrauf und du hast eine Redirect-Kette. Mit skipTrailingSlashRedirect: true schaltest du die automatische Weiterleitung ab und regelst das selbst in der Middleware.
Redirects vs. Rewrites
Beide nutzen dieselbe Syntax mit source und destination, machen aber etwas grundlegend anderes:
| Redirect | Rewrite | |
|---|---|---|
| URL im Browser | Wechselt zum Ziel | Bleibt unverändert |
| HTTP-Antwort | 3xx mit Location-Header | 200 mit dem Inhalt des Ziels |
| Suchmaschinen | Indexieren das Ziel | Indexieren die ursprüngliche URL |
| Typischer Einsatz | Umgezogene Inhalte, Domainwechsel | API durchreichen, saubere URLs, Multi-Zone-Apps |
async rewrites() {
return [
{ source: '/api/:path*', destination: 'https://api.example.com/:path*' },
]
}
Ist eine URL wirklich umgezogen, nimm einen Redirect. Ein Rewrite ließe die alte URL im Index und lieferte denselben Inhalt unter zwei Adressen aus.
Reihenfolge der Auswertung
Next.js arbeitet eine Anfrage in dieser Reihenfolge ab: headers, redirects, Middleware/Proxy, beforeFiles-Rewrites, Routen aus dem Dateisystem, afterFiles-Rewrites, dynamische Routen, fallback-Rewrites. Eine Regel in redirects() gewinnt also gegen eine Seite mit demselben Pfad und gegen deine Middleware.
Testen
next dev verhält sich nicht immer wie die Produktion, vor allem nicht hinter einem CDN. Schick nach dem Deployment die wichtigsten URLs durch den Redirect-Checker und prüf den Statuscode jedes Sprungs. Für viele URLs auf einmal gibt es den Bulk-Checker. Mehr zu den Statuscodes, die Next.js sendet, findest du unter 301 vs. 302.
Häufige Fragen
- Warum sendet Next.js 308 statt 301?
308ist die dauerhafte Weiterleitung, bei der die Anfragemethode erhalten bleibt. Nach einem301dürfen Browser aus einemPOSTeinGETmachen. Google behandelt 308 wie 301. Brauchst du trotzdem einen 301, setzstatusCode: 301stattpermanent.- Was ist der Unterschied zwischen redirect() und permanentRedirect()?
redirect()sendet einen vorübergehenden307(in Server Actions303),permanentRedirect()einen dauerhaften308. Nimm die dauerhafte Variante nur, wenn die alte URL aus den Suchergebnissen verschwinden soll.- Warum funktioniert redirect() in try/catch nicht?
redirect()wirft einen speziellen Fehler, den Next.js abfängt. Ein umschließendescatchfängt ihn vorher ab. Rufredirect()nach demtry/catch-Block auf oder wirf den Fehler erneut.
Passende Ratgeber
-
.htaccess-Weiterleitung auf Apache
So leitest du einzelne Seiten und ganze Domains per .htaccess weiter: mod_alias oder mod_rewrite, fertige Regeln für die typischen Fälle und wie du Schleifen und Ketten vermeidest.
-
Weiterleitungen mit nginx
Fertige nginx-Konfiguration für 301-Weiterleitungen: einzelne Seiten, Muster, ganze Domains, HTTPS und www in einem Hop und Hunderte URLs per map.
-
Weiterleitungen mit Cloudflare
Cloudflare kann Weiterleitungen direkt am Edge beantworten, bevor eine Anfrage deinen Server erreicht. So funktionieren Single Redirects, Bulk Redirects und Always Use HTTPS – und so verhinderst du, dass sie sich mit deinem Server in die Quere kommen.
-
Weiterleitungen in WordPress
In WordPress kannst du URLs auf mehreren Wegen weiterleiten: per Plugin, per Regel in der .htaccess oder mit ein paar Zeilen PHP. Hier erfährst du, welcher Weg wann passt und wie du die typischen Stolperfallen mit Caching und HTTPS umgehst.
-
Weiterleitungen bei Vercel und Netlify
Bei Vercel und Netlify fasst du keine Webserver-Konfiguration an. Weiterleitungen stehen in einer Datei in deinem Repository oder im Dashboard, und das Edge-Netzwerk der Plattform liefert sie aus. So funktionieren beide – und hier unterscheiden sie sich.
-
Weiterleitungen mit Microsoft IIS
IIS bietet dir zwei Wege für Weiterleitungen: die eingebaute HTTP-Umleitung und das URL-Rewrite-Modul. Hier erfährst du, wann du welches nimmst – mit web.config-Beispielen zum Kopieren.