Next.js Pages Router → App Router migráció 2026: teljes átállási útmutató napokban mérve

Pages Router → App Router migráció napokra bontva: Next.js 16 és React 19 upgrade, /pages és /app együttélése, adatlekérdezés, Route Handlers, useRouter átvezetése és production checklist nyolc éles migráció tapasztalatával.

Next.js Pages → App Router Migráció 2026

Frissítve: 2026. augusztus 3.

A Next.js Pages Router → App Router migráció egy jól megtervezett, inkrementális refaktor, amely egy közepes méretű alkalmazásnál (30–80 route) tipikusan 8–15 mérnöknapot vesz igénybe. A Next.js 16 és React 19 upgrade 1–2 nap, az /app mappa és a shared layout felállítása 1 nap, majd route-onként 1–3 óra átírás, plusz tesztelés. A Pages Router 2026-ban még nem deprecated, de minden új feature (Cache Components, Partial Prerendering, Server Actions) csak App Routeren működik, ezért a migráció halogatása technikai adósságot épít.

  • A /pages és /app mappa egyszerre létezhet ugyanabban a projektben, útvonalanként migrálhatsz production alatt.
  • Egy 50 route-os alkalmazás átállítása egy 2 fős csapatnál 2–3 hetes sprint, ebből a data fetching és metadata refaktor a legdrágább rész.
  • A getServerSideProps és getStaticProps teljesen megszűnik App Routerben. Helyette async Server Component és natív fetch() a cache-opciókkal.
  • Az _app.tsx, _document.tsx és next/head hármast a app/layout.tsx és generateMetadata váltja fel.
  • A useRouter viselkedése kompatibilitástörő: az next/router helyett next/navigation-t kell importálni, és a router.query szétesik három hookra.
  • Middleware, redirects és rewrites next.config.js-ben változatlanul működnek. Ez az egyetlen réteg, amit nem kell hozzányúlni.

Miért migrálj App Routerre 2026-ban?

2026-ban az App Router nem opció, hanem a Next.js default fejlesztési modellje. Nyolc migrációt végigvezetve a legfőbb üzleti indok mindig ugyanaz volt: az új teljesítménybeli és fejlesztői élményt javító feature-ök (Cache Components és Partial Prerendering, Server Actions, streaming Suspense boundary-k, next/after) kizárólag App Routeren működnek. A Pages Router formálisan még nincs deprecated, de a Vercel legalább két major release-en át bug-fix-only karbantartást ígért. Ez azt jelenti: új képességek nem érkeznek, a dokumentáció a példákat App Routerre írja.

A Pages Router megtartása mellett szóló érvek 2026-ban jellemzően ezek: (1) az getInitialProps-ra épülő legacy adatbetöltés, (2) egy külső CMS/preview integráció, ami még a Pages-alapú Draft Mode API-val van bekötve, (3) egy nagyméretű, teszttel gyengén lefedett kódbázis. Egyik sem blokkoló, inkább effort-becslést befolyásol. A tényleges seb: minden hónap, amit Pages-en töltesz, tovább növeli az inkompatibilis harmadik fél csomagok, saját HOC-k és _app.tsx-be épült side-effect-ek számát, amit később úgyis szét kell szedned.

Fair az alternatívákkal szemben: ha jelenlegi alkalmazásod alapvetően statikus content site (blog, dokumentáció, marketing), és nincs terved dinamikus renderelésre vagy autentikációra, a migráció ROI-ja gyenge. Érdemesebb lehet Astro-ra vagy tiszta static exportra váltani, mint App Routerbe fektetni. Minden más esetben (dashboard, SaaS, e-commerce, hitelesítés) a válasz: igen, migrálj.

Mennyi ideig tart egy Pages → App migráció?

A gyakori kérdés, hogy „mennyibe kerül", nem a route-ok számára skálázódik lineárisan, hanem a shared surface area-ra: az _app.tsx-be épült providerek, a globális HOC-k, a data fetching konvenciók és a metadata megoldás komplexitására. Az alábbi táblázat nyolc korábbi migrációm agregált adata alapján készült, TypeScript projekteknél, 2 fős dedikált csapatra vetítve.

