Пошук уроків, статей та іншого контенту
Порівняйте Route Handlers і Server Actions за способом виклику, безпекою, кешуванням та обмеженнями.
У Next.js App Router є два способи виконувати серверний код із клієнтського інтерфейсу:
Route Handlers — HTTP-обробники, які створюють API-маршрути.
Server Actions — асинхронні серверні функції, які можна викликати безпосередньо з React-компонентів або HTML-форм.
Обидва підходи виконуються на сервері, але мають різні призначення:
Route Handler підходить для публічного або внутрішнього HTTP API.
Server Action підходить для операцій із UI конкретного Next.js-застосунку, особливо для мутацій: створення, оновлення або видалення даних.
Route Handler — це файл route.ts або route.js у директорії app. Експортована функція відповідає HTTP-методу.
app/
└── api/
└── tasks/
└── route.ts// app/api/tasks/route.ts
import { NextResponse } from "next/server";
type Task = {
id: number;
title: string;
};
const tasks: Task[] = [
{ id: 1, title: "Вивчити Route Handlers" },
];
export async function GET() {
return NextResponse.json(tasks);
}
export async function POST(request: Request) {
const body: unknown = await request.json();
if (
typeof body !== "object" ||
body === null ||
!("title" in body) ||
typeof body.title !== "string" ||
body.title.trim().length === 0
) {
return NextResponse.json(
{ error: "Поле title є обов'язковим" },
{ status: 400 },
);
}
const task: Task = {
id: tasks.length + 1,
title: body.title.trim(),
};
tasks.push(task);
return NextResponse.json(task, { status: 201 });
}Такий обробник доступний за адресою:
GET /api/tasks
POST /api/tasksЙого можна викликати з браузера, мобільного застосунку, іншого сервера або будь-якого HTTP-клієнта:
const response = await fetch("/api/tasks", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Написати тест",
}),
});
const task = await response.json();Route Handlers:
працюють із HTTP-запитами та відповідями;
підтримують GET, POST, PUT, PATCH, DELETE, HEAD і OPTIONS;
мають доступ до заголовків, cookies, URL і тіла запиту;
можуть повертати JSON, текст, файли або іншу HTTP-відповідь;
добре підходять для REST API та інтеграцій із зовнішніми клієнтами.
Приклад доступу до параметрів запиту:
// app/api/tasks/route.ts
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const url = new URL(request.url);
const search = url.searchParams.get("search") ?? "";
return NextResponse.json({
search,
tasks: [],
});
}Запит:
/api/tasks?search=тестServer Action — це асинхронна функція, яка виконується на сервері. Щоб позначити функцію як Server Action, використовують директиву "use server".
Server Action можна оголосити:
у серверному компоненті;
в окремому файлі з "use server" на початку;
усередині функції, якщо вона оголошена в серверному компоненті.
Найзручніше зберігати дії в окремому файлі:
// app/tasks/actions.ts
"use server";
export async function createTask(formData: FormData) {
const title = formData.get("title");
if (typeof title !== "string" || title.trim().length === 0) {
return {
ok: false,
error: "Введіть назву завдання",
};
}
// Тут зазвичай виконується запис у базу даних.
console.log("Створення завдання:", title.trim());
return {
ok: true,
};
}Server Action можна передати в атрибут action форми:
// app/tasks/page.tsx
import { createTask } from "./actions";
export default function TasksPage() {
return (
<main>
<h1>Завдання</h1>
<form action={createTask}>
<label htmlFor="title">Назва</label>
<input id="title" name="title" required />
<button type="submit">Створити</button>
</form>
</main>
);
}У цьому випадку браузер надсилає дані форми на сервер, а Next.js викликає createTask. Не потрібно вручну створювати URL, викликати fetch або розбирати JSON-запит.
Server Action також можна імпортувати в Client Component і викликати через startTransition або спеціальні React API для стану форми.
// app/tasks/actions.ts
"use server";
export async function deleteTask(id: number) {
if (!Number.isInteger(id) || id <= 0) {
throw new Error("Некоректний ідентифікатор");
}
// Тут зазвичай виконується видалення з бази даних.
console.log("Видалення завдання:", id);
}// app/tasks/delete-button.tsx
"use client";
import { useTransition } from "react";
import { deleteTask } from "./actions";
type DeleteButtonProps = {
id: number;
};
export function DeleteButton({ id }: DeleteButtonProps) {
const [isPending, startTransition] = useTransition();
return (
<button
type="button"
disabled={isPending}
onClick={() => {
startTransition(async () => {
await deleteTask(id);
});
}}
>
{isPending ? "Видалення..." : "Видалити"}
</button>
);
}Клієнтський код не отримує тіло Server Action як звичайну JavaScript-функцію. Next.js створює механізм, який передає виклик на сервер через HTTP.
Route Handler викликають через HTTP:
const response = await fetch("/api/tasks", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: "Нове завдання" }),
});
if (!response.ok) {
throw new Error("Не вдалося створити завдання");
}Клієнт сам відповідає за:
URL;
HTTP-метод;
заголовки;
серіалізацію тіла;
обробку статусу відповіді;
розбір відповіді.
Server Action викликають як серверну функцію або передають формі:
<form action={createTask}>
<input name="title" />
<button type="submit">Створити</button>
</form>Next.js сам організовує передачу виклику на сервер. Це зменшує кількість шаблонного коду, особливо для форм.
Використовуйте Route Handler, якщо:
API має викликати мобільний клієнт;
API має викликати інший сервер;
потрібен чіткий HTTP-контракт;
потрібно підтримати різні HTTP-методи;
інтеграція очікує JSON API;
потрібні особливі статуси, заголовки або формат відповіді.
Використовуйте Server Action, якщо:
операція напряму пов’язана з UI Next.js;
потрібно обробити надсилання форми;
виконується мутація даних;
не потрібен окремий публічний API;
хочеться викликати серверну логіку без ручного fetch.
І Route Handlers, і Server Actions виконуються на сервері, але це не означає, що вони автоматично захищені.
Кожна операція, яка працює з приватними даними, повинна самостійно перевіряти:
чи користувач автентифікований;
чи має він потрібні права;
чи належить йому ресурс;
чи є вхідні дані коректними.
Перевірка лише в інтерфейсі недостатня. Користувач може вручну сформувати HTTP-запит або спробувати викликати серверну дію іншим способом.
// app/api/profile/route.ts
import { NextResponse } from "next/server";
async function getCurrentUser() {
// У реальному застосунку тут перевіряється сесія.
return { id: 42 };
}
export async function GET() {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: "Необхідна автентифікація" },
{ status: 401 },
);
}
return NextResponse.json({
userId: user.id,
});
}// app/settings/actions.ts
"use server";
async function getCurrentUser() {
// У реальному застосунку тут перевіряється сесія.
return { id: 42 };
}
export async function updateDisplayName(formData: FormData) {
const user = await getCurrentUser();
if (!user) {
return {
ok: false,
error: "Необхідна автентифікація",
};
}
const displayName = formData.get("displayName");
if (
typeof displayName !== "string" ||
displayName.trim().length < 2
) {
return {
ok: false,
error: "Ім'я має містити щонайменше два символи",
};
}
// Оновлення має виконуватися для user.id,
// отриманого із сесії, а не з довільного поля форми.
console.log("Оновлення користувача:", user.id);
return {
ok: true,
};
}Дані від форми або HTTP-запиту не можна вважати безпечними лише тому, що вони надійшли із власного інтерфейсу.
Потрібно перевіряти:
типи;
довжину рядків;
допустимі значення;
ідентифікатори ресурсів;
права доступу до конкретного ресурсу.
Server Actions мають обмежений набір серіалізованих аргументів і результатів. Не слід передавати через них довільні екземпляри класів, відкриті з’єднання, функції або інші несеріалізовані об’єкти. Для форм найчастіше використовують FormData, а для інших викликів — прості значення, масиви та об’єкти.
Route Handler є HTTP-ендпоїнтом, тому для нього важливі правила кешування відповіді.
Кешування залежить від:
HTTP-методу;
статичності або динамічності маршруту;
версії Next.js;
використаних API запиту;
налаштувань кешу та заголовків відповіді.
Для GET Route Handler Next.js може використовувати кешування, якщо маршрут може бути статично обчислений. У сучасних версіях Next.js поведінка за замовчуванням змінювалася, тому для критичних маршрутів не варто покладатися лише на неявні налаштування.
Щоб явно вимкнути кешування, можна використати динамічний режим:
// app/api/tasks/route.ts
import { unstable_noStore as noStore } from "next/cache";
import { NextResponse } from "next/server";
export async function GET() {
noStore();
const tasks = await loadTasksFromDatabase();
return NextResponse.json(tasks);
}
async function loadTasksFromDatabase() {
return [];
}Також поведінку можна контролювати заголовками відповіді або налаштуваннями маршруту. Для даних, які часто змінюються, важливо явно визначити, коли відповідь може бути повторно використана.
POST, PUT, PATCH і DELETE зазвичай використовують для змін, а не для кешування результатів як звичайних GET-відповідей.
Server Actions не є звичайними кешованими GET-ендпоїнтами. Їх використовують передусім для виконання серверних операцій.
Після зміни даних Server Action може явно оновити кеш:
// app/tasks/actions.ts
"use server";
import { revalidatePath } from "next/cache";
export async function createTask(formData: FormData) {
const title = formData.get("title");
if (typeof title !== "string" || title.trim() === "") {
return {
ok: false,
error: "Назва є обов'язковою",
};
}
// await db.task.create({
// data: { title: title.trim() },
// });
revalidatePath("/tasks");
return {
ok: true,
};
}revalidatePath("/tasks") повідомляє Next.js, що кеш, пов’язаний зі сторінкою /tasks, потрібно оновити під час наступного отримання даних.
Тому типовий сценарій для Server Action такий:
перевірити користувача;
перевірити вхідні дані;
змінити дані в базі;
викликати revalidatePath або revalidateTag;
повернути результат або виконати перенаправлення.
Якщо сторінка показує кешовані дані, простого запису в базу недостатньо. Після мутації потрібно повідомити Next.js, які дані більше не актуальні.
Для Route Handler це можна зробити після внутрішньої зміни даних:
// app/api/tasks/route.ts
import { revalidatePath } from "next/cache";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const body = await request.json();
// Збереження body у базі даних.
revalidatePath("/tasks");
return NextResponse.json({ ok: true }, { status: 201 });
}Водночас якщо Route Handler є API для зовнішніх клієнтів, ці клієнти самі визначають, як обробляти отриману відповідь і коли повторно завантажувати дані.
Server Actions зручні, але не є універсальною заміною API.
Server Actions найкраще підходять для дій, пов’язаних із конкретним застосунком:
створення запису через форму;
зміна налаштувань;
видалення ресурсу;
запуск серверної операції від імені поточного користувача.
Якщо один API повинні використовувати різні клієнти, краще створити Route Handler із чітким HTTP-контрактом.
Server Action не може безпосередньо приймати або повертати:
функції;
з’єднання з базою даних;
екземпляри потоків;
довільні серверні об’єкти;
значення, які не підтримує механізм серіалізації Next.js.
Передавайте прості дані:
await updateTask({
id: 10,
title: "Оновлена назва",
});А серверні ресурси створюйте вже всередині дії.
Код Server Action виконується на сервері, тому секрети не потрапляють у браузер лише через те, що дія викликається з Client Component. Проте не можна повертати секретні значення з дії або вставляти їх у повідомлення про помилки.
Неправильно:
return {
debug: process.env.DATABASE_URL,
};Правильно — повертати клієнту лише необхідний результат:
return {
ok: false,
error: "Не вдалося зберегти зміни",
};Server Actions передають дані на сервер через запит. Для великих даних, зокрема файлів, потрібно враховувати обмеження розміру тіла запиту та конфігурацію Next.js.
Для звичайних текстових форм Server Actions підходять добре. Для складних сценаріїв завантаження файлів або спеціального потокового протоколу Route Handler часто дає більше контролю.
Route Handlers також мають власну ціну:
потрібно вручну розбирати тіло запиту;
потрібно явно формувати статуси й відповіді;
потрібно самостійно обробляти помилки;
потрібно вручну оновлювати клієнтський стан після мутації;
потрібно окремо продумати автентифікацію, авторизацію та кешування.
Наприклад, fetch не вважає відповідь із кодом 400 або 500 винятком автоматично. Клієнтський код має перевіряти response.ok або response.status.
дія запускається із форми або кнопки в Next.js;
операція є мутацією;
API не потрібен іншим клієнтам;
важливіша простота інтеграції з UI, ніж універсальний HTTP-контракт;
після зміни потрібно оновити конкретний маршрут або кеш.
потрібен URL, доступний через HTTP;
API буде використовувати мобільний застосунок;
endpoint викликає сторонній сервіс;
потрібні різні HTTP-методи;
необхідно контролювати заголовки, статуси або формат відповіді;
потрібно створити REST-подібний контракт.
Server Action має серверний HTTP-механізм виклику, але це не означає, що його слід використовувати як стабільний API для сторонніх клієнтів.
Якщо клієнту потрібен документований endpoint із URL і HTTP-методами, створіть Route Handler.
Прихована кнопка «Видалити» не захищає дані. Користувач може напряму викликати серверну операцію.
Перевірки автентифікації, авторизації та належності ресурсу мають бути всередині Route Handler або Server Action.
Запис у базу даних не гарантує, що сторінка одразу покаже нові дані. Після мутації потрібно використати відповідний механізм інвалідації, наприклад revalidatePath або revalidateTag.
Не передавайте функції, з’єднання або серверні об’єкти. Передавайте ідентифікатори та прості дані, а доступ до серверних ресурсів отримуйте всередині дії.
GET для зміни данихGET має читати дані, а не видаляти або змінювати їх. Для мутацій використовуйте Server Action або HTTP-методи POST, PUT, PATCH чи DELETE у Route Handler.
Route Handler — це HTTP endpoint у app/**/route.ts.
Server Action — це серверна функція з директивою "use server".
Route Handler викликають через HTTP і він підходить для різних клієнтів.
Server Action викликають із форм або компонентів Next.js і він особливо зручний для мутацій.
Жоден із підходів не додає автоматичну бізнес-авторизацію: доступ потрібно перевіряти на сервері.
Кешування Route Handler залежить від його динамічності та налаштувань Next.js.
Після мутацій через Route Handler або Server Action кеш потрібно інвалідувати явно, якщо сторінка використовує кешовані дані.
Для простих дій усередині одного Next.js-застосунку зазвичай зручніший Server Action.
Для універсального API, інтеграцій і зовнішніх клієнтів зазвичай потрібен Route Handler.