コンテンツへスキップ
アプリケーションの構築データフェッチサーバーアクションとミューテーション

サーバーアクションとミューテーション

Server Actionsは、サーバーで実行される**非同期関数**です。これらは、Next.js アプリケーションでフォームの送信やデータのミューテーションを処理するために、サーバーコンポーネントとクライアントコンポーネントの両方で呼び出すことができます。

🎥 動画: Server Actions を使用したミューテーションの詳細はこちら → YouTube (10分)

規約

Server Action は、React の "use server" ディレクティブを使用して定義できます。このディレクティブを `async` 関数の先頭に配置して関数を Server Action としてマークすることも、個別のファイルの先頭に配置してそのファイルのエクスポートすべてを Server Action としてマークすることもできます。

サーバーコンポーネント

サーバーコンポーネントは、インライン関数レベルまたはモジュールレベルの `"use server"` ディレクティブを使用できます。Server Action をインラインで記述するには、関数本体の先頭に `"use server"` を追加します。

app/page.tsx
export default function Page() {
  // Server Action
  async function create() {
    'use server'
    // Mutate data
  }
 
  return '...'
}

クライアントコンポーネント

クライアントコンポーネントで Server Action を呼び出すには、新しいファイルを作成し、そのファイルの先頭に `"use server"` ディレクティブを追加します。ファイル内のエクスポートされたすべての関数は、クライアントコンポーネントとサーバーコンポーネントの両方で再利用できる Server Action としてマークされます。

app/actions.ts
'use server'
 
export async function create() {}
app/button.tsx
'use client'
 
import { create } from './actions'
 
export function Button() {
  return <button onClick={() => create()}>Create</button>
}

propsとしてアクションを渡す

Server Action を Client Component に props として渡すこともできます。

<ClientComponent updateItemAction={updateItem} />
app/client-component.tsx
'use client'
 
export default function ClientComponent({
  updateItemAction,
}: {
  updateItemAction: (formData: FormData) => void
}) {
  return <form action={updateItemAction}>{/* ... */}</form>
}

通常、Next.js TypeScript プラグインは `client-component.tsx` 内の `updateItemAction` にフラグを付けます。これは、関数が通常クライアントとサーバーの境界を越えてシリアル化できないためです。しかし、`action` という名前または `Action` で終わる props は Server Actions を受け取ると仮定されます。これは TypeScript プラグインが実際に Server Action を受け取っているのか、それとも通常の関数を受け取っているのかを認識しないため、単なるヒューリスティックに過ぎません。ランタイムの型チェックは、誤って関数をクライアントコンポーネントに渡してしまわないように引き続き保証します。

動作

  • サーバーアクションは、<form> 要素の `action` 属性を使用して呼び出すことができます。
    • サーバーコンポーネントはデフォルトでプログレッシブエンハンスメントをサポートしており、JavaScript がまだロードされていない場合や無効になっている場合でもフォームが送信されます。
    • クライアントコンポーネントでは、Server Actions を呼び出すフォームは、JavaScript がまだロードされていない場合、クライアントのハイドレーションを優先して送信をキューに入れます。
    • ハイドレーション後、フォーム送信時にブラウザはリフレッシュされません。
  • Server Actions は `
    ` に限定されず、イベントハンドラー、`useEffect`、サードパーティライブラリ、および `
  • Server Actions は Next.js のキャッシングおよび再検証アーキテクチャと統合されています。アクションが呼び出されると、Next.js は更新された UI と新しいデータの両方を単一のサーバーラウンドトリップで返すことができます。
  • 内部では、アクションは `POST` メソッドを使用し、この HTTP メソッドのみがそれらを呼び出すことができます。
  • Server Actions の引数と戻り値は React によってシリアル化可能である必要があります。シリアル化可能な引数と値のリストについては、React のドキュメント (serializable arguments and values) を参照してください。
  • Server Actions は関数です。これは、アプリケーションのどこでも再利用できることを意味します。
  • Server Actions は、使用されるページまたはレイアウトからランタイムを継承します。
  • Server Actions は、使用されるページまたはレイアウトから `maxDuration` のようなフィールドを含むルートセグメント設定を継承します。

フォーム

React は HTML の <form> 要素を拡張し、`action` プロパティを使用して Server Actions を呼び出せるようにします。

フォーム内で呼び出されると、アクションは自動的に `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>
}

知っておくと良いこと

追加の引数を渡す

JavaScript の `bind` メソッドを使用して、Server Action に追加の引数を渡すことができます。

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

Server Action は、フォームデータに加えて `userId` 引数を受け取ります。

app/actions.ts
'use server'
 
export async function updateUser(userId: string, formData: FormData) {}