Projekt méretRoute-okUpgrade + setupRoute konvertálásTesztelés + QAÖsszesen
Kicsi (blog, marketing)10–251 nap2–4 nap1 nap4–6 nap
Közepes (SaaS dashboard)30–802 nap5–10 nap1–3 nap8–15 nap
Nagy (e-commerce, több domain)100–3003 nap15–25 nap4–7 nap22–35 nap
Enterprise (monorepo, több app)300+5 nap25–50 nap8–15 nap38–70 nap

A becslés két rejtett tényezőre érzékeny. Az első a tesztlefedettség: ha a kritikus flow-kra van Playwright end-to-end teszted, a QA időt körülbelül harmadolod, mert a regressziót nem manuálisan kell vadászni. A második a third-party integrációk: minden olyan csomag, ami saját _app.tsx wrappert, custom getInitialProps-t vagy Pages-specifikus HOC-t szállít (régebbi analytics, feature flag, i18n libek), 0.5–1 napot ad hozzá a becsléshez, mert vagy verzió upgrade-re, vagy alternatíva bevezetésére kényszerülsz.

Előkészületek: Next.js 16 és React 19 upgrade

Mielőtt bármelyik route-hoz hozzányúlnál, hozd fel a projektet Next.js 16-ra és React 19-re, Pages Routeren. Ez fontos szigetelési stratégia: külön PR-ban kezelheted az upgrade breaking changes-t (React 19 új refs, new ref cleanup, hydration szigorítások), és külön a router-migrációt. Ha egyszerre csinálod, két hetet fogsz debug-olni, hogy melyik változtatás okozott regressziót. (Az utolsó ügyfelemnél pontosan ez történt, és mind a kettőnknek egy egész napja rámehetett.)

A hivatalos Next.js 16 upgrade guide a következő automatizált parancsot ajánlja:

npx @next/codemod@latest upgrade latest

Ez elvégzi a package.json-ban az upgrade-et, futtatja a codemod-okat (pl. az aszinkron params-ra átállást, ami már 15-ben elkezdődött), és riportot ír azokról a fájlokról, amikhez manuális beavatkozás kell. Egy tipikus projektnél 20–40 fájlt érint, jellemzően:

  • a useRouter hookok, amiket kézzel kell átvezetni (a codemod csak jelzi, mert az API-eltérés nem 1:1),
  • a Image komponens onLoad eseménye, ami React 19-ben szigorúbb,
  • bármely forwardRef használat. React 19-ben a ref már közönséges prop, a codemod ezt átírja, de a saját type definitions-ödet érdemes lektorálni.

Fusson le a build és a teszt: ha ez a lépés zöld, van egy stabil bázisod, amire biztonságosan lehet App Router route-okat rákötni.

Együttélés: /pages és /app egyszerre

A Next.js 16 App Router és Pages Router egyidejűleg működik ugyanabban a projektben. Ez az inkrementális migráció alapja: nem kell mindent egyszerre átírni, csak arra kell figyelni, hogy útvonalütközés ne legyen. Ha van /pages/dashboard.tsx és /app/dashboard/page.tsx egyszerre, App Router nyer, és a Pages route soha nem szolgálódik ki.

A stratégia, ami nálam a legjobban bevált: ne route-ról route-ra migrálj, hanem funkcionális szigetről. Egy dashboard, egy admin felület, egy blog: külön egységek, amiknek saját layoutjuk, saját providereik, saját QA ciklusuk lehet. Ez azért lényeges, mert az App Router „root layout" fogalma azt jelenti: minden /app-beli útvonalnak ugyanaz a app/layout.tsx a szülője, amit a Pages Router _app.tsx-e nem érint. Két provider fát fenntartani rövid ideig kényelmetlen, de kockázatot csökkent.

Kezdd egy zero-risk route-tal, például egy statikus „About" oldallal, aminek nincs adatbetöltése. Ez validálja a build pipeline-t, a deploy folyamatot és a QA workflow-t anélkül, hogy production traffic-ot veszélyeztetne. Utána jöhet egy közepes komplexitású olvasás-only oldal, aztán a mutáló flow-k. Ez a sorrend nem kötelező, de a mérnöki bizalom-építés miatt megéri.

Route konvertálás: page, layout, loading, error

Az App Router négy alap fájlkonvenciót vezet be minden mappában: page.tsx (route content), layout.tsx (megosztott shell), loading.tsx (Suspense fallback), error.tsx (error boundary). A Pages Router egyetlen fájlja (pl. pages/blog/[slug].tsx) tipikusan szétbomlik App Routerben egy layoutra és egy page-re.

