服务器函数
2026年9月6日
Server Actions(也叫服务器函数)让 <form action={fn}> 直接打到一段在服务器跑的函数,不必先写 /api/login。函数收到原生 FormData,可以校验、写库、再 redirect。
和手写 API 的差别
传统:页面 fetch → route.ts 解析 body → 数据库 → JSON → 前端再画结果。
Actions:提交表单 → 带 'use server' 的函数在服务器执行 → 直接碰数据库。少一层自己维护的 REST 形状,渐进增强也在:JS 没到时,表单仍能提交。
静态导出用不了这个,需要 Node 运行时。
基本写法
函数里必须有 'use server'。可以写在页面里,更常见是单独 actions.ts,文件顶上一行指令。
export default function Login() {
async function handleLogin(formData: FormData) {
"use server";
const username = formData.get("username");
const password = formData.get("password");
const all = Object.fromEntries(formData);
console.log(username, password, all);
}
return (
<form action={handleLogin}>
<input name="username" placeholder="用户名" />
<input name="password" type="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
);
}
tsx
name 对得上才能从 FormData 取到。
额外参数:bind
表单字段之外还要带文章 id、当前用户一类「页面已知、输入框没有」的值,用 bind:
async function handleLogin(id: number, formData: FormData) {
"use server";
console.log(id, Object.fromEntries(formData));
}
export default function Login() {
const action = handleLogin.bind(null, 1);
return <form action={action}>{/* ... */}</form>;
}
tsx
bind 的参数会出现在 FormData 前面。不要 bind 密钥或未校验的权限,客户端能看见序列化后的闭包。
zod + useActionState
校验放服务器,错误回给页面。useActionState 是客户端 hook。
"use server";
import { z } from "zod";
const schema = z.object({
username: z.string().min(6, "用户名至少 6 位"),
password: z.string().min(6, "密码至少 6 位"),
});
export async function handleLogin(
_prev: { message: string },
formData: FormData
) {
const parsed = schema.safeParse(Object.fromEntries(formData));
if (!parsed.success) {
const msg = parsed.error.issues.map((i) => i.message).join("\n");
return { message: msg };
}
return { message: "登录成功" };
}
ts
"use client";
import { useActionState } from "react";
import { handleLogin } from "./actions";
const initial = { message: "" };
export default function Login() {
const [state, formAction, pending] = useActionState(handleLogin, initial);
return (
<form action={formAction}>
{pending ? <p>提交中…</p> : null}
{state.message ? <p>{state.message}</p> : null}
<input name="username" />
<input name="password" type="password" />
<button type="submit" disabled={pending}>
登录
</button>
</form>
);
}
tsx
useActionState 三个返回值:当前 state、交给 form 的 action、是否进行中。第三个可选参数 permalink 给「JS 加载前的回跳 URL」用。
下一篇开始 SEO:先分清白帽 / 黑帽,再落到 Next 能配的那些文件。