知っておくと良いこと:

  • 別の方法として、フォームの非表示入力フィールドとして引数を渡すこともできます (例: ``)。ただし、この値はレンダリングされた HTML の一部となり、エンコードされません。
  • `.bind` はサーバーコンポーネントとクライアントコンポーネントの両方で機能します。また、プログレッシブエンハンスメントもサポートしています。

ネストされたフォーム要素

Server Action は、`` 内にネストされた要素 (例: `

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

これにより、最も近い `` の祖先要素の送信がトリガーされ、Server Action が呼び出されます。

サーバーサイドのフォームバリデーション

基本的なクライアントサイドのフォームバリデーションには、`required` や `type="email"` などの HTML 属性を使用できます。

より高度なサーバーサイドバリデーションには、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
}

フィールドがサーバーで検証されたら、アクションでシリアル化可能なオブジェクトを返し、React の `useActionState` フックを使用してユーザーにメッセージを表示できます。

app/actions.ts
'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')
}

その後、アクションを `useActionState` フックに渡し、返された `state` を使用してエラーメッセージを表示できます。

app/ui/signup.tsx
'use client'
 
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
 
const initialState = {
  message: '',
}
 
export function Signup() {
  const [state, formAction, pending] = useActionState(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 disabled={pending}>Sign up</button>
    </form>
  )
}

保留中の状態

useActionState フックは、アクションの実行中にローディングインジケーターを表示するために使用できる `pending` (保留中) ブール値を公開します。

別の方法として、useFormStatus フックを使用して、アクションの実行中にローディングインジケーターを表示することもできます。このフックを使用する場合は、ローディングインジケーターをレンダリングするための別のコンポーネントを作成する必要があります。例えば、アクションが保留中のときにボタンを無効にするには、次のようにします。

app/ui/button.tsx
'use client'
 
import { useFormStatus } from 'react-dom'
 
export function SubmitButton() {
  const { pending } = useFormStatus()
 
  return (
    <button disabled={pending} type="submit">
      Sign Up
    </button>
  )
}

その後、`SubmitButton` コンポーネントをフォーム内にネストできます。

app/ui/signup.tsx
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
 
export function Signup() {
  return (
    <form action={createUser}>
      {/* Other form elements */}
      <SubmitButton />
    </form>
  )
}

知っておくと良いこと: React 19 では、`useFormStatus` は、データ、メソッド、アクションなど、返されるオブジェクトに追加のキーが含まれます。React 19 を使用していない場合は、`pending` キーのみが利用可能です。

楽観的更新

応答を待つ代わりに、React の useOptimistic フックを使用して、Server Action の実行が完了する前に 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[],
    string
  >(messages, (state, newMessage) => [...state, { message: newMessage }])
 
  const formAction = async (formData: 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>
  )
}

イベントハンドラー

Server Actionsは<form>要素内で使用されるのが一般的ですが、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>
    </>
  )
}

フォーム要素にイベントハンドラーを追加することもできます。例えば、フォームフィールドを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>
  )
}

このように、複数のイベントが立て続けに発生する可能性がある場合、不要なServer Actionの呼び出しを防ぐためにデバウンスすることをお勧めします。

useEffect

ReactのuseEffectフックを使用して、コンポーネントがマウントされたときや依存関係が変更されたときにServer Actionを呼び出すことができます。これは、グローバルイベントに依存する、または自動的にトリガーする必要があるミューテーションに役立ちます。例えば、アプリのショートカットのためのonKeyDown、無限スクロールのためのIntersection Observerフック、またはビュー数を更新するためにコンポーネントがマウントされたときなどです

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

useEffect動作と注意点を考慮することを忘れないでください。

エラーハンドリング

エラーがスローされると、クライアントの最も近いerror.jsまたは<Suspense>境界でキャッチされます。詳細については、「エラーハンドリング」を参照してください。

知っておくと良いこと

データの再検証

Server Actions内で、Next.jsキャッシュrevalidatePath APIで再検証できます

app/actions.ts
'use server'
 
import { revalidatePath } from 'next/cache'
 
export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }
 
  revalidatePath('/posts')
}

または、キャッシュタグを使用して特定のデータフェッチをrevalidateTagで無効化することもできます。

app/actions.ts
'use server'
 
import { revalidateTag } from 'next/cache'
 
export async function createPost() {
  try {
    // ...
  } catch (error) {
    // ...
  }
 
  revalidateTag('posts')
}

リダイレクト

Server Actionの完了後にユーザーを別のルートにリダイレクトしたい場合は、redirect APIを使用できます。redirecttry/catchブロックの外で呼び出す必要があります

app/actions.ts
'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
}

クッキー

Server Action内でcookies APIを使用してクッキーをgetsetdeleteできます

app/actions.ts
'use server'
 
import { cookies } from 'next/headers'
 
export async function exampleAction() {
  const cookieStore = await cookies()
 
  // Get cookie
  cookieStore.get('name')?.value
 
  // Set cookie
  cookieStore.set('name', 'Delba')
 
  // Delete cookie
  cookieStore.delete('name')
}

Server Actionからクッキーを削除するための追加の例を参照してください。

セキュリティ

デフォルトでは、Server Actionが作成・エクスポートされると、公開HTTPエンドポイントが生成され、同じセキュリティ仮定と認証チェックで扱われるべきです。これは、Server Actionやユーティリティ関数がコードの他の場所でインポートされていなくても、公開アクセス可能であることを意味します。

セキュリティを向上させるため、Next.jsには以下の組み込み機能があります。

知っておくと良いこと:

IDはコンパイル中に作成され、最大14日間キャッシュされます。新しいビルドが開始されるか、ビルドキャッシュが無効化されると、これらは再生成されます。このセキュリティ改善は、認証レイヤーが欠落している場合のリスクを軽減します。ただし、Server Actionは引き続き公開HTTPエンドポイントとして扱うべきです。

// app/actions.js
'use server'
 
// This action **is** used in our application, so Next.js
// will create a secure ID to allow the client to reference
// and call the Server Action.
export async function updateUserAction(formData) {}
 
// This action **is not** used in our application, so Next.js
// will automatically remove this code during `next build`
// and will not create a public endpoint.
export async function deleteUserAction(formData) {}

認証と認可

ユーザーがそのアクションを実行する権限を持っていることを確認する必要があります。例えば

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

クロージャと暗号化

コンポーネント内でServer Actionを定義すると、アクションが外部関数のスコープにアクセスできるクロージャが作成されます。例えば、publishアクションはpublishVersion変数にアクセスできます

app/page.tsx
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)をキャプチャし、アクションが呼び出されたときに後でそれを使用できるようにする場合に便利です。

ただし、これが機能するためには、アクションが呼び出されるときにキャプチャされた変数がクライアントに送信され、サーバーに戻されます。機密データがクライアントに公開されるのを防ぐため、Next.jsは自動的にクロージャ内の変数を暗号化します。Next.jsアプリケーションがビルドされるたびに、各アクションに対して新しい秘密鍵が生成されます。これは、アクションが特定のビルドに対してのみ呼び出せることを意味します。

知っておくと良いこと: クライアントに機密値が公開されるのを防ぐために、暗号化だけに頼ることは推奨しません。代わりに、特定のデータがクライアントに送信されるのを能動的に防ぐために、Reactの汚染APIを使用してください。

暗号化キーの上書き(高度)

Next.jsアプリケーションを複数のサーバーで自己ホストする場合、各サーバーインスタンスで異なる暗号化キーが使用され、潜在的な不整合につながる可能性があります。

これを緩和するため、process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY環境変数を使用して暗号化キーを上書きできます。この変数を指定することで、暗号化キーがビルド間で永続化され、すべてのサーバーインスタンスが同じキーを使用するようになります。この変数はAES-GCMで暗号化されている必要があります

これは、複数のデプロイメント間で一貫した暗号化動作がアプリケーションにとって不可欠となる高度なユースケースです。キーローテーションや署名などの標準的なセキュリティプラクティスを考慮すべきです。

知っておくと良いこと: VercelにデプロイされたNext.jsアプリケーションは、これを自動的に処理します。

許可されたオリジン(高度)

Server Actionは<form>要素内で呼び出すことができるため、CSRF攻撃に対して脆弱になります。

舞台裏では、Server ActionはPOSTメソッドを使用しており、このHTTPメソッドのみがそれらを呼び出すことが許可されています。これにより、特にSameSiteクッキーがデフォルトである現代のブラウザでは、ほとんどのCSRF脆弱性が防止されます。

追加の保護として、Next.jsのServer Actionは、OriginヘッダーHostヘッダー(またはX-Forwarded-Host)を比較します。これらが一致しない場合、リクエストは中止されます。言い換えれば、Server Actionはそれをホストするページと同じホスト上でのみ呼び出すことができます。

リバースプロキシや多層バックエンドアーキテクチャ(サーバーAPIが本番ドメインと異なる場合)を使用する大規模アプリケーションの場合、安全なオリジンのリストを指定するために、構成オプションserverActions.allowedOriginsを使用することをお勧めします。このオプションは文字列の配列を受け入れます。

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

セキュリティとServer Actionについてさらに詳しく学ぶ。

追加リソース

詳細については、以下のReactドキュメントを参照してください。

次のステップ

Next.jsでServer Actionを設定する方法を学ぶ