Server Actions とデータ変更
Server Actions は 非同期関数 であり、サーバー上で実行されます。これらはServerおよびClient Componentsの中で、Next.jsアプリケーションでのフォーム送信とデータの変更を管理するために呼び出すことができます。
🎥 視聴: Server Actions を用いたデータ変更について詳しく学びましょう → YouTube (10 分)。
規約
Server Action はReactの"use server"
ディレクティブを用いて定義できます。非同期関数の上部にディレクティブを置いて、関数をServer Actionとしてマークするか、別ファイルの上部にディレクティブを置いて、そのファイルのすべてのエクスポートをServer Actionとしてマークすることができます。
Server Components
Server Components は、インライン関数レベルまたはモジュールレベルで"use server"
ディレクティブを使用できます。Server Actionをインラインで使うには、関数本体の上部に"use server"
を追加してください:
- TypeScript
- JavaScript
export default function Page() {
// Server Action
async function create() {
'use server'
// データを変更する
}
return '...'
}
export default function Page() {
// Server Action
async function create() {
'use server'
// データを変更する
}
return '...'
}
Client Components
Client ComponentでServer Actionを呼び出すには、新しいファイルを作成して、その上部に「use server」ディレクティブを追加します。そのファイル内でエクスポートされるすべての関数が、ClientとServer Componentの両方で再利用可能なServer Actionとしてマークされます。
- TypeScript
- JavaScript
'use server'
export async function create() {}
'use server'
export async function create() {}
- TypeScript
- JavaScript
'use client'
import { create } from '@/app/actions'
export function Button() {
return <button onClick={() => create()}>Create</button>
}
'use client'
import { create } from '@/app/actions'
export function Button() {
return <button onClick={() => create()}>Create</button>
}
アクションを props として渡す
Server Action を Client Component に props として渡すこともできます:
<ClientComponent updateItemAction={updateItem} />
- TypeScript
- JavaScript
'use client'
export default function ClientComponent({
updateItemAction,
}: {
updateItemAction: (formData: FormData) => void
}) {
return <form action={updateItemAction}>{/* ... */}</form>
}
'use client'
export default function ClientComponent({ updateItemAction }) {
return <form action={updateItemAction}>{/* ... */}</form>
}
通常、Next.js TypeScriptプラグインは client-component.tsx
の updateItemAction
を警告しますが、これは一般にクライアント側とサーバー側の境界を越えてシリアル化できない関数だからです。しかし、action
として名前付けされた props または Action
で終了する props は Server Actions を受け取ることが想定されています。これはあくまでヒューリスティックであり、TypeScriptプラグインは実際に Server Action を受け取っているか、通常の関数を受け取っているかはわかりません。ランタイム型チェックにより、間違って関数をClient Componentに渡さないようにします。
行動
- Server actionは、
<form>
要素 のaction
属性を使用して呼び出すことができます:- Server Componentsはデフォルトでプログレッシブエンハンスメントをサポートしており、JavaScriptがまだ読み込まれていない場合や無効になっている場合でもフォームが送信されます。
- Client Componentsでは、JavaScriptがまだ読み込まれていない場合、Server Actions を呼び出すフォームは送信をキューに入れ、クライアントのハイドレーションを優先します。
- ハイドレーション後、ブラウザはフォーム送信時にリフレッシュしません。
- Server Actionsは
<form>
に限定されず、イベントハンドラ、useEffect
、サードパーティのライブラリ、<button>
のような他のフォーム要素からも呼び出すことができます。 - Server Actionsは、Next.jsのキャッシングと再検証アーキテクチャと統合されています。アクションが呼び出されたとき、Next.jsは更新されたUIと新しいデータの両方を単一のサーバー・ラウンドトリップで返すことができます。
- アクションは内部で
POST
メソッドを使用し、このHTTPメソッドのみがそれらを呼び出すことができます。 - Server Actionsの引数と戻り値はReactによってシリアル化可能でなければなりません。シリアル化可能な引数と値のリストについては、Reactドキュメントを参照してください。serializable arguments and values.
- Server Actionsは関数です。つまり、アプリケーションのどこでも再利用可能です。
- Server Actionsは、それらが配置されているページまたはレイアウトからランタイムを継承します。
- Server Actionsは、それらが配置されているページまたはレイアウトからRoute Segment Configを継承し、
maxDuration
のようなフィールドを含みます。
例
フォーム
ReactはHTMLの<form>
要素を拡張し、action
プロップでServer Actionsを呼び出すことができます。
フォーム内で呼び出されると、アクションは自動的にFormData
オブジェクトを受け取ります。フィールドを管理するためにReactのuseState
を使用する必要はなく、ネイティブのFormData
メソッドを使用してデータを抽出できます:
- TypeScript
- JavaScript
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'),
}
// データを変更する
// キャッシュを再検証する
}
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'),
}
// データを変更する
// キャッシュを再検証する
}
return <form action={createInvoice}>...</form>
}
Good to know:
- 例:Form with Loading & Error States
- 多くのフィールドを持つフォームを使う場合、JavaScriptの
Object.fromEntries()
メソッドと共にentries()
メソッドを使用することを検討してください。例えば、const rawFormData = Object.fromEntries(formData)
となります。一点注意すべきは、formData
には追加の$ACTION_
プロパティが含まれることです。- 詳しくは、React
<form>
ドキュメントを参照してください。
追加の引数を渡す
Server Action に追加の引数を渡すには、JavaScriptのbind
メソッドを使用します。
- TypeScript
- JavaScript
'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>
)
}
Server Action はフォームデータに加えて userId
引数を受け取ります:
- TypeScript
- JavaScript
'use server'
export async function updateUser(userId: string, formData: FormData) {}
'use server'
export async function updateUser(userId, formData) {}
Good to know:
- 代替として、引数をフォームの隠し入力フィールドとして渡すことができます(例:
<input type="hidden" name="userId" value={userId} />
)。ただし、その値はレンダリングされたHTMLの一部になり、エンコードされません。.bind
はServerとClient Componentsの両方で機能します。それはまた、プログレッシブエンハンスメントをサポートしています。
ネストされたフォーム要素
Server Action を <form>
内にネストされた要素、例えば <button>
、<input type="submit">
、<input type="image">
で呼び出すこともできます。これらの要素は formAction
プロップまたはイベントハンドラを受け取ります。
これにより、フォーム内で複数のサーバーアクションを呼び出したい場合に便利です。例えば、投稿を公開する他に、下書きを保存するための特定の <button>
要素を作成できます。React <form>
のドキュメント で詳しく知ることができます。
プログラムによるフォーム送信
requestSubmit()
メソッドを使用して、フォーム送信をプログラムでトリガーできます。例えば、ユーザーが ⌘
+ Enter
キーボードショートカットを使用してフォームを送信する場合、onKeyDown
イベントをリッスンすることができます:
- TypeScript
- JavaScript
'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>
祖先の送信がトリガーされ、Server Action が呼び出されます。
サーバー側フォーム検証
基本的なクライアント側のフォーム検証には、required
や type="email"
のようなHTML属性を使用できます。
より高度なサーバー側の検証には、zod のようなライブラリを使用して、データを変更する前にフォームフィールドを検証することができます:
- TypeScript
- JavaScript
'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'),
})
// フォームデータが無効な場合は早期に終了する
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// データを変更する
}
'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'),
})
// フォームデータが無効な場合は早期に終了する
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// データを変更する
}
フィールドがサーバーで検証されたら、アクション内でシリアル化可能なオブジェクトを返し、ReactのuseFormState
フックを使用してユーザーにメッセージを表示できます。
- アクションを
useFormState
に渡すことで、アクションの関数シグネチャが変わり、最初の引数として新しいprevState
またはinitialState
パラメータを受け取るようになります。 useFormState
はReactのフックであるため、Client Componentで使用する必要があります。
- TypeScript
- JavaScript
'use server'
import { redirect } from 'next/navigation'
export async function createUser(prevState: any, formData: FormData) {
const res = await fetch('https://...')
const json = await res.json()
if (!res.ok) {
return { message: 'Please enter a valid email' }
}
redirect('/dashboard')
}
'use server'
import { redirect } from 'next/navigation'
export async function createUser(prevState, formData) {
const res = await fetch('https://...')
const json = await res.json()
if (!res.ok) {
return { message: 'Please enter a valid email' }
}
redirect('/dashboard')
}
次に、アクションを useFormState
フックに渡し、返された state
を使ってエラーメッセージを表示します。
- TypeScript
- JavaScript
'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">{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">{state?.message}</p>
<button>Sign up</button>
</form>
)
}
Good to know:
- これらの例は、Next.jsのApp RouterにバンドルされているReactの
useFormState
フックを使用します。React 19を使用している場合は、代わりにuseActionState
を使用してください。詳細は、Reactドキュメントを参照してください。
保留中の状態
- データを変更する前に、ユーザーがアクションを実行するための権限を持っていることを常に確認してください。認証と認可を参照してください。
useFormStatus
フックを使うと、アクションが実行されている間に読み込みインジケーターを表示するために pending
ブーリアンを利用できます。
- TypeScript
- JavaScript
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
Good to know:
- React 19では、
useFormStatus
は返されるオブジェクトに、データ、メソッド、アクションのようにさらに多くのキーを含みます。React 19を使用していない場合、pending
キーのみが利用可能です。- React 19では、
useActionState
も返される状態にpending
キーを含んでいます。
楽観的な更新
ReactのuseOptimistic
フックを使用して、Server Actionの実行が完了する前にUIを楽観的に更新し、レスポンスを待つことなく更新することができます:
- TypeScript
- JavaScript
'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 }])
const formAction = async (formData) => {
const message = formData.get('message') as string
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<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 }]
)
const formAction = async (formData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
イベントハンドラ
Server Actions を <form>
要素内で使用するのが一般的ですが、onClick
などのイベントハンドラーで呼び出すこともできます。たとえば、いいね数を増やす場合:
- TypeScript
- JavaScript
'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>
</>
)
}
以下のようにフォーム要素にもイベントハンドラーを追加できます:
'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>
)
}
このような場合には、不要なServer Actionの呼び出しを防ぐためにデバウンス処理をお勧めします。
useEffect
ReactのuseEffect
フックを使って、コンポーネントがマウントされたときや依存関係が変更されたときにServer Actionを呼び出すことができます。これは、グローバルイベントに依存する変更や自動的にトリガーされる必要のある変更に便利です。たとえば、アプリのショートカット用のonKeyDown
、無限スクロールの交差オブザーバーフック、または表示回数を更新するために、コンポーネントがマウントされたときに実行されるなど:
- TypeScript
- JavaScript
'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
の振る舞いと注意点を忘れずに考慮してください。
エラーハンドリング
エラーがスローされると、クライアント側で最も近い error.js
または <Suspense>
バウンダリによってキャッチされます。詳しくはエラーハンドリングを参照してください。
Good to know:
- エラーをスローする以外に、オブジェクトを
useFormState
で処理するために返すこともできます。サーバーサイドの検証とエラーハンドリングを参照してください。
データの再検証
revalidatePath
APIを使ってServer Actions内でNext.jsキャッシュを再検証することができます:
- TypeScript
- JavaScript
'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
APIを使用してください:
- TypeScript
- JavaScript
'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')
}
リダイレクト
Server Action の完了後にユーザーを別のルートにリダイレクトさせたい場合は、redirect
APIを使用できます。redirect
はtry/catch
ブロックの外側で呼ばれる必要があります:
- TypeScript
- JavaScript
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function createPost(id: string) {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts') // キャッシュされた投稿を更新する
redirect(`/post/${id}`) // 新しいポストページに移動
}
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function createPost(id) {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts') // キャッシュされた投稿を更新する
redirect(`/post/${id}`) // 新しいポストページに移動
}
Cookie
cookies
APIを使って、Server Action内でCookieを取得、設定、および削除できます:
- TypeScript
- JavaScript
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
const cookieStore = await cookies()
// Cookieを取得する
cookieStore.get('name')?.value
// Cookieを設定する
cookieStore.set('name', 'Delba')
// Cookieを削除する
cookieStore.delete('name')
}
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
// Cookieを取得する
const cookieStore = await cookies()
// Cookieを取得する
cookieStore.get('name')?.value
// Cookieを設定する
cookieStore.set('name', 'Delba')
// Cookieを削除する
cookieStore.delete('name')
}
サーバーアクションからCookieを削除するための追加の例を参照してください。
セキュリティ
デフォルトでは、Server Actionが作成されてエクスポートされると、パブリックなHTTPエンドポイントが作成され、 それに伴うセキュリティ仮説と認可チェックが組み込まれます。つまり、サーバーのアクションまたはユーティリティ関数があなたのコードの他の場所でインポートされていなくても、それはまだパブリックにアクセス可能です。
セキュリティを向上させるため、Next.jsには次の組み込み機能があります:
- セキュアなアクションID: Next.jsはクライアントがServer Actionを参照し呼び出すための暗号化された非同期IDを作成します。 これらのIDは、強化されたセキュリティのためにビルド間で定期的に再計算されます。
- デッドコードの除去: そのIDによって参照される未使用のServer Actionsは、サードパーティによるパブリックアクセスを避けるために クライアントバンドルから除去されます。
Good to know:
IDはコンパイル中に作成され、最長14日間キャッシュされます。それらは新しいビルドが開始されるたび、またはビルドキャッシュが無効になったときに再生成されます。このセキュリティの向上は、認証レイヤーが欠けている場合のリスクを低減します。ただし、あなたはServer ActionsをパブリックなHTTPエンドポイントのように取り扱う必要があります。
// app/actions.js
'use server'
// このアクションはアプリケーションで使用されているため、Next.jsは
// クライアントがServer Actionを参照し呼び出すことを可能にするための
// セキュアなIDを作成します。
export async function updateUserAction(formData) {}
// このアクションは私たちのアプリケーションで使用されていないため、Next.js
// は自動でビルド中にこのコードを削除し、パブリックなエンドポイントを
// 作成しません。
export async function deleteUserAction(formData) {}
認証と認可
ユーザーがアクションを実行する権限があることを確認する必要があります。例えば:
'use server'
import { auth } from './lib'
export function addItem() {
const { user } = auth()
if (!user) {
throw new Error('このアクションを実行するにはサインインする必要があります')
}
// ...
}
クロージャと暗号化
コンポーネント内でServer Actionを定義すると、アクションが外部関数のスコープにアクセスできるクロージャを作成します。たとえば、publish
アクションはpublishVersion
変数にアクセスできます:
- TypeScript
- JavaScript
export default async function Page() {
const publishVersion = await getLatestVersion();
async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('公開ボタンを押してからバージョンが変更されました。');
}
...
}
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('公開ボタンを押してからバージョンが変更されました。');
}
...
}
return (
<form>
<button formAction={publish}>Publish</button>
</form>
);
}
クロージャは特にレンダリング時点のデータのスナップショット(例えばpublishVersion
)をキャプチャして、後でアクションが呼び出されたときに使用するために便利です。
しかし、これが行われるためには、キャプチャされた変数はアクションが呼び出されたときクライアントに送信され、サーバーに送り返されることになります。機密データがクライアントに公開されないようにするため、Next.jsは自動的にクロージャされた変数を暗号化します。各アクションには、新しいプライベートキーがNext.jsアプリケーションがビルドされるたびに生成されます。これは、特定のビルドに対してのみアクションが呼び出されうることを意味します。
Good to know: 機密値がクライアントに公開されないようにするために、暗号化だけに依存することはお勧めしません。代わりに、React taint APIを使用して、特定のデータがクライアントに送信されるのを積極的に防ぐようにしてください。
暗号化キーの上書き(上級)
複数のサーバーにまたがってNext.jsアプリケーションをセルフホスティングする場合、各サーバーインスタンスが異なる暗号化キーを持つ可能性があり、その結果一貫性のない動作が発生する可能性があります。
これを防ぐには、process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
環境変数を使って暗号化キーを上書きすることができます。この変数を指定することで、暗号化キーの持続性がビルド間で保証され、すべてのサーバーインスタンスが同じキーを使用するようになります。
これは、あなたのアプリケーションにとって複数のデプロイにわたって一貫した暗号化の動作が重要である場合は上級のユースケースです。キーのローテーションや署名といった標準的なセキュリティプラクティスも考慮すべきです。
Good to know: VercelにデプロイされたNext.jsアプリケーションは自動的にこれを処理します。
許可されたオリジン(上級)
Server Actionsは<form>
要素内で呼び出せるため、CSRF攻撃にさらされることになります。
Server ActionsはPOST
メソッドを使用し、これがそれらを呼び出せる唯一のHTTPメソッドです。これによりモダンなブラウザでCSRFの脆弱性のほとんどが防止され、特にSameSite Cookieがデフォルトで有効です。
追加の保護として、Next.jsのServer ActionsはOrigin
ヘッダーとHost
ヘッダー(またはX-Forwarded-Host
)を比較します。これらが一致しない場合、リクエストは中断されます。つまり、Server Actionsはそれをホストしているページと同じホストでのみ呼び出すことができます。
リバースプロキシや多段階バックエンドアーキテクチャを使用する大規模なアプリケーション(サーバーAPIが本番環境のドメインと異なる場合)では、設定オプションとして
serverActions.allowedOrigins
オプションを使用して安全なオリジンのリストを指定することをお勧めします。このオプションは文字列の配列を受け入れます。
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}
セキュリティとServer Actionsについてさらに詳しく学びましょう。
追加資料
詳細については、以下のReactドキュメントを参照してください: