⛏️

Next.js(App Router)でMSWを使用する

2024/09/22に公開

tl;dr

コードを読めばわかるという人はこちらをご参照のほど。
https://github.com/code-application/front-skeleton-nextjs

前提知識

MSWとは

https://mswjs.io/
APIをモックするためのライブラリ。
WebアプリケーションにはWeb Workerという仕組みがあり、UIとは別スレッドで動いてバックグラウンド処理を行う。その1つがブラウザ・サーバー間の通信をプロキシするService Workerであり、MSWはこの仕組みを使ってブラウザからの通信を受け取りダミーの値を返す。

Instrumentation

Next.jsでは、監視ツールなどのミドルウェアを起動するためにInstrumentationの機能を使用することができる。
気をつけることとしては2点ある。

参考リンク

Next.jsのランタイム

Next.jsのアプリケーション内では、Node.jsとEdgeの2つのランタイムを認識できる。(公式ドキュメント
MSWでサーバーコンポーネント側のAPI通信をモックするモジュールはEdgeランタイムでは動作しないため、設定する際にこれを考慮した実装にする必要がある。

手順

基本的にはMSWの公式ドキュメントに従う。
この辺この辺も見ると良いかも。

まずMSWをインストールする。(npmやyarnなど他のパッケージマネージャーのコマンドは割愛。)

pnpm add --save-dev msw

mswコマンドを使ってService Workerの登録をする。(コマンド提供してくれてるのむちゃくちゃ助かる...)
クライアントコンポーネント側のモックをする場合は必須。

pnpm exec msw init public --save

これでpublicディレクトリにmockServiceWorker.jsというファイルができるとともに、package.jsonに以下の記述が増えているはず。

package.json
  {
    ...
    "devDependencies": {
+     "msw": "^2.3.5",
    ...
+   "msw": {
+     "workerDirectory": ["public"]
+   }
  }

次に、モックの定義をする。

src/lib/msw/handlers/index.ts
import { http, HttpResponse, type RequestHandler } from "msw";

const helloHandler = http.get("https://example.com/hello", () => {
  return HttpResponse.json({
    message: "Hello, world!",
  });
});

export const handlers: RequestHandler[] = [helloHandler];

そしてモックを起動するスクリプトを書く。

src/lib/msw/setup/browser.ts(クライアントコンポーネント側)
import { setupWorker } from "msw/browser";
import { handlers } from "../handlers";

export const worker = setupWorker(...handlers);
src/lib/msw/setup/server.ts(サーバーコンポーネント側)
import { setupServer } from "msw/node";
import { handlers } from "../handlers";

export const server = setupServer(...handlers);
src/lib/msw/setup/index.ts(起動スクリプト)
async function initMocks() {
  if (typeof window === "undefined") {
    const { server } = await import("./server");
    server.listen();
  } else {
    const { worker } = await import("./browser");
    worker.start();
  }
}

export { initMocks };
src/lib/msw/index.ts
export { initMocks } from "./setup";

最後に、Instrumentationの設定をする。

next.config.mjs
  /** @type {import('next').NextConfig} */
  const nextConfig = {
+   experimental: {
+     instrumentationHook: true,
+   },
  };
src/instrumentation.ts
export async function register() {
  // "msw/node"がNode.jsランタイムでのみ利用可能(=Edgeランタイムで利用不可)
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { initMocks } = await import("./lib/msw");
    initMocks();
  }
}

これで設定は以上。🎉
あとはアプリケーション内でfetch APIを使えばモックが返ってくる。

この記事を執筆するモチベーション

Next.js+〇〇ライブラリという組み合わせのリポジトリを公式がたくさん公開しているが、執筆時点ではPages Router用のコードしかなくて困ったので。。。

Discussion