Skip to main content

服务器操作和突变

服务器操作是在服务器上执行的异步函数。它们可以在服务器和客户端组件中使用,以处理 Next.js 应用中的表单提交和数据突变。

¥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.

🎥 监视:通过服务器操作了解有关形式和突变的更多信息 → YouTube(10 分钟)

¥🎥 Watch: Learn more about forms and mutations with Server Actions → YouTube (10 minutes).

惯例

¥Convention

可以使用 React "use server" 指令定义服务器操作。你可以将该指令放置在 async 函数的顶部以将该函数标记为服务器操作,或者放置在单独文件的顶部以将该文件的所有导出标记为服务器操作。

¥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.

服务器组件

¥Server Components

服务器组件可以使用内联函数级别或模块级别 "use server" 指令。要内联服务器操作,请将 "use server" 添加到函数体的顶部:

¥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:

// Server Component
export default function Page() {
// Server Action
async function create() {
'use server'

// ...
}

return (
// ...
)
}
// Server Component
export default function Page() {
// Server Action
async function create() {
'use server'

// ...
}

return (
// ...
)
}

客户端组件

¥Client Components

客户端组件只能导入使用模块级 "use server" 指令的操作。

¥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'

export async function create() {
// ...
}
'use server'

export async function create() {
// ...
}
import { create } from '@/app/actions'

export function Button() {
return (
// ...
)
}
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} />
'use client'

export default function ClientComponent({ updateItem }) {
return <form action={updateItem}>{/* ... */}</form>
}

行为

¥Behavior

  • 可以使用 <form> 元素 中的 action 属性来调用服务器操作:

    ¥Server actions can be invoked using the action attribute in a <form> element:

    • 服务器组件默认支持渐进增强,这意味着即使 JavaScript 尚未加载或被禁用,表单也会被提交。

      ¥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.

    • 水合后,浏览器在提交表单时不会刷新。

      ¥After hydration, the browser does not refresh on form submission.

  • 服务器操作不限于 <form>,可以从事件处理程序、useEffect、第三方库和其他表单元素(如 <button>)调用。

    ¥Server Actions are not limited to <form> and can be invoked from event handlers, useEffect, third-party libraries, and other form elements like <button>.

  • 服务器操作与 Next.js 缓存和重新验证 架构集成。调用操作时,Next.js 可以在单个服务器往返中返回更新的 UI 和新数据。

    ¥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.

  • 在幕后,操作使用 POST 方法,并且只有此 HTTP 方法可以调用它们。

    ¥Behind the scenes, actions use the POST method, and only this HTTP method can invoke them.

  • 服务器操作的参数和返回值必须可由 React 序列化。请参阅 React 文档以获取 可序列化的参数和值 的列表。

    ¥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.

  • 服务器操作是函数。这意味着它们可以在你的应用中的任何位置重复使用。

    ¥Server Actions are functions. This means they can be reused anywhere in your application.

  • 服务器操作从它们所使用的页面或布局继承 runtime

    ¥Server Actions inherit the runtime from the page or layout they are used on.

  • 服务器操作从它们所使用的页面或布局继承 路由片段配置,包括 maxDuration 等字段。

    ¥Server Actions inherit the Route Segment Config from the page or layout they are used on, including fields like maxDuration.

示例

¥Examples

表单

¥Forms

React 扩展了 HTML <form> 元素,以允许使用 action 属性调用服务器操作。

¥React extends the HTML <form> element to allow Server Actions to be invoked with the action prop.

当在表单中调用时,该操作会自动接收 FormData 对象。你不需要使用 React useState 来管理字段,而是可以使用原生 FormData 方法 提取数据:

¥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:

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>
}
export default function Page() {
async function createInvoice(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:

传递附加参数

¥Passing Additional Arguments

你可以使用 JavaScript bind 方法将其他参数传递给服务器操作。

¥You can pass additional arguments to a Server Action using the JavaScript bind method.

'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>
)
}
'use client'

import { updateUser } from './actions'

export function UserProfile({ userId }) {
const updateUserWithId = updateUser.bind(null, userId)

return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}

