Next.js Server Actions
Next.js Server Actions 是供 React UI 触发服务端 Mutation 的 Server Function 使用方式,通过 use server、FormData、框架管理的 POST 调用、鉴权校验与缓存重新验证把 UI 写操作连接到服务器业务逻辑。
[!info] related notes
- 所属 MOC:
- 前置概念: nextjs-route-handlers、nextjs-server-component-data-fetching
- 并列概念:
- 易混淆概念:
- 关系笔记: nextjs-server-component-server-action-route-handler-boundaries
Next.js Server Actions
一句话定义
Server Action 是 React / Next.js 中一种 由 UI 发起、在服务器执行的状态变更入口。它让应用代码以“调用服务器函数”的方式表达 Mutation,而不必为每一个 React 内部写操作手工设计一个 /api/* HTTP Endpoint。
React UI
↓
Server Action
↓
Application / Service
↓
Repository 或 Owning Service API
↓
authoritative state changes
更严格地说,Server Function 是更广的概念;当它被 React 用作表单提交、按钮操作等 Mutation 入口时,通常称为 Server Action。
"use server" 建立什么边界
可以在独立模块顶部声明:
"use server"
export async function createGoal(formData: FormData) {
// server only
}
也可以在 Server Component 内部的异步函数中声明:
export default function Page() {
async function createGoal(formData: FormData) {
"use server"
// mutation
}
return <form action={createGoal}>...</form>
}
"use server" 的关键不是“这段代码没有网络请求”,而是:
Developer-facing call
→ looks like a function reference
Actual runtime
→ browser/framework transport
→ server execution
Next.js 会为这类调用管理底层 POST 通信,所以开发者不需要自己维护 URL、HTTP Method、JSON 编解码等样板代码。
最适合的场景:React UI Mutation
典型:
Create
Update
Delete
Login / Logout
Toggle
Submit
Archive
Cancel
Mark as completed
例如 MemoFlow:
Create Goal button
↓
createGoal Server Action
↓
goalService.create()
↓
Repository / DB
如果 Goal 领域由当前 Next.js 应用拥有,这是一条自然链路。
如果真正 owner 是独立服务,例如 BodySense 的 Assessment 由 Go Backend 管理:
React UI
↓
Next Server Action
↓
BodySense Go API
↓
Go Service / Domain
↓
DB
Server Action 只改变 UI → Server 的调用方式,不会改变 数据所有权与服务边界。
Server Action 不等于 Service
不要把 Action 写成巨型业务层:
Server Action
→ parse / validate input
→ authenticate
→ authorize
→ call Application Service
→ revalidate / redirect
而复杂业务逻辑继续属于:
Application / Domain Service
→ transaction
→ state machine
→ invariant
→ repository / downstream service
所以:
Server Action
≈ React mutation adapter
Service
≈ business use case
Form 与 FormData
Server Action 与 React <form action={...}> 深度集成:
<form action={createGoal}>
<input name="title" />
<button type="submit">Create</button>
</form>
Action 会收到 FormData:
export async function createGoal(formData: FormData) {
"use server"
const title = formData.get("title")
}
但 FormData 仍是外部输入,不能直接视为可信领域对象。
正确模型:
FormData / action args
→ untrusted input
→ runtime validation
→ trusted DTO
→ Service
例如:
const input = CreateGoalSchema.parse({
title: formData.get("title"),
})
Progressive Enhancement
由 Server Component 输出的 <form action={serverAction}> 可以利用 Web Form 的基础提交能力,在客户端 JavaScript 尚未完全加载时仍具备可工作的提交路径。
这说明 Server Action 并不是完全抛弃 Web Platform,而是把:
HTML Form
+ server execution
+ React enhancement
组合在一起。
下一节表单专题再深入 Pending、Validation Error、useActionState 与 Optimistic UI。
Client Component 也可以触发 Action
Client Component 可以 import 独立 "use server" 模块中的 Server Action:
"use client"
import { deleteGoal } from "./actions"
export function DeleteButton({ goalId }: { goalId: string }) {
return (
<button onClick={() => deleteGoal(goalId)}>
Delete
</button>
)
}
因此:
Server Action ≠ form only
更准确是:
Server Action
= React UI initiated server-side mutation
Action 参数仍然不可信
即使参数来自:
updateGoal.bind(null, goal.id)
也不能认为 goal.id 无法被恶意构造。
必须重新验证:
argument shape
resource existence
current actor
object-level permission
business invariant
所以:
UI visibility
≠ authorization
Client passed id
≠ ownership proof
Server Action 应被视为一个 public mutation surface:虽然开发者没有手写公共 REST URL,但客户端仍然可以触发这个服务器入口。
Authentication 与 Authorization 都不能省
例如:
export async function deleteGoal(goalId: string) {
"use server"
const session = await requireSession()
await goalService.delete({
actorId: session.user.id,
goalId,
})
}
requireSession() 只回答“是谁”,Service/Policy 还要回答“这个人能不能删除这个 Goal”。
与 Cache Revalidation 的连接
Mutation 成功以后,Server Cache 可能仍保存旧 projection。
典型链路:
Server Action
→ Service mutation succeeds
→ authoritative truth changes
→ updateTag / revalidateTag / revalidatePath
→ cached projection converges
例如 read-your-own-writes:
await goalService.create(...)
updateTag(`user:${userId}:goals`)
然后可:
redirect("/goals")
形成:
submit
→ mutate truth
→ invalidate stale cache
→ redirect
→ fresh read
注意:updateTag() 是 mutation 成功后的 server-cache invalidation,不等于 Optimistic Update。
不要把 Server Action 当普通 Query RPC
虽然 Server Action 技术上可以返回数据,但它的主要语义是 Mutation。
不推荐:
Client Component
→ getGoalsServerAction()
→ 当作常规数据查询 API
常规读取更自然的路径:
首屏 / server render
→ Server Component
浏览器持续查询 / refetch
→ TanStack Query / SWR
→ Route Handler 或真实后端 API
这样 Read 与 Write 的职责更清楚。
与 Route Handler 的核心区别
React UI 内部 Mutation
→ Server Action
需要稳定 HTTP Contract
→ Route Handler
例如:
MemoFlow Web 创建 Goal
→ Server Action
MemoFlow iOS 创建 Goal
→ POST /api/goals → Route Handler
Stripe Webhook
→ Route Handler
外部系统不能把 Server Action 当成稳定公开 API 使用。
详细边界见 Server Component、Server Action 与 Route Handler 的边界。
最短记忆
Server Component
→ Read / Render
Server Action
→ React UI Mutation
Route Handler
→ Explicit HTTP Endpoint
Server Action 简化的是调用 transport,不会取消 validation、authentication、authorization、Service 分层或领域 ownership。