Storybook 8 でビルドエラー・コンポーネントが表示されない時の5つの原因と対策【React / Vite / v7移行】
公開日:2026-07-16/カテゴリ:Storybook・React・フロントエンド・テスト/AnchorUp 技術ブログ
Storybook 7→8の移行は破壊的変更が多く、ビルドエラー、コンポーネント表示崩れ、テスト失敗が頻発する。本記事では移行時によくある5つの原因と対処法を解説する。
Storybook 8で何が変わったか
Storybook 8は2024年4月にリリースされた。主な変更点は以下の通り。
- テストAPIの統合(
@storybook/jest+@storybook/testing-library→@storybook/test) - Reactプロジェクト向けにViteビルダーがデフォルト化
react-docgenからの型ドキュメント生成改善- CSF(Component Story Format)3のパターン整備
@storybook/addon-interactionsのAPI更新
v7からv8へのマイグレーションでよく踏む罠を5つまとめた。
Storybook 8 でビルドエラー・表示崩れが起きる5つの原因一覧
| # | 症状 | 原因 |
|---|---|---|
| 1 | Module not found: Can't resolve '@storybook/jest' |
テストAPI統合により削除 |
| 2 | ストーリーのコンポーネントが表示されない | Viteビルダー設定が引き継がれない |
| 3 | composeStories でTypeScriptエラーが出る |
型定義のAPI変更 |
| 4 | インタラクションテストが動かない | userEvent が非同期APIに変更 |
| 5 | 特定ディレクトリのストーリーが読み込まれない | stories グロブパターンの変更 |
原因1: @storybook/jest @storybook/testing-library が見つからない
症状
Module not found: Error: Can't resolve '@storybook/jest'
Module not found: Error: Can't resolve '@storybook/testing-library'
原因
Storybook 8 で @storybook/jest(Jestの再エクスポート)と @storybook/testing-library(@testing-library/userの再エクスポート)は @storybook/test に統合・削除された。
対策
# v7でインストールしていたパッケージを削除
npm uninstall @storybook/jest @storybook/testing-library
# @storybook/test をインストール
npm install --save-dev @storybook/test
インポートパスを変更する:
// v7(旧)
import { expect } from '@storybook/jest'
import { userEvent, within } from '@storybook/testing-library'
// v8(新)
import { expect, userEvent, within } from '@storybook/test'
@storybook/test は内部でVitestのAPIを使っており、Jest互換のAPIを提供している。インポートパスを変えるだけで既存のテストコードがそのまま動く場合がほとんどだ。
原因2: React + Vite 環境でコンポーネントが表示されない・画面が真っ白になる
症状
- ストーリーのCanvasタブが真っ白になる
- DevToolsコンソールに
Failed to fetch dynamically imported moduleエラーが出る - pathエイリアス(
@/components/...)が解決できずビルドエラーになる
原因
Storybook 8 では React + ViteプロジェクトにViteビルダーがデフォルトで使用される。しかし vite.config.ts に書いたpathエイリアスや環境変数の設定がStorybookのViteビルダーに自動では引き継がれない。
対策
.storybook/main.ts で viteFinal フックを使い、プロジェクトのVite設定を明示的に適用する:
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite'
import { mergeConfig } from 'vite'
import path from 'path'
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
async viteFinal(config) {
return mergeConfig(config, {
resolve: {
alias: {
'@': path.resolve(__dirname, '../src'),
},
},
})
},
}
export default config
viteFinal で mergeConfig を使い、プロジェクト固有のVite設定をStorybookに適用するのがポイント。vite.config.ts をそのままimportしてmergeする方法もある:
import viteConfig from '../vite.config'
async viteFinal(config) {
return mergeConfig(config, viteConfig)
},
原因3: composeStories でTypeScriptの型エラーが出る
症状
Argument of type '...' is not assignable to parameter of type 'Meta<...>'
Property 'component' is missing in type...
Type 'StoryObj<typeof Button>' is not assignable to type 'StoryObj<typeof meta>'
原因
Storybook 8 で composeStories の型定義が更新され、Meta 型の扱いが変わった。v7では StoryObj<typeof Component> と書くパターンが一般的だったが、v8では typeof meta を使うCSF3の推奨パターンに統一された。
対策
satisfies 演算子と typeof meta を使う形に変更する:
// v7(旧)
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta: Meta = {
title: 'Example/Button',
component: Button,
}
export default meta
type Story = StoryObj<typeof Button> // ← typeof Button を使う
export const Primary: Story = {
args: { label: 'Primary', variant: 'primary' },
}
// v8(新)
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta = {
title: 'Example/Button',
component: Button,
} satisfies Meta<typeof Button> // ← satisfies で型チェック
export default meta
type Story = StoryObj<typeof meta> // ← typeof meta を使う
export const Primary: Story = {
args: { label: 'Primary', variant: 'primary' },
}
composeStories を使うテストコードは変更不要な場合が多いが、型エラーが出る場合はストーリーファイル側のパターンを上記に揃えることで解消する:
// Button.test.tsx
import { composeStories } from '@storybook/react'
import * as stories from './Button.stories'
const { Primary, Secondary } = composeStories(stories)
test('Primary button renders', () => {
render(<Primary />)
expect(screen.getByRole('button', { name: 'Primary' })).toBeInTheDocument()
})
原因4: インタラクションテスト(play 関数)が動かない・クリックが効かない
症状
TypeError: Cannot read properties of undefined (reading 'click')
Warning: An update to Component inside a test was not wrapped in act(...)
または play 関数は実行されているのにUIが期待通りに変化しない。
原因
Storybook 8(@storybook/test 経由)では @testing-library/user-event v14 が使われており、v13以前と異なり全ての操作が非同期になった。v7のコードで await を付けていなかった箇所が問題になる。
対策
play 関数内の userEvent の操作すべてに await を付ける:
// v7(旧): awaitなしでも動いていた
import { userEvent, within } from '@storybook/testing-library'
export const Filled: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
userEvent.type(canvas.getByRole('textbox'), 'Hello') // ← awaitなし
userEvent.click(canvas.getByRole('button')) // ← awaitなし
},
}
// v8(新): 全操作にawaitが必要
import { userEvent, within, expect } from '@storybook/test'
export const Filled: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.type(canvas.getByRole('textbox'), 'Hello') // ← await必須
await userEvent.click(canvas.getByRole('button')) // ← await必須
await expect(canvas.getByRole('textbox')).toHaveValue('Hello')
},
}
type, click, selectOptions, clear, tab など userEvent の全メソッドに await が必要だ。
原因5: 特定ディレクトリのストーリーが読み込まれない・Failed to index エラー
症状
- 特定フォルダのストーリーだけSidebar(左メニュー)に出ない
Failed to index ./src/features/xxx/Button.stories.tsxエラーが出る.mdxドキュメントが表示されない
原因
Storybook 8 では stories グロブの解釈が変わり、.mdx ファイルを含める場合に明示的な指定が必要になった。また、CSF形式のストーリーファイルは必ず export default(meta オブジェクト)が必要で、これが欠けていると読み込みエラーになる。
対策
.storybook/main.ts の stories グロブに .mdx を明示的に追加する:
// .storybook/main.ts
const config: StorybookConfig = {
stories: [
'../src/**/*.mdx', // ← .mdxを明示
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
// ...
}
monorepoで複数パッケージのストーリーを参照する場合:
stories: [
'../packages/*/src/**/*.mdx',
'../packages/*/src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
'../apps/*/src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
また、ストーリーファイルに export default がない場合は必ず追加する:
// NG: export default がない
export const Primary = () => <Button label="Primary" />
// OK: meta オブジェクトを export default する
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta = { component: Button } satisfies Meta<typeof Button>
export default meta
export const Primary: StoryObj<typeof meta> = {
args: { label: 'Primary' },
}
自動マイグレーションツールを先に試す
Storybook 8には自動マイグレーションコマンドが用意されている。手動対応の前にまずこれを実行しよう:
npx storybook@latest upgrade
対話形式で確認しながら進む。@storybook/jest → @storybook/test のインポート置換、main.ts のフォーマット更新なども自動でやってくれる。ただし viteFinal 設定や play 関数内の await 対応は手動修正が必要なケースが多い。
まとめ
Storybook 7→8移行でよくある5つの問題と対策をまとめる。
| 原因 | 対策 |
|---|---|
@storybook/jest が見つからない |
@storybook/test に統合。インポートパスを変更 |
| コンポーネントが表示されない | viteFinal でVite設定(pathエイリアス等)を明示的にマージ |
composeStories 型エラー |
satisfies Meta<typeof T> + StoryObj<typeof meta> に統一 |
play 関数が動かない |
userEvent の全操作に await を付ける |
| ストーリーが読み込まれない | stories グロブに **/*.mdx を追加。export default を確認 |
まずは npx storybook@latest upgrade で自動マイグレーションを試し、その後に個別対応するのが効率的だ。上記5点を押さえれば、ほとんどのReact + Viteプロジェクトでのv7→v8移行を完了できる。