Next.jsJuniorCoding
Как настроить API-маршруты в App Router с помощью Route Handlers?
Route Handlers в App Router — файлы route.ts с именованными экспортами GET/POST/PUT/DELETE и т.д., работающие через стандартный Web API (NextRequest/NextResponse). Params в Next.js 15 — асинхронный Promise.
Route Handlers в Next.js App Router
Route Handlers — это аналог API-маршрутов из Pages Router, написанный по стандарту Web API (Request/Response). Файл route.ts внутри папки app/ создаёт HTTP-эндпоинт. Каждый именованный экспорт соответствует HTTP-методу.
Базовая структура
// app/api/hello/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
return NextResponse.json({ message: 'Hello, World!' });
}
export async function POST(req: NextRequest) {
const body = await req.json();
return NextResponse.json({ received: body }, { status: 201 });
}
// Поддерживаемые методы: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Доступ к параметрам URL
// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> } // в Next.js 15 params — Promise
) {
const { id } = await params;
const user = await db.users.findUnique({ where: { id } });
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
return NextResponse.json(user);
}
Работа с query-параметрами
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const { searchParams } = req.nextUrl;
const page = parseInt(searchParams.get('page') ?? '1');
const limit = parseInt(searchParams.get('limit') ?? '20');
const category = searchParams.get('category') ?? undefined;
const products = await db.products.findMany({
where: { category },
skip: (page - 1) * limit,
take: limit,
});
return NextResponse.json({ products, page, limit });
}
Работа с заголовками и cookies
// app/api/profile/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { cookies, headers } from 'next/headers';
export async function GET(req: NextRequest) {
// Вариант 1: через req.cookies
const token = req.cookies.get('auth_token')?.value;
// Вариант 2: через next/headers (работает в любом серверном контексте)
const cookieStore = await cookies();
const userId = cookieStore.get('user_id')?.value;
const headersList = await headers();
const userAgent = headersList.get('user-agent');
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const profile = await db.users.findUnique({ where: { id: userId } });
return NextResponse.json(profile);
}
Обработка FormData и файлов
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get('file') as File | null;
const name = formData.get('name') as string;
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
}
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
// Сохраняем в S3/MinIO
await s3.putObject({
Bucket: process.env.S3_BUCKET!,
Key: `uploads/${Date.now()}-${file.name}`,
Body: buffer,
ContentType: file.type,
});
return NextResponse.json({ success: true, name });
}
Streaming Response
// app/api/stream/route.ts
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
const lines = ['Первая строка\n', 'Вторая строка\n', 'Готово\n'];
for (const line of lines) {
controller.enqueue(encoder.encode(line));
await new Promise(resolve => setTimeout(resolve, 500));
}
controller.close();
},
});
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' },
});
}
Подводные камни
- route.ts нельзя размещать рядом с page.tsx: в одной папке не может быть одновременно
page.tsxиroute.ts— они конфликтуют. - GET Route Handlers в Next.js 15 динамические по умолчанию: в отличие от Next.js 14, GET-маршруты больше не кэшируются автоматически. Добавьте
export const dynamic = 'force-static'для кэширования. - params — Promise в Next.js 15: забытый
await paramsдаст[object Promise]вместо реального значения и сложные для отладки баги. - req.body нельзя читать дважды: после
await req.json()тело запроса исчерпано — нельзя повторно вызватьreq.formData()илиreq.text(). - CORS требует ручной настройки: Route Handlers не добавляют CORS-заголовки автоматически — нужно явно добавить
Access-Control-Allow-Originи обработать OPTIONS-запрос. - Отсутствие middleware-like цепочки: нет встроенного аналога Express middleware — для общей логики (логирование, авторизация) создайте HOF-обёртку (
withAuth(handler)). - Большие файлы зависают без стриминга: при загрузке больших файлов через
req.arrayBuffer()весь файл буферизуется в памяти — для файлов > 10 MB используйте стриминг.
Common mistakes
- Полагаться на встроенную CORS-настройку
- Возвращать
NextResponse.json({error})без статуса 4xx/5xx - Кешировать персональные GET без
dynamic = 'force-dynamic' - Использовать Route Handler там, где достаточно Server Action
What the interviewer is testing
- Знает соответствие экспорт = метод
- Использует zod/валидацию входа
- Различает Edge и Node runtime
- Понимает, когда выбирать handler vs Server Action