Формы в Next.js 15

Модуль 4: Node.js, Next.js и Angular
10 уровень , 2 лекция
Открыта

1. Server Actions и формы

В мире фронтенда формы — это как хлеб и масло: без них не обходится ни один сайт, где есть хоть какая-то интерактивность. Раньше (и до сих пор в большинстве проектов) отправка формы означала или "перезагрузку" страницы, или вызов fetch/axios и возню с обработкой состояния, ошибок, загрузки и так далее.

Next.js 15 с App Router и Server Actions предлагает совершенно новый опыт: теперь можно отправлять форму на сервер, буквально передав функцию (Server Action) как обработчик, и получать результат обратно — без ручного fetch, без возни с REST, и с полной интеграцией в реактивную модель Next.js.

Это не просто "ещё один способ", а целая философия: теперь сервер и клиент работают как единое целое, а форма становится не просто HTML-тегом, а настоящим мостом между пользователем и серверной логикой.

Вспоминаем Server Action

Server Action — это асинхронная функция, объявленная с директивой 'use server', которую можно вызывать прямо из формы. Она выполняется на сервере, имеет полный доступ ко всем серверным возможностям (БД, секреты, файловая система), и возвращает результат обратно в компонент.


// app/actions.js
'use server';

export async function addTodo(formData) {
  // Здесь можно обращаться к базе, отправлять письма и т.д.
  const title = formData.get('title');
  // ...добавить todo в БД
}

Как связать форму и Server Action?

Всё просто: передаём функцию в атрибут action у тега <form>:


<form action={addTodo}>
  <input name="title" />
  <button type="submit">Добавить</button>
</form>

Теперь при отправке формы Next.js сам сериализует данные, отправит их на сервер, вызовет вашу функцию, и обновит компонент.

Внимание: Это работает только в Server/Client Components в папке app/, и требует Next.js 15.

2. Минимальный пример: форма добавления задачи

Давайте попробуем реализовать простейшую форму для добавления задачи в список дел.

Шаг 1. Создаём Server Action


// app/actions.js
'use server';

export async function addTodo(formData) {
  const title = formData.get('title');
  // Здесь могла бы быть запись в базу данных
  console.log('Добавлена задача:', title);
  // Можно вернуть что-то, если нужно
}

Шаг 2. Используем Server Action в форме


// app/page.jsx
import { addTodo } from './actions';

export default function HomePage() {
  return (
    <main>
      <h1>Список задач</h1>
      <form action={addTodo}>
        <input name="title" placeholder="Новая задача" required />
        <button type="submit">Добавить</button>
      </form>
    </main>
  );
}

Что происходит?
— При отправке формы данные попадут в функцию addTodo на сервере.
— Форма не перезагрузит страницу (если это не Client Component).
— После выполнения Server Action компонент перерисуется (если есть связанные данные).

3. Как работает formData и что можно передавать

В Server Action первым аргументом всегда приходит объект FormData, который содержит все поля формы.

  • formData.get('имя_поля') — получить значение поля (строка или файл).
  • Можно использовать несколько полей, чекбоксы, select, file input и т.д.

Пример:


export async function handleForm(formData) {
  const name = formData.get('name');
  const email = formData.get('email');
  // ...работаем с этими данными
}

4. Пример с несколькими полями

Давайте добавим больше полей: имя пользователя и email.


// app/actions.js
'use server';

export async function registerUser(formData) {
  const name = formData.get('name');
  const email = formData.get('email');
  // Здесь могла бы быть регистрация пользователя
  console.log('Регистрация:', name, email);
}

// app/register/page.jsx
import { registerUser } from '../actions';

export default function RegisterPage() {
  return (
    <form action={registerUser}>
      <input name="name" placeholder="Имя" required />
      <input name="email" type="email" placeholder="Email" required />
      <button>Зарегистрироваться</button>
    </form>
  );
}

5. Как обновлять список после отправки формы

В реальных приложениях после отправки формы нужно обновить список задач, пользователей и т.д. В Next.js 15 App Router это делается автоматически, если вы используете Server Components и загружаете данные на сервере.

Пример: список задач


// app/actions.js
'use server';

let todos = [];

export async function addTodo(formData) {
  const title = formData.get('title');
  todos.push({ title });
}

// app/page.jsx
import { addTodo } from './actions';

async function getTodos() {
  // В реальном мире — запрос к БД
  return todos;
}

