Bevezetés: Miért van szükséged Route Handlerekre?
Ha Next.js-sel fejlesztesz, előbb-utóbb eljön az a pillanat, amikor saját API végpontokra lesz szükséged. Talán egy mobilalkalmazás szeretné lekérdezni az adataidat, talán egy webhook-ot kell fogadnod egy CMS-től, vagy egyszerűen csak egy klasszikus REST API-t akarsz húzni az alkalmazásod alá. Ilyenkor jönnek képbe a Route Handlerek — az App Router beépített megoldása HTTP végpontok létrehozására.
A Next.js 13-ban debütált App Router alapjaiban forgatta fel, hogyan gondolkodunk a backend logikáról. A korábbi Pages Router pages/api/* konvenciója helyett ma már a route.ts fájlok jelentik a szabványt, és ami igazán klassz: ezek a Web Platform natív Request és Response API-jaira épülnek. Szóval amit itt megtanulsz, az nem pusztán Next.js-specifikus tudás — hanem a web platform ismerete, amit bárhol kamatoztathatsz.
Ebben az útmutatóban az alapoktól a haladó mintákig mindent végigveszünk: CORS-kezelés, hitelesítés, streaming válaszok, webhook-fogadás. Gyakorlati kódpéldákat adok, amelyeket azonnal beilleszthetsz a saját projektjeidbe.
Route Handlerek vs. Pages Router API Routes: Mi változott?
Ha korábban a Pages Routerrel dolgoztál, a pages/api/ könyvtárban hoztál létre API végpontokat. Az App Router Route Handlerei viszont több fontos ponton eltérnek ettől. Nézzük, miben.
Web szabványokra épülnek
A Pages Router API Routes-ok Node.js-specifikus req és res objektumokat használtak — lényegében az Express.js mintájára. A Route Handlerek ezzel szemben a Request és Response Web API-kat használják. Ez szabványosabb, hordozhatóbb, és őszintén szólva egyszerűbb is.
// RÉGI: Pages Router API Route (pages/api/hello.ts)
export default function handler(req, res) {
res.status(200).json({ message: 'Helló!' });
}
// ÚJ: App Router Route Handler (app/api/hello/route.ts)
export async function GET() {
return Response.json({ message: 'Helló!' });
}
Metódusonkénti exportálás
A Pages Routerben egyetlen handler függvényt exportáltunk, aztán a req.method-dal különböztettük meg a metódusokat. Elég körülményes volt. A Route Handlereknél minden HTTP metódushoz külön nevesített exportot írunk: GET, POST, PUT, PATCH, DELETE, HEAD és OPTIONS.
Sokkal tisztább így.
Statikus exporttal is működnek
A Pages Router API Routes nem működtek statikus exporttal (next export). Az App Router Route Handlerei viszont igen — ha a GET handlered nem használ dinamikus adatokat, akár buildkor is legenerálódhat a válasz. Ez egy apró, de néha nagyon hasznos különbség.
Az első Route Handler létrehozása
Fájlszerkezet és konvenció
A Route Handlereket az app/ könyvtáron belül, route.ts (vagy route.js) fájlokban definiáljuk. A fájl elérési útja határozza meg a végpont URL-jét:
app/
api/
users/
route.ts → GET /api/users
[id]/
route.ts → GET /api/users/123
posts/
route.ts → GET /api/posts
webhook/
route.ts → POST /webhook
Fontos szabály: egy adott útvonal szegmensben nem lehet egyszerre route.ts és page.tsx. Kölcsönösen kizárják egymást, szóval ha oldalként is meg API-ként is el akarod érni ugyanazt az útvonalat, azt másképp kell megoldanod.
GET végpont: Adatok lekérdezése
// app/api/users/route.ts
import { NextResponse } from 'next/server';
// Adatbázis szimuláció
const felhasznalok = [
{ id: 1, nev: 'Kovács Anna', email: '[email protected]' },
{ id: 2, nev: 'Nagy Péter', email: '[email protected]' },
];
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const nev = searchParams.get('nev');
// Szűrés név alapján, ha van keresési paraméter
const eredmeny = nev
? felhasznalok.filter(f =>
f.nev.toLowerCase().includes(nev.toLowerCase())
)
: felhasznalok;
return NextResponse.json(eredmeny);
}
POST végpont: Új erőforrás létrehozása
// app/api/users/route.ts (POST kiegészítés)
export async function POST(request: Request) {
const body = await request.json();
// Egyszerű validáció
if (!body.nev || !body.email) {
return NextResponse.json(
{ hiba: 'A név és az email mező kötelező' },
{ status: 400 }
);
}
const ujFelhasznalo = {
id: felhasznalok.length + 1,
nev: body.nev,
email: body.email,
};
felhasznalok.push(ujFelhasznalo);
return NextResponse.json(ujFelhasznalo, { status: 201 });
}
Dinamikus útvonalak és paraméterek
A dinamikus szegmensek kezelése a Next.js 15 óta változott — és ez egy olyan dolog, amin sokan meglepődnek, amikor először találkoznak vele. A params objektum immár Promise-ként érkezik, amit await-tel kell feloldani.
// app/api/users/[id]/route.ts
import { NextResponse } from 'next/server';
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const userId = parseInt(id, 10);
// Adatbázis-lekérdezés az id alapján
const felhasznalo = await getFelhasznaloById(userId);
if (!felhasznalo) {
return NextResponse.json(
{ hiba: 'A felhasználó nem található' },
{ status: 404 }
);
}
return NextResponse.json(felhasznalo);
}
export async function PUT(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
const frissitett = await frissitFelhasznalo(parseInt(id, 10), body);
if (!frissitett) {
return NextResponse.json(
{ hiba: 'A felhasználó nem található' },
{ status: 404 }
);
}
return NextResponse.json(frissitett);
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await torolFelhasznalo(parseInt(id, 10));
return new NextResponse(null, { status: 204 });
}
TypeScript típusbiztonság a RouteContext segítségével
Ha szeretnéd, hogy a TypeScript compiler is a hátadat védje, használd a Next.js globálisan elérhető RouteContext típusát:
// app/api/posts/[slug]/route.ts
import type { NextRequest } from 'next/server';
export async function GET(
_req: NextRequest,
ctx: RouteContext<'/api/posts/[slug]'>
) {
const { slug } = await ctx.params;
// A slug típusa automatikusan string
return Response.json({ slug });
}
Így a dinamikus szegmensek típusai automatikusan lekövethetők — nem kell manuálisan karbantartanod a típusdefiníciókat.
Fejlécek, cookie-k és válaszkonfigurálás
Fejlécek olvasása és beállítása
// app/api/profil/route.ts
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET() {
const fejlecek = await headers();
const authHeader = fejlecek.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json(
{ hiba: 'Hitelesítés szükséges' },
{ status: 401 }
);
}
const valasz = NextResponse.json({ felhasznalo: 'Kovács Anna' });
valasz.headers.set('X-Custom-Header', 'egyedi-ertek');
valasz.headers.set('Cache-Control', 'private, max-age=60');
return valasz;
}
Cookie-k kezelése
A cookie-k kezelése a Next.js-ben meglepően kényelmes. Az await cookies() hívással olvashatod, a válasz objektumon keresztül pedig beállíthatod őket:
// app/api/munkamenet/route.ts
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function GET() {
const cookieStore = await cookies();
const token = cookieStore.get('auth-token')?.value;
if (!token) {
return NextResponse.json(
{ hiba: 'Nincs aktív munkamenet' },
{ status: 401 }
);
}
return NextResponse.json({ bejelentkezve: true });
}
export async function POST(request: Request) {
const { email, jelszo } = await request.json();
// Hitelesítési logika...
const token = await letrehozToken(email);
const valasz = NextResponse.json({ siker: true });
// Cookie beállítása a válaszban
valasz.cookies.set('auth-token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 nap
path: '/',
});
return valasz;
}
CORS-kezelés: Külső kliensek kiszolgálása
Ha az API végpontodat más domainről is el akarják érni — mondjuk egy mobilalkalmazásból vagy egy külön frontendes projektből —, CORS fejléceket kell beállítanod. Ez egy olyan téma, ami rengeteg fejlesztőnek okoz fejfájást, de valójában nem olyan bonyolult.
Végpontonkénti CORS konfiguráció
// app/api/public/adatok/route.ts
const corsHeaders = {
'Access-Control-Allow-Origin': 'https://mobilapp.pelda.hu',
'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: corsHeaders,
});
}
export async function GET() {
const adatok = await getPublikusAdatok();
return Response.json(adatok, {
headers: corsHeaders,
});
}
Az OPTIONS handlert ne felejtsd el! A böngészők CORS preflight kéréseket küldenek (OPTIONS metódussal), mielőtt az Authorization fejlécet vagy application/json tartalomtípust használnák. Ha nincs OPTIONS handler, a kérés csendben meghiúsul, és csak egy rejtélyes CORS hibát kapsz a konzolban. (Igen, már jártam így.)
Globális CORS a next.config.js-ben
Ha nem akarsz minden egyes végpontba külön bemásolni a CORS fejléceket, a next.config.js-ben központilag is megoldhatod:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: 'https://mobilapp.pelda.hu',
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
];
},
};
Ez elegánsabb megoldás, különösen ha sok végpontod van.
Hitelesítés és validáció Route Handlerekben
Egy fontos alapelv: a Route Handlereket mindig kezeld nyilvános végpontokként. Még ha a proxy (middleware) réteg már ellenőrizte is a hitelesítést, minden érzékeny végpontban ismételd meg az ellenőrzést. Ez a mélységi védelem (defense-in-depth) elve, és a gyakorlatban nem egyszer mentett meg komoly bajoktól.
Strukturált kérés-feldolgozási minta
Az ajánlott feldolgozási sorrend: Validálás → Hitelesítés → Jogosultságellenőrzés → Feldolgozás. Minden lépésnél a megfelelő HTTP státuszkóddal térj vissza hiba esetén.
// lib/api-utils.ts — Újrafelhasználható segédfüggvények
import { cookies } from 'next/headers';
import { jwtVerify } from 'jose';
const titkosKulcs = new TextEncoder().encode(
process.env.JWT_SECRET!
);
export async function hitelesitKereset() {
const cookieStore = await cookies();
const token = cookieStore.get('auth-token')?.value;
if (!token) {
return { hitelesitett: false, felhasznalo: null } as const;
}
try {
const { payload } = await jwtVerify(token, titkosKulcs);
return {
hitelesitett: true,
felhasznalo: {
id: payload.sub as string,
szerep: payload.role as string,
},
} as const;
} catch {
return { hitelesitett: false, felhasznalo: null } as const;
}
}
// Használat egy Route Handlerben
// app/api/admin/statisztikak/route.ts
import { NextResponse } from 'next/server';
import { hitelesitKereset } from '@/lib/api-utils';
export async function GET() {
const { hitelesitett, felhasznalo } = await hitelesitKereset();
if (!hitelesitett || !felhasznalo) {
return NextResponse.json(
{ hiba: 'Hitelesítés szükséges' },
{ status: 401 }
);
}
if (felhasznalo.szerep !== 'admin') {
return NextResponse.json(
{ hiba: 'Nincs jogosultságod ehhez a művelethez' },
{ status: 403 }
);
}
const statisztikak = await getAdminStatisztikak();
return NextResponse.json(statisztikak);
}
Bemeneti validáció Zod-dal
A Zod a TypeScript ökoszisztéma egyik legjobb validációs könyvtára, és a Route Handlerekkel nagyon jól működik együtt. Nézzük, hogyan:
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
import { z } from 'zod';
const UjBejegyzesSema = z.object({
cim: z.string().min(3, 'A cím legalább 3 karakter legyen'),
tartalom: z.string().min(10, 'A tartalom legalább 10 karakter legyen'),
kategoriaId: z.number().int().positive(),
cimkek: z.array(z.string()).optional(),
});
export async function POST(request: Request) {
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ hiba: 'Érvénytelen JSON formátum' },
{ status: 400 }
);
}
const validacio = UjBejegyzesSema.safeParse(body);
if (!validacio.success) {
return NextResponse.json(
{
hiba: 'Validációs hiba',
reszletek: validacio.error.flatten().fieldErrors,
},
{ status: 400 }
);
}
// Innentől a validacio.data típusbiztos
const bejegyzes = await letrehozBejegyzes(validacio.data);
return NextResponse.json(bejegyzes, { status: 201 });
}
Route Handlerek vs. Server Actions: Mikor melyiket válaszd?
Őszintén, ez az egyik leggyakoribb kérdés, amivel találkozom Next.js fejlesztőktől 2026-ban. A rövid válasz: Server Actions-t belső mutációkhoz, Route Handlereket külső hozzáféréshez. De ennél azért van itt bővebben is miről beszélni.
Mikor használj Server Actions-t?
- Űrlapok kezelése — A Server Actions natívan integrálódnak a React űrlapokkal, és progresszív javítást is biztosítanak (vagyis JavaScript nélkül is működnek).
- Belső mutációk — Ha csak a saját Next.js frontended hívja meg a szerveroldali műveletet, a Server Actions egyszerűbb és típusbiztosabb.
- Teljes típusbiztonság — End-to-end TypeScript típusok a kliens és a szerver között, manuális típuskasztolás nélkül. Ez komoly előny.
Mikor használj Route Handlereket?
- Külső kliensek — Mobilalkalmazások, harmadik féltől származó integrációk, más backend rendszerek nem tudják közvetlenül hívni a Server Actions-t.
- Webhook-ok fogadása — Stripe, GitHub, CMS rendszerek webhook-okat küldenek, amelyeket Route Handlerekkel fogadhatsz.
- GET kérések gyorsítótárazása — A Server Actions mindig POST-ot használnak, ami nem gyorsítótárazható. Ha HTTP cache-re van szükséged, Route Handler kell.
- Részletes HTTP-vezérlés — Egyedi státuszkódok, streaming válaszok, CORS fejlécek.
- RESTful API — Ha explicit HTTP szemantikát (GET, PUT, DELETE) szeretnél, a Route Handlerek az egyetlen út.
Összehasonlító táblázat
| Szempont | Server Actions | Route Handlerek |
|---|---|---|
| Használati terület | Belső mutációk, űrlapok | Külső API-k, webhook-ok |
| HTTP metódus | Csak POST | GET, POST, PUT, DELETE stb. |
| Gyorsítótárazás | Nem támogatott | Támogatott (GET) |
| Típusbiztonság | End-to-end | Manuális típusdefiníció |
| Külső hozzáférés | Csak Next.js kliens | Bármilyen HTTP kliens |
| Bonyolultság | Alacsony | Közepes |
Haladó minták: Webhook-ok, streaming és BFF
Webhook fogadása aláírás-ellenőrzéssel
Na, itt már a komolyabb dolgokhoz érkeztünk. A webhook-ok fogadásakor mindig ellenőrizd az aláírást — enélkül bárki hamis eseményeket küldhet a végpontodra, és ez komoly biztonsági kockázat.
// app/api/webhook/stripe/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
if (!signature) {
return NextResponse.json(
{ hiba: 'Hiányzó aláírás' },
{ status: 400 }
);
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
webhookSecret
);
} catch (err) {
return NextResponse.json(
{ hiba: 'Érvénytelen aláírás' },
{ status: 400 }
);
}
// Esemény feldolgozása
switch (event.type) {
case 'payment_intent.succeeded':
await kezeldFizetesUtani(event.data.object);
break;
case 'customer.subscription.deleted':
await kezeldElofizetesTorles(event.data.object);
break;
}
return NextResponse.json({ fogadva: true });
}
Fontos részlet: a kérés törzsét request.text()-tel olvasd be, ne request.json()-nel. Az aláírás-ellenőrzéshez a nyers szöveges formátum kell.
Streaming válaszok
A Route Handlerek támogatják a streaming válaszokat a Web Streams API-n keresztül. Ez különösen hasznos hosszú futású folyamatoknál vagy AI-generált tartalmaknál (gondolj egy ChatGPT-szerű válaszfolyamra):
// app/api/stream/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 5; i++) {
controller.enqueue(
encoder.encode(`Adat csomag ${i + 1}\n`)
);
await new Promise(resolve => setTimeout(resolve, 1000));
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Transfer-Encoding': 'chunked',
},
});
}
Backend-for-Frontend (BFF) proxy minta
Ez az egyik kedvenc mintám. A Route Handlerek kiválóan alkalmasak közvetítő rétegként egy külső API és a frontend között. Elrejtheted az API kulcsokat, átalakíthatod az adatstruktúrát, és leegyszerűsítheted a frontend kódot — mindezt egy helyen.
// app/api/idojaras/[varos]/route.ts
import { NextResponse } from 'next/server';
export async function GET(
_request: Request,
{ params }: { params: Promise<{ varos: string }> }
) {
const { varos } = await params;
// A külső API kulcs a szerveren marad
const valasz = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${varos}&appid=${process.env.WEATHER_API_KEY}&units=metric&lang=hu`,
{ next: { revalidate: 300 } } // 5 percenként frissül
);
if (!valasz.ok) {
return NextResponse.json(
{ hiba: 'Nem sikerült lekérdezni az időjárást' },
{ status: 502 }
);
}
const adat = await valasz.json();
// Csak a szükséges adatokat küldjük a frontendnek
return NextResponse.json({
varos: adat.name,
homerseklet: Math.round(adat.main.temp),
leiras: adat.weather[0].description,
paratartalom: adat.main.humidity,
});
}
Gyorsítótárazás és teljesítmény
A Route Handlerek gyorsítótárazási viselkedése egy olyan terület, ahol érdemes pontosan tudni, mi történik a háttérben.
Alapértelmezett viselkedés Next.js 15+ alatt
A Next.js 15 óta a GET handlerek alapértelmezetten dinamikusak, vagyis nem gyorsítótárazottak. Ez változás a korábbi viselkedéshez képest. Ha statikus választ szeretnél, explicit módon kell bekapcsolnod:
// app/api/beallitasok/route.ts
// Statikus válasz kikényszerítése
export const dynamic = 'force-static';
export async function GET() {
return Response.json({
alapertelmezett_nyelv: 'hu',
tema: 'vilagos',
});
}
ISR-szerű revalidáció Route Handlerekben
Ha a fetch hívásaidban használod a next.revalidate opciót (ahogy a BFF példában is láttuk), az ISR-hez hasonlóan működik: a válasz gyorsítótárazódik, és az adott időintervallum elteltével automatikusan frissül a háttérben. Egyszerű, hatékony.
Hibakezelés: Jó gyakorlatok
A jól strukturált hibakezelés az API minőségének egyik sarokköve. Ne küldj vissza általános „Internal Server Error" üzeneteket — adj értelmes visszajelzést a kliensnek, mert a debuggolás során (és higgy nekem, előbb-utóbb debuggolni fogsz) a részletes hibaüzenetek aranyat érnek.
// lib/api-hibak.ts
export class ApiHiba extends Error {
constructor(
public statusz: number,
public uzenet: string,
public reszletek?: unknown
) {
super(uzenet);
}
}
export function hibaValasz(hiba: unknown) {
if (hiba instanceof ApiHiba) {
return Response.json(
{
hiba: hiba.uzenet,
reszletek: hiba.reszletek,
},
{ status: hiba.statusz }
);
}
console.error('Váratlan hiba:', hiba);
return Response.json(
{ hiba: 'Szerverhiba történt' },
{ status: 500 }
);
}
Next.js 16 változások: proxy.ts és a Route Handlerek
Egy gyors kitérő a Next.js 16-os újdonságaira. A middleware.ts fájlt átnevezték proxy.ts-re, és immár a Node.js runtime-on fut (nem Edge-on). Ez azt jelenti, hogy a proxy rétegben is elérheted a teljes Node.js API-t, beleértve az adatbázis-kapcsolatokat is.
De fontos: ez nem változtat a Route Handlerek szerepén. A proxy továbbra is a kérés előfeldolgozásáért felel (átirányítás, hitelesítés-ellenőrzés, fejléc-módosítás), míg a tényleges API logika a Route Handlerekben marad.
Gyakran ismételt kérdések (GYIK)
Mire valók a Next.js Route Handlerek és miben különböznek az API routes-tól?
A Route Handlerek az App Router beépített megoldása HTTP végpontok létrehozására. A korábbi Pages Router pages/api/ megoldásával szemben a Web Platform szabványos Request/Response API-jaira épülnek, metódusonkénti exportálást használnak (GET, POST stb.), és statikus exporttal is működnek. A cél ugyanaz, a megközelítés viszont modernebb és szabványosabb.
Lehet-e Route Handlereket és Server Actions-t egyszerre használni ugyanabban a projektben?
Igen, sőt ez az ajánlott megközelítés! Használj Server Actions-t a belső mutációkhoz és űrlapkezeléshez (ahol a React frontend hívja meg közvetlenül), és Route Handlereket a külső kliensek számára (mobilalkalmazások, webhook-ok, harmadik fél integrációk). A kettő kiegészíti egymást.
Hogyan kezelhetek CORS-t a Next.js Route Handlerekben?
Három lehetőséged van: (1) végpontonként egyedi CORS fejlécek a Response objektumban, az OPTIONS preflight handlerrel együtt, (2) globális CORS konfiguráció a next.config.js headers() szekciójában, vagy (3) CORS kezelés a proxy.ts rétegben. A legfontosabb: mindig legyen OPTIONS handlered is az Authorization fejlécet vagy application/json tartalomtípust használó végpontoknál.
A GET Route Handlerek automatikusan gyorsítótárazódnak?
Next.js 15 és 16 alatt nem — a GET handlerek alapértelmezetten dinamikusak. Ha statikus viselkedést szeretnél, explicit módon kell beállítanod az export const dynamic = 'force-static' opciót. A fetch hívásokon belül a next.revalidate opcióval ISR-szerű revalidáció is elérhető.
Hogyan fogadhatok biztonságosan webhook-okat Route Handlerrel?
Három lépés kell hozzá: (1) olvasd be a nyers kérés törzset a request.text() metódussal (ne JSON-ként!), (2) ellenőrizd a szolgáltató által küldött aláírás fejlécet a titkos kulcsoddal, és (3) csak sikeres ellenőrzés után dolgozd fel az eseményt. Az aláírás-ellenőrzés kihagyása komoly biztonsági rés — bárki küldhet hamis eseményeket a végpontodra.