Server Actions в Next.js
Server Actions это асинхронные функции, которые выполняются на сервере. Они позволяют мутировать данные (создавать, обновлять, удалять) напрямую из компонентов без создания отдельных API-эндпоинтов.
Как определить Server Action
Server Action это функция, помеченная директивой 'use server':
// actions/problem.ts
'use server'
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
export async function solveProblem(problemId: string, userId: string) {
await db.user.update({
where: { id: userId },
data: {
solvedProblems: { push: problemId }
}
})
revalidatePath('/problems')
}
Директиву можно указать на уровне файла (как выше) или на уровне отдельной функции:
export default async function Page() {
async function handleSubmit(formData: FormData) {
'use server'
const name = formData.get('name')
await db.user.update({ where: { id: userId }, data: { name } })
}
return <form action={handleSubmit}>...</form>
}
Использование с формами
Server Actions можно передать напрямую в action формы. Форма работает даже без JavaScript на клиенте (progressive enhancement):
// app/settings/page.tsx
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
export default async function SettingsPage() {
async function updateProfile(formData: FormData) {
'use server'
const name = formData.get('name') as string
await db.user.update({
where: { id: currentUserId },
data: { name }
})
revalidatePath('/settings')
}
return (
<form action={updateProfile}>
<input name="name" placeholder="Имя на Hack Frontend" />
<button type="submit">Сохранить</button>
</form>
)
}
Использование с useTransition
Для оптимистичных обновлений и индикации загрузки:
'use client'
import { useTransition } from 'react'
import { solveProblem } from '@/actions/problem'
export function SolveButton({
problemId,
userId
}: {
problemId: string
userId: string
}) {
const [isPending, startTransition] = useTransition()
return (
<button
disabled={isPending}
onClick={() => {
startTransition(() => solveProblem(problemId, userId))
}}
>
{isPending ? 'Проверка...' : 'Решил'}
</button>
)
}
Валидация данных
Server Actions получают пользовательский ввод, поэтому валидация обязательна:
'use server'
import { z } from 'zod'
const schema = z.object({
name: z.string().min(2).max(50),
email: z.string().email()
})
export async function updateUser(formData: FormData) {
const result = schema.safeParse({
name: formData.get('name'),
email: formData.get('email')
})
if (!result.success) {
return { error: result.error.flatten().fieldErrors }
}
await db.user.update({
where: { id: currentUserId },
data: result.data
})
revalidatePath('/settings')
return { success: true }
}
Ревалидация после мутации
После изменения данных нужно обновить кеш:
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createProblem(data: ProblemData) {
await db.problem.create({ data })
revalidateTag('problems') // инвалидирует Data Cache по тегу
revalidatePath('/problems') // инвалидирует Full Route Cache
redirect('/problems') // перенаправляет после создания
}
На собеседовании:
Server Actions не замена REST API. Они предназначены для мутаций данных из UI. Для публичного API, интеграций с другими сервисами или webhook'ов используй Route Handlers.
Полезные ресурсы
- nextjs.org/docs — Server Actions
- Forms and Mutations