こんにちは、サイオステクノロジーの遠藤です。
前回はReact Router v7でMSWを利用する方法を整理しました。
今回はNext.jsのApp routerでMSW(Mock Service Worker)を利用する方法について整理していきます。
はじめに
今回の内容は以下のDraft状態のmsw exampleのPull requestを参考に作成しています。
https://github.com/mswjs/examples/pull/101
記事執筆を行っている2025/03の段階ではDraftかつ、Next.js側の問題でブラウザ側でのHMR(Hot Module Replacement) の問題があることが以下のissueで言及されています。
https://github.com/mswjs/msw/issues/1644#issuecomment-2433234922
時間が経てば状況が変わることが予想されますので、そのときには本記事は参考程度にしていただき、上記のPull requestやissueの内容を確認していただければと思います。
MSWの準備を行う
MSWを使用するために各種準備を行っていきます。Next.jsアプリは用意済みという前提で進めていきます。
MSWをインストールする
最初にMSWのインストールを行います。
npm install msw -D
mockServiceWorker.jsをコピー
MSWでは、worker scriptをアプリケーションのpulbicディレクトリに配置することでクライアント上でリクエストをキャッチできるようになります。こちらは以下の形でMSWのCLIを利用することで自分のディレクトリにworkerscriptである mockServiceWorker.jsがpublic直下に作成されます。
npx msw init ./public --save
統合モジュールを用意する
server componentsとclient componentsで利用する統合モジュールを用意していきます。これらのファイルを配置するディレクトリとしてプロジェクトのrootディレクトリにmocksというディレクトリを用意してその中にファイルを配置していきます。
MSWのhandlersを定義する
mockの内容となるMSWのhandlersをmocks/handlers.tsに定義します。
// mocks/handlers.ts
import { graphql, http, HttpResponse } from "msw";
export type User = {
firstName: string;
lastName: string;
};
export type Movie = {
id: string;
title: string;
};
export const handlers = [
http.get<never, never, User>("<https://api.example.com/user>", () => {
return HttpResponse.json({
firstName: "Sarah",
lastName: "Maverick",
});
}),
graphql.query<{ movies: Array<Movie> }>("ListMovies", () => {
return HttpResponse.json({
data: {
movies: [
{
id: "6c6dba95-e027-4fe2-acab-e8c155a7f0ff",
title: "123 Lord of The Rings",
},
{
id: "a2ae7712-75a7-47bb-82a9-8ed668e00fe3",
title: "The Matrix",
},
{
id: "916fa462-3903-4656-9e76-3f182b37c56f",
title: "Star Wars: The Empire Strikes Back",
},
],
},
});
}),
];
browser用の統合モジュールを作成する
browser用(client component用)の統合モジュールをmocks/browser.tsに定義します。
// mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
node.js用の統合モジュールを作成する
node.js用(server component用)の統合モジュールをmocks/node.jsに定義します。
// mocks/node.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
App routerでMSWを使用する
ここからはapp以下で作業を進めていきます。
MSW用のProviderを作成する
layout.tsxでMSW用のProviderをapp/mswProvider.tsxに定義します。このProviderで囲うことで、このProvider内のclient componentについてMSWを利用することが出来るようになります。
// app/mswProvider.tsx
"use client";
import { handlers } from "@/mocks/handlers";
import { Suspense, use } from "react";
const mockingEnabledPromise =
typeof window !== "undefined" && process.env.NODE_ENV === "development"
? import("@/mocks/browser").then(async ({ worker }) => {
await worker.start({
onUnhandledRequest(request, print) {
if (request.url.includes("_next")) {
return;
}
print.warning();
},
});
worker.start();
console.log(worker.listHandlers());
})
: Promise.resolve();
export function MSWProvider({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
// If MSW is enabled, we need to wait for the worker to start,
// so we wrap the children in a Suspense boundary until it's ready.
return (
<Suspense fallback={null}>
<MSWProviderWrapper>{children}</MSWProviderWrapper>
</Suspense>
);
}
function MSWProviderWrapper({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
use(mockingEnabledPromise);
return children;
}
layout.tsxでnode.js用の統合モジュールの呼び出しとmsw用のProviderでchildrenをラップする
続いてlayout.tsxにてserver側(Runtimeがnode.js)でMSWが利用できるようにします。また、先ほど作成したmsw用のProviderでchildrenをラップします。ここまで終われば準備完了です!
// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { MSWProvider } from "./mswProvider";
if (
process.env.NEXT_RUNTIME === "nodejs" &&
process.env.NODE_ENV === "development"
) {
const { server } = await import("@/mocks/node");
server.listen();
}
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<MSWProvider>{children}</MSWProvider>
</body>
</html>
);
}
server component上でfetchを行ってみる
ではまずはserver component上でfetchを行ってみましょう。app/page.tsxでhandlerで定義したapiを呼び出してみましょう。
// app/page.tsx
import { User } from "@/mocks/handlers";
async function getUser() {
const response = await fetch("<https://api.example.com/user>");
const user = (await response.json()) as User;
return user;
}
export default async function Home() {
const user = await getUser();
return (
<main>
<p id="server-side-greeting">Hello, {user.firstName}!</p>
</main>
);
}
開発モードで起動して動作を確認してみると、handlerで定義したレスポンスが取れていることが確認できました!

client component上でfetchを行ってみる
続いてclient component上でfetchを行ってみます。client componentとして実行されるように”use client”をつけてapp/movieList.tsxを作成します。
// app/movieList.tsx
"use client";
import { Movie } from "@/mocks/handlers";
import { useState } from "react";
export function MovieList() {
const [movies, setMovies] = useState<Array<Movie>>([]);
const fetchMovies = () => {
fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query ListMovies {
movies {
id
title
}
}
`,
}),
})
.then((response) => response.json())
.then((response) => {
setMovies(response.data.movies);
})
.catch(() => setMovies([]));
};
return (
<div>
<button id="fetch-movies-button" onClick={fetchMovies}>
Fetch movies
</button>
{movies.length > 0 ? (
<ul id="movies-list">
{movies.map((movie) => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
) : null}
</div>
);
}
page.tsxでmovieList.tsxを呼び出す
そしたら作成したmovieList.tsxをpage.tsxに加えて動作を確認してみましょう。
// app/page.tsx
import { User } from "@/mocks/handlers";
import { MovieList } from "./movieList";
async function getUser() {
const response = await fetch("<https://api.example.com/user>");
const user = (await response.json()) as User;
return user;
}
export default async function Home() {
const user = await getUser();
return (
<main>
<p id="server-side-greeting">Hello, {user.firstName}!</p>
<MovieList />
</main>
);
}
ページを表示して 「Fetch movies」をクリックするとしっかりとclient component上でもfetch出来ていることを確認できました!

まとめ
今回はNext.jsのApp routerでMSWを利用する方法についてまとめました。「はじめに」にも書きましたが、今回紹介した内容はDraftのPull requestを参考にさせていただいたものになっているので、本記事に関しては参考程度にしていただくのが良いかと思います。
ではまた~