Next.js Middleware: Komplett guide till routing, autentisering och Edge Runtime (2026)
Middleware i Next.js ger dig kraften att fånga upp och modifiera varje HTTP-förfrågan innan den ens når din applikations sidor eller API-routes. Det handlar egentligen om att placera intelligent logik vid själva ingångspunkten till din applikation — oavsett om det gäller autentisering, internationalisering, hastighetsbegränsning eller avancerad routing.
I den här guiden går vi igenom allt du behöver veta om Next.js Middleware. Från grundläggande konfiguration till produktionsredo mönster, inklusive den ganska omtalade övergången till proxy.ts i Next.js 16.
Om du redan har läst vår artikel om Server Actions i Next.js vet du hur kraftfull progressiv förbättring fungerar på serversidan. Middleware kompletterar det arbetet genom att hantera förfrågningar på nätverksnivå — alltså innan Server Actions eller sidrendering ens aktiveras.
Vad är Middleware i Next.js?
Middleware är i grunden en funktion som körs innan cachad innehåll och routematchning bearbetas av Next.js. Det innebär att varje enda förfrågan till din applikation passerar genom middleware innan den når en sida, layout, API-route eller statisk fil.
Denna strategiska position i förfrågningslivscykeln gör middleware idealiskt för:
- Autentisering och auktorisering — kontrollera sessioner och omdirigera oautentiserade användare
- Internationalisering (i18n) — detektera användarens språk och omdirigera till rätt locale
- Hastighetsbegränsning — skydda API:er mot missbruk
- A/B-testning — dirigera användare till olika varianter
- Geolokalisering — anpassa innehåll baserat på användarens plats
- Loggning och övervakning — spåra förfrågningar för analys
En viktig begränsning att känna till: i Edge Runtime är middleware begränsat till 1 MB i storlek, och Edge Runtime stödjer inte alla Node.js API:er. Det här har lett till ganska betydande förändringar i hur middleware hanteras i nyare versioner av Next.js — mer om det längre ner.
Förfrågningslivscykeln
Så här ser flödet ut när en förfrågan når din Next.js-applikation:
- Förfrågan kommer in till servern
- Middleware körs (kan omdirigera, skriva om URL:er, sätta headers)
- Routematchning sker (Next.js bestämmer vilken sida som ska visas)
- Cachekontroll (ISR, statisk generering)
- Sidan renderas eller serveras från cache
- Svaret skickas tillbaka till klienten
Det är egentligen ganska elegant — middleware sitter som en grindvakt innan allt annat händer.
Grundläggande konfiguration av middleware.ts
Middleware definieras i en fil som heter middleware.ts (eller middleware.js) i projektets rotmapp — på samma nivå som app/- eller pages/-katalogen. Filen exporterar en funktion som tar emot ett NextRequest-objekt och returnerar ett NextResponse-objekt.
// middleware.ts — grundläggande struktur
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Logga varje inkommande förfrågan
console.log(`Förfrågan till: ${request.nextUrl.pathname}`);
// Fortsätt till nästa steg i kedjan
return NextResponse.next();
}
Simpelt, eller hur? Men det finns mer att utforska.
NextRequest — vad du har tillgång till
Objektet NextRequest utökar det vanliga Request-objektet med extra funktionalitet som är specifik för Next.js:
// middleware.ts — utforska NextRequest
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// URL och sökväg
const pathname = request.nextUrl.pathname;
const searchParams = request.nextUrl.searchParams;
// Cookies
const sessionCookie = request.cookies.get('session-token');
const allCookies = request.cookies.getAll();
// Headers
const userAgent = request.headers.get('user-agent');
const acceptLanguage = request.headers.get('accept-language');
// Geolokalisering (tillgänglig på Vercel)
const country = request.geo?.country;
const city = request.geo?.city;
// IP-adress
const ip = request.ip;
return NextResponse.next();
}
NextResponse — vad du kan göra
Med NextResponse kan du styra hur förfrågan hanteras. Här har du i princip fyra huvudsakliga åtgärder:
// middleware.ts — olika NextResponse-åtgärder
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// 1. Omdirigera till en annan URL
if (pathname === '/gammal-sida') {
return NextResponse.redirect(new URL('/ny-sida', request.url));
}
// 2. Skriv om URL:en (intern routing utan att URL:en ändras i webbläsaren)
if (pathname === '/blogg') {
return NextResponse.rewrite(new URL('/api/blogg-feed', request.url));
}
// 3. Sätt anpassade headers på svaret
const response = NextResponse.next();
response.headers.set('x-custom-header', 'mitt-värde');
response.headers.set('x-request-id', crypto.randomUUID());
// 4. Sätt cookies
response.cookies.set('besökare-id', crypto.randomUUID(), {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 365, // 1 år
});
return response;
}
Matcher-konfiguration — styra vilka routes som berörs
Som standard körs middleware för varje förfrågan. Det är sällan önskvärt (och ärligt talat, ganska slösaktigt). Med config.matcher kan du specificera exakt vilka sökvägar middleware ska aktiveras för.
// middleware.ts — matcher-konfiguration
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Denna kod körs bara för matchande routes
return NextResponse.next();
}
// Enkel matcher — en enda sökväg
export const config = {
matcher: '/dashboard/:path*',
};
Flera matchers och regex-mönster
// middleware.ts — avancerade matcher-mönster
export const config = {
matcher: [
// Matcha alla sökvägar under /dashboard
'/dashboard/:path*',
// Matcha alla API-routes
'/api/:path*',
// Matcha specifika sidor
'/profil',
'/installningar/:path*',
// Regex: matcha allt UTOM statiska filer och API:er
'/((?!_next/static|_next/image|favicon\\.ico|sitemap\\.xml|robots\\.txt).*)',
],
};
Exkludera statiska tillgångar
Ett vanligt (och viktigt!) mönster är att exkludera statiska filer och Next.js interna routes:
// middleware.ts — exkludera statiska tillgångar
export const config = {
matcher: [
// Matcha alla routes utom statiska filer
{
source: '/((?!_next/static|_next/image|favicon\\.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
};
Genom att använda missing-villkoret kan du dessutom undvika att middleware körs för prefetch-förfrågningar, vilket förbättrar prestanda avsevärt. Det här är en sån grej som inte alltid nämns i tutorialer men som gör rejäl skillnad i praktiken.
Autentisering och routskydd
Okej, nu kommer vi till ett av de absolut vanligaste användningsfallen för middleware — att skydda sidor som kräver inloggning. Istället för att kontrollera autentisering i varje enskild sida eller layout kan du centralisera hela logiken i middleware.
Det sparar enormt med duplicerad kod.
// middleware.ts — autentisering med sessionscookie
import { NextRequest, NextResponse } from 'next/server';
// Sökvägar som kräver inloggning
const protectedRoutes = ['/dashboard', '/profil', '/installningar'];
// Sökvägar som bara ska vara tillgängliga för utloggade användare
const authRoutes = ['/logga-in', '/registrera'];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const sessionToken = request.cookies.get('session-token')?.value;
// Kontrollera om användaren har en giltig session
const isAuthenticated = !!sessionToken;
// Skydda routes som kräver inloggning
const isProtectedRoute = protectedRoutes.some(route =>
pathname.startsWith(route)
);
if (isProtectedRoute && !isAuthenticated) {
const loginUrl = new URL('/logga-in', request.url);
// Spara den ursprungliga URL:en så vi kan omdirigera tillbaka efter inloggning
loginUrl.searchParams.set('callbackUrl', pathname);
return NextResponse.redirect(loginUrl);
}
// Omdirigera inloggade användare bort från login-sidor
const isAuthRoute = authRoutes.some(route =>
pathname.startsWith(route)
);
if (isAuthRoute && isAuthenticated) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/profil/:path*', '/installningar/:path*', '/logga-in', '/registrera'],
};
JWT-validering i middleware
För mer avancerad autentisering kan du validera JSON Web Tokens direkt i middleware. Observera att detta kräver lättviktiga bibliotek som är kompatibla med Edge Runtime (eller Node.js runtime om du använder det):
// middleware.ts — JWT-validering
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
const JWT_SECRET = new TextEncoder().encode(
process.env.JWT_SECRET!
);
async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, JWT_SECRET);
return payload;
} catch {
return null;
}
}
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/logga-in', request.url));
}
const payload = await verifyToken(token);
if (!payload) {
// Ogiltig eller utgången token — rensa cookien och omdirigera
const response = NextResponse.redirect(new URL('/logga-in', request.url));
response.cookies.delete('auth-token');
return response;
}
// Lägg till användarinformation i headers för nedströms användning
const response = NextResponse.next();
response.headers.set('x-user-id', payload.sub as string);
response.headers.set('x-user-role', payload.role as string);
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
};
Tipset här är att använda jose-biblioteket — det fungerar utmärkt i Edge Runtime och är betydligt lättare än det klassiska jsonwebtoken-paketet.
Internationalisering (i18n) med middleware
Middleware är ärligt talat det perfekta verktyget för att hantera flerspråkig routing. Du kan detektera användarens föredragna språk och omdirigera till rätt locale-prefix — allt helt transparent för användaren.
// middleware.ts — i18n-routing
import { NextRequest, NextResponse } from 'next/server';
const locales = ['sv', 'en', 'no', 'da', 'fi'];
const defaultLocale = 'sv';
function getPreferredLocale(request: NextRequest): string {
// 1. Kontrollera om användaren har sparat ett språkval i en cookie
const cookieLocale = request.cookies.get('preferred-locale')?.value;
if (cookieLocale && locales.includes(cookieLocale)) {
return cookieLocale;
}
// 2. Parsa Accept-Language-headern
const acceptLanguage = request.headers.get('accept-language');
if (acceptLanguage) {
const preferredLanguages = acceptLanguage
.split(',')
.map(lang => {
const [code, quality] = lang.trim().split(';q=');
return {
code: code.split('-')[0].toLowerCase(), // 'sv-SE' → 'sv'
quality: quality ? parseFloat(quality) : 1.0,
};
})
.sort((a, b) => b.quality - a.quality);
for (const lang of preferredLanguages) {
if (locales.includes(lang.code)) {
return lang.code;
}
}
}
// 3. Fallback till standardspråk
return defaultLocale;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Kontrollera om sökvägen redan har en locale-prefix
const hasLocale = locales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (hasLocale) {
return NextResponse.next();
}
// Detektera föredraget språk och omdirigera
const locale = getPreferredLocale(request);
const newUrl = new URL(`/${locale}${pathname}`, request.url);
// Behåll sökparametrar
newUrl.search = request.nextUrl.search;
return NextResponse.redirect(newUrl);
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon\\.ico|api/).*)'],
};
Alternativ: URL-omskrivning istället för omdirigering
Om du inte vill visa locale-prefixet i URL:en för standardspråket (vilket många föredrar) kan du använda rewrite istället för redirect:
// middleware.ts — rewrite för standardspråk
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const locale = getPreferredLocale(request);
// Om det är standardspråket, skriv om utan synlig URL-ändring
if (locale === defaultLocale) {
return NextResponse.rewrite(
new URL(`/${defaultLocale}${pathname}`, request.url)
);
}
// Annars, omdirigera till locale-prefix
return NextResponse.redirect(
new URL(`/${locale}${pathname}`, request.url)
);
}
Hastighetsbegränsning med middleware
Att skydda dina API-routes mot missbruk är kritiskt i produktion. Jag har själv sett projekt där avsaknaden av rate limiting ledde till oväntade kostnader — det är inte kul. Middleware ger dig möjligheten att implementera hastighetsbegränsning centralt.
Med Upstash Redis — produktionsredo
// middleware.ts — hastighetsbegränsning med Upstash Redis
import { NextRequest, NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
// Skapa Redis-klient — initieras utanför middleware-funktionen
// för att cachea anslutningen mellan förfrågningar
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// Tillåt 10 förfrågningar per 10 sekunder med sliding window
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 s'),
analytics: true, // Aktivera analytics i Upstash-konsolen
prefix: '@upstash/ratelimit',
});
export async function middleware(request: NextRequest) {
// Använd IP-adress som identifierare
const ip = request.ip ?? request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, limit, reset, remaining } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse('För många förfrågningar. Försök igen senare.', {
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString(),
},
});
}
// Lägg till rate limit-information i svarets headers
const response = NextResponse.next();
response.headers.set('X-RateLimit-Limit', limit.toString());
response.headers.set('X-RateLimit-Remaining', remaining.toString());
return response;
}
export const config = {
matcher: '/api/:path*',
};
Enkel in-memory-lösning — för utveckling och mindre projekt
Behöver du inte Redis-nivå av robusthet? Här är en enklare variant som fungerar bra under utveckling:
// middleware.ts — enkel in-memory hastighetsbegränsning
import { NextRequest, NextResponse } from 'next/server';
// Enkel in-memory-lagring (OBS: fungerar inte med flera serverinstanser)
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
const RATE_LIMIT = 20; // Max antal förfrågningar
const WINDOW_MS = 60 * 1000; // Tidsfönster i millisekunder (1 minut)
function checkRateLimit(identifier: string): { allowed: boolean; remaining: number } {
const now = Date.now();
const record = rateLimitMap.get(identifier);
if (!record || now > record.resetTime) {
// Nytt fönster — återställ räknaren
rateLimitMap.set(identifier, { count: 1, resetTime: now + WINDOW_MS });
return { allowed: true, remaining: RATE_LIMIT - 1 };
}
if (record.count >= RATE_LIMIT) {
return { allowed: false, remaining: 0 };
}
record.count++;
return { allowed: true, remaining: RATE_LIMIT - record.count };
}
export function middleware(request: NextRequest) {
const ip = request.ip ?? 'anonym';
const { allowed, remaining } = checkRateLimit(ip);
if (!allowed) {
return new NextResponse(
JSON.stringify({ error: 'Hastighetsgräns överskriden' }),
{ status: 429, headers: { 'Content-Type': 'application/json' } }
);
}
const response = NextResponse.next();
response.headers.set('X-RateLimit-Remaining', remaining.toString());
return response;
}
export const config = {
matcher: '/api/:path*',
};
A/B-testning och feature flags via middleware
Middleware är riktigt bra för A/B-testning utan att påverka klientsideslogiken. Genom att fördela användare i grupper redan i middleware kan du dirigera dem till olika varianter av en sida — och användaren märker ingenting.
// middleware.ts — A/B-testning
import { NextRequest, NextResponse } from 'next/server';
const EXPERIMENT_COOKIE = 'ab-experiment-hero';
const VARIANTS = ['kontroll', 'variant-a', 'variant-b'] as const;
function assignVariant(): typeof VARIANTS[number] {
// Slumpmässig tilldelning med viktning
const random = Math.random();
if (random < 0.33) return 'kontroll';
if (random < 0.66) return 'variant-a';
return 'variant-b';
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Bara köra A/B-test på startsidan
if (pathname !== '/') {
return NextResponse.next();
}
// Kontrollera om användaren redan har en tilldelad variant
let variant = request.cookies.get(EXPERIMENT_COOKIE)?.value;
if (!variant || !VARIANTS.includes(variant as any)) {
variant = assignVariant();
}
// Skriv om URL:en till rätt variant-sida
const response = NextResponse.rewrite(
new URL(`/experiment/hero/${variant}`, request.url)
);
// Spara varianten i en cookie så användaren alltid ser samma variant
response.cookies.set(EXPERIMENT_COOKIE, variant, {
maxAge: 60 * 60 * 24 * 30, // 30 dagar
httpOnly: false, // Tillåt klientsidesläsning för analytics
sameSite: 'strict',
});
return response;
}
export const config = {
matcher: '/',
};
Feature flags
Feature flags i middleware är oerhört smidigt. I produktionsmiljö skulle du förstås hämta dessa från en extern tjänst som LaunchDarkly eller liknande, men principen är densamma:
// middleware.ts — feature flags
import { NextRequest, NextResponse } from 'next/server';
// Feature flags — i produktion, hämta dessa från en extern tjänst
const featureFlags: Record<string, boolean> = {
'ny-checkout': true,
'ny-profil-sida': false,
'mörkt-tema': true,
};
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Omdirigera till ny checkout om flaggan är aktiv
if (pathname.startsWith('/checkout') && featureFlags['ny-checkout']) {
return NextResponse.rewrite(
new URL(pathname.replace('/checkout', '/checkout-v2'), request.url)
);
}
// Sätt feature flags som headers så sidor kan använda dem
const response = NextResponse.next();
Object.entries(featureFlags).forEach(([flag, enabled]) => {
response.headers.set(`x-feature-${flag}`, enabled.toString());
});
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon\\.ico).*)'],
};
Geolokalisering-baserad routing
Med geolokaliseringsdata kan du anpassa innehåll och routing baserat på användarens fysiska plats. Det här är särskilt smidigt på Vercel, där request.geo automatiskt populeras — ingen extra konfiguration behövs.
// middleware.ts — geolokalisering-baserad routing
import { NextRequest, NextResponse } from 'next/server';
// Mappa länder till regioner och butiker
const countryToRegion: Record<string, string> = {
'SE': 'nordics',
'NO': 'nordics',
'DK': 'nordics',
'FI': 'nordics',
'DE': 'europe',
'FR': 'europe',
'GB': 'europe',
'US': 'north-america',
'CA': 'north-america',
};
export function middleware(request: NextRequest) {
const country = request.geo?.country ?? 'SE'; // Standardvärde Sverige
const city = request.geo?.city ?? 'Okänd';
// Bestäm region baserat på land
const region = countryToRegion[country] ?? 'global';
// Skriv om till regionspecifik sida
if (request.nextUrl.pathname === '/butik') {
return NextResponse.rewrite(
new URL(`/butik/${region}`, request.url)
);
}
// Sätt geo-information i headers
const response = NextResponse.next();
response.headers.set('x-user-country', country);
response.headers.set('x-user-city', city);
response.headers.set('x-user-region', region);
return response;
}
export const config = {
matcher: ['/butik', '/erbjudanden/:path*'],
};
Node.js Runtime för middleware — stabiliserat sedan Next.js 15.5
Historiskt sett har Next.js Middleware alltid körts i Edge Runtime, som är baserat på Web API:er och har rätt strikta begränsningar. Edge Runtime stödjer inte alla Node.js API:er, vilket innebär att bibliotek som beror på fs, net, child_process eller andra Node.js-specifika moduler helt enkelt inte fungerar.
Det var frustrerande. Men i Next.js 15.2 introducerades experimentellt stöd för Node.js runtime i middleware, och i Next.js 15.5 blev detta stabilt.
Det här var en riktigt stor grej. Plötsligt kunde du använda hela Node.js ekosystem direkt i middleware.
Aktivera Node.js runtime
// middleware.ts — med Node.js runtime (Next.js 15.5+)
import { NextRequest, NextResponse } from 'next/server';
// Konfigurera Node.js runtime istället för Edge
export const config = {
runtime: 'nodejs',
matcher: ['/api/:path*'],
};
export async function middleware(request: NextRequest) {
// Nu kan du använda Node.js API:er!
const { createHash } = await import('crypto');
const ip = request.ip ?? '0.0.0.0';
const hashedIp = createHash('sha256').update(ip).digest('hex');
console.log(`Förfrågan från hashad IP: ${hashedIp}`);
const response = NextResponse.next();
response.headers.set('x-request-hash', hashedIp);
return response;
}
Fördelar med Node.js runtime jämfört med Edge
- Fullt Node.js API-stöd — använd
crypto,buffer,utiloch andra inbyggda moduler - Inga storleksbegränsningar — Edge Runtimes gräns på 1 MB gäller inte
- Kompatibilitet med befintliga bibliotek — npm-paket som kräver Node.js fungerar direkt
- Databasanslutningar — anslut direkt till databaser via drivrutiner som kräver Node.js
- Enklare felsökning — använd verktygen du redan känner
Nackdelen? Node.js runtime saknar Edges fördel med geografisk distribution — middleware körs på din server istället för nära användaren. Men för applikationer som inte behöver global edge-distribution är det sällan ett verkligt problem.
Övergången till proxy.ts i Next.js 16
Här kommer den stora nyheten. I Next.js 16 (släppt i oktober 2025) genomfördes en av de mest betydelsefulla namnändringarna i ramverkets historia: middleware.ts döptes om till proxy.ts.
Och nej, det är inte bara en kosmetisk ändring — det speglar en fundamental förändring i hur Next.js ser på denna fil i arkitekturen.
Varför bytet till proxy.ts?
Det finns flera bra anledningar till namnbytet:
- Tydligare nätverksgräns — namnet "proxy" signalerar att filen fungerar som en nätverksproxy framför din applikation, inte som Express.js-stil middleware
- Undviker begreppsförvirring — "middleware" associeras ofta med Express.js middleware-pattern, som fungerar fundamentalt annorlunda
- Node.js som standard —
proxy.tskör Node.js runtime som standard, och Edge Runtime stöds inte iproxy.ts - Fokus på routing — namnet klargör att filens primära syfte är rewrites, redirects och headers
Migreringsguide
Next.js tillhandahåller en officiell codemod som automatiserar hela migreringen åt dig:
# Kör den officiella codemod-migreringen
npx @next/codemod@canary middleware-to-proxy .
Denna codemod gör följande:
- Byter namn på
middleware.tstillproxy.ts - Byter namn på den exporterade funktionen från
middlewaretillproxy - Uppdaterar konfigurationsreferenser (t.ex.
skipMiddlewareUrlNormalizetillskipProxyUrlNormalize)
Ganska smärtfritt, faktiskt.
Före och efter migrering
// FÖRE: middleware.ts (Next.js 15)
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Autentiseringslogik
const token = request.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/logga-in', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*'],
};
// EFTER: proxy.ts (Next.js 16)
import { NextRequest, NextResponse } from 'next/server';
// Funktionsnamnet ändras från 'middleware' till 'proxy'
export function proxy(request: NextRequest) {
// Samma logik — bara funktionsnamn och filnamn ändras
const token = request.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/logga-in', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*'],
};
Manuell migrering
Om du föredrar att migrera manuellt (eller bara vill förstå vad som händer under huven), följ dessa steg:
- Byt namn på filen
middleware.tstillproxy.ts - Ändra
export function middleware()tillexport function proxy() - Ta bort eventuella
runtime: 'edge'-konfigurationer (Edge stöds inte i proxy.ts) - Uppdatera
next.config.jsom du använderskipMiddlewareUrlNormalize— ändra tillskipProxyUrlNormalize - Verifiera att din kod inte förlitar sig på Edge-specifika API:er
Viktigt: Under en övergångsperiod stödjer Next.js 16 fortfarande middleware.ts med en varning om att det är deprecated. Men du bör migrera snarast — framtida versioner kommer att ta bort stödet helt.
Prestandaoptimering — bästa praxis
Middleware (eller proxy) körs vid varje matchad förfrågan, så det är helt avgörande att hålla den lätt och snabb. Här är de viktigaste principerna jag har lärt mig genom åren:
1. Använd specifika matchers
Kör inte middleware för alla routes om det inte behövs. Ju mer specifik din matcher är, desto färre förfrågningar behöver passera genom middleware:
// Dåligt — kör för alla routes
export const config = {
matcher: '/:path*',
};
// Bra — kör bara för de routes som faktiskt behöver det
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
2. Undvik tung beräkning
// Dåligt — tung bearbetning i middleware
export async function middleware(request: NextRequest) {
// Anropa extern tjänst vid varje förfrågan — dyrt!
const userData = await fetch('https://api.example.com/user', {
headers: { Authorization: request.headers.get('authorization')! },
});
const user = await userData.json();
// Komplex logik baserat på användardata
// ...
}
// Bra — minimalt arbete, använd cookies/headers istället
export function middleware(request: NextRequest) {
// Snabb kontroll av en cookie — O(1)
const session = request.cookies.get('session')?.value;
if (!session) {
return NextResponse.redirect(new URL('/logga-in', request.url));
}
return NextResponse.next();
}
3. Cacha externa anrop
Om du verkligen måste göra externa anrop i middleware, se till att cacha resultaten. Upstash Redis-exemplet ovan visar hur du deklarerar klienten utanför middleware-funktionen så att den återanvänds mellan förfrågningar.
4. Returnera tidigt
Det här är en enkel men ofta förbisedd optimering — returnera så fort du kan:
// Bra — returnera tidigt för routes som inte behöver bearbetning
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Snabb exit för publika routes
if (pathname.startsWith('/offentlig')) {
return NextResponse.next();
}
// Autentiseringslogik körs bara för skyddade routes
const token = request.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/logga-in', request.url));
}
return NextResponse.next();
}
5. Minimera cookies och headers
Varje cookie och header du lägger till ökar storleken på varje förfrågan och svar. Var sparsam — sätt bara det som verkligen behövs:
// Undvik att sätta onödiga cookies
// Sätt bara det som verkligen behövs nedströms
const response = NextResponse.next();
response.headers.set('x-user-role', role); // Lätt, nödvändig header
return response;
Komplett praktiskt exempel: auth + i18n + loggning
Okej, nu kör vi. Låt oss sätta ihop allt vi har gått igenom i ett komplett, produktionsredo middleware-exempel som kombinerar autentisering, internationalisering och loggning. Det här visar hur de olika mönstren faktiskt samverkar i en riktig applikation.
// middleware.ts — komplett exempel med auth + i18n + loggning
// (I Next.js 16, byt filnamn till proxy.ts och funktionsnamn till proxy)
import { NextRequest, NextResponse } from 'next/server';
// === KONFIGURATION ===
const SUPPORTED_LOCALES = ['sv', 'en', 'no', 'da'] as const;
type Locale = typeof SUPPORTED_LOCALES[number];
const DEFAULT_LOCALE: Locale = 'sv';
const PROTECTED_ROUTES = ['/dashboard', '/profil', '/installningar', '/admin'];
const AUTH_ROUTES = ['/logga-in', '/registrera', '/glömt-lösenord'];
const PUBLIC_ROUTES = ['/', '/om-oss', '/kontakt', '/priser'];
// === HJÄLPFUNKTIONER ===
function getLocaleFromPath(pathname: string): Locale | null {
const segment = pathname.split('/')[1];
return SUPPORTED_LOCALES.includes(segment as Locale)
? (segment as Locale)
: null;
}
function getPathnameWithoutLocale(pathname: string): string {
const locale = getLocaleFromPath(pathname);
if (locale) {
return pathname.replace(`/${locale}`, '') || '/';
}
return pathname;
}
function detectLocale(request: NextRequest): Locale {
// 1. Cookie-baserat val
const cookieLocale = request.cookies.get('locale')?.value;
if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale as Locale)) {
return cookieLocale as Locale;
}
// 2. Accept-Language-header
const acceptLang = request.headers.get('accept-language') ?? '';
const languages = acceptLang.split(',').map(l => l.trim().split(';')[0].split('-')[0]);
for (const lang of languages) {
if (SUPPORTED_LOCALES.includes(lang as Locale)) {
return lang as Locale;
}
}
// 3. Standardspråk
return DEFAULT_LOCALE;
}
function isProtectedRoute(pathname: string): boolean {
return PROTECTED_ROUTES.some(route => pathname.startsWith(route));
}
function isAuthRoute(pathname: string): boolean {
return AUTH_ROUTES.some(route => pathname.startsWith(route));
}
function validateSession(token: string | undefined): boolean {
// I produktion: validera JWT, kontrollera utgångsdatum, etc.
// Förenklad version för exemplet
return !!token && token.length > 10;
}
// === LOGGNING ===
function logRequest(request: NextRequest, extra: Record<string, string> = {}) {
const logData = {
tidsstämpel: new Date().toISOString(),
metod: request.method,
sökväg: request.nextUrl.pathname,
ip: request.ip ?? 'okänd',
userAgent: request.headers.get('user-agent')?.substring(0, 100),
...extra,
};
// I produktion: skicka till loggningsservice (Datadog, Sentry, etc.)
console.log(JSON.stringify(logData));
}
// === MIDDLEWARE-FUNKTION ===
export function middleware(request: NextRequest) {
const startTime = Date.now();
const { pathname } = request.nextUrl;
// Steg 1: Logga inkommande förfrågan
logRequest(request, { steg: 'inkommande' });
// Steg 2: Hantera i18n — säkerställ locale-prefix
const currentLocale = getLocaleFromPath(pathname);
const cleanPathname = getPathnameWithoutLocale(pathname);
if (!currentLocale) {
// Ingen locale i URL:en — omdirigera till rätt språk
const detectedLocale = detectLocale(request);
const newUrl = new URL(`/${detectedLocale}${pathname}`, request.url);
newUrl.search = request.nextUrl.search;
logRequest(request, {
steg: 'i18n-omdirigering',
till: newUrl.pathname,
locale: detectedLocale,
});
return NextResponse.redirect(newUrl);
}
// Steg 3: Autentiseringskontroll
const sessionToken = request.cookies.get('session-token')?.value;
const isAuthenticated = validateSession(sessionToken);
if (isProtectedRoute(cleanPathname) && !isAuthenticated) {
// Omdirigera till inloggning med callback-URL
const loginUrl = new URL(`/${currentLocale}/logga-in`, request.url);
loginUrl.searchParams.set('callbackUrl', pathname);
logRequest(request, {
steg: 'auth-omdirigering',
anledning: 'ej-autentiserad',
});
return NextResponse.redirect(loginUrl);
}
if (isAuthRoute(cleanPathname) && isAuthenticated) {
// Redan inloggad — omdirigera till dashboard
logRequest(request, {
steg: 'auth-omdirigering',
anledning: 'redan-inloggad',
});
return NextResponse.redirect(
new URL(`/${currentLocale}/dashboard`, request.url)
);
}
// Steg 4: Skapa svar med extra headers
const response = NextResponse.next();
// Sätt användbara headers
response.headers.set('x-locale', currentLocale);
response.headers.set('x-authenticated', isAuthenticated.toString());
response.headers.set('x-request-id', crypto.randomUUID());
// Mät svarstid
const duration = Date.now() - startTime;
response.headers.set('x-middleware-duration', `${duration}ms`);
logRequest(request, {
steg: 'slutförd',
varaktighet: `${duration}ms`,
locale: currentLocale,
autentiserad: isAuthenticated.toString(),
});
// Spara locale-val i cookie
response.cookies.set('locale', currentLocale, {
maxAge: 60 * 60 * 24 * 365,
sameSite: 'lax',
path: '/',
});
return response;
}
// === MATCHER-KONFIGURATION ===
export const config = {
matcher: [
// Matcha alla routes utom statiska tillgångar
'/((?!_next/static|_next/image|favicon\\.ico|sitemap\\.xml|robots\\.txt|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)',
],
};
Motsvarande proxy.ts-version för Next.js 16
Migreringen till Next.js 16 kräver minimala ändringar tack vare codemod-verktyget. Här visar vi den uppdaterade versionen för tydlighetens skull:
// proxy.ts — samma logik, anpassad för Next.js 16
import { NextRequest, NextResponse } from 'next/server';
// All konfigurations- och hjälpkod förblir identisk...
// (se ovan för fullständiga hjälpfunktioner)
// Den enda ändringen: funktionsnamnet
export function proxy(request: NextRequest) {
// Exakt samma implementering som middleware-funktionen ovan
const startTime = Date.now();
const { pathname } = request.nextUrl;
logRequest(request, { steg: 'inkommande' });
// ... resten av logiken är identisk
return NextResponse.next();
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon\\.ico|sitemap\\.xml|robots\\.txt|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)',
],
};
Integration med Server Actions och progressiv förbättring
Middleware och Server Actions arbetar på olika nivåer i Next.js-arkitekturen men kompletterar varandra riktigt bra. Som vi beskrev i vår artikel om Server Actions, hanterar de datamutationer och formulärbehandling på serversidan med progressiv förbättring — de fungerar även helt utan JavaScript i webbläsaren.
Middleware kompletterar detta genom att:
- Skydda Server Actions — middleware kan verifiera autentisering innan en Server Action ens körs
- Hastighetsbegränsa formulärinlämningar — genom att applicera rate limiting på POST-förfrågningar till Server Action-endpoints
- Logga Server Action-anrop — spåra alla mutationer för revisionsloggar
- Sätta nödvändiga headers — till exempel CSRF-tokens eller användar-ID som Server Actions kan läsa
// middleware.ts — skydda Server Actions
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Server Actions skickas som POST-förfrågningar
// med en speciell Next-Action header
const isServerAction = request.headers.get('next-action');
if (isServerAction) {
// Verifiera session för alla Server Actions
const session = request.cookies.get('session-token')?.value;
if (!session) {
return new NextResponse('Obehörig', { status: 401 });
}
// Lägg till extra säkerhetsinformation
const response = NextResponse.next();
response.headers.set('x-action-timestamp', Date.now().toString());
return response;
}
return NextResponse.next();
}
Vanliga misstag och felsökning
Innan vi avslutar, låt oss gå igenom de vanligaste problemen utvecklare stöter på med Next.js Middleware — och hur du löser dem. (Jag har gjort de flesta av dessa misstag själv, så du slipper!)
Oändliga omdirigeringsloopar
Det absolut vanligaste misstaget. Du skapar en omdirigering som leder tillbaka till middleware, som omdirigerar igen, som leder tillbaka...
// DÅLIGT — skapar oändlig loop
export function middleware(request: NextRequest) {
if (!request.cookies.get('session')) {
// Omdirigerar till /logga-in som också triggar middleware
// som ser att det inte finns någon session, och omdirigerar igen...
return NextResponse.redirect(new URL('/logga-in', request.url));
}
}
// BRA — exkludera auth-routes från omdirigering
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Hoppa över auth-sidor
if (pathname.startsWith('/logga-in') || pathname.startsWith('/registrera')) {
return NextResponse.next();
}
if (!request.cookies.get('session')) {
return NextResponse.redirect(new URL('/logga-in', request.url));
}
}
Middleware körs för statiska filer
Om du inte konfigurerar matcher korrekt kan middleware köras för varje bild, CSS-fil och JavaScript-bundle. Det är inte bara onödigt — det kan rejält försämra prestanda:
// Lösning — alltid exkludera statiska tillgångar i matcher
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon\\.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|css|js)$).*)',
],
};
Edge Runtime-begränsningar
Om du får felmeddelanden om att en modul inte stöds i Edge Runtime har du två alternativ:
- Byt till ett edge-kompatibelt alternativ (t.ex.
joseistället förjsonwebtoken) - Aktivera Node.js runtime (Next.js 15.5+) eller migrera till
proxy.ts(Next.js 16)
Sammanfattning och framtidsblick
Next.js Middleware har utvecklats från en relativt enkel Edge-funktion till en central del av ramverkets arkitektur. Här är de viktigaste slutsatserna:
- Middleware körs före all annan bearbetning — det är din första försvarslinje och routinglogikens hem
- Använd specifika matchers — prestanda beror direkt på hur många förfrågningar som passerar genom middleware
- Node.js runtime eliminerar Edge-begränsningar — sedan Next.js 15.5 kan du använda hela Node.js ekosystem
- proxy.ts är framtiden — Next.js 16 döper om middleware.ts till proxy.ts med Node.js som standardruntime
- Håll det lätt — undvik tunga beräkningar och externa API-anrop när det är möjligt
- Kombinera mönster varsamt — auth + i18n + loggning fungerar bra ihop, men testa prestanda noggrant
Med förståelse för dessa mönster och bästa praxis är du väl rustad att bygga robusta, snabba och säkra Next.js-applikationer. Middleware — och snart proxy — är lagret som binder samman din applikations routing, säkerhet och internationalisering. Kombinerat med Server Actions för datamutationer har du en riktigt komplett verktygslåda för modern webbutveckling.