Egy tipikus Pages route:

// pages/blog/[slug].tsx
import Layout from '@/components/Layout';
import type { GetStaticProps, GetStaticPaths } from 'next';

export const getStaticPaths: GetStaticPaths = async () => {
  const slugs = await fetchAllSlugs();
  return { paths: slugs.map(slug => ({ params: { slug } })), fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const post = await fetchPost(params!.slug as string);
  return { props: { post }, revalidate: 3600 };
};

export default function BlogPost({ post }) {
  return <Layout><article>{post.body}</article></Layout>;
}

Ugyanez App Routerben két fájlra bomlik:

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

export const revalidate = 3600;

export async function generateStaticParams() {
  const slugs = await fetchAllSlugs();
  return slugs.map(slug => ({ slug }));
}

export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await fetchPost(slug);
  if (!post) notFound();
  return <article>{post.body}</article>;
}

// app/blog/layout.tsx
export default function BlogLayout({ children }: { children: React.ReactNode }) {
  return <div className="blog-shell">{children}</div>;
}

Két lényeges változás: a params egy Promise (a Next.js 15 óta), amit await-elned kell, és a data fetching a komponensen belül történik, nincs többé külön exportált függvény. Ez az egyszerűsítés a Server Components ereje: a komponens maga a data-loader.

Adatlekérdezés migrálása: getServerSideProps és getStaticProps helyett

A Pages Router három data fetching primitivéjéből (getServerSideProps, getStaticProps, getStaticPaths) az App Routerben mindössze egyet kell megjegyezned: await fetch() egy async Server Component-ben. Ez a legnagyobb konceptuális ugrás, és a leggyakoribb hiba forrása, ha a csapat még nem ismeri az RSC data fetching mintákat.

A mapping a következő:

  • getStaticPropsawait fetch(url, { cache: 'force-cache' }) vagy export const revalidate = N,
  • getServerSidePropsawait fetch(url, { cache: 'no-store' }) vagy export const dynamic = 'force-dynamic',
  • getStaticPathsgenerateStaticParams() exportált async függvény,
  • ISR (revalidate mező) → export const revalidate = 60 vagy a fetch-hez { next: { revalidate: 60 } },
  • on-demand revalidation → revalidateTag() és revalidatePath() Server Actionből vagy Route Handlerből hívva.

A gyakorlati váltás nemcsak szintaxis, hanem gondolkodásmód. Pages Routerben egy oldal egyetlen adatbetöltési kaput hívott (getServerSideProps), majd megkapta a props-ot. App Routerben minden komponens saját maga tudja lekérni az adatát: a layout hívhat egy user-lekérdezést, a page a fő tartalmat, egy beágyazott komponens a related items-et, és az RSC runtime dedupolja a request-eket ugyanahhoz a URL-hez. Ez lokalizálja a komplexitást, és megszünteti a props drill-inget.

API Routes → Route Handlers átállás

Ha van /pages/api/* mappád, azokat Route Handlers-re konvertálva /app/api/*/route.ts alakban tarthatod meg, mélyebb átírás nélkül. A régi API Routes eleve tovább működnek Pages Routeren, de ha egységesítenéd a stackot, itt a lépés. A részletekért nézd meg a külön oldalt a Route Handlers és REST API architektúráról.

A leggyakoribb minta:

// Pages API — pages/api/users/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    const user = await db.user.findUnique({ where: { id: req.query.id as string } });
    if (!user) return res.status(404).json({ error: 'Not found' });
    return res.status(200).json(user);
  }
  res.setHeader('Allow', 'GET');
  return res.status(405).end();
}

Route Handlers ugyanez, Web-standard Request/Response API-val:

// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server';

export async function GET(
  _req: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const user = await db.user.findUnique({ where: { id } });
  if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
  return NextResponse.json(user);
}

Négy funkcionális előny: (1) minden HTTP method saját exportált függvény, nincs többé if (req.method === ...) elágaztatás, (2) a Request/Response Web API standard, azonos node-on és edge-en, (3) a body streaming natívan támogatott, (4) automatikus 405 Method Not Allowed a nem exportált method-okra. A tesztelés is egyszerűbb, mert a handler egy tiszta függvény, ami Request-et kap és Response-t ad.

_app.tsx, _document.tsx és next/head helyettesítése

