🐾

Next.js Server Actionsの負荷テストをしてみた

に公開

はじめに

Next.js の App Router で導入された Server Actions は、フォーム送信やサーバーサイドでの処理を簡潔に記述できる優れた機能です。(v15から明示的にServer Actionsという表現は無くなってます。)

しかし、本格的な運用環境でパフォーマンスを検証したい場合、Server Actions に対する負荷テストの情報がまだあまり出回っていないのが現状です。

私は1つのプロジェクトで、Next.jsでフロントエンドもバックエンドもフルスタックに開発しているのですが、こちらの負荷テストを行う必要がありました。

そこでこの記事では、実際に私が試行錯誤しながら確立した Server Actions の負荷テスト手法 を紹介します。

前提

前提として、今回は、Next.jsをフルスタックに開発した場合を想定しております。

別途APIを立てておらず、サーバー側は、Next.jsのServer Actions / Server Component / 'server-only'をつけたServer専用ファイルのみになります。

フルスタックNext.jsで負荷テストをするときのつまづき

従来の開発手法(バックエンドがAPIを立てて、フロントがそこに対してリクエストする)の場合、負荷テストをするには、APIに対してリクエストをかけていくのが良いかと思いますが、フルスタックでNext.jsを書いていると、APIを別途立てないので、直感的にAPIをいじめづらいです。(Route HandlersでAPIを立ててる場合を除く。)

Server Actionsはコードで見ると、ただの関数なので、従来の開発手法のように、単純にAPIにリクエストをすることができません、、

しかし、Server ActionsはPOST APIのエンドポイントになります。
なのでServer Actionsの記述からNext.jsが内部的にPOST APIのエンドポイントに対してリクエストをして負荷をかけることで、負荷テストができるということです。

では、Server ActionsのPOST APIに直接リクエストするには、どのようにリクエストすれば良いでしょうか、、

Server ActionsのPOST APIに直接リクエストする方法

ここがこの記事のメインになると思います。

Server Actions は内部的に POST API ということは分かりましたね。
しかし、エンドポイントは何か、リクエストボディはどうすればいいのか、などわからないことが多いです。

ですので、実際にServer Actionsの処理を動かしてみて、Chrome DevTools でリクエストの詳細することで、テストでリクエストするべき内容がわかります。

Chrome DevTools でリクエストの詳細確認手順

以下の手順で、リクエストの詳細を確認します。

  1. Chrome の DevTools を開く(F12)
  2. Network タブを選択
  3. 対象の Server Action を含むフォームを送信
  4. リクエストの詳細を確認

確認すべき項目

  • Request URL: Server Action のエンドポイント
  • Request Headers: 特に next-action ヘッダー
  • Request Body: multipart/form-data 形式のペイロード

例えば、ログインのServer Actionsだと、以下のようになります。

リクエストヘッダー

FormData

大事なところ

next-action

next-actionヘッダーは、Server Actionsを疎通するためのキーのようなものみたいです。これがないとリクエストしても弾かれます。

Content-type

multipart/form-dataになります。
form-dataの形式になるように、リクエストbodyの値を作成します。
Cursorとか使えば、form-dataの形式のリクエストbodyを作ってくれます。

負荷テスト手法の概要

上記を踏まえて、
Server Actions の負荷テストは以下の手順で行います:

  1. Chrome DevTools でリクエストの詳細を確認
  2. Postman で疎通確認
  3. k6 で負荷テストスクリプトを作成・実行

Step 1: Chrome DevTools でリクエスト詳細を確認

前述した通りの内容になります。

リクエストヘッダー(再掲)

FormData(再掲)

Step 2: Postman で疎通確認

DevTools で確認した内容を元に、Postman でリクエストの疎通確認を行います。

Headers 設定

Content-Type: multipart/form-data; boundary=----formdata-xxx
next-action: [DevToolsで確認した値]

Body 設定(form-data)

大体以下のようになってます。

1_email: test@example.com
1_password: password123
0: [ActionのメタデータJSON]

正常にレスポンスが返ってくることを確認できれば、実際に負荷テストをします。
Server Actionsの実装でisSucces: trueを返していてかつ、200が返ってきてるので、成功していることがわかります。

Step 3: k6 で負荷テストスクリプトを作成

k6 を使用して負荷テストスクリプトを作成します。

k6については以下の記事などが参考になります。javascriptで負荷テストコードが書けるツールです。かなり使いやすかったです。
https://zenn.dev/pharmax/articles/98ed49994cdaf2

負荷テストスクリプトの例

import http from 'k6/http';
import { check } from 'k6';

// ログインするテストユーザーの事前作成
export function setup() {
  // ユーザーを作成する処理を記載する。
}

// 負荷テスト設定
export const options = {
  stages: [
    { duration: '15s', target: 50 }, // ウォームアップ
    { duration: '30s', target: 100 }, // 負荷増加
    { duration: '30s', target: 100 }, // 維持
    { duration: '15s', target: 0 }, // クールダウン
  ],
};

export default function () {
  const boundary = __ENV.BOUNDARY;
  const nextAction = __ENV.NEXT_ACTION;
  const actionMetaData = __ENV.ACTION_META_DATA;

  const ENDPOINT = 'https://yourapp.com/login';
  const headers = {
    'Content-Type': `multipart/form-data; boundary=${boundary}`,
    'next-action': nextAction,
  };

  // multipart/form-data形式でリクエストボディを作成
  const formData = [
    `--${boundary}`,
    'Content-Disposition: form-data; name="1_email"',
    '',
    'test@example.com',
    `--${boundary}`,
    'Content-Disposition: form-data; name="1_password"',
    '',
    'password123',
    `--${boundary}`,
    'Content-Disposition: form-data; name="0"',
    '',
    actionMetaData,
    `--${boundary}--`,
  ].join('\r\n');

  const res = http.post(ENDPOINT, formData, { headers });

  check(res, {
    'HTTP status is 200': (r) => r.status === 200,
    'Server Action success': (r) => {
      // RSCストリーミングレスポンスから成功判定を抽出
      const match = r.body.match(/1:\s*(\{.*?"isSuccess".*?\})/);
      if (match) {
        const resultJson = JSON.parse(match[1]);
        return resultJson.isSuccess === true;
      }
      return false;
    },
  });
}

実行方法

k6 run login-load-test.js

こんな感じの結果

注意点とベストプラクティス

1. next-action ヘッダーの重要性

Server Actions では next-action ヘッダーが必須です。このヘッダーはビルド時に生成されるため、本番環境とテスト環境で値が異なる場合があります。

2. multipart/form-data の正確な形式

リクエストボディは正確な multipart/form-data 形式である必要があります。境界文字列や改行コード(\r\n)も重要です。

3. レスポンス形式の理解

Server Actions のレスポンスは RSC(React Server Components)のストリーミング形式で返されます。成功判定にはレスポンスボディのパースが必要です。
文字列型のjsonような感じになっております。

まとめ

Server Actions の負荷テストは、従来の REST API とは異なる特殊な形式を理解することが重要です。

  1. Chrome DevTools でリクエスト詳細を確認
  2. Postman で疎通確認
  3. k6 で負荷テストスクリプトを作成

この手順を踏むことで、Server Actions のパフォーマンス特性を正確に測定できます。

Next.js の Server Actions を本格運用する際の参考になれば幸いです。

参考資料

株式会社ソニックムーブ

Discussion