loneProgrammer

Server Actions In Next.js(v15)

NextJs 14から正式に導入されたServer Actionsは、サーバーサイドの非同期関数をクライアントサイドから直接呼び出すことを可能にする機能です。

セクション 概要
基本概念 Server Actionsの基本的な説明と機能
実装パターン 異なる実装方法とユースケース
セキュリティ考慮事項 最新のセキュリティ機能と注意点
実装例 具体的なコード例と解説

基本概念

  • 主な特徴
  • フォームハンドリングの簡素化
  • 従来のAPI実装の削減
  • シームレスなサーバー・クライアント連携
  • よくある誤解
  • 「クライアントサイドから実行する際、Server Actionsはレイヤーが1つ減るため高速」
  • これは誤りです。以下が実際の動作です:
  • クライアントからの実行時も内部的にはAPIリクエストが発生
  • デベロッパーツールのNetwork tabで確認可能
  • 実質的なレイヤー数は従来のAPI実装と同じ

セキュリティ強化(v15)

Server Actionsのセキュリティについて、重要な注意点があります。

クライアントに配信されるJavaScriptコード内には、Server Actionsの関数名とそれに紐づくIDの情報が含まれています。

このIDさえ特定できれば、攻撃者がServer Actionsの関数を不正に実行できてしまう可能性があります。Next.js 15ではこの対策として、IDの暗号化強化が行われ、IDは最大14日間キャッシュされる仕組みが導入されました。新しいビルド実行時やビルドキャッシュ無効化時には、IDは自動的に再生成されます。

  • 新機能
  • 未使用関数の検知
  • ビルド時に未使用のServer Actions関数を自動検出
  • 不要なエンドポイントの削除
  • ID管理の改善
  • IDの暗号化強化
  • 最大14日間のIDキャッシュ
  • ビルド時の自動無効化

実装パターン

  • インラインパターン
  • // app/page.tsx
    export default function Page() {
     async function sampleFnc(formData: FormData) {
      'use server'
      const search = formData.get('search');
      console.log(search)
      // --省略--
     }
     
     return(
      <form action={sampleFnc}>
       <input
        name='search'
        type='text'
        // ...
       />
      </form>
     )
    }
    
  • モジュール分離パターン
  • actions/index.ts
  • 'use server'
    
    async function sampleFnc(formData: FormData) {
     const search = formData.get('search');
     console.log(search)
     // --省略--
    }
    
  • src/app/page.tsx
  • import {sampleFnc} from '@/actions';
    
    export default function Page() {
     return(
      <form action={sampleFnc}>
       <input
        name='search'
        type='text'
        // ...
       />
      </form>
     )
    }
    

高度な実装パターン

  • useActionState活用パターン
  • actions/index.ts
  • 'use server'
    
    export const signIn = async (state: SignUpFormState, formData: FormData) => {
     const name = formData.get('name') as string;
     const password = formData.get('password') as string;
     --省略--
    }
    
  • components/SignIn.tsx
  • 'use client'
    import {signIn} from '@/actions';
    import { useActionState } from 'react-dom';
    
    const initialState = {
     error:'',
    };
    
    export default function SignIn() {
     const [state, formAction, isPending] = useActionState(signIn, initialState);
    
     return(
      <form action={formAction}>
       <input
        name='name'
        type='text'
       />
       <input
        name='password'
        type='password'
       />
       <button
        disabled={isPending}
        type="submit"
       >
        {isPending ? '・・Loading・・' : text}
       </button>
      </form>
     )
    }
    
  • bind + useActionStateパターン
  • actions/index.ts
  • 'use server' export const mailAuth = async (  typeValue: 'SignUp'|'SignIn',  state: MailAuthFormState,  formData: FormData ) => {  const authenticationPassword = formData.get('authenticationPassword') as string;  --省略--  if(typeValue==='SignUp'){   // --SignUpの処理--  }else{   // --SignInの処理--  } }
  • components/MailAuth.tsx
  • 'use client'
    import {mailAuth} from '@/actions';
    import { useActionState } from 'react-dom';
    
    const initialState = {
     error:'',
    };
    
    export default function MailAuth({
     typeValue,
    }:{
     typeValue: 'SignUp'|'SignIn',
    }) {
     const mailAuthWithTypeValue = mailAuth.bind(null, typeValue);
     const [state, formAction, isPending] = useActionState(mailAuthWithTypeValue, initialState);
    
     return(
      <form action={formAction}>
       <input
        name='name'
        type='text'
       />
       <input
        name='authenticationPassword'
        type='text'
       />
       <button
        disabled={isPending}
        type="submit"
       >
        {isPending ? '・Loading・' : text}
       </button>
      </form>
     )
    }
    

Server Actionsでbindを使用することで、フォームのサブミット時に追加のパラメータを渡すことができます。上記の例では、SignUpとSignInで同じフォームコンポーネントを使い回しながら、異なる処理を実現しています。これにより、コードの再利用性が高まり、型安全性も確保されます。

まとめ

Server Actionsは、Next.jsにおけるサーバー・クライアント間の通信を簡素化する強力な機能です。しかし、その利点を最大限に活用するためには、正しい理解と適切な実装が不可欠です。

Note: この記事の内容は Next.js v14,v15を対象としています。

↓ お勧め記事 ↓