Storybook 8 でビルドエラー・コンポーネントが表示されない時の5つの原因と対策【React / Vite / v7移行】

公開日:2026-07-16/カテゴリ:Storybook・React・フロントエンド・テスト/AnchorUp 技術ブログ

Storybook 7→8の移行は破壊的変更が多く、ビルドエラー、コンポーネント表示崩れ、テスト失敗が頻発する。本記事では移行時によくある5つの原因と対処法を解説する。


Storybook 8で何が変わったか

Storybook 8は2024年4月にリリースされた。主な変更点は以下の通り。

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 環境でコンポーネントが表示されない・画面が真っ白になる

症状

原因

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 エラー

症状

原因

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移行を完了できる。

技術ブログの記事一覧へ