Server Actions and Mutations 服务器操作和数据变更
Server Actions are asynchronous functions that are executed on the server. They can be used in Server and Client Components to handle form submissions and data mutations in Next.js applications.
服务器操作是服务器上执行的异步函数。它们可用于服务器和客户端组件来处理 Next.js 应用程序中的表单提交和数据突变。
1.Convention 约定
A Server Action can be defined with the React "use server" directive. You can place the directive at the top of an async function to mark the function as a Server Action, or at the top of a separate file to mark all exports of that file as Server Actions.
可以使用 React "use server" 指令定义服务器操作。您可以在 async 函数的顶部放置该指令,以将该函数标记为服务器操作,或者在单独文件的顶部放置该指令,以将该文件的全部导出内容标记为服务器操作。
a.Server Components 服务器组件
Server Components can use the inline function level or module level "use server" directive. To inline a Server Action, add "use server" to the top of the function body:
服务器组件可以使用内联函数级别或模块级别的 "use server" 指令。要内联服务器操作,请将 "use server" 添加到函数主体的顶部:
// Server Component
export default function Page() {
// Server Action
async function create() {
'use server'
// ...
}
return (
// ...
)
}
b.Client Components 客户端组件
Client Components can only import actions that use the module-level "use server" directive.
客户端组件只能导入使用模块级 "use server" 指令的操作。
To call a Server Action in a Client Component, create a new file and add the "use server" directive at the top of it. All functions within the file will be marked as Server Actions that can be reused in both Client and Server Components:
要在客户端组件中调用服务器操作,请创建一个新文件并在其顶部添加 "use server" 指令。文件中的所有函数都将标记为服务器操作,可以在客户端和服务器组件中重复使用:
// app/actions.ts
'use server'
export async function create() {
// ...
}
// app/ui/button.tsx
import { create } from '@/app/actions'
export function Button() {
return (
// ...
)
}
You can also pass a Server Action to a Client Component as a prop:
您还可以将服务器操作作为道具传递给客户端组件:
<ClientComponent updateItem={updateItem} />
// app/client-component.jsx
'use client'
export default function ClientComponent({ updateItem }) {
return <form action={updateItem}>{/* ... */}</form>
}
2.Behavior 行为
Server actions can be invoked using the
action
attribute in a<form>
element:
服务器操作可以使用<form>
元素中的action
属性调用:Server Components support progressive enhancement by default, meaning the form will be submitted even if JavaScript hasn't loaded yet or is disabled.
默认情况下,服务器组件支持渐进增强,这意味着即使 JavaScript 尚未加载或被禁用,表单也会被提交。In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration.
在客户端组件中,如果 JavaScript 尚未加载,调用服务器操作的表单会将提交排队,以优先考虑客户端水化。After hydration, the browser does not refresh on form submission.
水化后,浏览器在表单提交时不会刷新。
Server Actions are not limited to
<form>
and can be invoked from event handlers,useEffect
, third-party libraries, and other form elements like<button>
.
服务器操作不仅限于<form>
,还可以从事件处理程序、useEffect
、第三方库和其他表单元素(如<button>
)调用。Server Actions integrate with the Next.js caching and revalidation architecture. When an action is invoked, Next.js can return both the updated UI and new data in a single server roundtrip.
服务器操作与 Next.js 缓存和重新验证架构集成。调用操作时,Next.js 可以在单次服务器往返中返回更新后的 UI 和新数据。Behind the scenes, actions use the
POST
method, and only this HTTP method can invoke them.
在后台,动作使用POST
方法,并且只有此 HTTP 方法可以调用它们。The arguments and return value of Server Actions must be serializable by React. See the React docs for a list of serializable arguments and values.
服务器动作的参数和返回值必须可由 React 序列化。请参阅 React 文档以获取可序列化参数和值列表。Server Actions are functions. This means they can be reused anywhere in your application.
服务器动作是函数。这意味着它们可以在应用程序中的任何位置重复使用。Server Actions inherit the runtime from the page or layout they are used on.
服务器动作继承其所用页面或布局的运行时。Server Actions inherit the Route Segment Config from the page or layout they are used on, including fields like
maxDuration
.
服务器操作继承了它们所用页面或布局的路由段配置,包括maxDuration
等字段。
3.Examples 示例
a.Form 表单
React extends the HTML <form> element to allow Server Actions to be invoked with the action prop.
React 扩展了 HTML <form> 元素,允许使用 action 属性调用服务器操作。
When invoked in a form, the action automatically receives the FormData object. You don't need to use React useState to manage fields, instead, you can extract the data using the native FormData methods:
在表单中调用时,操作会自动接收 FormData 对象。您无需使用 React useState 来管理字段,而是可以使用原生 FormData 方法提取数据:
// app/invoices/page.tsx
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate cache
}
return <form action={createInvoice}>...</form>
}
Good to know: 值得了解:
Example: Form with Loading & Error States
示例:带有加载和错误状态的表单When working with forms that have many fields, you may want to consider using the
entries()
method with JavaScript'sObject.fromEntries()
. For example:const rawFormData = Object.fromEntries(formData.entries())
在使用具有许多字段的表单时,您可能需要考虑将entries()
方法与 JavaScript 的Object.fromEntries()
一起使用。例如:const rawFormData = Object.fromEntries(formData.entries())
See React
<form>
documentation to learn more.
请参阅 React<form>
文档以了解更多信息。
Ⅰ.Passing Additional Arguments 传递其他参数
You can pass additional arguments to a Server Action using the JavaScript bind method.
您可以使用 JavaScript bind 方法向服务器操作传递其他参数。
// app/client-component.tsx
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
The Server Action will receive the userId argument, in addition to the form data:
除了表单数据之外,服务器操作还将接收 userId 参数:
// app/actions.js
'use server'
export async function updateUser(userId, formData) {
// ...
}
Good to know: 值得了解:
An alternative is to pass arguments as hidden input fields in the form (e.g.
<input type="hidden" name="userId" value={userId} />
). However, the value will be part of the rendered HTML and will not be encoded.
另一种方法是将参数作为隐藏的输入字段传递到表单中(例如<input type="hidden" name="userId" value={userId} />
)。但是,该值将成为呈现的 HTML 的一部分,并且不会被编码。.bind
works in both Server and Client Components. It also supports progressive enhancement..bind
同时适用于服务器和客户端组件。它还支持渐进增强。
Ⅱ.Pending states 挂起状态
You can use the React useFormStatus hook to show a pending state while the form is being submitted.
您可以在表单提交时使用 React useFormStatus 钩子显示挂起状态。
useFormStatus
returns the status for a specific<form>
, so it must be defined as a child of the<form>
element.useFormStatus
返回特定<form>
的状态,因此必须将其定义为<form>
元素的子元素。useFormStatus
is a React hook and therefore must be used in a Client Component.useFormStatus
是一个 React hook,因此必须在客户端组件中使用。
// app/submit-button.tsx
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" aria-disabled={pending}>
Add
</button>
)
}
<SubmitButton /> can then be nested in any form:
<SubmitButton /> 然后可以嵌套在任何表单中:
// app/page.tsx
import { SubmitButton } from '@/app/submit-button'
import { createItem } from '@/app/actions'
// Server Component
export default async function Home() {
return (
<form action={createItem}>
<input type="text" name="field-name" />
<SubmitButton />
</form>
)
}
Ⅲ.Server-side validation and error handling 服务器端验证和错误处理
We recommend using HTML validation like required and type="email" for basic client-side form validation.
我们建议使用 HTML 验证,例如 required 和 type="email" ,进行基本的客户端表单验证。
For more advanced server-side validation, you can use a library like zod to validate the form fields before mutating the data:
对于更高级的服务器端验证,您可以使用 zod 之类的库在更改数据之前验证表单字段:
// app/actions.ts
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Return early if the form data is invalid
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Mutate data
}
Once the fields have been validated on the server, you can return a serializable object in your action and use the React useFormState hook to show a message to the user.
一旦字段在服务器上得到验证,您可以在您的操作中返回一个可序列化的对象,并使用 React useFormState 钩子向用户显示一条消息。
By passing the action to
useFormState
, the action's function signature changes to receive a newprevState
orinitialState
parameter as its first argument.
通过将操作传递给useFormState
,操作的函数签名会更改为接收一个新的prevState
或initialState
参数作为其第一个参数。useFormState
is a React hook and therefore must be used in a Client Component.useFormState
是一个 React hook,因此必须在客户端组件中使用。
// app/actions.ts
'use server'
export async function createUser(prevState: any, formData: FormData) {
// ...
return {
message: 'Please enter a valid email',
}
}
Then, you can pass your action to the useFormState hook and use the returned state to display an error message.
然后,您可以将您的操作传递给 useFormState 钩子,并使用返回的 state 来显示错误消息。
// app/ui/signup.tsx
'use client'
import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite" className="sr-only">
{state?.message}
</p>
<button>Sign up</button>
</form>
)
}
Good to know: 值得了解:
Before mutating data, you should always ensure a user is also authorized to perform the action. See Authentication and Authorization.
在更改数据之前,您应始终确保用户也获得执行该操作的授权。请参阅身份验证和授权。
Ⅳ.Optimistic updates 乐观更新
You can use the React useOptimistic hook to optimistically update the UI before the Server Action finishes, rather than waiting for the response:
您可以使用 React useOptimistic 钩子在服务器操作完成之前乐观地更新 UI,而不是等待响应:
// app/page.tsx
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
messages,
(state: Message[], newMessage: string) => [
...state,
{ message: newMessage },
]
)
return (
<div>
{optimisticMessages.map((m, k) => (
<div key={k}>{m.message}</div>
))}
<form
action={async (formData: FormData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}}
>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
Ⅴ.Nested elements 嵌套元素
You can invoke a Server Action in elements nested inside <form> such as <button>, <input type="submit">, and <input type="image">. These elements accept the formAction prop or event handlers.
您可以在嵌套在 <form> 中的元素(例如 <button> 、 <input type="submit"> 和 <input type="image"> )中调用服务器操作。这些元素接受 formAction prop 或事件处理程序。
This is useful in cases where you want to call multiple server actions within a form. For example, you can create a specific <button> element for saving a post draft in addition to publishing it. See the React <form> docs for more information.
这在您希望在表单中调用多个服务器操作时很有用。例如,除了发布帖子外,您还可以创建一个用于保存帖子草稿的特定 <button> 元素。有关更多信息,请参阅 React <form> 文档。
Ⅵ.Programmatic form submission 以编程方式提交表单
You can trigger a form submission using the requestSubmit() method. For example, when the user presses ⌘ + Enter, you can listen for the onKeyDown event:
您可以使用 requestSubmit() 方法触发表单提交。例如,当用户按下 ⌘ + Enter 时,您可以监听 onKeyDown 事件:
// app/entry.tsx
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
This will trigger the submission of the nearest <form> ancestor, which will invoke the Server Action.
这将触发提交最近的 <form> 祖先,它将调用服务器操作。
b.Non-form Elements
While it's common to use Server Actions within <form> elements, they can also be invoked from other parts of your code such as event handlers and useEffect.
虽然在 <form> 元素中使用服务器操作很常见,但也可以从代码的其他部分调用它们,例如事件处理程序和 useEffect 。
Ⅰ.Event Handlers 事件处理程序
You can invoke a Server Action from event handlers such as onClick. For example, to increment a like count:
您可以从事件处理程序(例如 onClick )调用服务器操作。例如,要增加喜欢计数:
// app/like-button.tsx
'use client'
import { incrementLike } from './actions'
import { useState } from 'react'
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes)
return (
<>
<p>Total Likes: {likes}</p>
<button
onClick={async () => {
const updatedLikes = await incrementLike()
setLikes(updatedLikes)
}}
>
Like
</button>
</>
)
}
To improve the user experience, we recommend using other React APIs like useOptimistic and useTransition to update the UI before the Server Action finishes executing on the server, or to show a pending state.
为了改善用户体验,我们建议使用其他 React API,如 useOptimistic 和 useTransition ,以便在服务器操作在服务器上执行完毕之前更新 UI,或显示挂起状态。
You can also add event handlers to form elements, for example, to save a form field onChange:
您还可以将事件处理程序添加到表单元素,例如,保存表单字段 onChange :
// app/ui/edit-post.tsx
'use client'
import { publishPost, saveDraft } from './actions'
export default function EditPost() {
return (
<form action={publishPost}>
<textarea
name="content"
onChange={async (e) => {
await saveDraft(e.target.value)
}}
/>
<button type="submit">Publish</button>
</form>
)
}
For cases like this, where multiple events might be fired in quick succession, we recommend debouncing to prevent unnecessary Server Action invocations.
对于这种情况,可能会快速连续地触发多个事件,我们建议使用防抖来防止不必要的服务器操作调用。
Ⅱ.useEffect
You can use the React useEffect hook to invoke a Server Action when the component mounts or a dependency changes. This is useful for mutations that depend on global events or need to be triggered automatically. For example, onKeyDown for app shortcuts, an intersection observer hook for infinite scrolling, or when the component mounts to update a view count:
您可以使用 React useEffect 钩子在组件挂载或依赖项更改时调用服务器操作。这对于依赖全局事件或需要自动触发的突变非常有用。例如, onKeyDown 用于应用快捷方式、用于无限滚动的交集观察器钩子,或在组件挂载时更新视图计数:
// app/view-count.tsx
'use client'
import { incrementViews } from './actions'
import { useState, useEffect } from 'react'
export default function ViewCount({ initialViews }: { initialViews: number }) {
const [views, setViews] = useState(initialViews)
useEffect(() => {
const updateViews = async () => {
const updatedViews = await incrementViews()
setViews(updatedViews)
}
updateViews()
}, [])
return <p>Total Views: {views}</p>
}
c.Error Handling 错误处理
When an error is thrown, it'll be caught by the nearest error.js or <Suspense> boundary on the client. We recommend using try/catch to return errors to be handled by your UI.
当抛出错误时,它将被客户端上最接近的 error.js 或 <Suspense> 边界捕获。我们建议使用 try/catch 将错误返回给 UI 处理。
For example, your Server Action might handle errors from creating a new item by returning a message:
例如,您的服务器操作可能会通过返回消息来处理创建新项目时的错误:
// app/actions.ts
'use server'
export async function createTodo(prevState: any, formData: FormData) {
try {
// Mutate data
} catch (e) {
throw new Error('Failed to create task')
}
}
Good to know: 值得了解:
Aside from throwing the error, you can also return an object to be handled by
useFormState
. See Server-side validation and error handling.
除了抛出错误外,您还可以返回一个由useFormState
处理的对象。请参阅服务器端验证和错误处理。
d.Revalidating data 重新验证数据
You can revalidate the Next.js Cache inside your Server Actions with the revalidatePath API:
您可以在服务器操作中使用 revalidatePath API 重新验证 Next.js 缓存:
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
try {
// ...
} catch (error) {
// ...
}
revalidatePath('/posts')
}
Or invalidate a specific data fetch with a cache tag using revalidateTag:
或使用 revalidateTag 通过缓存标记使特定数据获取无效:
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts')
}
f.Redirecting 重定向
If you would like to redirect the user to a different route after the completion of a Server Action, you can use redirect API. redirect needs to be called outside of the try/catch block:
如果您想在服务器操作完成后将用户重定向到其他路由,可以使用 redirect API。需要在 try/catch 块外部调用 redirect :
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function createPost(id: string) {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}
g.Cookies
You can get, set, and delete cookies inside a Server Action using the cookies API:
您可以在 Server Action 中使用 cookies API 来 get 、 set 和 delete cookie:
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
// Get cookie
const value = cookies().get('name')?.value
// Set cookie
cookies().set('name', 'Delba')
// Delete cookie
cookies().delete('name')
}
See additional examples for deleting cookies from Server Actions.
请参阅其他示例,了解如何从 Server Action 中删除 cookie。
4.Security 安全性
a.Authentication and authorization
You should treat Server Actions as you would public-facing API endpoints, and ensure that the user is authorized to perform the action. For example:
您应该将服务器操作视为面向公众的 API 端点,并确保用户被授权执行操作。例如:
// app/actions.ts
'use server'
import { auth } from './lib'
export function addItem() {
const { user } = auth()
if (!user) {
throw new Error('You must be signed in to perform this action')
}
// ...
}
b.Closures and encryption 闭包和加密
Defining a Server Action inside a component creates a closure where the action has access to the outer function's scope. For example, the publish action has access to the publishVersion variable:
在组件内定义服务器操作会创建一个闭包,操作可以在其中访问外部函数的范围。例如, publish 操作可以访问 publishVersion 变量:
// app/page.tsx
export default function Page() {
const publishVersion = await getLatestVersion();
async function publish(formData: FormData) {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('The version has changed since pressing publish');
}
...
}
return <button action={publish}>Publish</button>;
}
Closures are useful when you need to capture a snapshot of data (e.g. publishVersion) at the time of rendering so that it can be used later when the action is invoked.
闭包在您需要在渲染时捕获数据快照(例如 publishVersion )以便在调用操作时稍后使用时非常有用。
However, for this to happen, the captured variables are sent to the client and back to the server when the action is invoked. To prevent sensitive data from being exposed to the client, Next.js automatically encrypts the closed-over variables. A new private key is generated for each action every time a Next.js application is built. This means actions can only be invoked for a specific build.
但是,要实现这一点,捕获的变量在调用操作时会发送到客户端,然后发送回服务器。为了防止敏感数据暴露给客户端,Next.js 会自动加密闭合变量。每次构建 Next.js 应用程序时,都会为每个操作生成一个新的私钥。这意味着只能针对特定构建调用操作。
Good to know: We don't recommend relying on encryption alone to prevent sensitive values from being exposed on the client. Instead, you should use the React taint APIs to proactively prevent specific data from being sent to the client.
温馨提示:我们不建议仅依靠加密来防止敏感值在客户端上暴露。相反,您应该使用 React 污染 API 主动防止将特定数据发送到客户端。
c.Overwriting encryption keys (advanced) 覆盖加密密钥(高级)
When self-hosting your Next.js application across multiple servers, each server instance may end up with a different encryption key, leading to potential inconsistencies.
在多台服务器上自托管 Next.js 应用程序时,每个服务器实例最终可能拥有不同的加密密钥,从而导致潜在的不一致。
To mitigate this, you can overwrite the encryption key using the process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY environment variable. Specifying this variable ensures that your encryption keys are persistent across builds, and all server instances use the same key.
为了减轻这种情况,您可以使用 process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY 环境变量覆盖加密密钥。指定此变量可确保您的加密密钥在构建中保持一致,并且所有服务器实例都使用相同的密钥。
This is an advanced use case where consistent encryption behavior across multiple deployments is critical for your application. You should consider standard security practices such key rotation and signing.
这是一个高级用例,其中跨多个部署的一致加密行为对您的应用程序至关重要。您应该考虑标准的安全实践,例如密钥轮换和签名。
Good to know: Next.js applications deployed to Vercel automatically handle this.
值得注意的是:部署到 Vercel 的 Next.js 应用程序会自动处理此问题。
d.Allowed origins (advanced) 允许的来源(高级)
Since Server Actions can be invoked in a <form> element, this opens them up to CSRF attacks.
由于服务器操作可以在 <form> 元素中调用,因此它们容易受到 CSRF 攻击。
Behind the scenes, Server Actions use the POST method, and only this HTTP method is allowed to invoke them. This prevents most CSRF vulnerabilities in modern browsers, particularly with SameSite cookies being the default.
在后台,服务器操作使用 POST 方法,并且只允许此 HTTP 方法调用它们。这可以防止现代浏览器中的大多数 CSRF 漏洞,特别是 SameSite cookie 是默认设置的情况下。
As an additional protection, Server Actions in Next.js also compare the Origin header to the Host header (or X-Forwarded-Host). If these don't match, the request will be aborted. In other words, Server Actions can only be invoked on the same host as the page that hosts it.
作为额外的保护,Next.js 中的服务器操作还会将 Origin 标头与 Host 标头(或 X-Forwarded-Host )进行比较。如果两者不匹配,则会中止请求。换句话说,服务器操作只能在与托管它的页面相同的主机上调用。
For large applications that use reverse proxies or multi-layered backend architectures (where the server API differs from the production domain), it's recommended to use the configuration option serverActions.allowedOrigins option to specify a list of safe origins. The option accepts an array of strings.
对于使用反向代理或多层后端架构(其中服务器 API 与生产域不同)的大型应用程序,建议使用配置选项 serverActions.allowedOrigins 选项来指定安全来源列表。该选项接受一个字符串数组。
// next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}
5.Additional resources 其他资源
For more information on Server Actions, check out the following React docs:
有关服务器操作的更多信息,请查看以下 React 文档:
"use server"
<form>
useFormStatus
useFormState
useOptimistic