Next.js Middleware Praxisleitfaden: Pfad-Matching, Edge-Runtime-Einschränkungen und häufige Fallstricke

Vercel-Fehlerlogs. Frisch deploytes Admin-Backend: lokal funktionierte der Schutz aller /dashboard-Routen – nicht eingeloggte Nutzer landeten auf der Login-Seite. In Produktion? Direktzugriff auf /dashboard/settings/profile umging die Prüfung, sensible Daten sichtbar.
Code geöffnet: Middleware lag korrekt, Logik stimmte. Was also?
Nach einer halben Stunde Next.js-Doku fiel es auf – in der matcher-Sektion: Ich hatte /dashboard/:path geschrieben. Das matcht nur eine Ebene wie /dashboard/settings, nicht mehrstufige Pfade. Richtig: /dashboard/:path*. Ein kleiner Stern, der fast meinen Ruf gekostet hätte.
Kurz gesagt: Middleware-Fallstricke kenne ich. Edge Runtime blockiert Bibliotheken, matcher greift nicht, Endlosschleifen bei Redirects – fast alles schon einmal passiert.
Wenn Sie Next.js Middleware für Auth, i18n oder A/B-Tests nutzen oder planen, kann dieser Artikel helfen. Fallstricke, undurchsichtige Regeln und drei vollständige Praxisbeispiele – Schritt für Schritt.
Kein „In der heutigen Webentwicklung“-Blabla. Konkret: wie schreiben, wie vermeiden, wie Middleware wirklich funktioniert.
Was ist Middleware – und wofür?
Kurz: Middleware ist ein Kontrollpunkt.
Bevor eine Anfrage Ihre Seite oder API erreicht, passiert sie diesen Punkt. Hier prüfen Sie Identität, ändern Requests oder antworten direkt – z. B. nicht eingeloggte Nutzer zur Login-Seite, oder Sprachversion nach Region.
Sie läuft auf der Edge Runtime. Nicht auf Ihrem Server, sondern an Edge-Knoten (CDN) nahe am Nutzer. Geringe Latenz, fast kein Cold Start. Middleware ist die Schicht am nächsten zum Nutzer.
Edge Runtime vs. Node.js Runtime
| Eigenschaft | Edge Runtime | Node.js Runtime |
|---|---|---|
| Startgeschwindigkeit | Fast kein Cold Start | Hunderte Millisekunden |
| Standort | Globale Edge-Knoten | Bestimmter Server |
| API-Unterstützung | Web-Standard-APIs | Vollständige Node.js-APIs |
| Einsatz | Leichte Logik, schnelle Antwort | Komplexe Berechnung, DB |
Kurz: schnell, aber begrenzt. Keine Node-Module wie fs oder path, die meisten DB-Treiber nicht – ein häufiger Fallstrick.
Wann Middleware?
Nicht alles gehört in Middleware. Typische Szenarien:
1. Authentifizierung (Auth Gate)
Klassiker: Login prüfen, sonst Redirect. Schneller als in Server Components, weil die Anfrage den Server noch nicht erreicht hat.
2. Internationalisierung (i18n)
Sprache aus URL, Cookie oder Browser – automatisch zu /zh oder /en. / → /zh oder /en.
3. A/B-Tests
Nutzer zufällig in Gruppen, verschiedene Seitenversionen. Cookie hält die Gruppe stabil.
4. Bot-Erkennung und Rate Limiting
Crawler blockieren oder IP-Limits.
5. Logging und Analytics
Pfad, Referrer, UA pro Request an Analytics.
6. Content Rewrite
URL intern auf anderen Pfad mappen, Adressleiste unverändert – nützlich für dynamisches Routing und A/B-Tests.
Warum nicht in Page Components?
Geht, aber langsamer. Server- oder Client-Logik läuft erst nach Server-Ankunft oder beim Rendern. Middleware fängt an der Edge ab – schneller, bessere UX.
Außerdem: zentral. Auth nicht auf jeder geschützten Seite wiederholen – ein Ort reicht.
Aber: komplexe Logik, DB, schwere Berechnung → API-Route oder Server Component. Middleware leicht und schnell halten.
Middleware: Grundkonfiguration und Dateistruktur
Wo liegt die Datei?
Next.js verlangt: Projektroot oder src, Dateiname middleware.ts (oder .js).
Projektroot/
├── app/
├── middleware.ts ← hier
├── package.json
Mit src-Ordner:
Projektroot/
├── src/
│ ├── app/
│ ├── middleware.ts ← hier
├── package.json
Hinweis: Nur eine middleware.ts pro Projekt. Keine mehreren unter app – Middleware ist globaler „Türsteher“.
Minimales Beispiel
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
console.log('Request eingegangen:', request.url);
return NextResponse.next(); // durchlassen
}
So einfach. NextResponse.next() heißt: weiter zur Zielseite oder API.
Kern-APIs: NextRequest und NextResponse
NextRequest erweitert die Web Request API:
request.nextUrl: geparste URL mit pathname, search usw.request.cookies: Cookies lesen/schreibenrequest.geo: Geo-Infos (Plattform wie Vercel nötig)
NextResponse bietet:
1. Durchlassen
return NextResponse.next();
2. Redirect (neue URL, Adressleiste ändert sich)
return NextResponse.redirect(new URL('/login', request.url));
3. Rewrite (intern, URL unverändert)
return NextResponse.rewrite(new URL('/dashboard/v2', request.url));
Nutzer sieht /dashboard, Inhalt von /dashboard/v2 – gut für A/B und Versionen.
4. Direkte Antwort
return new NextResponse('Zugriff verweigert', { status: 403 });
Praxis: Custom Header
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('x-custom-header', 'my-value');
return response;
}
Nützlich für Timestamps oder Request-Herkunft auf allen Responses.
Versionshinweis (wichtig)
Ab Next.js 15 heißt die Datei offiziell proxy.ts; middleware.ts bleibt kompatibel. Neues Projekt: proxy.ts. Dieser Artikel basiert auf Next.js 14/15 – Konzepte und APIs gleich.
Pfad-Matching (Matcher): der größte Fallstrick
Ehrlich: matcher hat mir am meisten Ärger gemacht.
Warum matcher?
Ohne matcher läuft Middleware bei jeder Anfrage – CSS, JS, Bilder, Fonts. 20 statische Dateien = 20 Middleware-Läufe. Verschwendung, langsamere Antworten.
matcher sagt Next.js: nur auf diesen Pfaden ausführen.
Grundsyntax
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
}
Nur Pfade unter /dashboard und /api triggern Middleware.
Modifikatoren: *, +, ?
* (null oder mehr)
/dashboard/:path* matcht:
/dashboard✓/dashboard/settings✓/dashboard/settings/profile✓
+ (eins oder mehr)
/dashboard/:path+ matcht:
/dashboard✗/dashboard/settings✓/dashboard/settings/profile✓
? (null oder eins)
/dashboard/:path? matcht:
/dashboard✓/dashboard/settings✓/dashboard/settings/profile✗
Meist reicht *.
Fallstricke und Lösungen (wichtig)
| Problem | Falsch | Richtig | Grund |
|---|---|---|---|
| Mehrstufige dynamische Route | /dashboard/:path | /dashboard/:path* | Ohne * nur eine Ebene |
| Root fehlt | matcher: ['/dashboard/:path*'] | matcher: ['/', '/dashboard/:path*'] | / nicht automatisch enthalten |
| Statische Assets abgefangen | matcher: ['/:path*'] | matcher: ['/((?!_next|favicon.ico).*)'] | _next usw. ausschließen |
| API nicht geschützt | matcher: ['/api/users'] | matcher: ['/api/:path*'] | Ein Pfad vs. alle APIs |
Statische Assets
export const config = {
matcher: ['/:path*'] // alle Pfade
}
→ _next/static wird mitgematcht, Middleware läuft ständig, Seite langsam.
Lösung: negative Lookahead
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
„Alle Pfade außer api, _next/static, _next/image, favicon.ico.“ Regex ist knifflig – offizielles Beispiel kopieren reicht.
Keine dynamischen Werte im matcher
const lang = 'zh';
export const config = {
matcher: [`/${lang}/:path*`] // ❌ geht nicht
}
matcher muss statisch, zur Build-Zeit fest sein. Keine Template-Strings mit Variablen.
Dynamik in die middleware-Funktion:
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname.startsWith('/zh') || pathname.startsWith('/en')) {
// Logik
}
return NextResponse.next();
}
export const config = {
matcher: ['/:locale/:path*']
}
Empfohlene matcher-Vorlagen
Bestimmte Routen schützen (Admin):
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
Alle Routen, statische Assets ausgeschlossen:
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.png$).*)',
],
}
Alle API-Routen:
export const config = {
matcher: ['/api/:path*']
}
Next.js-Doku zu matcher ist knapp – viel lernt man durch Trial and Error. Diese Tabelle soll helfen.
Edge-Runtime-Einschränkungen und Workarounds
Auth in Middleware, DB-Check für Token – lokal: Native Node.js APIs are not supported in the Edge Runtime.
Edge Runtime ist kein vollständiges Node.js; viele APIs und Libraries fehlen.
Was Edge Runtime nicht unterstützt
| Kategorie | Nicht unterstützt | Auswirkung |
|---|---|---|
| Dateisystem | fs, path | Kein lokales Lesen/Schreiben |
| Subprozesse | child_process | Keine externen Befehle |
| Krypto | Teile von crypto | Web Crypto API nutzen |
| Datenbank | MongoDB, MySQL native Treiber | Meiste klassische Treiber nein |
| Sonstiges | process.emit, setImmediate | Tiefe Node-APIs |
Praktische Folgen
- Kein direkter DB-Zugriff für Auth
- Keine Config-Dateien wie
config.json - Keine npm-Pakete, die Node-APIs brauchen
Es gibt Workarounds.
Edge als „Vorposten“
Middleware: leichte Prüfung. Schweres: API-Route oder Server Component.
| Bedarf | ❌ Edge-Limit | ✅ Lösung |
|---|---|---|
| Auth | Keine DB | JWT lokal oder API-Route |
| Verschlüsselung | crypto eingeschränkt | Web Crypto API |
| Config | Kein FS | process.env oder API |
| Logging | Keine lokale Datei | Logtail & Co. |
| DB | Klassische Treiber | Edge-DB (Vercel Postgres, Supabase) |
Praxis: JWT (empfohlen)
JWT ist stateless – ideal für Middleware, kein DB-Lookup nötig.
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose'; // Edge-kompatibel
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
const response = NextResponse.next();
response.headers.set('x-user-id', payload.userId as string);
return response;
} catch (error) {
const response = NextResponse.redirect(new URL('/login', request.url));
response.cookies.delete('auth-token');
return response;
}
}
export const config = {
matcher: ['/dashboard/:path*']
}
Wichtig:
josestattjsonwebtoken(letzteres braucht Nodecrypto)- Secret aus
process.env(in Edge verfügbar) - Bei Fehler Cookie löschen
DB unvermeidbar?
Z. B. gesperrter Nutzer – API-Route aus Middleware:
export async function middleware(request: NextRequest) {
const userId = request.cookies.get('user-id')?.value;
if (!userId) {
return NextResponse.redirect(new URL('/login', request.url));
}
const apiUrl = new URL('/api/check-user-status', request.url);
const response = await fetch(apiUrl, {
headers: { 'x-user-id': userId }
});
const { isActive } = await response.json();
if (!isActive) {
return NextResponse.redirect(new URL('/account-suspended', request.url));
}
return NextResponse.next();
}
API-Route läuft auf Node.js, DB ok – aber mehr Latenz, nur wenn nötig.
Web Crypto API
const data = new TextEncoder().encode('hello world');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
Umständlicher als Node crypto, in Edge die Option.
Edge-Kompatibilität prüfen
Dokumentation lesen oder testen. Native Node.js APIs are not supported = nein.
Edge-Varianten:
- JWT:
josestattjsonwebtoken - DB: Vercel Postgres, Supabase, Prisma (teilweise)
- Logs: Logtail, Axiom
Tipp: In Middleware nichts Schweres. Schnell rein, schnell raus – API-Route für den Rest.
Praxis: drei Kernszenarien
Drei Beispiele aus echten Projekten – kopierbar.
Fall 1: Auth und Routenschutz
Szenario: Admin unter /dashboard nur mit Login.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const token = request.cookies.get('auth-token')?.value;
if (!token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
const expiresAt = payload.exp as number;
const now = Math.floor(Date.now() / 1000);
const shouldRefresh = expiresAt - now < 3600;
const response = NextResponse.next();
if (shouldRefresh) {
response.headers.set('x-token-refresh-needed', 'true');
}
response.headers.set('x-user-id', payload.userId as string);
response.headers.set('x-user-role', payload.role as string);
return response;
} catch (error) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
loginUrl.searchParams.set('reason', 'expired');
const response = NextResponse.redirect(loginUrl);
response.cookies.delete('auth-token');
return response;
}
}
export const config = {
matcher: ['/dashboard/:path*']
}
Punkte:
fromfür Rücksprung nach Login- Token-Ablauf prüfen, vorzeitig erneuern
- User-Infos optional in Header
Test:
- Cookie löschen,
/dashboard→/login?from=/dashboard - Mit Cookie → Seite normal
Häufig: lokal ok, Prod nicht – JWT_SECRET in Vercel/Netlify setzen.
Fall 2: i18n-Redirect
Szenario: DE/EN/ZH – / leitet zu /zh oder /en.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const supportedLocales = ['en', 'zh', 'ja'];
const defaultLocale = 'en';
function getPreferredLocale(request: NextRequest): string {
const urlLocale = request.nextUrl.searchParams.get('lang');
if (urlLocale && supportedLocales.includes(urlLocale)) {
return urlLocale;
}
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
if (cookieLocale && supportedLocales.includes(cookieLocale)) {
return cookieLocale;
}
const acceptLanguage = request.headers.get('accept-language');
if (acceptLanguage) {
const browserLang = acceptLanguage.split(',')[0].split('-')[0];
if (supportedLocales.includes(browserLang)) {
return browserLang;
}
}
return defaultLocale;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const pathnameHasLocale = supportedLocales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (!pathnameHasLocale) {
const locale = getPreferredLocale(request);
const newUrl = new URL(`/${locale}${pathname}`, request.url);
newUrl.search = request.nextUrl.search;
const response = NextResponse.redirect(newUrl);
response.cookies.set('NEXT_LOCALE', locale, {
maxAge: 60 * 60 * 24 * 30,
path: '/'
});
return response;
}
return NextResponse.next();
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|.*\\.).*)'
]
}
Punkte:
- Priorität: URL > Cookie > Browser
- Cookie merkt Sprache
- matcher schließt statische Assets aus
Mit next-intl:
import { createI18nMiddleware } from 'next-intl/middleware';
export default createI18nMiddleware({
locales: ['en', 'zh', 'ja'],
defaultLocale: 'en'
});
export const config = {
matcher: ['/((?!api|_next|.*\\.).)']
};
Fall 3: A/B-Test und Feature Flags
Szenario: 50 % neue Homepage, Daten sammeln, dann Rollout.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname !== '/') {
return NextResponse.next();
}
let variant = request.cookies.get('ab-test-homepage')?.value;
if (!variant) {
variant = Math.random() < 0.5 ? 'A' : 'B';
}
let response: NextResponse;
if (variant === 'B') {
response = NextResponse.rewrite(new URL('/homepage-v2', request.url));
} else {
response = NextResponse.next();
}
response.cookies.set('ab-test-homepage', variant, {
maxAge: 60 * 60 * 24 * 7,
path: '/'
});
response.headers.set('x-ab-variant', variant);
return response;
}
export const config = {
matcher: ['/']
}
Punkte:
rewritestattredirect– URL bleibt/- Cookie hält Variante stabil
- Header für Analytics
Tracking in der Page:
// app/page.tsx
import { headers } from 'next/headers';
export default function HomePage() {
const headersList = headers();
const abVariant = headersList.get('x-ab-variant');
useEffect(() => {
analytics.track('page_view', {
page: 'homepage',
variant: abVariant
});
}, [abVariant]);
return <div>...</div>;
}
Stabile Gruppe per User-ID:
const userId = request.cookies.get('user-id')?.value;
if (userId) {
const hash = simpleHash(userId);
variant = hash % 2 === 0 ? 'A' : 'B';
} else {
variant = request.cookies.get('ab-test-homepage')?.value ||
(Math.random() < 0.5 ? 'A' : 'B');
}
function simpleHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
Auth + i18n kombinierbar.
Performance und Best Practices
Modular organisieren
Eine middleware.ts, Logik in Module:
Projektroot/
├── middleware/
│ ├── auth.ts
│ ├── i18n.ts
│ ├── ab-test.ts
│ └── rate-limit.ts
├── middleware.ts
Einstieg:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { checkAuth } from './middleware/auth';
import { handleI18n } from './middleware/i18n';
import { handleABTest } from './middleware/ab-test';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const i18nResponse = handleI18n(request);
if (i18nResponse) return i18nResponse;
if (pathname.startsWith('/dashboard')) {
const authResponse = await checkAuth(request);
if (authResponse) return authResponse;
}
if (pathname === '/') {
return handleABTest(request);
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
auth.ts:
// middleware/auth.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
export async function checkAuth(request: NextRequest): Promise<NextResponse | null> {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
await jwtVerify(token, secret);
return null;
} catch {
return NextResponse.redirect(new URL('/login', request.url));
}
}
Caching
Token-gültige Zeit: nicht jedes Mal neu validieren.
Vercel Edge Config:
import { get } from '@vercel/edge-config';
export async function middleware(request: NextRequest) {
const featureFlags = await get('feature-flags');
if (featureFlags?.newDashboard) {
return NextResponse.rewrite(new URL('/dashboard-v2', request.url));
}
return NextResponse.next();
}
Edge Config: globaler KV-Store, schnell für selten ändernde Config.
Header-Größe
Zu große Header → 431 Request Header Fields Too Large.
- Unter 8 KB bleiben
- Nur Nötiges, kein ganzes User-Objekt
// ❌ nicht so
response.headers.set('x-user-data', JSON.stringify(userData));
// ✅ so
response.headers.set('x-user-id', user.id);
response.headers.set('x-user-role', user.role);
matcher präzise statt Wildcard
// schwächer
export const config = {
matcher: ['/:path*']
}
// besser
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
}
Monitoring und Debug
Development:
export function middleware(request: NextRequest) {
if (process.env.NODE_ENV === 'development') {
console.log('Middleware:', {
path: request.nextUrl.pathname,
method: request.method,
cookies: request.cookies.getAll()
});
}
// ...
}
Production: wenig console.log; bei Fehler z. B. Logtail:
import { Logger } from '@logtail/edge';
const logger = new Logger(process.env.LOGTAIL_TOKEN);
export async function middleware(request: NextRequest) {
try {
// ...
} catch (error) {
await logger.error('Middleware error', {
path: request.nextUrl.pathname,
error: error.message
});
throw error;
}
}
Checkliste
✅ Tun:
- Middleware leicht halten, Schweres in API-Route
- matcher präzise
- JWT für Auth
- Module nach Funktion
- Debug-Logs in Dev
- Sinnvolle Cookie-Laufzeiten
❌ Vermeiden:
- Schwere Berechnung/DB in Middleware
- Kein matcher (alles läuft)
- Node-only npm-Pakete
- Header > 8 KB
- Massen-Logs in Prod
- Redirect-Endlosschleifen
Häufige Fehler und Debugging
Fehler 1: Middleware läuft nicht
Symptom: Kein Effekt, Requests durch.
| Ursache | Prüfung | Lösung |
|---|---|---|
| Falscher Pfad | Root oder src? | Verschieben |
| matcher | pathname loggen | matcher anpassen |
| Syntax | Kompilierfehler? | Fixen |
| Export | export function middleware? | Syntax prüfen |
| Cache | .next löschen | rm -rf .next && npm run dev |
export function middleware(request: NextRequest) {
console.log('🔥 Middleware! Pfad:', request.nextUrl.pathname);
// ...
}
Kein Log → obige Ursachen.
Fehler 2: Native Node.js APIs are not supported in the Edge Runtime
Stack trace zeigt Modul/API. Übeltäter: fs, path, jsonwebtoken (→ jose), MongoDB/MySQL native.
- Edge-Alternative
- Logik in API-Route
- Web Crypto statt Node
crypto
// ❌
import jwt from 'jsonwebtoken';
// ✅
import { jwtVerify } from 'jose';
Fehler 3: Invalid middleware found
Leeres matcher:
export const config = {
matcher: [] // ❌
}
Kein Export:
const middleware = (request: NextRequest) => { ... } // ❌
export function middleware(request: NextRequest) { ... } // ✅
Kein Return:
export function middleware(request: NextRequest) {
console.log('...');
// ❌ kein return
}
export function middleware(request: NextRequest) {
return NextResponse.next(); // ✅
}
Fehler 4: Endlosschleife bei Redirect
Symptom: ERR_TOO_MANY_REDIRECTS.
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url)); // /login auch gematcht
}
return NextResponse.next();
}
export const config = {
matcher: ['/:path*']
}
Fix: Login ausschließen
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname === '/login' || pathname === '/') {
return NextResponse.next();
}
// ...
}
Oder matcher enger: ['/dashboard/:path*'].
Fehler 5: Umgebungsvariablen undefined
.env.localfehlt oder Tippfehler- Prod: Vercel/Netlify nicht gesetzt
- Namenskonvention Next.js
Lokal:
// .env.local
JWT_SECRET=your-secret-here
Prod: in Plattform setzen, neu deployen.
Nach Änderung Dev-Server neu starten. Client braucht NEXT_PUBLIC_-Präfix.
Debug-Zusammenfassung
1. Header zur Nachverfolgung
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('x-middleware-executed', 'true');
response.headers.set('x-middleware-path', request.nextUrl.pathname);
return response;
}
2. Schrittweise auskommentieren
export function middleware(request: NextRequest) {
console.log('Schritt 1');
console.log('Schritt 2');
console.log('Schritt 3');
return NextResponse.next();
}
3. Vercel Edge Function Logs
Unter Functions in Vercel.
4. Turbo Dev
npm run dev -- --turbo
Next.js 15+: schneller Start, klarere Fehler.
Zusammenfassung
Drei Sätze:
1. Grenzen von Middleware kennen
Leichte Checks und Weiterleitung – Auth, Redirects, A/B. Schweres: API-Route oder Server Component. Middleware = schneller Pförtner, kein Butler.
2. matcher ist entscheidend
Dynamische Routen: *. Statische Assets ausschließen. Login nicht abfangen. Vorlagen nutzen oder in Dev loggen.
3. Edge-Runtime-Limits akzeptieren
Kein volles Node.js – bewusster Trade-off für Speed und globale Verteilung. JWT, Web Crypto, API-Aufrufe als Workaround.
Next.js Middleware ist nicht komplex, aber detailreich. matcher, Edge-Limits, Redirect-Schleifen – hier dokumentiert, damit Sie weniger selbst treten.
Globale Interception geplant? Middleware ausprobieren, dann optimieren. Bei Problemen: Fehlerkapitel oben.
Wenn der Artikel half, gerne teilen. Als Nächstes: Server Actions – ebenfalls viele Fallstricke.
Viel Erfolg – möge Ihre Middleware beim ersten Mal laufen.
Vollständiger Next.js-Middleware-Konfigurations-Workflow
Vom Anlegen der Middleware-Datei bis zu Routenschutz, Internationalisierung und A/B-Tests
⏱️ Estimated time: 3 hr
- 1
Step 1: Middleware-Datei anlegen
Datei erstellen:
• Pfad: middleware.ts (Projektroot)
• config-Objekt für matcher exportieren
• middleware-Funktion für Request-Handling exportieren
Grundstruktur:
export const config = {
matcher: '/dashboard/:path*'
}
export function middleware(request: NextRequest) {
// Logik hier
} - 2
Step 2: matcher-Regeln konfigurieren
Matching-Regeln:
• Einzelner Pfad: '/dashboard'
• Dynamische Route: '/dashboard/:path*' (Stern für mehrere Ebenen)
• Mehrere Pfade: ['/dashboard/:path*', '/admin/:path*']
• Pfade ausschließen: negative Lookahead, z. B. '/((?!api|_next/static|_next/image|favicon.ico).*)'
Hinweise:
• Dynamische Routen brauchen * für mehrere Ebenen
• Statische Assets ausschließen (_next/static, _next/image usw.)
• Öffentliche Seiten (Login) nicht abfangen - 3
Step 3: Routenschutz (Authentifizierung) implementieren
Schritte:
1. Token aus Cookie lesen
2. Token validieren (JWT oder API-Aufruf)
3. Nicht eingeloggte Nutzer zur Login-Seite umleiten
4. Eingeloggte Nutzer durchlassen
Code-Punkte:
• NextRequest.cookies für Cookies
• NextResponse.redirect für Umleitung
• NextResponse.next zum Weiterleiten
• Endlosschleifen bei Redirects vermeiden - 4
Step 4: Internationalisierung (Sprachwechsel) implementieren
Schritte:
1. Sprachpräferenz erkennen (Cookie, Header, Standardwert)
2. Prüfen, ob Redirect nötig ist
3. Sprachpräfix zur URL hinzufügen
4. Sprach-Cookie setzen
Code-Punkte:
• request.headers.get('accept-language')
• request.nextUrl.pathname für Pfad
• NextResponse.rewrite für URL-Umschreibung
• Sprachwechsel ohne URL-Strukturänderung unterstützen - 5
Step 5: Edge-Runtime-Einschränkungen handhaben
Limits und Lösungen:
• Keine Node.js-APIs → Web-Standard-APIs nutzen
• Kein Dateisystem → Umgebungsvariablen oder API-Aufrufe
• Manche npm-Pakete nicht unterstützt → Edge-Kompatibilität prüfen
• JWT-Validierung → Web Crypto API oder API-Route
Debugging:
• console.log für Debug-Ausgaben
• Vercel Edge Functions Logs prüfen
• try-catch für Fehler - 6
Step 6: Testen und debuggen
Test-Schwerpunkte:
• Alle gematchten Pfade testen
• Nicht gematchte Pfade (kein Abfangen)
• Redirect-Logik
• Edge-Runtime-Kompatibilität
Debug-Methoden:
• console.log in middleware
• Browser Network-Tab
• Vercel Edge Functions Logs
• Next.js Dev-Modus für Warnungen
FAQ
Was tun, wenn die matcher-Konfiguration der Middleware nicht greift?
1) matcher-Pfad korrekt? (dynamische Routen brauchen *)
2) Statische Assets ausgeschlossen?
3) Pfadformat korrekt? (keine Regex, Next.js-Format nutzen)
console.log in middleware zeigt, welche Pfade gematcht werden.
Warum werden mehrstufige Pfade nicht gematcht?
Was, wenn die Edge Runtime eine Bibliothek nicht unterstützt?
Lösungen:
1) Edge-Kompatibilität der Bibliothek prüfen
2) Web-Standard-APIs nutzen
3) Komplexe Logik in API-Route oder Server Component
4) Edge-kompatible Alternative
Wie Endlosschleifen bei Redirects vermeiden?
Kann Middleware auf eine Datenbank zugreifen?
Bei DB-Abfragen:
1) API-Route aus Middleware aufrufen
2) Konfiguration in Umgebungsvariablen
3) Komplexe Logik in API-Route oder Server Component
Wie debugge ich Middleware?
1) console.log in middleware
2) Browser Network-Tab für Request/Response
3) Vercel Edge Functions Logs
4) Next.js Dev-Modus für Warnungen und Fehler
Unterschied Middleware und API-Route?
• Edge Runtime
• Vor Seite oder API-Route
• Leichte Interception und Weiterleitung
API-Route:
• Node.js Runtime
• Volle Node.js-APIs und DB
• Komplexe Geschäftslogik
13 Min. Lesezeit · Veröffentlicht am: 25. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Server Actions Tutorial: Formularverarbeitung und Validierung – Best Practices
Praxisbeispiele zu Next.js Server Actions: Formularverarbeitung, Zod-Validierung, Sicherheit und UX-Optimierung – damit Sie dieses Feature für einen schlankeren Entwicklungsworkflow nutzen
Teil 10 von 51
Nächster
Next.js-Routenschutz und Berechtigungskontrolle: Middleware und mehrschichtige Absicherung – vollständiger Leitfaden
Next.js-Routenschutz und Berechtigungskontrolle im Detail: von Middleware bis mehrschichtiger Absicherung, mit NextAuth und getServerSession für ein sicheres RBAC-System – inklusive vollständiger Codebeispiele.
Teil 12 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen