Design wechseln

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

Easton editorial illustration: rendering-mode selector

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

EigenschaftEdge RuntimeNode.js Runtime
StartgeschwindigkeitFast kein Cold StartHunderte Millisekunden
StandortGlobale Edge-KnotenBestimmter Server
API-UnterstützungWeb-Standard-APIsVollständige Node.js-APIs
EinsatzLeichte Logik, schnelle AntwortKomplexe 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/schreiben
  • request.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)

ProblemFalschRichtigGrund
Mehrstufige dynamische Route/dashboard/:path/dashboard/:path*Ohne * nur eine Ebene
Root fehltmatcher: ['/dashboard/:path*']matcher: ['/', '/dashboard/:path*']/ nicht automatisch enthalten
Statische Assets abgefangenmatcher: ['/:path*']matcher: ['/((?!_next|favicon.ico).*)']_next usw. ausschließen
API nicht geschütztmatcher: ['/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

KategorieNicht unterstütztAuswirkung
Dateisystemfs, pathKein lokales Lesen/Schreiben
Subprozessechild_processKeine externen Befehle
KryptoTeile von cryptoWeb Crypto API nutzen
DatenbankMongoDB, MySQL native TreiberMeiste klassische Treiber nein
Sonstigesprocess.emit, setImmediateTiefe Node-APIs

Praktische Folgen

  1. Kein direkter DB-Zugriff für Auth
  2. Keine Config-Dateien wie config.json
  3. 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
AuthKeine DBJWT lokal oder API-Route
Verschlüsselungcrypto eingeschränktWeb Crypto API
ConfigKein FSprocess.env oder API
LoggingKeine lokale DateiLogtail & Co.
DBKlassische TreiberEdge-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:

  • jose statt jsonwebtoken (letzteres braucht Node crypto)
  • 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: jose statt jsonwebtoken
  • 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:

  1. from für Rücksprung nach Login
  2. Token-Ablauf prüfen, vorzeitig erneuern
  3. 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:

  1. Priorität: URL > Cookie > Browser
  2. Cookie merkt Sprache
  3. 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:

  1. rewrite statt redirect – URL bleibt /
  2. Cookie hält Variante stabil
  3. 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.

UrsachePrüfungLösung
Falscher PfadRoot oder src?Verschieben
matcherpathname loggenmatcher anpassen
SyntaxKompilierfehler?Fixen
Exportexport function middleware?Syntax prüfen
Cache.next löschenrm -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.

  1. Edge-Alternative
  2. Logik in API-Route
  3. 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

  1. .env.local fehlt oder Tippfehler
  2. Prod: Vercel/Netlify nicht gesetzt
  3. 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. 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. 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. 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. 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. 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. 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?
Prüfen:
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?
Dynamische Routen brauchen :path* (mit Stern) für mehrere Ebenen. /dashboard/:path* matcht /dashboard/settings/profile; /dashboard/:path nur /dashboard/settings (eine Ebene).
Was, wenn die Edge Runtime eine Bibliothek nicht unterstützt?
Edge Runtime ist kein vollständiges Node.js und unterstützt nicht alle npm-Pakete.

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?
Redirect-Ziel darf nicht im matcher liegen. Bei Redirect zu /login matcher /login ausschließen, z. B. '/((?!login|api).*)'.
Kann Middleware auf eine Datenbank zugreifen?
Nicht empfohlen. Middleware läuft auf Edge Runtime und soll leicht bleiben.

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?
Methoden:
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?
Middleware:
• 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

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog