Пошук уроків, статей та іншого контенту
Навчитеся позначати функції директивою "use server" і визначати, де саме вони можуть виконуватися.
"use server""use server" — це спеціальна директива Next.js, яка позначає функцію як серверну.
Серверна функція:
виконується на сервері, а не в браузері;
може містити код, який не потрібно надсилати клієнту;
може використовувати змінні середовища та серверні ресурси;
може викликатися з клієнтського інтерфейсу через спеціальний механізм Next.js.
Такі функції часто називають Server Actions, особливо коли їх використовують для оброблення форм або зміни даних.
Директива має бути першим оператором у файлі або першим оператором у тілі функції:
"use server";Це не звичайний виклик функції та не імпорт. Next.js розпізнає цю директиву під час збирання застосунку.
Якщо додати "use server" на початок файлу, усі експортовані з цього файлу функції вважатимуться серверними функціями.
app/actions.js"use server";
export async function saveMessage(formData) {
const message = formData.get("message");
if (!message || typeof message !== "string") {
throw new Error("Повідомлення не може бути порожнім");
}
// Тут код виконується на сервері
console.log("Отримано повідомлення:", message);
return {
success: true,
message: "Повідомлення збережено",
};
}У цьому прикладі:
"use server" застосовується до всього файлу;
saveMessage є серверною функцією;
функція не виконується безпосередньо в браузері;
аргументом є об’єкт FormData, який Next.js передає під час відправлення форми.
Серверну функцію можна передати атрибуту action HTML-форми.
app/page.jsximport { saveMessage } from "./actions";
export default function Page() {
return (
<main>
<h1>Надсилання повідомлення</h1>
<form action={saveMessage}>
<label htmlFor="message">Повідомлення</label>
<input
id="message"
name="message"
type="text"
placeholder="Введіть повідомлення"
required
/>
<button type="submit">Зберегти</button>
</form>
</main>
);
}Після надсилання форми Next.js:
збере значення полів форми в FormData;
викличе saveMessage на сервері;
передасть FormData до функції;
обробить результат відповідно до логіки застосунку.
Поле форми повинно мати атрибут name, інакше його значення не потрапить до FormData.
<input name="message" />Отримати значення можна так:
const message = formData.get("message");"use server" можна додати не лише на рівні файлу, а й усередині конкретної асинхронної функції.
export default function Page() {
async function createMessage(formData) {
"use server";
const message = formData.get("message");
console.log("Повідомлення:", message);
}
return (
<form action={createMessage}>
<input name="message" required />
<button type="submit">Надіслати</button>
</form>
);
}У цьому випадку серверною є лише функція createMessage.
Директива повинна бути на початку тіла функції:
async function createMessage() {
"use server";
// Серверний код
}Між початком функції та директивою не повинно бути іншого коду.
Розгляньмо такий приклад:
"use server";
export async function getServerValue() {
const value = process.env.SECRET_VALUE;
return value;
}Код функції виконується на сервері. Тому серверна функція може звертатися до серверних ресурсів, наприклад до змінних середовища або бази даних.
Її результат може бути переданий до іншого коду, але сама реалізація функції не стає звичайним клієнтським JavaScript-кодом.
Важливо розрізняти:
місце виклику — наприклад, форма у браузері;
місце виконання — сервер.
Користувач натискає кнопку в браузері, але тіло серверної функції виконується на сервері.
Клієнтський компонент можна позначити директивою "use client" і використати серверну функцію як action.
app/actions.js"use server";
export async function addTask(formData) {
const title = formData.get("title");
if (!title || typeof title !== "string") {
throw new Error("Назва завдання є обов'язковою");
}
// Тут можна зберегти завдання в базі даних
console.log("Нове завдання:", title);
}app/TaskForm.jsx"use client";
import { addTask } from "./actions";
export default function TaskForm() {
return (
<form action={addTask}>
<input
name="title"
type="text"
placeholder="Назва завдання"
required
/>
<button type="submit">Додати завдання</button>
</form>
);
}Файл із серверними функціями має містити "use server" на початку. Клієнтський компонент може імпортувати експортовану серверну функцію та передавати її до action.
Не потрібно додавати "use client" до файлу з серверною функцією.
Параметри та результат серверної функції повинні бути даними, які Next.js може передати між браузером і сервером.
Для форм найчастіше використовується FormData:
"use server";
export async function submitForm(formData) {
const name = formData.get("name");
const email = formData.get("email");
return {
name,
email,
};
}У серверну функцію також можна передавати прості значення з клієнтського компонента:
"use client";
import { updateName } from "./actions";
export default function NameButton() {
return (
<button onClick={() => updateName("Олена")}>
Оновити ім'я
</button>
);
}"use server";
export async function updateName(name) {
if (typeof name !== "string") {
throw new Error("Ім'я має бути рядком");
}
console.log("Нове ім'я:", name);
}Для початку варто використовувати рядки, числа, логічні значення, масиви, прості об’єкти та FormData. Не слід передавати функції або довільні об’єкти, які неможливо серіалізувати.
Якщо функція позначена "use server", її не слід розглядати як звичайну функцію, яка просто виконується в тому самому файлі.
Під час виклику Next.js може виконати мережевий запит до сервера. Через це:
виклик може бути асинхронним;
функція може завершитися з помилкою;
результат потрібно обробляти як результат серверної операції;
не слід передавати їй великі або складні об’єкти без потреби.
Для серверних функцій зазвичай використовують async:
"use server";
export async function saveData(data) {
// Серверна операція
return { success: true };
}"use server" визначає, де виконується код, але не замінює перевірку прав доступу.
Серверну функцію потрібно захищати так само, як будь-який серверний обробник:
перевіряти вхідні дані;
перевіряти, чи має користувач право виконувати операцію;
не довіряти значенням із форми;
не повертати клієнту секретні дані.
"use server";
export async function deleteTask(formData) {
const taskId = formData.get("taskId");
if (!taskId || typeof taskId !== "string") {
throw new Error("Некоректний ідентифікатор завдання");
}
// Тут також потрібно перевірити, чи має поточний користувач
// право видаляти це завдання.
console.log("Видалення завдання:", taskId);
}Серверне виконання приховує реалізацію від браузера, але дані, які надходять від користувача, все одно можуть бути змінені. Тому перевірка потрібна всередині серверної функції.
Неправильно:
const value = 1;
"use server";Правильно:
"use server";
const value = 1;Не можна довільно визначати серверну функцію в компоненті, позначеному "use client":
"use client";
async function saveData() {
"use server";
// Такий підхід не використовується для серверного модуля клієнтського компонента
}Винесіть функцію в окремий файл із "use server" на початку:
"use server";
export async function saveData() {
// Серверний код
}exportЯкщо серверна функція зберігається в окремому файлі та імпортується в інший компонент, її потрібно експортувати:
"use server";
export async function saveData() {
// Серверний код
}nameНеправильно:
<input type="text" />Правильно:
<input name="title" type="text" />Без name значення поля не буде доступним через formData.get("title").
Директива не перевіряє автентифікацію та права доступу. Ці перевірки потрібно реалізувати самостійно всередині функції.
"use server" позначає код як серверний.
Директива може бути на початку файлу або на початку тіла окремої функції.
Функції з файлу "use server" потрібно експортувати, якщо їх викликають з іншого компонента.
Серверна функція виконується на сервері, навіть якщо її виклик ініціює форма в браузері.
Для форм серверну функцію можна передати до атрибуту action.
Значення форми надходять у вигляді FormData.
Вхідні дані та права доступу потрібно перевіряти всередині серверної функції.