Route Handlers w Next.js 16: Kompletny przewodnik po REST API w App Router (2026)
Praktyczny przewodnik po Route Handlers w Next.js 16: jak zbudować REST API w App Router, skonfigurować cache i CORS, obsłużyć dynamiczne segmenty i kiedy wybrać Route Handlers zamiast Server Actions. Z działającym kodem TypeScript.
Route Handlers to sposób, w jaki App Router w Next.js 16 pozwala zdefiniować własne endpointy HTTP przy użyciu standardowych Web API Request i Response. Zamiast pliku pages/api/* tworzysz plik app/api/<segment>/route.ts i eksportujesz funkcje o nazwach metod HTTP (GET, POST, PUT, DELETE, PATCH). Poniżej znajdziesz kompletny przewodnik z działającym kodem, konfiguracją cache w Next.js 16, obsługą CORS i porównaniem z Server Actions.
Route Handlers zastąpiły pages/api. Tworzysz plik app/api/<path>/route.ts i eksportujesz nazwane funkcje HTTP zwracające Response.
W Next.js 16 pamięć podręczna fetch jest domyślnie wyłączona (opt-in). Aby buforować odpowiedź, ustaw export const dynamic = 'force-static' lub użyj unstable_cache.
Dynamiczne segmenty przekazywane są w drugim argumencie: { params } jako obietnica, którą musisz await. To zmiana z Next.js 15.
Helpery cookies() i headers() z next/headers są asynchroniczne i działają wewnątrz handlera.
Wybieraj Route Handlers do publicznych API, webhooków i endpointów konsumowanych przez klientów spoza aplikacji; Server Actions do mutacji z własnego frontendu.
Obsługa CORS wymaga samodzielnego ustawienia nagłówków. Next.js nie robi tego za Ciebie w route.ts.
Czym są Route Handlers w Next.js 16
Route Handlers to funkcje po stronie serwera przypisane do jednego adresu URL, w których obsługujesz żądanie HTTP i zwracasz standardowy obiekt Response. Plik route.ts zawsze siedzi wewnątrz folderu segmentu (np. app/api/products/route.ts) i nie może współistnieć w tym samym folderze z plikiem page.tsx. Segment jest albo stroną, albo endpointem, nigdy jednym i drugim. Handler może eksportować kilka metod HTTP jednocześnie; wszystko, co nie zostało wyeksportowane, zwraca automatycznie 405 Method Not Allowed.
Historycznie Next.js miał pages/api/* z sygnaturą (req, res) pochodzącą z Node.js/Express. App Router idzie w drugą stronę: przyjmujesz i zwracasz obiekty ze specyfikacji Web Fetch, co oznacza że ten sam kod można w dużej mierze przenieść na Cloudflare Workers, Deno albo Bun. Szczerze? Piszę w Next.js jeszcze od czasów getInitialProps i to jedna z tych zmian, które faktycznie ułatwiają życie. Testy jednostkowe handlera nie potrzebują już mockowanego res.status().json(). Po prostu wywołujesz funkcję i sprawdzasz zwrócony Response.
Konwencja jest ścisła: eksport nazywa się dokładnie jak metoda HTTP wielkimi literami. Dozwolone są GET, POST, PUT, PATCH, DELETE, HEAD i OPTIONS. Cała reszta zostaje zignorowana, a klient dostanie 405.
Uruchom next dev i wywołaj endpoint: curl http://localhost:3000/api/products. Zwrócenie Response.json(...) automatycznie ustawia nagłówek content-type: application/json. Jeśli chcesz zwrócić tekst albo HTML, użyj new Response('...', { status: 200 }) i ustaw nagłówki ręcznie.
Dynamiczne segmenty i parametry zapytania
Aby obsłużyć trasę typu /api/products/123, umieść plik w folderze z nawiasami klamrowymi: app/api/products/[id]/route.ts. W Next.js 15 i wyżej drugi argument przekazuje params jako obietnicę, którą trzeba rozpakować:
Parametry zapytania (query string) czytasz z request.nextUrl.searchParams. To standardowy URLSearchParams, więc get(), getAll() i has() działają jak w przeglądarce:
To jedna z największych zmian, na którą starzy użytkownicy powinni uważać. W Next.js 14 i wcześniejszych GET handler był domyślnie statyczny. Next buforował odpowiedź na build i serwował ją bez wywoływania handlera. W Next.js 15 zmieniono to na dynamiczne domyślnie, a w Next.js 16 wraz z tzw. Cache Components pamięć podręczna fetch jest opt-in: musisz świadomie ją włączyć.
Osobiście złapałem się na tym niedawno przy migracji z 14. Endpoint kursów walut serwował ten sam JSON przez dwie godziny po deploy'u, bo zapomniałem dodać dyrektyw cache. Aby utrzymać stare, statyczne zachowanie, dodaj eksport konfiguracji segmentu:
Wewnątrz handlera masz dwie ścieżki do ciasteczek: request.cookies (tylko odczyt bieżącego żądania) oraz asynchroniczny cookies() z next/headers, który pozwala także ustawiać ciasteczka w odpowiedzi:
Do przekierowań używaj funkcji redirect() z next/navigation, albo permanentRedirect() dla stałych 308. Pod spodem rzucają wyjątek, więc kod po nich nie zostanie wykonany. Do zwrócenia zwykłego 302 wystarczy jednak natywne Response.redirect(new URL('/login', request.url), 302).
Jak obsłużyć CORS w Route Handlers
Next.js nie ustawia nagłówków CORS za Ciebie w plikach route.ts. Jeśli Twoje API ma być wywoływane z innej domeny, musisz sam dodać Access-Control-Allow-* i obsłużyć preflight:
// app/api/public/route.ts
const CORS = {
'Access-Control-Allow-Origin': 'https://twoj-frontend.pl',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
}
export async function OPTIONS() {
return new Response(null, { status: 204, headers: CORS })
}
export async function GET() {
return Response.json({ hello: 'świat' }, { headers: CORS })
}
Jeśli takich handlerów masz dużo, wyciągnij logikę do middleware Next.js i tam dodawaj nagłówki na wszystkie żądania pasujące do wzorca /api/public/*. Middleware wykonuje się przed handlerem, więc możesz z niego bezpośrednio zwrócić NextResponse na preflight, bez replikowania OPTIONS w każdym pliku.
Route Handlers vs Server Actions: co wybrać
To pytanie zadaje mi każda osoba przechodząca z pages routera. Krótko: Server Actions są dla mutacji wywoływanych z Twojego własnego frontendu w tej samej aplikacji. Route Handlers są dla wszystkiego innego (zewnętrznych klientów, mobilek, webhooków, cronów, publicznych REST/JSON API).
Kryterium
Route Handlers
Server Actions
Protokół
Standard HTTP, dowolna metoda
POST z encoded RSC payloadem
Konsument
Dowolny klient (cURL, mobile, inny serwis)
Twój własny React w tej samej aplikacji
Wywołanie
fetch('/api/...') lub SDK
Bezpośrednie wywołanie funkcji w JSX
Typy end-to-end
Musisz sam pilnować (Zod, tRPC)
Automatycznie z TypeScriptem
Cache
Konfigurujesz revalidate / dynamic
Rewalidacja przez revalidatePath/Tag
Webhooki
Tak, naturalny wybór
Nie, brak dowolnej metody
Progresywne wzbogacanie <form>
Wymaga JS na kliencie
Działa bez JS
W praktyce najczęściej mam mieszankę: Server Actions dla formularzy w panelu administracyjnym i Route Handlers dla publicznego API oraz webhooków ze Stripe. Głębsze omówienie mutacji z formularzy znajdziesz w moim tekście o Server Actions w Next.js.
Streaming odpowiedzi i Edge Runtime
Route Handlers zwracają standardowy Response, więc możesz wpiąć w niego ReadableStream i strumieniować dane w miarę ich powstawania. Dokładnie tak, jak robią to endpointy LLM-owe zwracające tokeny z Server-Sent Events:
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 1; i <= 5; i++) {
controller.enqueue(encoder.encode(`data: chunk ${i}\n\n`))
await new Promise((r) => setTimeout(r, 500))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
},
})
}
Aby uruchomić handler na Edge (bliżej użytkownika, ale bez dostępu do Node API), dodaj eksport:
export const runtime = 'edge'
Uwaga na ograniczenia. Edge Runtime bazuje na V8 isolates, więc nie użyjesz pakietów zależnych od fs, net czy natywnych sterowników bazy. Klienci baz danych typu @neondatabase/serverless albo @planetscale/database są zrobione pod ten model. Szczegóły wspieranych API opisano w specyfikacji Fetch API na MDN.
Jak zabezpieczyć endpoint API w Next.js
Podstawy, których pilnuję w każdym projekcie:
Waliduj body i query przez Zod. Nigdy nie ufaj wejściu, nawet jeśli klient jest Twój.
Sprawdzaj sesję na początku handlera, zanim dotkniesz bazy. Auth.js, Clerk czy własny cookie, sprawdź autoryzację jako pierwszy krok.
Ogranicz rozmiar body.request.json() nie ma domyślnego limitu; parsuj strumieniowo albo odrzucaj po nagłówku Content-Length.
Rate limiting. Najprościej z @upstash/ratelimit. Bez tego pierwszy skrypt kiddie z GitHuba położy Ci endpoint (mówię z doświadczenia z jednego projektu w 2024).
Weryfikuj webhooki podpisem HMAC zanim odczytasz body. Stripe, GitHub i Vercel dają Ci sekret, użyj go.
Dodatkowa warstwa uwierzytelniania? API Key w nagłówku dla partnerów B2B, JWT dla mobilnych klientów, ciasteczko sesji dla własnego frontendu. Wzorce middleware'owe (dodawanie nagłówków bezpieczeństwa, blokowanie botów, geolokalizacja) opisałem w przewodniku po Auth.js v5 w Next.js 16. Warto też przejrzeć oficjalne wytyczne bezpieczeństwa w dokumentacji Next.js, szczególnie sekcję o Data Access Layer.
Najczęściej zadawane pytania
Czy Route Handlers zastępują pages/api w Next.js 16?
Tak. Jeśli używasz App Router, wszystkie nowe endpointy piszesz jako route.ts. Stary katalog pages/api nadal działa w projektach hybrydowych, ale nie ma sensu tam pisać nowego kodu. Web Fetch API jest wygodniejsze i przenośne między środowiskami runtime.
Jak zwrócić plik binarny albo obraz z Route Handlera?
Zwróć new Response(buffer, { headers: { 'Content-Type': 'image/png' } }), gdzie buffer to Uint8Array lub ArrayBuffer. Dla dużych plików strumieniuj przez ReadableStream zamiast wczytywać całość do pamięci.
Dlaczego mój GET handler nie jest cachowany w Next.js 16?
Bo domyślne zachowanie się zmieniło. Od Next.js 15 GET jest dynamiczny, a w 16 z Cache Components cache fetch jest opt-in. Ustaw jawnie export const dynamic = 'force-static' oraz export const revalidate = <sekundy>, albo owiń logikę w unstable_cache.
Czy w Route Handlerze mogę użyć Prisma albo Drizzle?
Tak, w domyślnym Node runtime bez ograniczeń. Jeśli przełączasz się na runtime = 'edge', potrzebujesz sterownika kompatybilnego z Edge, np. @prisma/adapter-neon albo drizzle-orm/neon-http. Klasyczne sterowniki na TCP nie zadziałają w V8 isolates.
Jak testować Route Handler jednostkowo?
Importujesz eksport bezpośrednio, tworzysz new Request(url, { method, body }) i sprawdzasz zwrócone Response. Nie potrzebujesz startować serwera. To duża przewaga nad pages/api, gdzie musiałeś mockować NextApiRequest/Response.
Partial Prerendering łączy najlepsze cechy renderowania statycznego i dynamicznego w jednej trasie. Sprawdź, jak włączyć cacheComponents w Next.js 16, projektować granice Suspense i unikać typowych pułapek migracji.
Praktyczny przewodnik po Parallel Routes i Intercepting Routes w Next.js 16. Buduj modale z deep linking, dashboardy z niezależnymi slotami i poznaj najczęstsze pułapki — z gotowymi przykładami kodu.