Next.js 15 wszedł na produkcję w mojej agencji rok temu. Wszystkie nowe projekty buduję na App Routerze, ze Server Components, z React Server Actions. Stare projekty na Pages Router działają stabilnie i nie migruję na siłę.

Po roku w produkcji wiem, co działa naprawdę, a co tylko w demo z Vercel. Wiem, gdzie Server Components są game changerem, a gdzie tylko komplikują życie. Wiem, kiedy nie używać App Routera.

Ten post to mój honest review po 12 miesiącach pracy z najnowszą wersją Next.js. Nie sponsored content. Z perspektywy programisty robiącego web dla klientów.

Krótko: czy używać App Routera w 2026

Tak, dla nowych projektów. Jeśli zaczynasz dziś, idź w App Router, React Server Components, Server Actions.

Z wyjątkami:

  • Aplikacja w 95% SPA z bardzo małą częścią SSR: zostań przy Pages Router albo użyj Vite + React Router
  • Aplikacja statyczna bez backendowej logiki: Astro albo czysty static site (Jekyll, jak mój digital garden)
  • Backendowo-heavy aplikacja, gdzie Next.js robi tylko frontend: rozważ Remix albo splitnij na Next.js + osobny backend

W reszcie przypadków App Router jest lepszym wyborem niż Pages Router. Reszta posta o tym dlaczego, plus przestrogi.

Co realnie wnoszą Server Components

Główna obietnica: część komponentów renderuje się na serwerze, nie idzie do klienta jako JS bundle. Mniejszy bundle, szybsze ładowanie, możliwość bezpośredniego dostępu do bazy z komponentu.

W praktyce, po roku:

Wygrywa:

  • Mniejszy JS bundle o 40-60% w typowym projekcie. To realna, mierzalna oszczędność.
  • Brak waterfall’u na fetch’ach. Server component fetchuje dane na serwerze, nie strzela request po hydratacji. Lepsze LCP, lepsze Core Web Vitals.
  • Direct database access. Komponent async, pisze await db.query(...), koniec. Bez API routes pośrednictwa. Pisałem o tym w kontekście Strapi, ale przy własnej bazie efekt jest ten sam.
  • Sekrety zostają na serwerze. API key do OpenAI w server component jest bezpieczny, nie idzie do bundle.

Przegrywa:

  • Mental model jest trudniejszy. Client/server boundary nie zawsze oczywiste. Pierwsze tygodnie zespół popełnia błędy: import server-only kod w client component, zapomina o "use client", hydration errors.
  • Forms i interaktywność. Server actions ratują, ale tylko częściowo. Walidacja w czasie pisania, complex state, optimistic updates są trudniejsze niż w Pages Router z React Query.
  • Stack trace. Błędy w Server Components są mniej czytelne niż w client. Dwie warstwy serializacji, kolejne źródła problemów.

Server Actions: największa zmiana po roku

Server Actions to funkcje, które piszesz w komponencie i wywołujesz jak normalne funkcje, ale wykonują się na serwerze. Pod spodem Next.js robi za Ciebie API route plus fetch.

async function createProduct(formData: FormData) {
  "use server"
  
  const name = formData.get("name") as string
  await db.products.create({ data: { name } })
  revalidatePath("/products")
}

export default function NewProductForm() {
  return (
    <form action={createProduct}>
      <input name="name" />
      <button>Create</button>
    </form>
  )
}

W jednym pliku: form, akcja, mutacja bazy, rewalidacja cache. Bez API route, bez fetch’a, bez React Query.

Co działa świetnie:

  • Proste mutacje CRUD
  • Form submitting bez ceremonii
  • Rewalidacja danych po mutacji

Co nie działa, mimo że dokumentacja sugeruje, że działa:

  • Skomplikowany state z optimistic updates (musisz dodać useOptimistic, niby OK, w praktyce sporo kombinowania)
  • Wieloetapowe formularze
  • Real-time updates (idź w WebSockets, nie próbuj forsować Server Actions)
  • Walidacja w czasie pisania (potrzebujesz dodatkowo client-side state)

Moja zasada: Server Actions dla prostych form i mutacji. Złożone interakcje robione przez API routes plus React Query.

App Router struktura

Plików w app/ jest dużo i ich rola nie zawsze oczywista. Mój cheat sheet:

  • page.tsx = strona (default export to component)
  • layout.tsx = layout, dziedziczony przez children
  • loading.tsx = fallback dla Suspense
  • error.tsx = error boundary
  • not-found.tsx = 404
  • route.ts = API route (zamiast pages/api/)
  • (group)/ = grupa routów (bez wpływu na URL)
  • [param]/ = dynamic segment
  • @parallel/ = parallel routes (zaawansowane)

Konwencja “default export jest componentem strony” jest świetna. Konwencja “wszystko inne robisz przez nazwane pliki” jest skok dla osób z Pages Router.

Caching: tu boli najmocniej

Next.js 15 ma 4 warstwy cache, czasem konfliktujące:

  1. Request Memoization (per-request dedupe)
  2. Data Cache (między requestami)
  3. Full Route Cache (rendered HTML)
  4. Router Cache (client-side)

Każdy ma własne kontrolki, własne reset triggers, własne pułapki.

Najczęstszy problem: fetchujesz dane, są stare. Klient dzwoni “dlaczego nie widzę nowego produktu”. Sprawdzasz, w bazie jest. Po deploy też nie ma. Co?

