お疲れ様です。SJC共同開発推進室の境田です。
今回は社内プロジェクトに Storybook と MSW(Mock Service Workers) を導入した際の手順と、遭遇したエラーおよび対処法をまとめました。同様の構成(React 18, TypeScript, MUI, Vite など)で開発している方の参考になれば幸いです。
はじめに
React 18 / Vite / MUI / TypeScript ベースのプロジェクトにて、Storybook + MSW(Mock Service Workers)を導入した際の手順と、実際に遭遇したトラブル・落とし穴を整理しました。
単純に「動く Story を作る」だけでなく、既存システムの構造や型制約との整合を取るうえで重要だったポイントを共有します。
Storybook と MSW とは?
Storybook とは?
Storybook は、React コンポーネントなど UI コンポーネントを独立して開発・検証・ドキュメント化できる開発支援ツールです。アプリ全体を立ち上げなくても、個々の UI を単体で表示・テストできるのが大きなメリットです。
MSW(Mock Service Workers)とは?
MSW は、API 通信をモックするライブラリです。Service Worker を利用して、フロントエンド側からの HTTP リクエストに対して、任意のレスポンスを返せるようになります。実際の API を使わずにフロントエンドを開発・検証できる点が特長です。
なぜ導入したのか?
今回 Storybook と MSW を導入した背景は以下の通りです:
- 開発中のバックエンド API が未実装でも、UI コンポーネントの動作確認やレビューを可能にしたい
 - 実際の API 仕様に基づいたモックで、UI の挙動・状態変化を事前に検証したい
 - フロントエンドエンジニア間でのデザインレビューや QA の効率化を図りたい
 
Storybook + MSW を使うことで、API レスポンスの制御が容易になり、さまざまな UI 状態を再現した Story の作成が可能になります。
前提環境
- React: 18.2.0
 - TypeScript: 5.3.3
 - Vite
 - Material UI: @mui/material(v5)
 - TanStack Query
 - Storybook 7.x
 - WSL2 + Ubuntu(VSCode Remote)
 
Step 1: Storybook 初期化と依存エラーの対応
| 
					 1 2  | 
						npm create storybook@latest  | 
					

初期化コマンドで自動生成される設定には、プロジェクトによって依存関係の衝突や非対応エラーが発生することがあります。
主な依存関係トラブル
| ライブラリ | 問題 | 解決策 | 
|---|---|---|
@material-ui/core@4.x | 
React 18 に非対応 | @mui/material(v5)に移行 | 
@testing-library/react-hooks | 
React 18 に非対応 | 削除し @testing-library/react に統合 | 
react-apexcharts | 
apexcharts バージョン不一致 | 
apexcharts@^4.0.0 を手動指定 | 
Step 2: Storybook + MSW を使って API をモック
以下は、Sample コンポーネントを MSW でモックした Story の一例です。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36  | 
						// 省略した import 群あり const queryClient = new QueryClient(); const meta: Meta<typeof Sample> = {   title: 'Components/Sample',   component: Sample,   decorators: [     (Story) => (       <MemoryRouter initialEntries={['/sample/123456']}>         <QueryClientProvider client={queryClient}>           <HeaderProvider>             <Story />           </HeaderProvider>         </QueryClientProvider>       </MemoryRouter>     ),   ], }; export default meta; export const Default: StoryObj<typeof Sample> = {   parameters: {     msw: {       handlers: [         http.get(BACKEND_BASE_URL + '/sample/:id', async ({ params }) => {           return HttpResponse.json({             id: params.id,             name: 'Mock Sample',           });         }),       ],     },   }, };  | 
					
起動結果

ハマりポイント:data を返してないのに res.data.id に値が入っていた
一見、以下のような JSON を返しているはずの MSW モック:
| 
					 1 2 3 4 5  | 
						{   "id": "123456",   "name": "Mock Sample" }  | 
					
にもかかわらず、アプリ側では res.data.id を参照していて、値が表示されないという事象に遭遇。
原因:fetcher 側で .data を抽出していた
共通 fetcher の実装に以下のようなロジックが入っており、MSW 側も data プロパティを含む必要があることが原因でした。
| 
					 1 2 3 4  | 
						// utils/fetcher.ts export const fetcher = <T>(url: string): Promise<T> =>   fetch(url).then((res) => res.json()).then((json) => json.data);  | 
					
本番 API が data: {...} を返す仕様になっていたため、モックでもこの形式に合わせる必要があったのです。
解決策:Storybook 専用の fetcher を導入
MSW のレスポンスを本番に合わせて data を含めるのも一案ですが、Storybook 上だけ独自の fetcher を使うことで対応しました。
mockFetcher の導入例
| 
					 1 2 3 4  | 
						// storybook/fetcher.ts export const mockFetcher = <T>(url: string): Promise<T> =>   fetch(url).then((res) => res.json()); // .data の抽出なし  | 
					
Provider での切り替え
| 
					 1 2 3 4  | 
						<MyApiProvider value={{ fetcher: mockFetcher }}>   <Story /> </MyApiProvider>  | 
					
型との整合を保つためのヒント
Storybook 用の mockFetcher を導入する際、型の整合性を保つために ApiContext の型を以下のように調整しました:
| 
					 1 2 3 4 5 6  | 
						type ApiContextType = {   fetcher: <T>(url: string) => Promise<T>; }; export const ApiContext = createContext<ApiContextType | undefined>(undefined);  | 
					
その他の Tips
| 項目 | 内容 | 
|---|---|
msw が動かない | 
.storybook/preview.ts に initialize() と mswLoader を記述する必要あり | 
| コンフリクトアドオンの混在 | @storybook/addon-interactions と experimental-addon-test は共存不可 | 
| ルーターが必要な Storybook | MemoryRouter で initialEntries を明示 | 
| TanStack Query が再利用できない | QueryClient を Storybook 用に別管理し、キャッシュを切る | 
まとめ
| トピック | 学び・対応方法 | 
|---|---|
| Storybook 導入時の依存エラー | MUI v5 への移行、react-hooks ライブラリの削除 | 
| MSW のレスポンス構造とアプリの期待の不一致 | fetcher の仕様に合わせて data ラップを追加 or Storybook 専用 fetcher を導入 | 
型の制約で data を使えない場合 | 
mockFetcher 側で型の調整・context 経由で切り替え | 
| Storybook アドオン競合 | 安定版の @storybook/addon-interactions のみ採用 | 
| WSL | |
| 権限エラー | chown -R $(whoami):$(whoami) でプロジェクト所有者を変更 | 
おわりに
Storybook の導入は見た目上はスムーズですが、既存の fetcher や型定義と整合性を取るには注意が必要です。
また、MSW はただの「仮データ生成」ではなく、実 API と構造を揃えることで、安心・確実な UI 検証環境を構築できます。
今後、Storybook や MSW を導入する方の一助となれば幸いです!
最後まで閲覧ありがとうございます。
また、エコモットでは、ともに未来の常識を創る仲間を募集しています。
弊社に少しでも興味がある方はぜひ下記の採用ページをご覧ください!
      


