6月2日 23:10
Next.js Server Actions 怎么用?表单提交、状态管理和安全验证
Server Actions 是 Next.js 的服务端函数——在服务端执行,客户端直接调用,不需要手写 API 路由。底层是 POST 请求 + 加密签名,比传统 fetch + API Route 更简洁。
基本用法
tsx// app/actions.ts 'use server' export async function createPost(formData: FormData) { const title = formData.get('title') as string; const content = formData.get('content') as string; await db.post.create({ data: { title, content } }); revalidatePath('/posts'); // 刷新缓存 }
tsx// app/posts/page.tsx import { createPost } from './actions'; export default function NewPost() { return ( <form action={createPost}> <input name="title" /> <textarea name="content" /> <button type="submit">发布</button> </form> ); }
没有 API 路由,没有 fetch,没有 JSON 序列化。表单提交直接触发服务端函数。
Server Action 的本质
Server Action 编译后变成一个 POST 请求:
shellPOST /_next/data/... HTTP/1.1 Content-Type: text/x-component Next-Action: hashed-action-id FormData body
Next.js 自动给 action 生成加密 ID,客户端调用时带这个 ID。请求到达服务端后,Next.js 根据 ID 找到对应函数执行。所以即使有人猜到 URL,没有正确的 action ID 也无法调用。
useActionState 管理状态
tsx'use client' import { useActionState } from 'react'; import { createPost } from './actions'; export default function NewPost() { const [state, formAction, isPending] = useActionState( async (prevState, formData) => { const result = await createPost(formData); return result; }, null ); return ( <form action={formAction}> <input name="title" /> <button type="submit" disabled={isPending}> {isPending ? '提交中...' : '发布'} </button> {state?.error && <p>{state.error}</p>} </form> ); }
isPending 在请求期间为 true,可以显示加载状态。state 保存上一次 action 的返回值。
输入验证
永远不要信任客户端数据。Server Action 里必须验证:
typescript'use server' import { z } from 'zod'; const schema = z.object({ title: z.string().min(1).max(200), content: z.string().min(10), }); export async function createPost(formData: FormData) { const parsed = schema.safeParse({ title: formData.get('title'), content: formData.get('content'), }); if (!parsed.success) { return { error: parsed.error.flatten().fieldErrors }; } await db.post.create({ data: parsed.data }); revalidatePath('/posts'); }
Zod 验证比手写 if-else 更可靠,错误信息也结构化。
revalidatePath 和 revalidateTag
Server Action 修改数据后,需要告诉 Next.js 刷新缓存:
revalidatePath('/posts'):刷新指定路径的缓存revalidateTag('posts'):刷新所有带fetch(..., { next: { tags: ['posts'] } })标记的请求缓存
revalidateTag 更灵活——一个 tag 可以对应多个页面,改一次全部刷新。
安全注意事项
Server Action 虽然有加密 ID 保护,但仍然是公开的 HTTP 端点:
- 必须在 action 内做认证检查(
getServerSession()) - 必须验证输入(Zod)
- 不要在 action 里返回敏感信息(返回值会发给客户端)
typescript'use server' export async function deleteAccount(formData: FormData) { const session = await getServerSession(); if (!session) throw new Error('Unauthorized'); // ... }
和 API Route 的区别
- Server Action:适合表单提交、数据变更,自动处理 loading/error 状态
- API Route:适合第三方回调(Webhook)、文件上传、需要自定义响应头
简单场景用 Server Action 更简洁。需要精细控制 HTTP 响应时用 API Route。