Next.js Middleware: Komplett guide till routing, autentisering och Edge Runtime (2026)

Komplett guide till Next.js Middleware — från konfiguration och matcher-mönster till autentisering, i18n, hastighetsbegränsning, A/B-testning och övergången till proxy.ts i Next.js 16.

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:

  1. Förfrågan kommer in till servern
  2. Middleware körs (kan omdirigera, skriva om URL:er, sätta headers)
  3. Routematchning sker (Next.js bestämmer vilken sida som ska visas)
  4. Cachekontroll (ISR, statisk generering)
  5. Sidan renderas eller serveras från cache
  6. 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, util och 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 standardproxy.ts kör Node.js runtime som standard, och Edge Runtime stöds inte i proxy.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:

  1. Byter namn på middleware.ts till proxy.ts
  2. Byter namn på den exporterade funktionen från middleware till proxy
  3. Uppdaterar konfigurationsreferenser (t.ex. skipMiddlewareUrlNormalize till skipProxyUrlNormalize)

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:

  1. Byt namn på filen middleware.ts till proxy.ts
  2. Ändra export function middleware() till export function proxy()
  3. Ta bort eventuella runtime: 'edge'-konfigurationer (Edge stöds inte i proxy.ts)
  4. Uppdatera next.config.js om du använder skipMiddlewareUrlNormalize — ändra till skipProxyUrlNormalize
  5. 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:

  1. Byt till ett edge-kompatibelt alternativ (t.ex. jose istället för jsonwebtoken)
  2. 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.

Om Författaren Editorial Team

Our team of expert writers and editors.