export default async function HomePage() {
  const todos = await getTodos();

  return (
    <main>
      <h1>Список задач</h1>
      <form action={addTodo}>
        <input name="title" placeholder="Новая задача" required />
        <button type="submit">Добавить</button>
      </form>
      <ul>
        {todos.map((todo, idx) => (
          <li key={idx}>{todo.title}</li>
        ))}
      </ul>
    </main>
  );
}

Важно:
— После отправки формы страница перерисуется (SSR), и список обновится.
— Если используете базу данных, данные будут актуальны.

6. Полезные нюансы

Как обработать ошибки и показать пользователю результат

Server Action может выбросить ошибку или вернуть значение. Для обработки ошибок и состояния формы рекомендуется использовать хуки useFormState и useFormStatus (см. следующую лекцию), но базовую обработку можно сделать так:


// app/actions.js
'use server';

export async function addTodo(formData) {
  const title = formData.get('title');
  if (!title || title.length < 3) {
    throw new Error('Название задачи слишком короткое');
  }
  todos.push({ title });
}

В компоненте можно отобразить ошибку через отдельный стейт или с помощью useFormState (подробнее об этом — в следующей лекции).

Особенности и ограничения

  • Server Action можно использовать только в App Router (папка app/), не работает в pages/.
  • Форма должна быть обычной HTML-формой, не кастомной обработкой через onSubmit.
  • Можно отправлять файлы! Просто используйте <input type="file" name="avatar" />, а на сервере получите файл через formData.
  • Если нужна интерактивность до отправки (валидация, динамические поля), используйте Client Component или гибридный подход.
  • Server Action нельзя передавать в props обычным компонентам — только в action формы.

Форма с загрузкой файла (аватар)


// app/actions.js
'use server';

export async function uploadAvatar(formData) {
  const file = formData.get('avatar');
  if (!file) throw new Error('Файл не выбран');
  // Здесь file — это Blob (или File)
  // Можно сохранить файл на диск, в облако и т.д.
  console.log('Загружен файл:', file.name, file.size);
}

// app/profile/page.jsx
import { uploadAvatar } from '../actions';

export default function ProfilePage() {
  return (
    <form action={uploadAvatar} encType="multipart/form-data">
      <input type="file" name="avatar" accept="image/*" />
      <button>Загрузить аватар</button>
    </form>
  );
}

Как работает обновление страницы и SSR

  • Когда вы отправляете форму с Server Action:
    • Next.js выполняет функцию на сервере.
    • После завершения действия происходит "перезапрос" страницы (SSR), и компонент получает свежие данные.
    • Это похоже на старый добрый POST-Redirect-GET, но без ручных редиректов и сложностей.
    • Если форма находится внутри Client Component, состояние можно контролировать вручную.

Советы и best practices

  • Используйте Server Actions для логики, связанной с сервером: работа с БД, валидация, отправка писем.
  • Давайте полям формы осмысленные имена: чтобы на сервере легко получать значения через formData.get('name').
  • Если нужна обратная связь пользователю (ошибка, успех) — используйте useFormState или Client Component.
  • Не храните важные данные в глобальных переменных на сервере (как в наших примерах с массивом): используйте БД!
  • Для сложных форм можно комбинировать Server Actions с Client Components.

7. Типичные ошибки при работе с формами и Server Actions

Ошибка №1: Не указываете атрибут name у input.
Если у поля формы нет атрибута name, оно не попадёт в formData. Проверьте, что у каждого input есть name!

Ошибка №2: Пытаетесь передать Server Action в onSubmit.
Server Action работает только через action формы: <form action={serverAction}>, а не через <form onSubmit={serverAction}>.

Ошибка №3: Используете Server Action вне папки app/
Server Actions поддерживаются только в App Router (директория app/), а не в старом pages/.

Ошибка №4: Ожидаете, что состояние формы обновится автоматически в Client Component.
Если компонент клиентский, обновление состояния после Server Action нужно реализовать вручную.

Ошибка №5: Не обрабатываете ошибки на сервере.
Если Server Action выбрасывает ошибку, пользователь увидит "ничего" или дефолтную ошибку. Используйте useFormState или возвращайте статус.

Ошибка №6: Пытаетесь получить поля формы через event.target.value.
В Server Action нет доступа к event — только к formData, который приходит первым аргументом.

Ошибка №7: Забыли добавить encType="multipart/form-data" для загрузки файлов.
Без этого браузер не отправит файл корректно.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