Introduktion: Hvorfor Middleware Er Så Vigtig i Next.js
Middleware i Next.js er ærligt talt en af de mest undervurderede funktioner i hele frameworket. Det kører før enhver request når din applikation, og giver dig mulighed for at inspicere, omskrive og redirecte trafik på Edge Runtime — tæt på brugeren og lynhurtigt.
Tænk på det som en intelligent gatekeeper for hele din app.
Har du allerede styr på autentificering med Auth.js (som vi dækkede i vores Auth.js v5-guide), så er middleware det naturlige næste skridt. Det er her, du implementerer rutebeskyttelse, håndterer internationalisering, sætter sikkerhedsheaders og meget mere — alt sammen uden at brugeren mærker nogen forsinkelse overhovedet.
I denne guide gennemgår vi alt fra grundlæggende opsætning til avancerede mønstre som middleware-chaining, rate limiting og geolocation-baseret routing. Alle eksempler bruger Next.js 15 med App Router og TypeScript, så du kan copy-paste dem direkte ind i dit projekt.
Hvad Er Next.js Middleware, og Hvordan Virker Det?
Middleware er en funktion der kører på Vercels Edge Runtime (eller Node.js runtime, afhængigt af din konfiguration) for hver eneste request til din applikation. Den kører efter Next.js har resolved den matchende rute, men før noget indhold renderes eller leveres.
Her er det vigtigste at forstå:
- Edge Runtime — Middleware kører som standard på Edge Runtime, hvilket betyder det eksekveres i datacentre tæt på brugeren med minimal latency (typisk under 1ms overhead)
- Ingen fuld Node.js API — Edge Runtime har begrænset API-adgang. Du kan ikke bruge
fs,child_processeller andre Node.js-specifikke moduler - Web Standard APIs — Du har adgang til
Request,Response,Headers,fetchog andre Web APIs - Én middleware-fil — Hele din middleware lever i én fil:
middleware.tsi projektets rod (eller isrc/hvis du bruger src-mappen)
Flowet ser sådan ud: Bruger sender request → Edge Network modtager → Middleware kører → Request sendes videre (eller redirectes/blokeres) → Side renderes. Simpelt, men utroligt kraftfuldt.
Grundlæggende Opsætning af Middleware
Okay, lad os starte med det helt basale. Opret filen middleware.ts i din projektrod (eller src/middleware.ts):
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
console.log('Middleware kører for:', request.nextUrl.pathname);
// Fortsæt til den ønskede side
return NextResponse.next();
}
// Konfigurer hvilke ruter middleware skal matche
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)'
],
};
Denne simple middleware logger alle requests og sender dem videre. config.matcher er kritisk vigtig — den bestemmer hvilke URL-mønstre middleware aktiveres for. Uden den kører middleware for alle requests, inklusiv statiske filer, og det er simpelthen spild af ressourcer.
Matcher-syntaksen understøtter:
- Statiske stier:
'/about'matcher kun/about - Dynamiske segmenter:
'/blog/:slug'matcher/blog/hvad-som-helst - Regex-mønstre:
'/((?!api|_next).*)'matcher alt undtagen API og interne ruter - Arrays: Du kan angive flere mønstre i et array
Rutebeskyttelse med Autentificering
Det absolut mest almindelige use case for middleware er at beskytte ruter, der kræver login. Her integrerer vi med Auth.js v5:
import { auth } from './auth';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const protectedRoutes = ['/dashboard', '/profile', '/settings', '/admin'];
const authRoutes = ['/login', '/register'];
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
const isProtectedRoute = protectedRoutes.some(route =>
nextUrl.pathname.startsWith(route)
);
const isAuthRoute = authRoutes.some(route =>
nextUrl.pathname.startsWith(route)
);
// Redirect uautentificerede brugere væk fra beskyttede ruter
if (isProtectedRoute && !isLoggedIn) {
const redirectUrl = new URL('/login', nextUrl.origin);
redirectUrl.searchParams.set('callbackUrl', nextUrl.pathname);
return NextResponse.redirect(redirectUrl);
}
// Redirect autentificerede brugere væk fra login/register
if (isAuthRoute && isLoggedIn) {
return NextResponse.redirect(new URL('/dashboard', nextUrl.origin));
}
return NextResponse.next();
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Det geniale her er, at Auth.js v5 eksporterer en auth()-wrapper, der automatisk tjekker session-tokenet. Vi behøver ikke manuelt parse cookies eller kalde en API — det hele er bygget ind. Ret elegant, faktisk.
Bemærk også callbackUrl-parameteren. Den sikrer at brugeren redirectes tilbage til den side, de prøvede at tilgå, efter succesfuldt login. Det er en lille detalje mange glemmer, men den gør en kæmpe forskel for brugeroplevelsen.
Rollebaseret Adgangskontrol i Middleware
Ofte er simpel autentificering ikke nok — du har brug for rollebaseret adgang. Her er et mønster, der har fungeret godt for mig:
import { auth } from './auth';
import { NextResponse } from 'next/server';
const roleRoutes: Record<string, string[]> = {
'/admin': ['admin'],
'/dashboard/billing': ['admin', 'billing'],
'/dashboard/reports': ['admin', 'manager', 'analyst'],
};
export default auth((req) => {
const { nextUrl } = req;
const user = req.auth?.user;
if (!user) {
return NextResponse.redirect(new URL('/login', nextUrl.origin));
}
// Tjek rollebaseret adgang
for (const [route, allowedRoles] of Object.entries(roleRoutes)) {
if (nextUrl.pathname.startsWith(route)) {
const userRole = (user as any).role || 'user';
if (!allowedRoles.includes(userRole)) {
return NextResponse.redirect(new URL('/unauthorized', nextUrl.origin));
}
}
}
return NextResponse.next();
});
Det her mønster er virkelig skalerbart — du tilføjer bare nye ruter og roller til roleRoutes-objektet. Det er også nemt at vedligeholde, fordi al adgangslogik er samlet ét sted i stedet for spredt ud over hele kodebasen.
Redirects og Rewrites
Middleware er perfekt til at håndtere redirects og rewrites dynamisk — uden at hardcode dem i next.config.js. Det giver dig meget mere fleksibilitet.
Dynamiske Redirects
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Redirect-regler der nemt kan opdateres
const redirectRules: Record<string, string> = {
'/old-blog': '/blog',
'/docs/v1': '/docs/v2',
'/about-us': '/about',
'/careers': 'https://careers.example.com',
};
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Tjek for redirects
for (const [from, to] of Object.entries(redirectRules)) {
if (pathname.startsWith(from)) {
const newPath = pathname.replace(from, to);
if (to.startsWith('http')) {
return NextResponse.redirect(to);
}
return NextResponse.redirect(
new URL(newPath, request.url),
{ status: 301 } // Permanent redirect for SEO
);
}
}
return NextResponse.next();
}
URL Rewrites (Intern Routing)
Rewrites er anderledes end redirects — brugeren ser stadig den originale URL, men indholdet serveres fra en anden sti. Det er super nyttigt til A/B-testing, feature flags og multi-tenant apps:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const hostname = request.headers.get('host') || '';
const { pathname } = request.nextUrl;
// Multi-tenant: Rewrite baseret på subdomain
const subdomain = hostname.split('.')[0];
if (subdomain !== 'www' && subdomain !== 'localhost:3000') {
// blog.example.com/posts → /tenants/blog/posts
const newUrl = new URL(`/tenants/${subdomain}${pathname}`, request.url);
return NextResponse.rewrite(newUrl);
}
// A/B testing baseret på cookie
const variant = request.cookies.get('ab-variant')?.value;
if (pathname === '/pricing' && variant === 'b') {
return NextResponse.rewrite(new URL('/pricing-v2', request.url));
}
return NextResponse.next();
}
Den multi-tenant tilgang er særligt kraftfuld. Du kan have én Next.js-app der serverer helt forskellige tenants baseret på subdomain — alt håndteret i middleware uden ekstra infrastruktur. Det er noget, der ville kræve en hel omvendt proxy i andre frameworks.
Sikkerhedsheaders og CORS
Middleware er det ideelle sted at tilføje sikkerhedsheaders til alle responses. Det sikrer konsistens på tværs af hele applikationen, og du glemmer ikke nogen endpoint:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Sikkerhedsheaders
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=()'
);
// Content Security Policy
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' blob: data: https:",
"font-src 'self' data:",
"connect-src 'self' https://api.example.com",
].join('; ');
response.headers.set('Content-Security-Policy', csp);
// CORS for API-ruter
if (request.nextUrl.pathname.startsWith('/api/')) {
const origin = request.headers.get('origin') || '';
const allowedOrigins = [
'https://example.com',
'https://app.example.com',
];
if (allowedOrigins.includes(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin);
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
response.headers.set('Access-Control-Max-Age', '86400');
}
// Håndter preflight requests
if (request.method === 'OPTIONS') {
return new NextResponse(null, { status: 204, headers: response.headers });
}
}
return response;
}
Disse headers beskytter mod clickjacking, MIME-type sniffing, XSS og andre almindelige angrebsvektorer. CSP-headeren er nok den vigtigste af dem alle — den kontrollerer præcis hvilke ressourcer browseren må indlæse, og kan forhindre de fleste injektionsangreb.
Rate Limiting på Edge
Rate limiting i middleware er en rigtig effektiv måde at beskytte dine API-endpoints mod misbrug. Fordi middleware kører på Edge, kan du blokere ondsindet trafik før den overhovedet når din server. Det er som at have en dørmand, der tjekker gæstelisten inden folk kommer ind.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// In-memory rate limiter (til demo — brug Redis i produktion)
const rateLimit = new Map<string, { count: number; resetTime: number }>();
const WINDOW_MS = 60 * 1000; // 1 minut
const MAX_REQUESTS = 60; // 60 requests per minut
function getRateLimitInfo(ip: string) {
const now = Date.now();
const record = rateLimit.get(ip);
if (!record || now > record.resetTime) {
const newRecord = { count: 1, resetTime: now + WINDOW_MS };
rateLimit.set(ip, newRecord);
return { allowed: true, remaining: MAX_REQUESTS - 1 };
}
record.count++;
const remaining = Math.max(0, MAX_REQUESTS - record.count);
return {
allowed: record.count <= MAX_REQUESTS,
remaining,
};
}
export function middleware(request: NextRequest) {
// Kun rate limit API-ruter
if (!request.nextUrl.pathname.startsWith('/api/')) {
return NextResponse.next();
}
const ip = request.headers.get('x-forwarded-for')?.split(',')[0] ||
request.headers.get('x-real-ip') ||
'unknown';
const { allowed, remaining } = getRateLimitInfo(ip);
if (!allowed) {
return NextResponse.json(
{ error: 'For mange requests. Prøv igen senere.' },
{
status: 429,
headers: {
'Retry-After': '60',
'X-RateLimit-Limit': MAX_REQUESTS.toString(),
'X-RateLimit-Remaining': '0',
},
}
);
}
const response = NextResponse.next();
response.headers.set('X-RateLimit-Limit', MAX_REQUESTS.toString());
response.headers.set('X-RateLimit-Remaining', remaining.toString());
return response;
}
Vigtigt: In-memory rate limiting fungerer kun i udviklingsmiljøet og på en enkelt serverinstans. I produktion med flere Edge-noder bør du bruge en distribueret løsning som Upstash Redis. De har en dedikeret @upstash/ratelimit-pakke, der er optimeret specifikt til Edge Runtime:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(60, '1 m'),
analytics: true,
});
export async function middleware(request: NextRequest) {
if (!request.nextUrl.pathname.startsWith('/api/')) {
return NextResponse.next();
}
const ip = request.headers.get('x-forwarded-for')?.split(',')[0] || 'anonymous';
const { success, limit, remaining } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Rate limit overskredet' },
{ status: 429 }
);
}
return NextResponse.next();
}
Internationalisering (i18n) med Middleware
Her er noget, der overrasker mange: Next.js 15 har faktisk fjernet den indbyggede i18n-konfiguration fra next.config.js i App Router. Det betyder, at du selv skal implementere sprogbaseret routing. Middleware er den oplagte løsning til det:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
const locales = ['da', 'en', 'de', 'sv'];
const defaultLocale = 'da';
function getLocale(request: NextRequest): string {
// 1. Tjek cookie for brugerens sprogpræference
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
if (cookieLocale && locales.includes(cookieLocale)) {
return cookieLocale;
}
// 2. Brug Accept-Language header
const headers: Record<string, string> = {};
request.headers.forEach((value, key) => {
headers[key] = value;
});
const languages = new Negotiator({ headers }).languages();
try {
return match(languages, locales, defaultLocale);
} catch {
return defaultLocale;
}
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Tjek om stien allerede har et locale-prefix
const pathnameHasLocale = locales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (pathnameHasLocale) {
return NextResponse.next();
}
// Redirect til den korrekte locale
const locale = getLocale(request);
const newUrl = new URL(`/${locale}${pathname}`, request.url);
return NextResponse.redirect(newUrl);
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Denne middleware gør følgende:
- Tjekker først om brugeren har sat en sprogpræference via cookie
- Falder tilbage til browserens
Accept-Languageheader - Matcher det bedste tilgængelige sprog med
@formatjs/intl-localematcher - Redirecter til den korrekte locale-prefixed URL
Din mappestruktur i app/ skal så afspejle sprogene:
app/
[locale]/
layout.tsx
page.tsx
blog/
page.tsx
Det kræver lidt mere opsætning end den gamle Pages Router-tilgang, men til gengæld har du fuld kontrol over sproghåndteringen.
Geolocation-Baseret Routing
Når du deployer på Vercel, får din middleware adgang til geolocation-data via request-objektet. Det åbner op for en masse spændende muligheder — regionsspecifikt indhold, compliance-håndtering og mere:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'DK';
const city = request.geo?.city || 'Unknown';
const region = request.geo?.region || 'Unknown';
// EU GDPR cookie-consent krav
const euCountries = [
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI',
'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU',
'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE'
];
const response = NextResponse.next();
// Sæt geo-headers til brug i Server Components
response.headers.set('x-user-country', country);
response.headers.set('x-user-city', city);
response.headers.set('x-user-region', region);
response.headers.set(
'x-requires-cookie-consent',
euCountries.includes(country) ? 'true' : 'false'
);
// Blokér adgang fra sanktionerede lande
const blockedCountries = ['XX', 'YY'];
if (blockedCountries.includes(country)) {
return NextResponse.rewrite(new URL('/blocked', request.url));
}
return response;
}
I dine Server Components kan du derefter læse disse headers. Det er en virkelig ren måde at sende geo-data videre uden at brugeren skal gøre noget:
import { headers } from 'next/headers';
export default async function Page() {
const headerList = await headers();
const country = headerList.get('x-user-country');
const requiresConsent = headerList.get('x-requires-cookie-consent') === 'true';
return (
<div>
{requiresConsent && <CookieConsentBanner />}
{/* Resten af siden */}
</div>
);
}
Middleware Chaining: Kombiner Flere Funktioner
Når din middleware vokser, bliver den hurtigt en stor rodet funktion. Det har vi alle prøvet. Middleware chaining er løsningen — et mønster der lader dig sammensætte flere middleware-funktioner, der hver især har ét ansvar:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
type MiddlewareFunction = (
request: NextRequest,
response: NextResponse
) => NextResponse | Response | undefined;
function chainMiddleware(functions: MiddlewareFunction[]) {
return function middleware(request: NextRequest) {
let response = NextResponse.next();
for (const fn of functions) {
const result = fn(request, response);
if (result instanceof Response && result.status !== 200) {
return result; // Redirect eller error — stop kæden
}
if (result) {
response = result as NextResponse;
}
}
return response;
};
}
// Individuelle middleware-funktioner
function withSecurityHeaders(request: NextRequest, response: NextResponse) {
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
return response;
}
function withAuthCheck(request: NextRequest, response: NextResponse) {
const token = request.cookies.get('session-token')?.value;
const isProtected = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtected && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return response;
}
function withLogging(request: NextRequest, response: NextResponse) {
// Log request til analytics
response.headers.set('x-request-path', request.nextUrl.pathname);
response.headers.set('x-request-time', Date.now().toString());
return response;
}
// Kombiner alle middleware-funktioner
export default chainMiddleware([
withSecurityHeaders,
withAuthCheck,
withLogging,
]);
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Med dette mønster er hver middleware-funktion isoleret, testbar og genanvendelig. Du kan nemt tilføje eller fjerne funktioner fra kæden, og rækkefølgen er eksplicit og forudsigelig. Det er min foretrukne tilgang til større projekter.
Performance-Optimering af Middleware
Selvom middleware er hurtig, kan dårlig implementering stadig skabe flaskehalse. Her er de vigtigste ting at holde øje med:
1. Brug Matcher Korrekt
Undgå at køre middleware for statiske assets — det er unødvendigt og spilder ressourcer:
export const config = {
matcher: [
// Match alle ruter UNDTAGEN:
'/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};
2. Undgå Tunge Operationer
Edge Runtime har et eksekveringstidsloft (typisk 25ms på Vercel). Det lyder måske af meget, men det er overraskende nemt at ramme. Undgå disse ting:
- Database-queries direkte i middleware (brug caching eller Edge Config i stedet)
- Komplekse beregninger eller store JSON-parses
- Eksterne API-kald der kan tage lang tid
3. Brug Vercel Edge Config for Dynamiske Data
import { get } from '@vercel/edge-config';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
// Edge Config har ultra-lav latency (<1ms)
const maintenanceMode = await get('maintenanceMode');
if (maintenanceMode) {
return NextResponse.rewrite(new URL('/maintenance', request.url));
}
// Feature flags fra Edge Config
const enableNewPricing = await get('enableNewPricing');
if (enableNewPricing && request.nextUrl.pathname === '/pricing') {
return NextResponse.rewrite(new URL('/pricing-v2', request.url));
}
return NextResponse.next();
}
Debugging og Test af Middleware
Middleware kan ærligt talt være en smule frustrerende at debugge, fordi den kører i et helt andet runtime-miljø end resten af din app. Her er nogle strategier, der gør det lettere:
Lokal Debugging
export function middleware(request: NextRequest) {
// Detaljeret logging i development
if (process.env.NODE_ENV === 'development') {
console.log({
path: request.nextUrl.pathname,
method: request.method,
cookies: Object.fromEntries(request.cookies.getAll().map(c => [c.name, c.value])),
headers: Object.fromEntries(request.headers.entries()),
geo: request.geo,
});
}
return NextResponse.next();
}
Unit Testing med Vitest
import { describe, it, expect, vi } from 'vitest';
import { middleware } from './middleware';
import { NextRequest } from 'next/server';
describe('Middleware', () => {
it('redirecter uautentificerede brugere fra /dashboard', () => {
const request = new NextRequest(
new URL('http://localhost:3000/dashboard')
);
// Ingen session cookie sat
const response = middleware(request);
expect(response.status).toBe(307);
expect(response.headers.get('location')).toContain('/login');
});
it('tillader autentificerede brugere adgang til /dashboard', () => {
const request = new NextRequest(
new URL('http://localhost:3000/dashboard'),
{ headers: { cookie: 'session-token=valid-token' } }
);
const response = middleware(request);
expect(response.status).toBe(200);
});
it('sætter sikkerhedsheaders på alle responses', () => {
const request = new NextRequest(
new URL('http://localhost:3000/')
);
const response = middleware(request);
expect(response.headers.get('X-Frame-Options')).toBe('DENY');
expect(response.headers.get('X-Content-Type-Options')).toBe('nosniff');
});
});
Almindelige Fejl og Faldgruber
Her er de fejl, jeg ser udviklere lave igen og igen med Next.js middleware. Lær af andres fejl (så du ikke selv skal lave dem):
- For bred matcher — Kører middleware for statiske filer, hvilket sænker performance markant. Brug altid en matcher der ekskluderer
_next/static, billeder og ikoner - Tungt arbejde i middleware — Database-queries, eksterne API-kald og kompleks logik hører ikke hjemme i middleware. Hold det let og hurtigt
- Glemmer Edge Runtime-begrænsninger — Du kan ikke bruge Node.js-specifikke APIs. Hvis du har brug for det, sæt
export const runtime = 'nodejs', men vær opmærksom på at du så mister Edge-fordelene - Infinite redirect-loops — Det sker når middleware redirecter til en rute, der selv trigger endnu en redirect. Sørg for at dine redirect-ruter (f.eks.
/login) er ekskluderet fra beskyttede ruter - Manglende error handling — Middleware der crasher kan tage hele applikationen ned. Wrap altid potentielt fejlende kode i try/catch
export function middleware(request: NextRequest) {
try {
// Din middleware-logik her
return NextResponse.next();
} catch (error) {
console.error('Middleware fejl:', error);
// Fail open — lad requesten gå igennem
return NextResponse.next();
}
}
FAQ
Kan jeg bruge flere middleware-filer i Next.js?
Nej, Next.js understøtter kun én middleware.ts-fil pr. projekt. Den skal placeres i projektets rod eller i src/-mappen. Hvis du har brug for at opdele logikken, brug middleware chaining-mønsteret som vist ovenfor — importér og sammensæt separate funktioner i den ene fil.
Hvad er forskellen på Edge Runtime og Node.js Runtime for middleware?
Edge Runtime kører middleware tæt på brugeren i distribuerede datacentre med minimal latency (under 1ms overhead), men har begrænset API-adgang — ingen fs, child_process osv. Node.js Runtime giver fuld API-adgang, men kører kun i ét datacenter, hvilket kan øge latencyen. Kort sagt: vælg Edge Runtime medmindre du specifikt har brug for Node.js APIs.
Påvirker middleware performance negativt?
Vel-implementeret middleware tilføjer typisk under 1ms til request-tiden på Edge Runtime. Problemer opstår kun ved tunge operationer som database-queries eller eksterne API-kald. Tommelfingerreglen: hold middleware let, brug cookies og headers til beslutninger, og udskyd tunge operationer til Server Components eller API Routes.
Hvordan tester jeg middleware lokalt?
Brug next dev med console.log til hurtig debugging. Til unit tests fungerer Vitest eller Jest godt med mocking af NextRequest og NextResponse. En ting at være opmærksom på: geolocation-data (request.geo) er kun tilgængelig i produktion på Vercel — lokalt vil det være undefined.
Kan middleware erstatte API Routes til autentificering?
Middleware er ideel til at tjekke autentificering (er brugeren logget ind? har de den rette rolle?), men selve autentificeringslogikken — login, logout, token-refresh — bør stadig håndteres i API Routes eller Server Actions. Tænk på middleware som gatekeeping, ikke forretningslogik.