除了表单数据之外,服务器操作还将接收 userId 参数:

¥The Server Action will receive the userId argument, in addition to the form data:

'use server'

export async function updateUser(userId, formData) {
// ...
}

很高兴知道:

¥Good to know:

  • 另一种方法是将参数作为表单中的隐藏输入字段传递(例如 <input type="hidden" name="userId" value={userId} />)。但是,该值将成为渲染的 HTML 的一部分,并且不会被编码。

    ¥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.

  • .bind 适用于服务器和客户端组件。它还支持渐进增强。

    ¥.bind works in both Server and Client Components. It also supports progressive enhancement.

待定状态

¥Pending states

你可以使用 React useFormStatus 钩子在提交表单时显示待处理状态。

¥You can use the React useFormStatus hook to show a pending state while the form is being submitted.

  • useFormStatus 返回特定 <form> 的状态,因此必须将其定义为 <form> 元素的子元素。

    ¥useFormStatus returns the status for a specific <form>, so it must be defined as a child of the <form> element.

  • useFormStatus 是一个 React hook,因此必须在客户端组件中使用。

    ¥useFormStatus is a React hook and therefore must be used in a Client Component.

'use client'

import { useFormStatus } from 'react-dom'

export function SubmitButton() {
const { pending } = useFormStatus()

return (
<button type="submit" disabled={pending}>
Add
</button>
)
}
'use client'

import { useFormStatus } from 'react-dom'

export function SubmitButton() {
const { pending } = useFormStatus()

return (
<button type="submit" disabled={pending}>
Add
</button>
)
}

然后 <SubmitButton /> 可以以任何形式嵌套:

¥<SubmitButton /> can then be nested in any form:

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>
)
}
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

我们建议使用 requiredtype="email" 等 HTML 验证来进行基本的客户端表单验证。

¥We recommend using HTML validation like required and type="email" for basic client-side form validation.

对于更高级的服务器端验证,你可以使用像 zod 这样的库在更改数据之前验证表单字段:

¥For more advanced server-side validation, you can use a library like zod to validate the form fields before mutating the data:

'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
}
'use server'

import { z } from 'zod'

const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})

export default async function createsUser(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
}

一旦字段在服务器上得到验证,你就可以在操作中返回一个可序列化的对象,并使用 React useFormState 钩子向用户显示消息。

¥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.

  • 通过将操作传递给 useFormState,操作的函数签名将更改为接收新的 prevStateinitialState 参数作为其第一个参数。

    ¥By passing the action to useFormState, the action's function signature changes to receive a new prevState or initialState parameter as its first argument.

  • useFormState 是一个 React hook,因此必须在客户端组件中使用。

    ¥useFormState is a React hook and therefore must be used in a Client Component.

'use server'

export async function createUser(prevState: any, formData: FormData) {
// ...
return {
message: 'Please enter a valid email',
}
}
'use server'

export async function createUser(prevState, formData) {
// ...
return {
message: 'Please enter a valid email',
}
}

然后,你可以将操作传递给 useFormState 钩子并使用返回的 state 显示错误消息。

¥Then, you can pass your action to the useFormState hook and use the returned state to display an error message.

'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>
)
}
'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

你可以使用 React useOptimistic 钩子在服务器操作完成之前乐观地更新 UI,而不是等待响应:

¥You can use the React useOptimistic hook to optimistically update the UI before the Server Action finishes, rather than waiting for the response:

'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[],
string
>(messages, (state, newMessage) => [...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>
)
}
'use client'

import { useOptimistic } from 'react'
import { send } from './actions'

export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage }]
)

return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form
action={async (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

你可以在嵌套在 <form> 内的元素(例如 <button><input type="submit"><input type="image">)中调用服务器操作。这些元素接受 formAction 属性或 事件处理程序

¥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.

当你想要在表单中调用多个服务器操作时,这非常有用。例如,你可以创建一个特定的 <button> 元素来保存帖子草稿以及发布它。请参阅 React <form> 文档 了解更多信息。

¥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.

程序化表单提交

¥Programmatic form submission

你可以使用 requestSubmit() 方法触发表单提交。例如,当用户按下 + Enter 时,可以监听 onKeyDown 事件:

¥You can trigger a form submission using the requestSubmit() method. For example, when the user presses + Enter, you can listen for the onKeyDown event:

'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>
)
}
'use client'

export function Entry() {
const handleKeyDown = (e) => {
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>
)
}

这将触发最近的 <form> 祖级的提交,这将调用服务器操作。

¥This will trigger the submission of the nearest <form> ancestor, which will invoke the Server Action.

非形式元素

¥Non-form Elements

虽然在 <form> 元素中使用服务器操作很常见,但也可以从代码的其他部分(例如事件处理程序和 useEffect)调用它们。

¥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.

事件处理程序

¥Event Handlers

你可以从事件处理程序(例如 onClick)调用服务器操作。例如,要增加相似计数:

¥You can invoke a Server Action from event handlers such as onClick. For example, to increment a like count:

'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>
</>
)
}
'use client'

import { incrementLike } from './actions'
import { useState } from 'react'

export default function LikeButton({ initialLikes }) {
const [likes, setLikes] = useState(initialLikes)

return (
<>
<p>Total Likes: {likes}</p>
<button
onClick={async () => {
const updatedLikes = await incrementLike()
setLikes(updatedLikes)
}}
>
Like
</button>
</>
)
}

为了改善用户体验,我们建议使用其他 React API(例如 useOptimisticuseTransition)在服务器操作完成在服务器上执行之前更新 UI,或者显示待处理状态。

¥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.

你还可以向表单元素添加事件处理程序,例如保存表单字段 onChange

¥You can also add event handlers to form elements, for example, to save a form field onChange:

'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

当组件安装或依赖更改时,你可以使用 React useEffect 钩子来调用服务器操作。这对于依赖全局事件或需要自动触发的突变非常有用。例如,onKeyDown 用于应用快捷方式,用于无限滚动的交叉观察者钩子,或者当组件安装以更新视图计数时:

¥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:

'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>
}
'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>
}

请记住考虑 行为和注意事项useEffect

¥Remember to consider the behavior and caveats of useEffect.

错误处理

¥Error Handling

当抛出错误时,客户端上最近的 error.js<Suspense> 边界将捕获该错误。我们建议使用 try/catch 返回错误以供你的 UI 处理。

¥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.

例如,你的服务器操作可能会通过返回消息来处理创建新项目时出现的错误:

¥For example, your Server Action might handle errors from creating a new item by returning a message:

'use server'

export async function createTodo(prevState: any, formData: FormData) {
try {
// Mutate data
} catch (e) {
throw new Error('Failed to create task')
}
}
'use server'

export async function createTodo(prevState, formData) {
try {
// Mutate data
} catch (e) {
throw new Error('Failed to create task')
}
}

很高兴知道:

¥Good to know:

重新验证数据

¥Revalidating data

你可以使用 revalidatePath API 重新验证服务器操作中的 Next.js 缓存

¥You can revalidate the Next.js Cache inside your Server Actions with the revalidatePath API:

'use server'

import { revalidatePath } from 'next/cache'

export async function createPost() {
try {
// ...
} catch (error) {
// ...
}

revalidatePath('/posts')
}
'use server'

import { revalidatePath } from 'next/cache'

export async function createPost() {
try {
// ...
} catch (error) {
// ...
}

revalidatePath('/posts')
}

或者使用 revalidateTag 使带有缓存标记的特定数据获取无效:

¥Or invalidate a specific data fetch with a cache tag using revalidateTag:

'use server'

import { revalidateTag } from 'next/cache'

export async function createPost() {
try {
// ...
} catch (error) {
// ...
}

revalidateTag('posts')
}
'use server'

import { revalidateTag } from 'next/cache'

export async function createPost() {
try {
// ...
} catch (error) {
// ...
}

revalidateTag('posts')
}

重定向

¥Redirecting

