1. Введение
Если вы когда-нибудь работали с REST API или слышали про backend, то знаете: иногда нужно не только показывать пользователю красивые странички, но и отдавать данные в формате JSON, принимать формы, делать обработку на сервере. В Next.js 15 это делается через Route Handlers – специальные серверные файлы, которые живут прямо внутри папки app/.
Зачем это нужно?
- Хочется быстро сделать API для фронтенда, не поднимая отдельный Express или Fastify.
- Нужно обрабатывать запросы из fetch/axios прямо в том же проекте, где живёт ваш сайт.
- Хотите реализовать логику для форм, обработку загрузки файлов, отправку писем, работу с базой данных и т.д.
Route Handlers — это ваш «мини-бэкенд» внутри Next.js. Не нужно отдельного сервера, всё работает из коробки!
Где живут Route Handlers: структура папок
Всё крутится вокруг папки app/. Для создания API-эндпоинта нужно просто создать папку (например, api) и внутри неё файл с именем route.ts (или route.js).
app/
api/
route.ts
page.tsx
layout.tsx
- app/api/route.ts — это и есть ваш API-эндпоинт по адресу /api.
- Можно делать вложенные папки:
app/api/users/route.ts → /api/users app/api/products/route.ts → /api/products app/api/blog/comments/route.ts → /api/blog/comments
Важно:
В отличие от Page Router (где были папки pages/api/...), в App Router все API-эндпоинты живут в app/ и всегда называются route.ts (или .js).
2. Сигнатура Route Handler: как выглядит базовый файл
Route Handler — это просто экспорт одной или нескольких функций, которые обрабатывают HTTP-методы: GET, POST, PUT, DELETE и т.д.
Минимальный пример:
// app/api/route.ts
export async function GET(request: Request) {
return new Response('Hello from API!', {
status: 200,
});
}
- Функция называется точно как HTTP-метод (заглавными буквами): GET, POST, PUT, DELETE и т.д.
- Получает объект Request (стандартный Web API Request, как в fetch).
- Возвращает объект Response (тоже стандартный Web API Response).
- Можно экспортировать несколько функций — для разных методов.
Пример с несколькими методами:
// app/api/route.ts
export async function GET(request: Request) {
return new Response('GET: Hello!');
}
export async function POST(request: Request) {
return new Response('POST: Data received!');
}
Теперь, если отправить GET-запрос на /api — сработает первая функция, если POST — вторая.
3. Как обращаться к Route Handler из браузера или fetch
GET-запрос:
Просто откройте /api в браузере — увидите "Hello from API!".
POST-запрос:
Можно отправить через fetch, curl, Postman и т.д.
Пример с fetch:
fetch('/api', { method: 'POST' })
.then(res => res.text())
.then(console.log); // "POST: Data received!"
4. Работа с Request и Response
Объект Request
Это стандартный объект, как в fetch:
- request.method — строка с методом (GET, POST и т.д.)
- request.url — полный URL запроса
- request.headers — заголовки запроса
- request.json(), request.text(), request.formData() — методы для чтения тела запроса (для POST/PUT и др.)
Пример: чтение query-параметров
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const name = searchParams.get('name') ?? 'Гость';
return new Response(`Привет, ${name}!`);
}
Теперь /api?name=Вася вернёт "Привет, Вася!".
Объект Response
new Response(body, options) — создаёт ответ.
- body — строка или объект (можно сериализовать через JSON.stringify).
- options — объект с параметрами: status, headers и т.д.
Пример: отдаём JSON
export async function GET(request: Request) {
const data = { message: 'Hello, JSON!' };
return new Response(JSON.stringify(data), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
5. Route Handlers и поддержка разных HTTP-методов
Route Handler может экспортировать сразу несколько функций для разных методов:
export async function GET(request: Request) { ... }
export async function POST(request: Request) { ... }
export async function PUT(request: Request) { ... }
export async function DELETE(request: Request) { ... }
Если отправить запрос с методом, для которого нет обработчика — будет 405 Method Not Allowed.
Пример:
export async function GET(request: Request) {
return new Response('Только GET!');
}
export async function POST(request: Request) {
return new Response('Только POST!');
}
6. Пример: создаём простой API для списка задач
Давайте сделаем мини-API для задач (todo-list), чтобы практиковаться.
Шаг 1. Создаём файл
app/api/todos/route.ts
Шаг 2. Пишем код
// app/api/todos/route.ts
let todos = [
{ id: 1, text: 'Купить хлеб', done: false },
{ id: 2, text: 'Выучить Next.js', done: false },
];
// GET /api/todos — получить список задач
export async function GET(request: Request) {
return new Response(JSON.stringify(todos), {
headers: { 'Content-Type': 'application/json' },
});
}
// POST /api/todos — добавить задачу
export async function POST(request: Request) {
const body = await request.json();
const newTodo = {
id: Date.now(),
text: body.text || '',
done: false,
};
todos.push(newTodo);
return new Response(JSON.stringify(newTodo), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}
Шаг 3. Протестируйте!
- GET-запрос на /api/todos вернёт весь список задач.
- POST-запрос с телом { "text": "Сделать домашку" } добавит задачу.
Внимание:
Всё хранится в памяти, при перезапуске сервера данные исчезнут. Для реального приложения нужен бы был база данных, но для практики этого достаточно.
7. Полезные нюансы
Особенности и ограничения Route Handlers
- Работают только на сервере. В Route Handler нельзя использовать браузерные API (document, window и т.д.).
- Нет доступа к React-компонентам. Это не страница, а серверная функция.
- Можно использовать любые npm-пакеты, Node.js API, обращаться к базе данных, файлам и т.д.
- Можно возвращать любые HTTP-статусы и заголовки.
- Файл всегда называется route.ts (или .js) — не index.ts, не api.ts, не handler.ts!
Структура вложенных и параметрических маршрутов
Вложенные маршруты
Можно создавать подпапки:
app/api/users/route.ts → /api/users
app/api/users/profile/route.ts → /api/users/profile
Динамические сегменты
Можно делать динамические сегменты с помощью квадратных скобок:
app/api/users/[id]/route.ts → /api/users/123
Пример:
// app/api/users/[id]/route.ts
export async function GET(request: Request, { params }) {
const { id } = params;
// Получить пользователя по id
return new Response(`Пользователь #${id}`);
}
Внимание: Второй аргумент — объект с params (в последних версиях Next.js). Если вы не видите params — обновите Next.js или проверьте документацию.
Работа с NextResponse и NextRequest
Next.js предоставляет свои расширенные версии стандартных объектов: NextRequest и NextResponse. Они дают дополнительные удобства (например, работу с cookies, headers, редиректы).
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ hello: 'world' });
}
- NextResponse.json(obj) — автоматически сериализует объект в JSON и выставляет правильный Content-Type.
- Можно легко делать редиректы, устанавливать cookies и т.д.
Рекомендуется использовать NextResponse для JSON-ответов — это проще и короче.
8. Типичные ошибки при работе с Route Handlers
Ошибка №1: забыли назвать файл route.ts
Если назвать файл handler.ts, index.ts или просто api.ts, Next.js не увидит ваш эндпоинт. Только route.ts (или .js).
Ошибка №2: забыли экспортировать функцию с правильным именем
Функция должна называться точно как HTTP-метод — GET, POST (в верхнем регистре!). Если написать get или post, обработчик не сработает.
Ошибка №3: неверная работа с телом запроса
В POST/PUT-запросах тело читается асинхронно:
const data = await request.json(); Если забыть
await, получите Promise вместо данных.
Ошибка №4: не выставили Content-Type
Если возвращаете JSON, обязательно укажите
headers: { 'Content-Type': 'application/json' } Или используйте
NextResponse.json() — он делает это автоматически.
Ошибка №5: забыли обработать разные HTTP-методы
Если не экспортировать функцию для нужного метода, Next.js вернёт 405 (Method Not Allowed).
Ошибка №6: пытаетесь использовать браузерные API
В Route Handler нельзя обращаться к window, document и т.д. — это серверный код!
Ошибка №7: не обрабатываете ошибки
Если ваш код может выбросить ошибку (например, при парсинге JSON), используйте try/catch и возвращайте корректный статус и сообщение.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