Загрузка…
Загрузка…
Server Action — 'use server' функция, вызываемая из формы; auth и валидация на сервере, после мутации — revalidateTag. На клиенте — useActionState и useFormStatus без useEffect.
Мутация — обычная серверная функция с явной авторизацией и Zod-валидацией. formData — attacker-controlled. После записи в БД — инвалидация кэша, который использует read-path.
// actions.ts — a mutation as a plain server function
'use server';
import { revalidateTag } from 'next/cache';
import { auth } from '@/lib/auth';
import { z } from 'zod';
const Schema = z.object({ title: z.string().min(1).max(200) });
export async function createTodo(_prev: unknown, formData: FormData) {
const session = await auth(); // ← authZ is YOURS. Do it first.
if (!session) return { error: 'Unauthorized' };
const parsed = Schema.safeParse({ title: formData.get('title') });
if (!parsed.success) return { error: 'Invalid input' }; // never trust the client
await db.todo.create({ data: { ...parsed.data, userId: session.userId } });
revalidateTag('todos'); // purge the cache the read uses
return { ok: true };
}// The form: progressive enhancement + pending + result state, no useEffect.
'use client';
import { useActionState } from 'react'; // React 19 (was useFormState)
import { useFormStatus } from 'react-dom';
import { createTodo } from './actions';
function Submit() {
const { pending } = useFormStatus(); // must be INSIDE the
return {pending ? 'Saving…' : 'Add'};
}
export function TodoForm() {
const [state, action] = useActionState(createTodo, null);
return (
{state?.error && {state.error}}
);
}Server Actions = серверные мутации с progressive enhancement; без auth, validate и revalidate UI «не обновится» и endpoint останется открытым.
