Русский

Полное руководство по Server Actions в Next.js 14, охватывающее лучшие практики обработки форм, валидацию данных, вопросы безопасности и продвинутые методы для создания современных веб-приложений.

Серверные действия (Server Actions) в Next.js 14: освоение лучших практик обработки форм

Next.js 14 представляет мощные функции для создания производительных и удобных веб-приложений. Среди них выделяются серверные действия (Server Actions) как преобразующий способ обработки отправки форм и мутаций данных непосредственно на сервере. Это руководство представляет собой всеобъемлющий обзор Server Actions в Next.js 14, с акцентом на лучшие практики обработки форм, валидации данных, безопасности и продвинутых техниках. Мы рассмотрим практические примеры и предоставим действенные советы, которые помогут вам создавать надежные и масштабируемые веб-приложения.

Что такое серверные действия (Server Actions) в Next.js?

Серверные действия (Server Actions) — это асинхронные функции, которые выполняются на сервере и могут быть вызваны непосредственно из компонентов React. Они устраняют необходимость в традиционных API-маршрутах для обработки отправки форм и мутаций данных, что приводит к упрощению кода, повышению безопасности и улучшению производительности. Server Actions являются серверными компонентами React (RSC), что означает, что они выполняются на сервере, обеспечивая более быструю начальную загрузку страниц и улучшенное SEO.

Ключевые преимущества серверных действий:

Настройка вашего проекта Next.js 14

Прежде чем погрузиться в Server Actions, убедитесь, что у вас настроен проект 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 (
    
); }

Объяснение:

Валидация данных

Валидация данных имеет решающее значение для обеспечения целостности данных и предотвращения уязвимостей безопасности. Серверные действия предоставляют отличную возможность для выполнения валидации на стороне сервера. Этот подход помогает снизить риски, связанные только с валидацией на стороне клиента.

Пример: Валидация входных данных

Измените серверное действие 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}

}
); }

Объяснение:

Использование библиотек валидации

Для более сложных сценариев валидации рассмотрите использование библиотек валидации, таких как:

Вот пример с использованием 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}

}
); }

Объяснение:

Вопросы безопасности

Серверные действия повышают безопасность, выполняя код на сервере, но все же крайне важно следовать лучшим практикам безопасности для защиты вашего приложения от распространенных угроз.

Предотвращение подделки межсайтовых запросов (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}

}
); }

Объяснение:

Санитизация входных данных

Санитизируйте входные данные, чтобы предотвратить атаки межсайтового скриптинга (XSS). XSS-атаки происходят, когда вредоносный код внедряется на веб-сайт, что потенциально может скомпрометировать данные пользователя или функциональность приложения.

Используйте библиотеки, такие как DOMPurify или sanitize-html, для санитизации пользовательского ввода перед его обработкой в ваших серверных действиях.

Продвинутые техники

Теперь, когда мы рассмотрели основы, давайте изучим некоторые продвинутые техники для эффективного использования серверных действий.

Оптимистичные обновления

Оптимистичные обновления улучшают пользовательский опыт, немедленно обновляя UI так, как будто действие будет успешным, еще до того, как сервер это подтвердит. Если действие на сервере завершается неудачей, UI возвращается в предыдущее состояние.

// 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);

    // Оптимистично обновляем UI
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      // В случае успеха обновление уже отражено в UI через setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'Произошла ошибка.');
      // Возвращаем UI в исходное состояние при ошибке
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Текущее имя: {itemName}

{errorMessage &&

{errorMessage}

}
); }

Объяснение:

Ревалидация данных

После того как серверное действие изменяет данные, вам может потребоваться ревалидировать кэшированные данные, чтобы убедиться, что UI отражает последние изменения. Next.js предоставляет несколько способов ревалидации данных:

Вот пример ревалидации пути после создания нового элемента:

// 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}

}
); }

Объяснение:

Лучшие практики для серверных действий

Чтобы максимизировать преимущества серверных действий, придерживайтесь следующих лучших практик:

Распространенные ошибки и как их избежать

Хотя серверные действия предлагают множество преимуществ, следует остерегаться некоторых распространенных ошибок:

Заключение

Серверные действия (Server Actions) в Next.js 14 предоставляют мощный и эффективный способ обработки отправки форм и мутаций данных непосредственно на сервере. Следуя лучшим практикам, изложенным в этом руководстве, вы сможете создавать надежные, безопасные и производительные веб-приложения. Используйте серверные действия для упрощения кода, повышения безопасности и улучшения общего пользовательского опыта. Интегрируя эти принципы, учитывайте глобальное влияние ваших решений в разработке. Убедитесь, что ваши формы и процессы обработки данных доступны, безопасны и удобны для разнообразной международной аудитории. Эта приверженность инклюзивности не только улучшит удобство использования вашего приложения, но и расширит его охват и эффективность в глобальном масштабе.