← [ BACK_TO_NOTES ]

Next.js Server Actions

Next.js Server Actions 是供 React UI 触发服务端 Mutation 的 Server Function 使用方式,通过 use server、FormData、框架管理的 POST 调用、鉴权校验与缓存重新验证把 UI 写操作连接到服务器业务逻辑。

#concept #growing #frame #react

[!info] related notes

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。

创建于 2026/9/16 更新于 2026/9/16
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本