Route Handlers v Next.js 16 jsou moderní náhradou starých pages/api souborů – a upřímně, jakmile si na ně zvyknete, zpátky se vám fakt nechce. Pokud pracujete s App Routerem, jsou jedinou oficiální cestou, jak vystavit HTTP endpointy přímo z vaší Next.js aplikace. Tenhle průvodce vás provede vším od první minimální syntaxe až po produkční nasazení s cache, validací a autentizací, s důrazem na novinky, které přinesla verze 16.
Co jsou Route Handlers a kdy je použít
Route Handler je jednoduše soubor route.ts (nebo route.js, pokud TypeScript neřešíte) umístěný uvnitř složky app/. Exportuje HTTP metody jako funkce: GET, POST, PUT, PATCH, DELETE, HEAD a OPTIONS. Na rozdíl od Server Actions, které se volají primárně z formulářů, jsou Route Handlers určené pro klasické REST API – mobilní klienty, externí webhooky, integrace třetích stran nebo prostě veřejné JSON endpointy.
Route Handlers vs. Server Actions
- Route Handlers – veřejné HTTP endpointy, libovolná metoda, vhodné pro externí klienty.
- Server Actions – RPC-styl volání mezi klientskou komponentou a serverem, integrace s formuláři, automatická revalidace.
- Server Components – přímé volání databáze bez nutnosti vystavovat endpoint.
Tady přiznám osobní názor: pokud data potřebujete pouze ve vlastní aplikaci, je často rychlejší (a bezpečnější) sáhnout po Server Components nebo Server Actions. Route Handlery dávají smysl tam, kde potřebujete skutečný HTTP kontrakt – tedy něco, na co se dá zavolat zvenčí.
Minimální Route Handler v Next.js 16
Tak, pojďme do toho. Vytvořte soubor app/api/hello/route.ts:
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
return NextResponse.json({ message: "Ahoj z Next.js 16!" });
}
A je to. Endpoint je okamžitě dostupný na /api/hello. Žádná další konfigurace, žádný custom server, nic. Návratová hodnota je standardní Response objekt z Web Fetch API – NextResponse je jen tenká vrstva nad ním s pohodlnými helpery.
Cache chování v Next.js 16: hlavní změna
Tohle je oblast, kde se Next.js 16 zásadně liší od starších verzí (a kde mě v minulosti několikrát překvapil v produkci, takže pozor). Ve verzích 13 a 14 byly GET handlery defaultně cachované. Od Next.js 15 jsou všechny Route Handlers defaultně dynamické – cache si musíte explicitně vyžádat. Verze 16 tenhle model dále zpřesnila pomocí direktivy "use cache" a Cache Components.
Explicitní cache pomocí "use cache"
// app/api/products/route.ts
import { NextResponse } from "next/server";
export async function GET() {
"use cache";
const products = await fetch("https://api.example.com/products", {
next: { revalidate: 3600 },
}).then((r) => r.json());
return NextResponse.json(products);
}
Direktiva "use cache" v Next.js 16 nahrazuje dřívější segment config export const dynamic = "force-static" a, řekněme si to upřímně, je výrazně srozumitelnější. Pokud potřebujete revalidovat manuálně, zkombinujte ji s tagy:
import { unstable_cacheTag as cacheTag } from "next/cache";
export async function GET() {
"use cache";
cacheTag("products");
const data = await db.product.findMany();
return Response.json(data);
}
Pak stačí v Server Action zavolat revalidateTag("products") – a cache se invaliduje napříč celou aplikací. Hezké, ne?
Dynamické segmenty a parametry
Stejně jako stránky, i Route Handlers podporují dynamické segmenty pomocí složek typu [id]. V Next.js 16 jsou ale params asynchronní – nezapomeňte na await (mně to v prvním projektu trvalo dlouho, než jsem si zvykl):
// app/api/products/[id]/route.ts
import { NextResponse } from "next/server";
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const product = await db.product.findUnique({ where: { id } });
if (!product) {
return NextResponse.json({ error: "Nenalezeno" }, { status: 404 });
}
return NextResponse.json(product);
}
Query string získáte přes request.nextUrl.searchParams:
export async function GET(request: NextRequest) {
const limit = Number(request.nextUrl.searchParams.get("limit") ?? 10);
const offset = Number(request.nextUrl.searchParams.get("offset") ?? 0);
const items = await db.product.findMany({ take: limit, skip: offset });
return NextResponse.json(items);
}
Validace vstupu pomocí Zod
Pravidlo číslo jedna: nikdy nedůvěřujte tělu requestu. Vážně, nikdy. V Next.js 16 je doporučeným postupem parsovat a validovat každý vstup pomocí Zod (případně Valibot, pokud chcete menší bundle):
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
const CreateProductSchema = z.object({
name: z.string().min(1).max(120),
price: z.number().int().positive(),
category: z.enum(["clothing", "electronics", "books"]),
});
export async function POST(request: NextRequest) {
const json = await request.json();
const result = CreateProductSchema.safeParse(json);
if (!result.success) {
return NextResponse.json(
{ errors: result.error.flatten() },
{ status: 422 }
);
}
const product = await db.product.create({ data: result.data });
return NextResponse.json(product, { status: 201 });
}
Tento vzor – safeParse + 422 odpověď s detailem chyb – kopíruje konvenci běžnou v Laravel a NestJS a klientům se s ní pracuje fakt pohodlně.
Autentizace Route Handlerů
Existují tři běžné scénáře. Pro session cookies (typicky Auth.js v5) ověřte session přímo:
import { auth } from "@/lib/auth";
export async function GET() {
const session = await auth();
if (!session?.user) {
return Response.json({ error: "Neautorizováno" }, { status: 401 });
}
// dále pracujte se session.user.id
}
Pro Bearer tokeny (mobilní klienti, server-to-server):
export async function POST(request: NextRequest) {
const header = request.headers.get("authorization");
const token = header?.replace(/^Bearer\s+/i, "");
if (!token || !(await verifyJwt(token))) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
// ...
}
A pro webhooky (Stripe, GitHub) – tady pozor – ověřujte HMAC podpis nad raw tělem, nikdy ne po volání request.json():
export async function POST(request: NextRequest) {
const raw = await request.text();
const signature = request.headers.get("stripe-signature")!;
const event = stripe.webhooks.constructEvent(raw, signature, process.env.STRIPE_WEBHOOK_SECRET!);
// ...
return new Response(null, { status: 204 });
}
Streamování odpovědí
Route Handlers v Next.js 16 mohou vracet ReadableStream, což je ideální pro LLM proxy, server-sent events nebo prostě velké exporty:
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
for (let i = 0; i < 5; i++) {
controller.enqueue(encoder.encode(`data: ${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-store",
Connection: "keep-alive",
},
});
}
Edge Runtime vs. Node.js Runtime
Každý Route Handler může běžet buď v Node.js runtime (default) nebo na Edge:
export const runtime = "edge"; // nebo "nodejs"
Edge Runtime má v polovině roku 2026 cold start kolem 50 ms oproti 300–800 ms u Node.js a běží globálně blízko uživatele. Není to ale zadarmo – cenou je omezené API: žádný fs, žádné nativní moduly, žádný klasický Prisma klient (potřebujete Prisma Accelerate nebo Drizzle s HTTP driverem). Pro geo-routing, A/B testy a lehké API je Edge skvělá volba. Pro práci s databází přes TCP nebo náročnější logiku raději zůstaňte u Node.js.
CORS a OPTIONS
Pokud vaše API volá frontend z jiné domény, musíte explicitně obsloužit preflight:
const CORS = {
"Access-Control-Allow-Origin": "https://app.example.com",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export async function OPTIONS() {
return new Response(null, { status: 204, headers: CORS });
}
export async function GET() {
return NextResponse.json({ ok: true }, { headers: CORS });
}
Hvězdičku v Allow-Origin používejte vážně jen u skutečně veřejných API. Pro autentizované endpointy whitelistujte konkrétní domény, jinak si koledujete o problém.
Rate limiting v praxi
Veřejné endpointy musí mít rate limit – jinak vás první škrabák jednoduše položí. Nejjednodušší přístup je s Upstash Redis:
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const limiter = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(20, "1 m"),
});
export async function POST(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for") ?? "anon";
const { success, remaining } = await limiter.limit(ip);
if (!success) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
// ...
}
Chybové stavy a observabilita
Drsná pravda produkce: nejvíc bolestí nepřinášejí 200 OK, ale neošetřené chyby. Obalte tělo handleru do try/catch, logujte se strukturovaným kontextem a vraťte konzistentní tvar chyby:
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// ...
return NextResponse.json(result);
} catch (error) {
console.error("[products:create]", { error, url: request.url });
return NextResponse.json(
{ error: "Interní chyba serveru" },
{ status: 500 }
);
}
}
Na Vercelu logy automaticky proudí do dashboardu. V self-hostu napojte výstup na Sentry, Axiom nebo OpenTelemetry exportér – cokoliv z toho je lepší než hádat, co se právě stalo.
Časté chyby a jak se jim vyhnout
- Zapomenutý
await params– v Next.js 16 jsou params Promise. TypeScript vám to napoví, ale runtime chyba je tichá. - Vrácení
NextResponse.jsonpro velká data – serializace blokuje event loop. Pro odpovědi nad ~5 MB raději streamujte. - Spoléhání na
request.ip– v Edge a za reverzní proxy jenull. Vždy preferujtex-forwarded-for. - Cache na endpointech závislých na cookies – tichý data leak. Takové handlery vždy nechte dynamické.
- Smíchání Route Handler a Server Action ve stejné cestě – Server Action v souboru
page.tsxnepoužívejte jako veřejné API; cesta totiž není stabilní.
Nasazení a testování
Na Vercelu Route Handlers fungují bez další konfigurace – výchozí runtime je Node.js, regiony si můžete omezit přes export const preferredRegion = ["fra1", "iad1"]. Pro self-host (Docker, AWS) běží stejný build, jen si pohlídejte, aby váš reverzní proxy nepřepisoval Host a x-forwarded-* hlavičky.
Integrační testy nejjednodušeji napíšete pomocí vitest a přímého volání exportované funkce:
import { GET } from "@/app/api/products/route";
it("vrací seznam produktů", async () => {
const res = await GET(new Request("http://localhost/api/products"));
expect(res.status).toBe(200);
const data = await res.json();
expect(Array.isArray(data)).toBe(true);
});
Často kladené otázky
Jaký je rozdíl mezi Route Handler a Server Action v Next.js 16?
Route Handler je veřejný HTTP endpoint s libovolnou metodou a URL – volají ho externí klienti i váš frontend přes fetch. Server Action je naproti tomu interní RPC-styl funkce vázaná na komponenty React; volá se přímo z formulářů nebo button handlerů a má automatickou revalidaci. Pokud potřebujete vystavit data mobilní aplikaci nebo třetí straně, použijte Route Handler.
Jsou Route Handlery v Next.js 16 ve výchozím nastavení cachované?
Ne. Od Next.js 15 a stále v Next.js 16 jsou defaultně dynamické. Pokud chcete cache, musíte ji explicitně zapnout pomocí direktivy "use cache" nebo nastavit fetch s next: { revalidate }. Je to obráceně oproti Next.js 13/14, kde byly GET handlery cachované automaticky (a kde se dalo snadno spálit).
Mohu používat Prisma s Edge Runtime?
Klasický Prisma klient běží pouze v Node.js runtime, protože vyžaduje TCP spojení. Pro Edge máte v podstatě tři možnosti: Prisma Accelerate (HTTP proxy nad vaší databází), Prisma Postgres, nebo přechod na Drizzle s HTTP driverem (Neon, PlanetScale, Turso). Pro většinu projektů v roce 2026 mi dává smysl Drizzle + Neon.
Jak v Route Handleru přečtu cookies?
Použijte helper cookies() z next/headers, který je v Next.js 16 asynchronní: const jar = await cookies(); const token = jar.get("session")?.value;. Nastavit cookie můžete buď přes jar.set() nebo přidáním hlavičky Set-Cookie do NextResponse.
Kde Route Handlery běží – na klientovi nebo na serveru?
Vždy na serveru – nikdy se neposílají v bundlu do prohlížeče. To znamená, že v nich můžete bezpečně používat tajné klíče, přímé databázové dotazy a Node.js API (pokud nezapnete Edge Runtime). Z tohoto důvodu jsou ideálním místem pro logiku, kterou klient prostě nesmí vidět.
Shrnutí
Route Handlers v Next.js 16 jsou robustní, plně typovaný nástroj pro stavbu REST API přímo uvnitř vaší aplikace. Klíčové změny oproti starším verzím – asynchronní params, defaultně dynamické chování, direktiva "use cache" a vylepšený Edge Runtime – tlačí vývojáře k explicitnímu, čitelnému kódu. A to je dobrá zpráva. Pokud zkombinujete validaci pomocí Zod, jasnou autentizační strategii a rate limiting, dostanete API, které je rychlé, bezpečné a snadno se škáluje – ať už na Vercelu, nebo v self-hostu.