Migrér fra Pages Router til App Router: Playbook for Next.js 15/16 (2026)
Playbook for migration fra Pages Router til App Router i Next.js 15/16. Dagsestimater, faseinddelt køreplan, codemod-fixes og de fem fælder, der oftest sprænger tidsplanen. Baseret på fire produktionsmigrationer.
Migration fra Pages Router til App Router tager typisk 3–5 dage for et lille projekt (under 10 sider), 2–4 uger for et mellemstort SaaS-produkt, og 6–12 uger for en stor multi-team monolit. Den korte version af playbooken: upgrader til Next.js 15 eller 16 på Pages Router først, kør codemod, opret app/-mappen ved siden af pages/, migrér én rute ad gangen, og efterlad legacy-rutene indtil produktionsmetrikker bekræfter, at App Router-versionen er stabil. Denne guide viser hele forløbet med dagsestimater, konkrete kodekonverteringer og de fem fælder, der oftest sprænger tidsplanen.
Pages Router er ikke deprecated i Next.js 16. Vercel har forpligtet sig til flerårig support, men alle nye features lander kun i App Router.
Kør npx @next/codemod@latest upgrade latestfør du opretter app/-mappen. Ellers bruger du timer på at debugge afhængighedskonflikter under selve migrationen.
pages/ og app/ kan køre samtidigt i samme projekt. App Router har præcedens ved rutekollisioner, hvilket giver dig kontrolleret cutover.
Den største regressionsrisiko i Next.js 15+ er caching-adfærd: fetch()-kald cacher nu ikke som standard, hvilket kan gøre dashboards og faktureringsdata inkonsistente.
Genbrugelige komponenter kan importere useRouter fra next/compat/router mens migrationen er i gang, så du undgår dobbelt kodebase.
Estimér 1–2 dage per side i starten og 3–4 timer per side, når teamet har rytmen. Læringskurven er stejl, men lineær.
Er Pages Router deprecated i Next.js 16?
Nej. Pages Router er ikke officielt deprecated i Next.js 16, og der er ingen fjernelsestimeline. Vercels holdning, senest bekræftet i RFC-diskussionen om Pages Routers fremtid, er at legacy-router forbliver understøttet "i mange år endnu". Der er ingen konsol-advarsler, ingen deprecation-flag og ingen tidsbombe i changelogen.
Men "understøttet" og "aktivt udviklet" er to forskellige ting. Alle features siden Next.js 13 er landet i App Router først (React Server Components, Server Actions, Partial Prerendering, det nye use cache-direktiv, Turbopack til produktionsbuilds, den nye proxy.ts-middleware), og størstedelen af dem kommer aldrig retroaktivt til Pages Router. Hvis du bygger nye features i 2026 på Pages Router, betaler du langsomt en compound-rente i form af manglende performance, ringere DX og et smallere ansættelsesmarked.
Jeg har flyttet fire produktionsapps på tværs af tre teams siden 2024. Ingen af dem migrerede fordi Pages Router var "gået i stykker". De migrerede fordi nye features (streaming, Server Actions, Partial Prerendering) enten krævede App Router eller var markant sværere at implementere uden.
Hvor lang tid tager en Pages-til-App Router-migration?
Dagsestimater for komplet migration, baseret på fire projekter jeg selv har ledt og cirka 15 jeg har rådgivet på:
Projektstørrelse
Sider/ruter
Team-størrelse
Estimeret varighed
Sværhedsgrad
Marketingsite
5–15
1 dev
3–5 dage
Lav
MVP/SaaS-prototype
10–30
1–2 devs
1–2 uger
Middel
Modent SaaS-produkt
30–100
2–4 devs
3–6 uger
Høj
Multi-team monolit
100+
4+ devs
2–4 måneder
Meget høj
E-commerce (checkout kritisk)
varierer
2–5 devs
+30% oveni
Høj
Læringskurven er ikke lineær. Den første side tager typisk 2–3 dage, fordi teamet skal internalisere Server Components, den nye datahentning, og de subtile forskelle mellem next/router og next/navigation. Fra og med den anden side falder tempoet dramatisk: teams jeg har fulgt migrerer ofte 10 sider på 5 dage efter opvarmningen. Regn med at de første to sprints er cirka 40% langsommere end de sidste to.
Tre faktorer forlænger tidsplanen mere end noget andet: (1) tredjeparts UI-biblioteker der ikke fungerer i Server Components (Framer Motion, ældre chart-libs), (2) autentificerings-flows der er tæt koblet til getServerSideProps, og (3) delte layouts med sideafhængig datahentning. Hvis alle tre gælder, læg 30–50% oven i basisestimatet.
Playbook: Migration fase for fase
Rækkefølgen betyder mere end den enkelte fase. Bryder du den, ender du med hydration-fejl og cache-drift der er svære at spore. Denne rækkefølge er den, jeg bruger på alle projekter, inklusive dem der er startet uden en klar migrationsplan. Den officielle Next.js migrations-guide beskriver de samme faser i mere kondenseret form.
Fase 1 (dag 1–2): Upgrader afhængigheder og codemod
Ryd git-træet. Alle codemods muterer filer, og du vil have én revertérbar commit per trin. Sørg for at du er på Next.js 14.2+ på Pages Router, før du overhovedet begynder at tænke på App Router. Og bland aldrig major-version-upgrades med router-migration i samme PR. (Jeg lærte det den hårde måde på et af mine første projekter.)
git checkout -b migration/nextjs-app-router
git status # skal være rent
npx @next/codemod@latest upgrade latest
npm run build # skal stadig virke paa Pages Router
git commit -am "chore: upgrade to Next.js 16, still on Pages Router"
Codemod'et opdaterer package.json, tsconfig.json, TypeScript-typer og et par konfigurationsfiler. Kør en fuld produktionsbuild og en E2E-test suite før du opretter app/-mappen. Hvis noget knækker her, ved du at det er upgrade-relateret og ikke migrationsrelateret.
Fase 2 (dag 2–3): Opret app/-mappen og root layout
Opret app/-mappen som søster til pages/. Erstat konceptuelt _app.tsx og _document.tsx med en enkelt app/layout.tsx, men slet dem ikke endnu. pages/-ruter afhænger af dem, indtil den sidste side er migreret.
Bemærk at root layout skal definere <html> og <body>. Next.js genererer dem ikke automatisk i App Router, og manglen giver den forvirrende fejl "Error: Missing <html> and <body> tags in the root layout".
Fase 3 (dag 3): Isolér Context Providers i en Client Component
Den mest almindelige begynderfejl: at flytte _app.tsx-providers direkte ind i app/layout.tsx. Layoutet er en Server Component, men React Context (og de fleste state-libraries) skal køre på klienten. Løsningen er en dedikeret providers.tsx-fil markeret med 'use client'.
// app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThemeProvider } from 'next-themes'
import { useState } from 'react'
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider attribute="class">{children}</ThemeProvider>
</QueryClientProvider>
)
}
Fase 4 (dag 4+): Migrér sider inkrementelt
Vælg en lav-risiko-side som pilot. Ikke forsiden, ikke checkout. Ideelt en statisk marketingside eller en indstillingsside. Konvertér den, deploy til en preview-branch, kør Lighthouse mod både gammel og ny version, og valider caching-adfærd med rigtige requests. Først når piloten er stabil i 48 timer, ruller du resten ud.
For hver side: (1) flyt den default-eksporterede komponent ind i en ny Client Component hvis den bruger hooks eller interaktivitet, (2) opret app/route/page.tsx der importerer Client Component'en, (3) migrér getServerSideProps/getStaticProps ind i den asynkrone Server Component, (4) slet den gamle pages/route.tsx først når den nye er verificeret i staging.
Hvordan retter jeg "NextRouter was not mounted"?
Denne fejl opstår næsten altid fordi en komponent inde i app/-mappen stadig importerer useRouter fra next/router. I App Router er den korrekte import next/navigation, og hooken har en anden returtype: du får ikke længere pathname, query, asPath, isReady eller locale direkte fra useRouter().
Hvis du deler komponenter mellem pages/ og app/ under migrationen, brug next/compat/router. Den returnerer null når komponenten renderes i App Router-kontekst, så du kan branche på det uden crashes:
import { useRouter } from 'next/compat/router'
function SharedButton() {
const router = useRouter()
if (router) {
// Pages Router-kontekst
return <button onClick={() => router.push('/x')}>Gaa</button>
}
// App Router-kontekst: brug next/navigation her
return <AppRouterButton />
}
Andre almindelige varianter af samme rodfejl: "invariant expected app router to be mounted" (typisk manglende <html>/<body> i root layout), og "useSearchParams() should be wrapped in a suspense boundary" (wrap den kaldende komponent i <Suspense> når den bruges i en statisk side). Se den officielle fejldokumentation for NextRouter was not mounted for hele overfladen.
Erstatning af getServerSideProps og getStaticProps
App Router afskaffer begge to. En Server Component kan hente data direkte, hvilket eliminerer det props-drilling-mønster Pages Router altid har haft. Dette er den enkelt største DX-forbedring i migrationen, men også den der oftest introducerer subtile bugs på grund af den ændrede caching-model i Next.js 15+.
Bemærk to ting. Først: params er nu en Promise i Next.js 15+, og skal awaites. Dernæst: fetch() cacher ikke som default længere, og den ene ændring har alene genereret hundredvis af GitHub-issues siden Next.js 15 blev udgivet. Hvis du migrerer en getStaticProps-side, sæt cache: 'force-cache' eller brug det nye use cache-direktiv. Vi har en dybere gennemgang i vores guide til use cache-direktivet.
Fra API Routes til Route Handlers
Pages Routers pages/api/* erstattes af App Routers app/api/route.ts. API'et er tættere på web-standarden Request/Response, og du eksporterer navngivne funktioner per HTTP-metode i stedet for én default handler med switch på req.method.
// FOR: pages/api/products.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
const products = await db.products.findMany()
return res.status(200).json(products)
}
if (req.method === 'POST') {
const created = await db.products.create({ data: req.body })
return res.status(201).json(created)
}
res.status(405).end()
}
// EFTER: app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET() {
const products = await db.products.findMany()
return NextResponse.json(products)
}
export async function POST(req: NextRequest) {
const body = await req.json()
const created = await db.products.create({ data: body })
return NextResponse.json(created, { status: 201 })
}
Route Handlers har ingen automatisk body-parser. await req.json() eller await req.formData() er nu eksplicit. Filuploads, webhooks og streaming responses fungerer også væsentligt anderledes; se vores detaljerede guide til Route Handlers for hele overfladen. Overvej også, om nogle af de gamle mutation-endpoints i stedet burde blive Server Actions, som er den moderne standard for form submits. I mit sidste projekt endte cirka halvdelen af de gamle API-routes som Server Actions, og det gjorde formularerne markant simplere.
Kan Pages og App Router køre samtidigt?
Ja, og det er faktisk hele pointen med den inkrementelle migrationsmodel. Next.js scanner begge mapper og resolver ruter i denne rækkefølge: app/ tager præcedens ved kollision. Hvis både pages/dashboard.tsx og app/dashboard/page.tsx eksisterer, serveres App Router-versionen. Det gør det trivielt at teste en migreret rute i produktion bag en feature flag: deploy begge versioner, brug middleware til at rute en procentdel af trafikken til den nye, og monitorér.
Sameksistens har dog reelle omkostninger. Navigation mellem en App Router-rute og en Pages Router-rute er ikke en client-side transition. Next.js behandler dem som to separate applikationer og hard-reloader hele JavaScript-bundlen. Brugere ser en kort hvid skærm, og analytics markerer det som en ny session. Undgå bevidst rutegrupper hvor brugere hopper meget frem og tilbage: hvis /checkout/* er halvt migreret, migrér hele flowet i samme sprint, ikke stykvis.
Middleware kører for begge routere. Hvis du allerede har middleware.ts til autentificering eller redirects, virker den uændret på tværs af migrationen. Vi går i dybden med de nye edge runtime-mønstre i vores middleware-masterclass. Bemærk at Next.js 16 introducerede en ny proxy.ts-fil som over tid vil overtage nogle middleware-ansvar.
Fem fælder der forsinker migrationen
Baseret på post-mortems fra migrations der løb over tid, disse er dem der spiser mest ekstra tid. Læs dem før du starter, ikke efter.
1. Caching-defaults der flipper mellem versioner
Som nævnt: fetch() cacher ikke i Next.js 15+ som default. Dette rammer især dashboards og admin-views hvor Pages Routers getServerSideProps altid gjorde en frisk request. Løsning: eksplicit { cache: 'no-store' } for altid-frisk data, { cache: 'force-cache' } for statisk, og { next: { revalidate: 60 } } for tidsbaseret revalidering. Dokumentér caching-strategien per endpoint i en simpel README før du deployer.
2. Layouts der deler datahentning på tværs af routes
I Pages Router kører hver sides getServerSideProps isoleret. I App Router kan et layout hente data (fx brugerdata i app/(dashboard)/layout.tsx), og alle child-routes deler resultatet. Det er en performance-gevinst, men et sikkerhedsproblem hvis dybere ruter har strengere auth-checks. Test at layout-fetch respekterer den mest restriktive rute i træet.
3. Klient-kun biblioteker der brænder i Server Components
Framer Motion, ældre chart-libs (Chart.js uden wrapper), lottie-web og de fleste WYSIWYG-editors ramler ved import i en Server Component. Markér den kaldende fil med 'use client'. Hvis biblioteket ikke har SSR-support overhovedet, wrap importet i next/dynamic med ssr: false. Lav en liste over alle direkte importer i din package.json før migrationen. Det tager en time og sparer dage.
4. Metadata-håndtering blandet mellem next/head og generateMetadata
Pages Router bruger <Head> fra next/head. App Router bruger den nye metadata-export eller generateMetadata-funktionen. Blandes de under migrationen, ender du med dobbelte <title>-tags i HTML, som skader SEO. Migrér metadata i samme PR som selve siden, aldrig separat. Vi har hele overfladen dokumenteret i vores Metadata API-guide.
5. E2E-tests der antager Pages Routers hydration-timing
Cypress- og Playwright-tests der venter på en specifik hydration-event eller på at en getServerSideProps-loader forsvinder, brænder ofte i App Router fordi streaming og Suspense ændrer den observerbare rendering-rækkefølge. Opdatér waits til at kigge efter faktisk indhold (rolle- eller data-testid-baseret) i stedet for tekniske hydration-signaler. Regn med at 20–30% af E2E-suiten skal opdateres.
Ofte stillede spørgsmål
Hvornår bliver Pages Router fjernet fra Next.js?
Der er ingen annonceret dato eller version. Vercels officielle holdning er flerårig support. I praksis betyder det, at nye features ikke kommer til Pages Router, men eksisterende produktionsapps kan blive stående i lang tid uden risiko for tvunget migration.
Skal jeg migrere hvis min Pages Router-app fungerer fint?
Kun hvis du har en forretningsmæssig grund: nye features der kræver Server Components eller Server Actions, performance-krav der kræver streaming, eller ansættelser der markedsfører sig på App Router. Migration som ren tech debt-øvelse vinder sjældent prioritering.
Kan jeg migrere gradvist uden at ødelægge SEO?
Ja. Sameksistens er sikker på rute-niveau, og App Router vinder ved kollision. Migrér én rute ad gangen, valider Lighthouse-scores i staging, og hold øje med indexerbarhed via Search Console i to uger efter hver deploy. Sørg for at metadata-migration sker samtidigt med selve siden.
Hvorfor får jeg "NextRouter was not mounted" i mit app-directory?
Fordi en komponent inde i app/ importerer useRouter fra next/router i stedet for next/navigation. Fixet er at ændre importstien, eller (hvis komponenten deles med Pages Router) at bruge next/compat/router, som returnerer null i App Router-kontekst i stedet for at crashe.
Er App Router altid hurtigere end Pages Router?
Ikke automatisk. App Router giver bedre værktøjer (streaming, partial prerendering, Server Components), men en uoptimeret App Router-app kan sagtens være langsommere end en tunet Pages Router-app, især hvis caching-defaults er sat forkert. Performance-gevinster kommer af at bruge de nye primitiver bevidst, ikke bare af at migrere.
revalidateTag rydder tag-baserede fetch-kald på tværs af appen; revalidatePath tømmer én rutes cache. Guide med kode, faldgruber og webhook-mønstre til Next.js 15/16.
Praktisk gennemgang af self-hosting af Next.js 15 med Docker: standalone output, multi-stage Dockerfile, sharp til next/image, Nginx-proxy for streaming, Redis-baseret ISR-cache og de faldgruber der får de fleste deploys til at fejle.