Вичерпний посібник із серверних дій в Next.js 14, що охоплює найкращі практики обробки форм, валідацію даних, аспекти безпеки та передові техніки для створення сучасних вебдодатків.
Серверні дії в Next.js 14: Опанування найкращих практик обробки форм
Next.js 14 представляє потужні функції для створення продуктивних та зручних для користувача вебдодатків. Серед них Серверні дії (Server Actions) виділяються як трансформаційний спосіб обробки надсилань форм та мутацій даних безпосередньо на сервері. Цей посібник надає вичерпний огляд Серверних дій у Next.js 14, зосереджуючись на найкращих практиках обробки форм, валідації даних, безпеки та передових техніках. Ми розглянемо практичні приклади та надамо дієві поради, які допоможуть вам створювати надійні та масштабовані вебдодатки.
Що таке Серверні дії в Next.js?
Серверні дії — це асинхронні функції, які виконуються на сервері та можуть викликатися безпосередньо з компонентів React. Вони усувають потребу в традиційних API-маршрутах для обробки надсилань форм та мутацій даних, що призводить до спрощення коду, покращення безпеки та підвищення продуктивності. Серверні дії є Серверними компонентами React (RSC), що означає, що вони виконуються на сервері, забезпечуючи швидше початкове завантаження сторінки та покращене SEO.
Ключові переваги Серверних дій:
- Спрощений код: Зменшення шаблонного коду завдяки усуненню потреби в окремих API-маршрутах.
- Покращена безпека: Виконання на стороні сервера мінімізує вразливості на стороні клієнта.
- Підвищена продуктивність: Виконуйте мутації даних безпосередньо на сервері для швидшого часу відповіді.
- Оптимізоване SEO: Використовуйте рендеринг на стороні сервера для кращої індексації пошуковими системами.
- Типова безпека: Отримайте переваги наскрізної типової безпеки з TypeScript.
Налаштування вашого проєкту Next.js 14
Перш ніж занурюватися в Серверні дії, переконайтеся, що у вас налаштовано проєкт Next.js 14. Якщо ви починаєте з нуля, створіть новий проєкт за допомогою наступної команди:
npx create-next-app@latest my-next-app
Переконайтеся, що ваш проєкт використовує структуру директорій app
, щоб повною мірою скористатися перевагами Серверних компонентів та дій.
Базова обробка форм за допомогою Серверних дій
Почнемо з простого прикладу: форма, яка надсилає дані для створення нового елемента в базі даних. Ми будемо використовувати просту форму з полем вводу та кнопкою надсилання.
Приклад: Створення нового елемента
Спочатку визначте функцію Серверної дії у вашому компоненті React. Ця функція буде обробляти логіку надсилання форми на сервері.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Симуляція взаємодії з базою даних
console.log('Створення елемента:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Симуляція затримки
console.log('Елемент успішно створено!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
await createItem(formData);
setIsSubmitting(false);
}
return (
);
}
Пояснення:
- Директива
'use client'
вказує, що це клієнтський компонент. - Функція
createItem
позначена директивою'use server'
, що вказує на те, що це Серверна дія. - Функція
handleSubmit
— це функція на стороні клієнта, яка викликає серверну дію. Вона також обробляє стан інтерфейсу, наприклад, деактивує кнопку під час надсилання. - Властивість
action
елемента<form>
встановлена на функціюhandleSubmit
. - Метод
formData.get('name')
отримує значення поля вводу 'name'. await new Promise
симулює операцію з базою даних і додає затримку.
Валідація даних
Валідація даних є надзвичайно важливою для забезпечення цілісності даних та запобігання вразливостям безпеки. Серверні дії надають чудову можливість для виконання валідації на стороні сервера. Цей підхід допомагає зменшити ризики, пов'язані лише з валідацією на стороні клієнта.
Приклад: Валідація вхідних даних
Змініть Серверну дію createItem
, щоб додати логіку валідації.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
if (!name || name.length < 3) {
throw new Error('Назва елемента повинна містити щонайменше 3 символи.');
}
// Симуляція взаємодії з базою даних
console.log('Створення елемента:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Симуляція затримки
console.log('Елемент успішно створено!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Сталася помилка.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Пояснення:
- Функція
createItem
тепер перевіряє, чи єname
валідним (довжина щонайменше 3 символи). - Якщо валідація не вдається, викидається помилка.
- Функція
handleSubmit
оновлена для перехоплення будь-яких помилок, що викидаються Серверною дією, та відображення повідомлення про помилку користувачеві.
Використання бібліотек для валідації
Для складніших сценаріїв валідації розгляньте можливість використання таких бібліотек, як:
- Zod: Бібліотека для оголошення та валідації схем, орієнтована на TypeScript.
- Yup: Конструктор схем на JavaScript для розбору, валідації та перетворення значень.
Ось приклад використання Zod:
// app/utils/validation.ts
import { z } from 'zod';
export const CreateItemSchema = z.object({
name: z.string().min(3, 'Назва елемента повинна містити щонайменше 3 символи.'),
});
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
const validatedFields = CreateItemSchema.safeParse({ name });
if (!validatedFields.success) {
return { errors: validatedFields.error.flatten().fieldErrors };
}
// Симуляція взаємодії з базою даних
console.log('Створення елемента:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Симуляція затримки
console.log('Елемент успішно створено!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Сталася помилка.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Пояснення:
CreateItemSchema
визначає правила валідації для поляname
за допомогою Zod.- Метод
safeParse
намагається валідувати вхідні дані. Якщо валідація не вдається, він повертає об'єкт з помилками. - Об'єкт
errors
містить детальну інформацію про невдалі перевірки.
Аспекти безпеки
Серверні дії підвищують безпеку, виконуючи код на сервері, але все одно важливо дотримуватися найкращих практик безпеки для захисту вашого додатку від поширених загроз.
Запобігання міжсайтовій підробці запитів (CSRF)
CSRF-атаки використовують довіру, яку вебсайт має до браузера користувача. Щоб запобігти CSRF-атакам, впроваджуйте механізми захисту від CSRF.
Next.js автоматично обробляє захист від CSRF при використанні Серверних дій. Фреймворк генерує та перевіряє CSRF-токен для кожного надсилання форми, забезпечуючи, що запит походить саме з вашого додатку.
Обробка автентифікації та авторизації користувачів
Переконайтеся, що лише авторизовані користувачі можуть виконувати певні дії. Впроваджуйте механізми автентифікації та авторизації для захисту конфіденційних даних та функціональності.
Ось приклад використання NextAuth.js для захисту Серверної дії:
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';
async function createItem(formData: FormData) {
'use server'
const session = await getServerSession(authOptions);
if (!session) {
throw new Error('Неавторизований');
}
const name = formData.get('name') as string;
// Симуляція взаємодії з базою даних
console.log('Створення елемента:', name, 'користувачем:', session.user?.email);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Симуляція затримки
console.log('Елемент успішно створено!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Сталася помилка.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Пояснення:
- Функція
getServerSession
отримує інформацію про сесію користувача. - Якщо користувач не автентифікований (немає сесії), викидається помилка, що запобігає виконанню Серверної дії.
Санітизація вхідних даних
Санітизуйте вхідні дані, щоб запобігти атакам міжсайтового скриптингу (XSS). XSS-атаки відбуваються, коли шкідливий код впроваджується на вебсайт, потенційно компрометуючи дані користувачів або функціональність додатку.
Використовуйте бібліотеки, такі як DOMPurify
або sanitize-html
, для санітизації даних, наданих користувачем, перед їх обробкою у ваших Серверних діях.
Передові техніки
Тепер, коли ми розглянули основи, давайте дослідимо деякі передові техніки для ефективного використання Серверних дій.
Оптимістичні оновлення
Оптимістичні оновлення забезпечують кращий досвід користувача, негайно оновлюючи інтерфейс так, ніби дія буде успішною, ще до того, як сервер це підтвердить. Якщо дія на сервері не вдається, інтерфейс повертається до свого попереднього стану.
// app/components/UpdateItemForm.tsx
'use client';
import { useState } from 'react';
async function updateItem(id: string, formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Симуляція взаємодії з базою даних
console.log('Оновлення елемента:', id, 'з назвою:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Симуляція затримки
// Симуляція невдачі (для демонстраційних цілей)
const shouldFail = Math.random() < 0.5;
if (shouldFail) {
throw new Error('Не вдалося оновити елемент.');
}
console.log('Елемент успішно оновлено!');
return { name }; // Повернути оновлену назву
}
export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [itemName, setItemName] = useState(initialName);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
// Оптимістично оновлюємо інтерфейс
const newName = formData.get('name') as string;
setItemName(newName);
try {
const result = await updateItem(id, formData);
//Якщо успішно, оновлення вже відображено в інтерфейсі через setItemName
} catch (error: any) {
setErrorMessage(error.message || 'Сталася помилка.');
// Повертаємо інтерфейс до початкового стану при помилці
setItemName(initialName);
} finally {
setIsSubmitting(false);
}
}
return (
Поточна назва: {itemName}
{errorMessage && {errorMessage}
}
);
}
Пояснення:
- Перед викликом Серверної дії інтерфейс негайно оновлюється новою назвою елемента за допомогою
setItemName
. - Якщо Серверна дія не вдається, інтерфейс повертається до початкової назви елемента.
Ревалідація даних
Після того, як Серверна дія змінює дані, вам може знадобитися ревалідувати кешовані дані, щоб інтерфейс відображав останні зміни. Next.js надає кілька способів ревалідації даних:
- Ревалідація шляху (Revalidate Path): Ревалідувати кеш для певного шляху.
- Ревалідація тегу (Revalidate Tag): Ревалідувати кеш для даних, пов'язаних з певним тегом.
Ось приклад ревалідації шляху після створення нового елемента:
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { revalidatePath } from 'next/cache';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Симуляція взаємодії з базою даних
console.log('Створення елемента:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Симуляція затримки
console.log('Елемент успішно створено!');
revalidatePath('/items'); // Ревалідувати шлях /items
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Сталася помилка.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Пояснення:
- Функція
revalidatePath('/items')
робить недійсним кеш для шляху/items
, забезпечуючи, що наступний запит до цього шляху отримає найсвіжіші дані.
Найкращі практики для Серверних дій
Щоб максимізувати переваги Серверних дій, дотримуйтесь таких найкращих практик:
- Робіть Серверні дії маленькими та сфокусованими: Серверні дії повинні виконувати одне, чітко визначене завдання. Уникайте складної логіки всередині Серверних дій для підтримки читабельності та можливості тестування.
- Використовуйте описові назви: Давайте вашим Серверним діям описові назви, які чітко вказують на їхнє призначення.
- Обробляйте помилки коректно: Впроваджуйте надійну обробку помилок, щоб надавати інформативний зворотний зв'язок користувачеві та запобігати збоям додатку.
- Ретельно валідуйте дані: Виконуйте комплексну валідацію даних для забезпечення їх цілісності та запобігання вразливостям безпеки.
- Захищайте ваші Серверні дії: Впроваджуйте механізми автентифікації та авторизації для захисту конфіденційних даних та функціональності.
- Оптимізуйте продуктивність: Моніторте продуктивність ваших Серверних дій та оптимізуйте їх за потреби для забезпечення швидкого часу відповіді.
- Ефективно використовуйте кешування: Використовуйте механізми кешування Next.js для покращення продуктивності та зменшення навантаження на базу даних.
Поширені помилки та як їх уникнути
Хоча Серверні дії пропонують численні переваги, є деякі поширені помилки, про які варто знати:
- Надто складні Серверні дії: Уникайте розміщення занадто великої кількості логіки в одній Серверній дії. Розбивайте складні завдання на менші, більш керовані функції.
- Нехтування обробкою помилок: Завжди включайте обробку помилок для перехоплення несподіваних помилок та надання корисного зворотного зв'язку користувачеві.
- Ігнорування найкращих практик безпеки: Дотримуйтесь найкращих практик безпеки для захисту вашого додатку від поширених загроз, таких як XSS та CSRF.
- Забування про ревалідацію даних: Переконайтеся, що ви ревалідуєте кешовані дані після того, як Серверна дія змінює дані, щоб підтримувати інтерфейс в актуальному стані.
Висновок
Серверні дії в Next.js 14 надають потужний та ефективний спосіб обробки надсилань форм та мутацій даних безпосередньо на сервері. Дотримуючись найкращих практик, викладених у цьому посібнику, ви зможете створювати надійні, безпечні та продуктивні вебдодатки. Використовуйте Серверні дії, щоб спростити свій код, підвищити безпеку та покращити загальний досвід користувача. Інтегруючи ці принципи, враховуйте глобальний вплив ваших рішень у розробці. Переконайтеся, що ваші форми та процеси обробки даних є доступними, безпечними та зручними для різноманітних міжнародних аудиторій. Ця прихильність до інклюзивності не лише покращить юзабіліті вашого додатку, але й розширить його охоплення та ефективність у глобальному масштабі.