服务端操作与数据变更
服务端操作 (Server Actions) 是在服务端执行的异步函数。它们可用于服务端组件 (Server Components) 和客户端组件 (Client Components) 中,以处理 Next.js 应用中的表单提交和数据变更。
🎥 观看视频: 通过服务端操作学习表单与数据变更 → YouTube (10分钟)。
约定
可以使用 React 的 "use server"
指令定义服务端操作。你可以将该指令放在 async
函数的顶部以标记该函数为服务端操作,或者放在单独文件的顶部以标记该文件的所有导出为服务端操作。
服务端组件
服务端组件可以使用函数级别或模块级别的 "use server"
指令。要内联定义服务端操作,请在函数体顶部添加 "use server"
:
客户端组件
客户端组件只能导入使用模块级 "use server"
指令的操作。
要在客户端组件中调用服务端操作,需创建一个新文件并在其顶部添加 "use server"
指令。文件中的所有函数都将被标记为服务端操作,可在客户端和服务端组件中复用:
你也可以将服务端操作作为 prop 传递给客户端组件:
行为
- 服务端操作可以通过
<form>
元素 的action
属性调用:- 服务端组件默认支持渐进增强 (progressive enhancement),即使 JavaScript 尚未加载或禁用,表单也会提交。
- 在客户端组件中,调用服务端操作的表单会在 JavaScript 未加载时排队提交,优先进行客户端水合 (hydration)。
- 水合后,表单提交时浏览器不会刷新。
- 服务端操作不仅限于
<form>
,还可以从事件处理程序、useEffect
、第三方库和其他表单元素(如<button>
)调用。 - 服务端操作与 Next.js 的缓存和重新验证架构集成。当操作被调用时,Next.js 可以在单次服务端往返中返回更新的 UI 和新数据。
- 在底层,操作使用
POST
方法,只有此 HTTP 方法可以调用它们。 - 服务端操作的参数和返回值必须可被 React 序列化。请参阅 React 文档了解可序列化的参数和值。
- 服务端操作是函数,这意味着它们可以在应用程序的任何地方复用。
- 服务端操作继承其所在页面或布局的运行时。
- 服务端操作继承其所在页面或布局的路由段配置,包括
maxDuration
等字段。
示例
表单
React 扩展了 HTML <form>
元素,允许通过 action
属性调用服务端操作。
在表单中调用时,操作会自动接收 FormData
对象。你无需使用 React 的 useState
管理字段,而是可以使用原生的 FormData
方法 提取数据:
须知:
- 示例:带加载和错误状态的表单
- 处理包含多个字段的表单时,可以考虑使用
entries()
方法与 JavaScript 的Object.fromEntries()
。例如:const rawFormData = Object.fromEntries(formData)
。需要注意的是,formData
会包含额外的$ACTION_
属性。- 参阅 React
<form>
文档 了解更多。
传递额外参数
可以使用 JavaScript 的 bind
方法向服务端操作传递额外参数。
服务端操作将接收 userId
参数以及表单数据:
须知:
- 另一种方法是在表单中传递隐藏输入字段的参数(例如
<input type="hidden" name="userId" value={userId} />
)。但是,该值会成为渲染 HTML 的一部分且不会被编码。.bind
在服务端和客户端组件中均有效,同时也支持渐进增强。
等待状态
可以使用 React 的 useFormStatus
钩子在表单提交期间显示等待状态。
useFormStatus
返回特定<form>
的状态,因此必须定义为<form>
元素的子元素。useFormStatus
是 React 钩子,因此必须在客户端组件中使用。
<SubmitButton />
可以嵌套在任何表单中:
服务端验证与错误处理
我们建议使用 required
和 type="email"
等 HTML 验证进行基本的客户端表单验证。
对于更高级的服务端验证,可以使用 zod 等库在变更数据前验证表单字段:
在服务端验证字段后,可以在操作中返回一个可序列化的对象,并使用 React 的 useFormState
钩子向用户显示消息。
- 通过将操作传递给
useFormState
,操作的函数签名会发生变化,接收一个新的prevState
或initialState
参数作为其第一个参数。 useFormState
是 React 钩子,因此必须在客户端组件中使用。
然后,可以将操作传递给 useFormState
钩子,并使用返回的 state
显示错误消息。
须知:
- 在变更数据之前,应始终确保用户有权执行该操作。请参阅身份验证与授权。
乐观更新
可以使用 React 的 useOptimistic
钩子在服务端操作完成前乐观地更新 UI,而不是等待响应:
嵌套元素
可以在 <form>
内的嵌套元素(如 <button>
、<input type="submit">
和 <input type="image">
)中调用服务端操作。这些元素接受 formAction
属性或事件处理程序。
这在需要在一个表单中调用多个服务端操作的情况下非常有用。例如,除了发布帖子外,还可以为保存帖子草稿创建一个特定的 <button>
元素。参阅 React <form>
文档 了解更多信息。
编程式表单提交
您可以使用 requestSubmit()
方法触发表单提交。例如,当用户按下 ⌘
+ Enter
时,可以监听 onKeyDown
事件:
这将触发最近的 <form>
祖先元素的提交,从而调用服务端操作 (Server Action)。
非表单元素
虽然通常在 <form>
元素中使用服务端操作,但它们也可以从代码的其他部分调用,例如事件处理程序和 useEffect
。
事件处理程序
您可以从 onClick
等事件处理程序中调用服务端操作。例如,增加点赞数:
为了提升用户体验,我们推荐使用其他 React API 如 useOptimistic
和 useTransition
,在服务端操作执行完成前更新 UI 或显示待处理状态。
您也可以为表单元素添加事件处理程序,例如在 onChange
时保存表单字段:
对于此类可能快速触发多次事件的情况,我们推荐使用防抖 (debouncing) 来避免不必要的服务端操作调用。
useEffect
您可以使用 React 的 useEffect
钩子在组件挂载或依赖项变化时调用服务端操作。这对于依赖于全局事件或需要自动触发的变更非常有用。例如,onKeyDown
处理应用快捷键、无限滚动的交叉观察器钩子,或在组件挂载时更新浏览量:
请务必考虑 useEffect
的行为和注意事项。
错误处理
当抛出错误时,它会被客户端最近的 error.js
或 <Suspense>
边界捕获。我们推荐使用 try/catch
将错误返回给 UI 处理。
例如,您的服务端操作可以通过返回消息来处理创建新项目时的错误:
须知:
- 除了抛出错误,您还可以返回一个对象供
useFormState
处理。参见服务端验证和错误处理。
重新验证数据
您可以在服务端操作中使用 revalidatePath
API 重新验证 Next.js 缓存:
或者使用 revalidateTag
通过缓存标签使特定数据获取失效:
重定向
如果希望在服务端操作完成后将用户重定向到不同路由,可以使用 redirect
API。redirect
需要在 try/catch
块外部调用:
Cookies
您可以在服务端操作中使用 cookies
API 获取、设置和删除 cookies:
查看更多示例了解如何从服务端操作中删除 cookies。
安全性
认证与授权
您应将服务端操作视为面向公众的 API 端点,并确保用户有权执行该操作。例如:
闭包与加密
在组件内定义服务端操作会创建一个闭包,使操作能够访问外部函数的作用域。例如,publish
操作可以访问 publishVersion
变量:
闭包在需要捕获数据快照(如 publishVersion
)以便在操作调用时使用时非常有用。
然而,为了实现这一点,捕获的变量会被发送到客户端并在操作调用时返回服务器。为了防止敏感数据暴露给客户端,Next.js 会自动加密闭包变量。每次构建 Next.js 应用时,都会为每个操作生成一个新的私钥。这意味着操作只能针对特定构建调用。
须知: 我们不建议仅依赖加密来防止敏感值暴露在客户端。相反,您应该使用 React taint APIs 主动防止特定数据发送到客户端。
覆盖加密密钥(高级)
当您在多台服务器上自托管 Next.js 应用时,每个服务器实例可能最终使用不同的加密密钥,导致潜在的不一致。
为了缓解这种情况,您可以使用 process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
环境变量覆盖加密密钥。指定此变量可确保加密密钥在构建之间保持持久性,并且所有服务器实例使用相同的密钥。
这是一个高级用例,适用于多部署环境下一致的加密行为对应用至关重要的场景。您应考虑标准的安全实践,如密钥轮换和签名。
须知: 部署到 Vercel 的 Next.js 应用会自动处理此问题。
允许的来源(高级)
由于服务端操作可以在 <form>
元素中调用,这使得它们容易受到 CSRF 攻击。
在底层,服务端操作使用 POST
方法,并且只允许此 HTTP 方法调用它们。这可以防止现代浏览器中的大多数 CSRF 漏洞,特别是默认情况下使用 SameSite cookies。
作为额外保护,Next.js 中的服务端操作还会比较 Origin 标头 和 Host 标头(或 X-Forwarded-Host
)。如果不匹配,请求将被中止。换句话说,服务端操作只能在与托管它的页面相同的主机上调用。
对于使用反向代理或多层后端架构(服务器 API 与生产域不同)的大型应用,建议使用配置选项 serverActions.allowedOrigins
指定安全来源列表。该选项接受字符串数组。
了解更多关于 安全性与服务端操作。
更多资源
有关服务端操作的更多信息,请查看以下 React 文档: