Route Handlers v Next.js 16: Kompletní průvodce REST API v App Router

Praktický průvodce Route Handlers v Next.js 16. Stavte REST API v App Router – cache, dynamické routy, validace pomocí Zod, autentizace, streamování a nasazení na Vercel Edge Runtime.

Next.js 16 Route Handlers: REST API 2026

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.json pro 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 je null. Vždy preferujte x-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.tsx nepouží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.

O Autorovi Editorial Team

Our team of expert writers and editors.