前言:一种名为"胶水代码"的疲惫
接上回。咱们用 Next.js App Router 和 Server Components 实现了服务端直接读数据库。页面加载速度快得离谱,Google 爬虫也爱死我们了。
但是,只要涉及到写操作(比如提交表单、点赞、收藏),你的痛苦回忆又回来了。

按照老规矩(Next.js Pages Router 时代),你要做一个"添加待办事项"的功能,你得这么折腾:
- 在
pages/api/todos.ts里写一个 API Handler,解析req.body,连数据库写入,返个 JSON。 - 在前端组件里,引入
axios。 - 写个
handleSubmit,调用axios.post('/api/todos', data)。 - 处理 loading 状态,处理 error。
- 请求成功后,为了让列表更新,还得手动去更新本地 state 或者触发 SWR/React Query 的
mutate。
累不累? 我不就为了存一行字吗?为什么要跨越千山万水,写这么多"胶水代码"来连接前后端?
今天,我们要把这层胶水撕掉。欢迎来到 Server Actions 的世界。在这里,前端按钮可以直接调用后端函数。
核心魔法:远程过程调用 (RPC) 的文艺复兴
什么是 Server Action? 简单说,就是你在服务器文件里写一个函数,标上 'use server',然后你可以直接把这个函数 import 到前端组件里,绑在 onClick 上。
Next.js 在背后会自动帮你把这个函数调用变成一个 HTTP POST 请求。你看着像是在调用本地函数,其实是在搞 RPC(远程过程调用)。
❌ 以前的写法(前后端分离,心也分离):
1. 后端 (pages/api/createTodo.ts):
ts
export default async function handler(req, res) {
if (req.method === 'POST') {
const todo = await db.todo.create({ data: req.body });
res.status(200).json(todo);
}
}
2. 前端 (components/AddTodo.tsx):
const
const [text, setText] = useState('');
const add = async () => {
await axios.post('/api/createTodo', { text }); // 还要记 URL
// 手动刷新列表...
};
return Add;
}
✅ Server Actions 写法(合二为一):
我们新建一个 actions.ts 文件,这里面的代码只会在服务器运行。
- 定义 Action (actions.ts):
'use
- 使用 Action (components/AddTodo.tsx):
import
const AddTodo = () => {
// 直接把 Server Action 绑在 form 的 action 上
// 甚至关了 JS 这表单都能提交(渐进增强)
return (
Add
);
}
发现了吗?axios 不见了,useEffect 不见了,手动刷新数据的逻辑也不见了。 你写代码的感觉就像回到了 PHP 时代(褒义),直接跟数据库对话,但享受着 React 的组件化体验。
进阶玩法:如果不只是 Form 提交怎么办?
"将军,我不是提交表单,我就想点个赞,或者点击按钮执行个逻辑,怎么办?"
这时候我们不能用 `` 了,我们需要在 Event Handler 里调用。 但是,直接调用 Server Action 是拿不到 loading 状态的。
这时候,React 的 useTransition 也就是为此而生的。
'use
这体验简直绝了。 你不需要自己定义 const [loading, setLoading] = useState(false)。useTransition 帮你搞定了一切 loading 态管理。
避坑指南:别太天真,这还是 HTTP
虽然写起来像本地函数,但你心里要有数:这依然是一次网络请求。
1. 安全,安全,还是TMD安全!
千万别以为这是一个普通函数,就觉得能在里面随便传东西。 Server Action 本质上就是一个公开的 API 接口。 任何人都可以通过抓包,模拟发送 POST 请求给这个 Action。
所以,必须在 Action 内部做身份验证和权限校验!
//
export async function deletePost(id: string) {
// 必须检查用户是否有权限!
const session = await getSession();
if (!session || session.user.role !== 'admin') {
throw new Error('你没有权限干这事儿');
}
// 必须校验输入参数!(Zod 再次登场)
const safeId = z.string().parse(id);
await db.post.delete({ where: { id: safeId } });
}
2. 不能传递复杂对象
因为 Action 调用要跨越网络(序列化),所以参数和返回值必须是可序列化 的。 你不能把一个 onClick 回调函数或者一个 Class 实例传给 Server Action。 传 JSON、String、FormData 都是最稳的。
3. 不要把敏感数据传回客户端
Server Action 的返回值会发给浏览器。 别手滑把 user.password_hash 或者整个数据库连接对象给 return 出来了。
总结:全栈的最后一公里
Server Actions 的出现,标志着 React 正式从一个 UI 库,进化成了一个全栈框架。
它打破了"前端"和"后端"之间那道厚厚的墙。
- 对于简单的 CRUD,你再也不用去写繁琐的 REST API。
- 你的业务逻辑高度内聚:数据库操作代码和触发它的按钮代码,物理距离只有几行 import。
当然,这并不意味着后端工程师失业了。对于复杂的微服务编排、高并发处理,还是需要专业的后端架构。 但对于我们这种做应用、做产品的开发者来说,Server Actions 让我们能用原来 1/3 的代码量,干完 100% 的活。
这就叫生产力。

下期预告 :代码写完了,测试跑通了。最后一步是什么?部署! 你还在手动 SSH 连服务器、装 Nginx、传文件吗?太 Low 了。 下一篇(完结篇),我们来聊聊 "CI/CD 与 Vercel 部署" 。教你如何把 GitHub 代码提交的一瞬间,自动触发构建、部署,并让你的应用跑在全世界的边缘节点(Edge)上。