Ez a lépés a Pages Router három leggyakrabban félreértett fájlját eltünteti, és cserébe egyetlen struktúrát ad. A leképzés:

  • _app.tsx (globális providerek, layoutek, CSS import) → app/layout.tsx,
  • _document.tsx (<html>, <body>, custom fonts) → szintén app/layout.tsx, ugyanaz a fájl, mert a root layout az egész HTML dokumentum,
  • next/head és getStaticProps-ban visszaadott head props → generateMetadata async függvény vagy statikus metadata export.

A minimum root layout:

// app/layout.tsx
import './globals.css';
import { Providers } from '@/components/Providers';

export const metadata = {
  title: { default: 'My App', template: '%s | My App' },
  description: 'Default description',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="hu">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Az oldal-specifikus metadata a page.tsx-ben:

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await fetchPost(slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: { images: [post.coverImage] },
  };
}

A fetch() hívások itt is dedupolódnak: ugyanaz az adatlekérdezés a generateMetadata-ban és a page komponensben egyetlen upstream requestet eredményez. Ez az egyik oka annak, hogy a Metadata API architekturálisan jobb, mint a next/head: nincs többé „a metadata és a page adata két külön betöltés" probléma.

A next/router-t next/navigation-re kell cserélni, és ez az egyetlen olyan lépés, ahol a codemod nem tudja teljesen elvégezni a munkát, mert az API szemantikája megváltozott. A leképzés:

  • router.pathnameusePathname(),
  • router.queryuseSearchParams() a query stringre + useParams() a dinamikus route paraméterekre (a Pages Router ezt egybegyúrta),
  • router.push('/x'), router.replace('/x'): változatlan, csak a useRouter importja jön a next/navigation-ből,
  • router.events (routeChangeStart stb.): megszűnt, helyette useEffect + usePathname()/useSearchParams() változás observálása vagy a NavigationEvents pattern,
  • router.isReady: nincs többé szükség rá, a Server Component adatai már renderelés előtt megvannak.
// Pages: pages/search.tsx
import { useRouter } from 'next/router';
export default function Search() {
  const router = useRouter();
  const q = router.query.q as string;
  return <div>Keresés: {q}</div>;
}

// App: app/search/page.tsx  ('use client' ha kliens hookot használsz)
'use client';
import { useSearchParams } from 'next/navigation';
export default function Search() {
  const q = useSearchParams().get('q') ?? '';
  return <div>Keresés: {q}</div>;
}

Ezen a bugon egy éles projekten estem hasra: a router.events hiánya a leggyakoribb migrációs footgun. Analytics library-k, progress bar-ok, session-timeout kezelők tipikusan erre épültek. A megoldás vagy egy dedikált 'use client' komponens a root layoutban, ami a pathname változásra reagál, vagy a library saját App Router-kompatibilis verziójára frissítés. Egy közepes projektben 3–5 komponenst érint ez a változás, csekély, de nem elhanyagolható tétel.

Middleware, redirects és rewrites

Jó hír: a middleware.ts, a next.config.js-beli redirects() és rewrites() változatlan formában működnek App Routeren. A Middleware továbbra is Edge Runtime-on fut default-ból, ugyanazokat a NextRequest és NextResponse típusokat kapja, és ugyanúgy interceptálja az összes request-et, függetlenül attól, hogy Pages vagy App route dolgozza fel őket.

Amire figyelj: a Middleware biztonsági modellje 2025-ben szigorodott a CVE-2025-29927 után. Ha bármilyen authorization logikát Middleware-ből hajtasz végre, a mélységi védelem elve most kötelező. Erről bővebben a Next.js Middleware biztonság anyagunkban.

// middleware.ts — változatlan a migráció során
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const country = request.geo?.country ?? 'US';
  if (country === 'HU' && request.nextUrl.pathname === '/') {
    return NextResponse.rewrite(new URL('/hu', request.url));
  }
  return NextResponse.next();
}

export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] };

Ez azt jelenti: a migráció alatt a routing infrastruktúrát nem kell hozzányúlni. A redirects, a geo-based rewrites, az A/B teszt bucket assignment mind ugyanúgy működik. Ez a réteg egy stabil talppont, amire lehet építkezni.

Tesztelés és production launch checklist

Mielőtt egy route-ot /pages-ből /app-be „átbillentesz" production-ben, futtasd le a következő checklist-et. Ez a lista nyolc migrációm gyakorlati üledéke, és a leggyakoribb éles incidenseket kiszűri.

  1. Build ellenőrzés: next build hibamentesen fut, és a route mindkét változata (ha még mindkettő létezik) szerepel a build outputban.
  2. Type check: tsc --noEmit, a Promise-alapú params/searchParams típusok teljesen migrálva.
  3. Cache viselkedés: production-hoz közeli környezetben (next start) ellenőrizd, hogy a route a várt render módban van (static/dynamic/ISR), a x-nextjs-cache response header segítségével.
  4. Metadata parity: view-source-ban vagy Playwright-tel hasonlítsd össze a régi és az új oldal <head> tartalmát; title, description, canonical, og image mindenütt egyezzen.
  5. Analytics event-ek: navigációs event-ek (page_view) az új útvonalon is regisztrálódnak; ha router.events-re épültek, most tesztelendő pontosan.
  6. Autentikáció: a Server Component-ekben lekért user session valóban megvan minden érintett route-on, nincs „user leaks" a cache-be (érzékeny adat csak dynamic vagy cache-tag-elt fetch-ben).
  7. Fokozatos rollout: Vercel esetén a redirect vagy Middleware alapú traffic split segítségével először 10%-nak szolgáld az új route-ot, monitorozd az error rate-et és a Core Web Vitals-t 24 órán át.
  8. Rollback terv: ha valami félrement, a Pages route helyreállítása egyetlen deploy; ne töröld a régi fájlokat, csak nevezd át .bak-ra, amíg 1 hét stabil production futás nincs mögötted.

Egy jól strukturált migráció végén az utolsó lépés a /pages mappa teljes törlése és a hozzá tartozó dependency-k (pl. next-page-router-only csomagok) kigyomlálása. Ez általában egy fél nap: a compiler majd megmondja, mit felejtettél. Ha nem indul be a projekt, valamit még használ a Pages Router. Ne törd meg, keresd meg és fejezd be a migrációt, mielőtt véglegesítesz.

Gyakran Ismételt Kérdések

Kell egyáltalán migrálnom, ha a Pages Router alkalmazásom stabil?

Rövid távon nem, a Pages Router 2026-ban még nincs deprecated és bug-fix támogatást kap. Hosszú távon viszont minden új Next.js feature (Cache Components, Partial Prerendering, Server Actions) csak App Routeren működik, tehát a migráció halogatása egyre nagyobb versenyhátrányt jelent. Új funkciófejlesztésnél mindenképp App Routeren kezdd az új útvonalakat.

Használhatom egyszerre a Pages és App Routert?

Igen, és ez az ajánlott inkrementális migrációs stratégia. A két mappa (/pages és /app) egymás mellett létezhet ugyanabban a projektben, útvonalütközés esetén az App Router nyer. Így route-onként, funkcionális szigetenként migrálhatsz production alatt is.

Gyorsabb az App Router, mint a Pages Router?

Első betöltésre általában igen, mert az RSC csak a szükséges kliens JavaScriptet küldi el (jellemzően 30–60%-kal kisebb bundle), és a streaming Suspense hamarabb kezd rendert. Client-side navigáció ugyanaz, mindkettő client-side routing. A valós teljesítménynyereség attól függ, mennyi komponensedet tudod Server Component-té alakítani.

Mi történik a getInitialPropsszal az App Routerben?

A getInitialProps App Routerben egyáltalán nem támogatott, sem oldalakon, sem az _app.tsx-ben. Ez a legmarkánsabb breaking change. A helyettesítés a Server Component-ekben történő async data fetching (fetch() vagy adatbázis-hívás közvetlenül a komponensben). Kliens oldali adatlekérdezéshez SWR vagy TanStack Query maradhat.

Hogyan kezelem a fokozatos rollout-ot production-ben?

A legbiztonságosabb minta a Middleware alapú traffic split: cookie vagy feature-flag alapján a request egy részét az új App Router route-ra irányítod, míg a többi a régi Pages route-on marad (különböző URL prefix-szel átmenetileg). Monitorozd az error rate-et és a Core Web Vitals-t, 24–48 óra stabil futás után emelheted a százalékot.

Jasmine Patel
A Szerzőről Jasmine Patel

Web framework specialist comparing Next.js to everything else so you don't have to. Migrates teams off legacy stacks for fun.