Odpowiedź: domyślnie fetch w Next.js 15 jest cached. Musisz albo:

  • fetch(url, { cache: 'no-store' }) dla per-request
  • fetch(url, { next: { revalidate: 60 } }) dla ISR
  • revalidatePath() w server action po mutacji

W Next.js 14 było inaczej. W Next.js 13 jeszcze inaczej. Każda wersja zmienia defaulty.

Mój standard dla nowych projektów: fetch dla danych statycznych z revalidate: 3600. fetch dla danych dynamicznych z cache: 'no-store'. Bez kombinowania.

Loading states i Suspense

Jeden z best feature’ów App Routera. Każdy fragment strony może mieć własny loading state.

app/dashboard/page.tsx
app/dashboard/loading.tsx
app/dashboard/orders/page.tsx
app/dashboard/orders/loading.tsx

Strona ładuje się streaming. Najpierw layout, potem dashboard, potem orders. Każdy fragment ma własny skeleton.

User experience jest znacząco lepszy niż w Pages Router. Bez loading state’u, bez react-query polling, bez kombinacji.

Pułapka: loading.tsx renderuje się też podczas client-side nawigacji. Czasem chcesz tego, czasem nie. Da się obejść przez useTransition, ale to dodatkowa wiedza.

Migracja z Pages Router

Czy migrować istniejący projekt? Moja odpowiedź: zwykle nie.

Powody, dla których nie migruję klientów z Pages na App:

  • Migracja to tygodnie pracy bez visible business value
  • Pages Router działa stabilnie i nie umiera
  • App Router nadal ma corner cases, których Pages nie ma
  • Klient płaci za feature’y, nie za “modernizacje”

Migruję tylko, jeśli:

  • Projekt ma poważne problemy z performance, których Pages Router nie rozwiąże
  • Klient świadomie chce inwestować w infrastrukturę
  • Robimy znaczną rozbudowę i App Router znacznie ułatwi nowe funkcjonalności

Pages Router nie jest deprecated. Nadal działa, nadal dostaje update’y. Vercel utrzyma go długo.

Deployment

Next.js działa najlepiej na Vercelu. To fakt, choć kontrowersyjny.

Vercel: 0 konfiguracji, ISR działa, Image Optimization działa, edge functions działają. 20 USD/miesiąc za team plan, więcej za enterprise.

Self-hosted (Node.js): działa, ale Image Optimization wymaga konfiguracji, ISR potrzebuje custom storage, brak edge functions. Możliwe, ale +tygodnia setup’u.

Coolify, Dokku, Railway: pośrednie. Tańsze niż Vercel, łatwiejsze niż self-hosted.

Dla klientów agencji prawie zawsze wybieram Vercel. Czas zaoszczędzony na infrastructure to więcej niż 20 USD różnicy.

AI tooling i Next.js

App Router jest dziś lepiej wspierany przez AI tooling niż rok temu. Cursor i Claude Code generują kod App Routera poprawnie w 90% przypadków.

Co nadal mylą:

  • Mieszają konwencje Pages i App (np. importują getServerSideProps do App Routera)
  • Nie zawsze pamiętają o "use client" tam, gdzie potrzeba
  • Sugerują nieaktualne API z Next.js 13 zamiast 15

Trzymaj .cursorrules w projekcie z linijką “Use Next.js 15 App Router conventions, never Pages Router”. Pomaga.

Najczęstsze błędy juniorów (i nie tylko)

Wrzucanie wszystkiego jako client component. “use client” na top każdego pliku. Tracisz benefit Server Components. Trzymaj się reguły: domyślnie server, client tylko, gdy potrzebujesz interaktywności.

Importowanie server-only kodu w client component. Crash w bundlowaniu. Trzymaj database client w server-only modules (server-only package).

Brak revalidatePath po mutacji. Robisz Server Action, mutuje bazę, ale UI nie pokazuje zmiany. Bo cache. Dodaj revalidatePath() na końcu Server Action.

Używanie useState w server component. Crash. Server Components to są stateless. Komponenty z hookami muszą być client.

Importowanie ciężkich bibliotek w layoutach. Layout renderuje się na każdej stronie. Importujesz tam 200kb biblioteki, mnożysz przez wszystkie strony.

Brak loading.tsx. User czeka na pełną stronę zamiast widzieć skeleton. Łatwy win, zero kosztu.

Co czytać dalej

  • Oficjalna dokumentacja Next.js. Lepsza niż 99% kursów.
  • Lee Robinson na YouTube i blogu. VP Product w Vercel, najlepiej tłumaczy nowe feature’y.
  • Theo Browne (t3.gg) na YouTube. Krytyczny, ale rzetelny. Często pokazuje, gdzie Next.js boli.

Nie kupuj kursów Next.js za 500 zł. Dokumentacja plus jeden projekt to wystarcza.

Od czego zacząć

Jeśli dziś rozpoczynasz pierwszy projekt w Next.js 15:

  1. Postaw fresh project z App Router (create-next-app, opcje: TypeScript, Tailwind, App Router, ESLint).
  2. Zbuduj prostą stronę z server component fetchującym dane.
  3. Dodaj formularz z Server Action.
  4. Dodaj loading state.
  5. Wrzuć na Vercel, sprawdź deploy.

Po tygodniu pracy znasz 80% tego, co potrzebujesz na 90% projektów. Reszta to corner cases, których uczysz się w boju.

Next.js w 2026 to nadal najlepszy framework do większości web app w stacku React. Migracja warsztatu na App Router była bolesna w pierwszych miesiącach, dziś nie wracam.