Zobacz, jak bezpiecznie migrować z Pages Router na App Router krok po kroku i bez przestojów na produkcji.
Maciej Sala
Founder StriveLab
5 min czytaniaOpublikowano 11 kwietnia 2026 (Aktualizacja 7 lipca 2026)
to główny kierunek rozwoju Next.js — **
,
streaming,
Server Actions, Parallel
Routes
i
PPR
projektowane są przede wszystkim pod app/**. nadal działa i jest wspierany, więc migracja nie musi być
natychmiastowa, ale długofalowo jest jednak nieunikniona. Są tego duże korzyści,
ponieważ otrzymamy mniejszy JS bundle, szybszy TTFB, zero duplikacji w nested
layouts, formularze bez API routes i granularny loading/error handling. Jeśli
chcesz najpierw zrozumieć, dlaczego model server-first zmienia architekturę,
zacznij od artykułu o React Server Components i Server
Actions.
Migracja Pages Router do App Router: oba routery mogą współistnieć
Next.js pozwala używać pages/ i app/ w tym samym projekcie, to najlepsza wiadomość przy migracji. Przenosisz jedną ścieżkę, testujesz, deployujesz i dopiero wtedy bierzesz następną i pamiętaj by nie ** migrować wszystkiego naraz.**
Code
next-app/
├── app/ ← Nowe strony (App Router)
│ ├── layout.tsx
│ ├── page.tsx ← Strona główna (już zmigrowana)
│ └── blog/
│ └── page.tsx ← Blog (już zmigrowany)
├── pages/ ← Stare strony (Pages Router)
│ ├── products/
│ │ └── [id].tsx ← Jeszcze nie zmigrowane
│ └── dashboard.tsx ← Jeszcze nie zmigrowane
app/ i pages/ mogą współistnieć, ale nie powinny definiować tej samej ścieżki. App Router ma priorytet, a duplikaty będą prowadzić do błędu builda, więc przenoś route i usuń jego odpowiednik z pages/.
Pamiętaj, że istnieje jeden efekt uboczny: przejście linkiem między stroną z pages/ a stroną z app/ to twarde przeładowanie (hard navigation), a next/link nie prefetchuje przez granicę routerów. Dopóki będzie trwała migracja, część nawigacji będzie wolniejsza niż w czysto klienckich przejściach. Jest to jednak normalne i znika, gdy wszystkie route wylądują w app/. Nie naprawiaj tego, problem rozwiąże się sam po migracji.
Plan migracji Pages Router do App Router krok po kroku
Faza 1: migracja _app.tsx i _document.tsx do layout.tsx
Code
// PRZED: pages/_app.tsximport type { AppProps } from 'next/app'import { ThemeProvider } from '@/components/theme-provider'import '@/styles/globals.css'export default function App({ Component, pageProps }: AppProps) { return ( <ThemeProvider> <Header /> <Component {...pageProps} /> <Footer /> </ThemeProvider> )}
// PO: app/layout.tsx — łączy _app.tsx i _document.tsx// ThemeProvider opiera się na React Context, więc jego plik musi mieć// dyrektywę 'use client' na górze — sam layout zostaje Server Componentem.import { ThemeProvider } from '@/components/theme-provider'import { Header } from '@/components/header'import { Footer } from '@/components/footer'import '@/styles/globals.css'export const metadata = { title: { default: 'Acme', template: '%s | Acme' }, description: 'Tworzenie stron w Next.js',}export default function RootLayout({ children,}: { children: React.ReactNode}) { return ( <html lang="pl"> <body> <ThemeProvider> <Header /> <main>{children}</main> <Footer /> </ThemeProvider> </body> </html> )}
W App Routerze <Head> z next/head jest zastępowany przez metadata, więc zamiast importować next/head w app/, należy użyć eksportu metadata albo funkcji generateMetadata.
Na tym etapie łatwo zapomnieć o dwóch kwestiach. Po pierwsze, app/layout.tsx musi samodzielnie renderować znaczniki <html> i <body>, ponieważ Next.js nie dodaje ich automatycznie, w przeciwieństwie do _document.tsx. Po drugie, nie należy od razu usuwać _app.tsx ani _document.tsx; dopóki w pages/ istnieją jakiekolwiek strony, te pliki nadal je obsługują, a style i logika z root layoutu nie mają zastosowania do tras z pages/. Można je usunąć dopiero, gdy katalog pages/ będzie całkowicie pusty.
Jeśli _app.tsx zawiera Context Providery (np. dla motywu, koszyka czy stanu autoryzacji), pamiętaj, że w app/ muszą one znaleźć się w osobnym komponencie z dyrektywą 'use client', ponieważ sam root layout pozostaje Server Componentem.
Faza 2: migracja statycznych stron do App Routera
Code
// PRZED: pages/about.tsximport Head from 'next/head'export default function About() { return ( <> <Head> <title>O nas — Acme</title> </Head> <h1>O nas</h1> <p>Acme — agencja software'owa</p> </> )}
Code
// PO: app/about/page.tsximport type { Metadata } from 'next'export const metadata: Metadata = { title: 'O nas', description: "Acme — agencja software'owa, Next.js, React.",}export default function AboutPage() { return ( <> <h1>O nas</h1> <p>Acme — agencja software'owa</p> </> )}
async Server Component + export const dynamic = 'force-dynamic'
getStaticPaths
generateStaticParams
revalidate w props
export const revalidate = N
notFound: true
notFound() z next/navigation
fallback: 'blocking'
export const dynamicParams = true (domyślne)
Faza 4: migracja API Routes do Route Handlers
w App Routerze zastępują API
Routes z pages/api/. Przy okazji migracji warto rozważyć, czy dany endpoint w
ogóle powinien zostać Route Handlerem, czy lepiej przepisać go na Server Action.
Tutaj może pomóc artykuł Route Handlers vs Server Actions: kiedy co
wybrać:
Code
// PRZED: pages/api/contact.tsimport type { NextApiRequest, NextApiResponse } from 'next'export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }) } const { name, email, message } = req.body // ... logika res.status(200).json({ success: true })}
Code
// PO: app/api/contact/route.tsimport { NextResponse } from 'next/server'export async function POST(request: Request) { const { name, email, message } = await request.json() // ... logika return NextResponse.json({ success: true })}// Opcjonalnie — jawny handler dla nieobsługiwanych metod nie jest potrzebny// Next.js automatycznie zwraca 405 dla metod bez handlera
Faza 5: Client Components i dyrektywa 'use client'
W Pages Router wszystkie komponenty są Client Components, natomiast w App Router domyślnie są Server Components. Komponenty z hookami (useState, useEffect, useRouter, onClick) muszą mieć 'use client':
Code
// Ten komponent musi mieć 'use client' w App Router'use client'import { useState } from 'react'export function Counter() { const [count, setCount] = useState(0) return <button onClick={() => setCount(count + 1)}>{count}</button>}
Najrozsądniej dodać dyrektywę 'use client' do komponentów, które tego wymagają, ale nie dodawaj go do layout.tsx ani page.tsx. Przenoś interaktywność do osobnych Client Components.
Typowe pułapki przy migracji na App Router
useRouter w App Router: inna paczka i inne API
Code
// Pages Routerimport { useRouter } from 'next/router' // next/router// App Routerimport { useRouter } from 'next/navigation' // next/navigation
API jest inne: router.query nie istnieje w App Router — użyj useParams() i useSearchParams().
Metadata API zamiast next/head
next/head nie działa w app/. Użyj export const metadata (statyczne) lub export async function generateMetadata (dynamiczne).
i18n w App Router: segmenty route zamiast next-i18next
next-i18next był projektowany pod Pages Router i nie przenosi się 1:1. W App Routerze internacjonalizację budujesz na segmentach [locale] plus bibliotece typu next-intl i jest to zmiana architektoniczna. W zwiazku z tym jest to osobny etap, który wymaga odpowiedniego, ostrożnego podejścia. Pełny setup wielojęzycznej strony opisuje internacjonalizacja (i18n) w Next.js App Router, a stronę SEO (hreflang, canonical) — hreflang i canonical w Next.js.
Nie. Pages Router i App Router współistnieją w jednym projekcie i to jest właśnie fundament inkrementalnej migracji. Przenosisz stronę po stronie, testujesz i deployujesz, zaczynając od najprostszych stron statycznych. Jedyne ograniczenie: app/ i pages/ nie mogą definiować tej samej ścieżki, bo App Router ma priorytet, a duplikat będzie psuł builda.
Jak zmapować data fetching z Pages na App Router?
getStaticProps staje się zwykłym async Server Component (domyślnie SSG), getServerSideProps to async component z export const dynamic = force-dynamic, getStaticPaths zamienia się na generateStaticParams, revalidate w props na export const revalidate = N, a notFound: true na wywołanie notFound() z next/navigation.
Czy migracja wpływa na SEO?
Nie, jeśli utrzymasz te same URL-e i strukturę treści. Metadata API generuje te same tagi HTML co next/head. Po migracji sprawdź w Google Search Console, czy zmiany nie miały wpływu na poprawność indeksacji.
Jak długo będzie wspierany Pages Router?
Next.js oficjalnie utrzymuje Pages Router jako stabilny i wspierany, aktualnie nie ma publicznej daty końca wsparcia. Nowe funkcje (Server Components, streaming, Server Actions, Parallel Routes, PPR) są jednak rozwijane głównie pod app/, więc długofalowo migracja powinna kiedyś mieć miejsce.
Co z next/router i router.query po migracji?
W App Router importujesz useRouter z next/navigation zamiast next/router, a API jest inne i router.query nie istnieje. Zamiast niego używasz useParams() do parametrów ścieżki i useSearchParams() do query stringa. To jedna z najczęstszych pułapek przy przenoszeniu interaktywnych komponentów.
Co zrobić z i18n opartym o next-i18next?
next-i18next był projektowany pod Pages Router. W App Routerze częściej wybiera się next-intl albo własną warstwę i18n opartą o segmenty route. Jeśli migrujesz projekt wielojęzyczny, zaplanuj i18n jako osobny etap, a nie część pierwszego PR-a, ponieważ to zmiana o charakterze architektonicznym.
Czy da się zautomatyzować część migracji do App Routera?
Częściowo. Next.js dostarcza oficjalny pakiet @next/codemod z transformacjami do najbardziej mechanicznych zmian. Najważniejszą jest next-async-request-api, który dodaje await do asynchronicznych params i searchParams (zmiana z Next.js 15). Uruchamiasz ją przez npx @next/codemod@latest next-async-request-api . Codemody nie przepiszą jednak logiki data fetchingu (getStaticProps na Server Component) ani routingu — to wciąż praca ręczna. Traktuj je jako akcelerator nudnych zamian, nie zamiennik strategii migracji.
O autorze
Maciej Sala
Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.
Next.js 13 wprowadził App Router , czyli nowy sposób budowania aplikacji oparty na React Server Components . Ale Pages Router nigdzie nie zniknął. Dwa routery, dwa podejścia, jedna decyzja do podjęcia na starcie projektu.
Maciej Sala
Founder StriveLab
W Pages Router Next.js miał wbudowane wsparcie dla i18n i wystarczyło dodać konfigurację i18n w next.config.js , by framework zarządzał routingiem językowym automatycznie. App Router nie ma tego mechanizmu i właśnie dlatego i18n trzeba zaimplementować samodzielnie lub użyć do tego celu biblioteki. Artykuł jest o zbudowaniu wielojęzycznej strony w Next.js.
Maciej Sala
Founder StriveLab
Artykuł nie jest manifestem o porzuceniu jednej technologii na rzecz drugiej, lecz bardziej analizą, w której staram się pokazać obiektywnie, w których obszarach Astro dostarcza bezdyskusyjną przewagę wydajnościową, a w jakich scenariuszach Next.js pozostaje niezastąpionym wyborem.