如果你想在服务器操作完成后将用户重定向到不同的路由,你可以使用 redirect API。redirect 需要在 try/catch 块之外调用:

¥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:

'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
}
'use server'

import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'

export async function createPost(id) {
try {
// ...
} catch (error) {
// ...
}

revalidateTag('posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}

Cookies

你可以使用 cookies API 在服务器操作中设置 getsetdelete cookie:

¥You can get, set, and delete cookies inside a Server Action using the cookies API:

'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')
}
'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')
}

请参阅 附加示例 以了解从服务器操作中删除 cookie。

¥See additional examples for deleting cookies from Server Actions.

安全

¥Security

认证与授权

¥Authentication and authorization

你应该像对待面向公众的 API 端点一样对待服务器操作,并确保用户有权执行该操作。例如:

¥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:

'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')
}

// ...
}

闭包和加密

¥Closures and encryption

在组件内部定义服务器操作会创建一个 closure,其中该操作可以访问外部函数的范围。例如,publish 操作可以访问 publishVersion 变量:

¥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:

export default async function Page() {
const publishVersion = await getLatestVersion();

async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('The version has changed since pressing publish');
}
...
}

return (
<form>
<button formAction={publish}>Publish</button>
</form>
);
}
export default async function Page() {
const publishVersion = await getLatestVersion();

async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('The version has changed since pressing publish');
}
...
}

return (
<form>
<button formAction={publish}>Publish</button>
</form>
);
}

当你需要在渲染时捕获数据快照(例如 publishVersion)以便稍后在调用操作时使用它时,闭包非常有用。

¥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.

然而,为了实现这一点,捕获的变量将被发送到客户端,并在调用操作时返回到服务器。为了防止敏感数据暴露给客户端,Next.js 自动对封闭变量进行加密。每次构建 Next.js 应用时,都会为每个操作生成一个新的私钥。这意味着只能针对特定构建调用操作。

¥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.

很高兴知道:我们不建议仅依靠加密来防止敏感值暴露在客户端上。相反,你应该使用 React 污点 API 主动阻止特定数据发送到客户端。

¥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.

覆盖加密密钥(高级)

¥Overwriting encryption keys (advanced)

当跨多个服务器自托管 Next.js 应用时,每个服务器实例最终可能会使用不同的加密密钥,从而导致潜在的不一致。

¥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.

为了缓解这种情况,你可以使用 process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY 环境变量覆盖加密密钥。指定此变量可确保你的加密密钥在各个版本中保持不变,并且所有服务器实例都使用相同的密钥。

¥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.

这是一个高级用例,其中跨多个部署的一致加密行为对于你的应用至关重要。你应该考虑标准安全实践,例如密钥轮换和签名。

¥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.

很高兴知道:部署到 Vercel 的 Next.js 应用会自动处理此问题。

¥Good to know: Next.js applications deployed to Vercel automatically handle this.

允许的来源(高级)

¥Allowed origins (advanced)

由于服务器操作可以在 <form> 元素中调用,因此这将它们打开到 CSRF 攻击

¥Since Server Actions can be invoked in a <form> element, this opens them up to CSRF attacks.

在幕后,服务器操作使用 POST 方法,并且只有此 HTTP 方法才允许调用它们。这可以防止现代浏览器中的大多数 CSRF 漏洞,特别是在 同站点 cookies 为默认浏览器的情况下。

¥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.

作为额外的保护,Next.js 中的服务器操作还会将 来源标头主机头(或 X-Forwarded-Host)进行比较。如果这些不匹配,请求将被中止。换句话说,服务器操作只能在与承载它的页面相同的主机上调用。

¥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.

对于使用反向代理或多层后端架构(其中服务器 API 与生产域不同)的大型应用,建议使用配置选项 serverActions.allowedOrigins 选项来指定安全来源列表。该选项接受字符串数组。

¥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.

/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}

了解有关 安全和服务器操作 的更多信息。

¥Learn more about Security and Server Actions.

其他资源

¥Additional resources

有关服务器操作的更多信息,请查看以下 React 文档:

¥For more information on Server Actions, check out the following React docs: