1. はじめに
プロジェクトを進めていると、1つの画面を完成させるまでの過程が、思った以上に複数の作業に分かれていることがあります。
私はプロジェクトで、画面のマークアップ、スタイル、インタラクションなどのUI実装を担当しています。以前までは、パブリッシング用リポジトリと開発用リポジトリが物理的に分かれていたため、私はパブリッシング用リポジトリ内だけで作業していました。画面のマークアップとスタイルを完成させておくと、開発者がそれを開発用リポジトリに移し、ロジックとAPIを組み込むという進め方でした。
しかし、今回のプロジェクトでは構成が変わりました。パブリッシャーが開発用リポジトリに直接入り、ブランチを分けて開発者と並行して作業を進める方式に変わったのです。同じリポジトリ、同じコードベースを共有することで得られるメリットもありましたが、同時に新たな難しさも生じました。同じ画面を同じリポジトリ内で作業しているにもかかわらず、お互いのブランチの作業がまだマージされる前は、完成した画面を一緒に確認することが実際には難しかったのです。
パブリッシング用ブランチで作業している時点では、まだAPIが準備されていなかったり、別のブランチで進行中の状態管理ロジックが完成していなかったりする場合もありました。
この記事では、このように1つのリポジトリ内でパブリッシングと開発がブランチ単位で分かれて進行する環境において、UIをより独立して開発・確認するためにStorybookを導入した経験と、実際の導入過程で直面した問題をまとめます。
2. 問題の定義:1つの画面を確認するために必要なもの
同じリポジトリ内でブランチごとに作業が分かれていたため、むしろ「マージすればすぐに見られそうなのに、実際はそうではない」というもどかしさがありました。自分のブランチのマークアップと開発ブランチのロジックがまだマージされていない時点では、完成した画面を実際に一緒に確認する方法がほとんどありませんでした。
この状態で特定の画面を実際の環境で確認するには、複数の条件が必要でした。
まず、その画面が含まれている開発環境を起動する必要があり、場合によっては複数のアプリケーションや開発サーバーも同時に起動する必要がありました。また、ログイン状態やAPIレスポンスが必要な画面の場合は、望む状態を作るために実際のデータを準備する必要がありました。
しかし、まだマージされていないブランチで作業している時点では、これらの条件が常に準備されているとは限りませんでした。
例えば、API仕様は定義されていても実際のサーバーがまだ実装されていなかったり、特定の状態のデータを意図的に作ることが難しかったりする場合がありました。空のリスト、特定のボタンの表示条件、申し込み完了状態、エラー状態などは、実際のサービスを起動したからといって、常に簡単に確認できるものではありませんでした。
結局、単に特定のUI状態を確認するためだけに開発環境全体を起動し、目的の画面まで移動したうえで、特定の条件を作らなければならない状況が発生しました。
この過程で、次のような問題がありました。
- パブリッシング作業が開発環境の準備状況に左右されます。
- 特定のUI状態を繰り返し再現することが困難です。
- APIデータが変更されるたびに、画面の確認プロセスも影響を受けます。
- 画面の状態を確認するには、その都度、開発側でデータや環境が準備されるのを待つか、依頼する必要があります。
特にUI作業では、正常状態だけでなく、空の状態、例外状態、ローディング状態など、さまざまな状況を同時に確認する必要があります。しかし、実際の開発環境では、これらの状態を毎回意図的に作るのに時間がかかりました。
したがって必要だったのは、単に画面を別途表示することではなく、特定のコンポーネントや画面の状態をいつでも同じように再現できる環境でした。
3. 解決方法:Storybookによる独立したUI開発環境の構築
この問題を解決するために、Storybookを導入しました。
特に、APIがまだ準備されていなかったり、極端な場合にはログイン自体ができず、開発環境からは画面に入ることすらできなかったりする時点でも、開発側にデータや環境を依頼せず、自分のローカルですぐに画面の状態を確認したいと考えたことが最大の動機でした。
Storybookは、コンポーネントをアプリケーション全体の環境から分離し、独立してレンダリング・確認できるツールです。ただし、単にコンポーネントを一覧で確認する用途として使うのではなく、プロジェクトの実際のUI開発環境とできるだけ近い状態を作ることを目標にしました。
そのため、主に次のような方針で構成しました。
- 実際のプロジェクトと同じVite設定をStorybookでも使用すること
- 実際のアプリケーションで使用しているProvider環境を再現すること
- 開発側のAPIやログイン環境が準備されていない場合でも、リクエストなしで自分のローカルから画面の状態をすぐに確認できること
3.1 実際のプロジェクト環境とStorybook環境を合わせる
Storybookを別の環境として構成すると便利ですが、実際のアプリケーションと設定が異なる場合、別の問題が発生する可能性があります。
例えば、Storybookでは正常に表示されるものの、実際のアプリケーションではalias設定やスタイル設定が異なる形で適用される場合です。
この差を減らすため、プロジェクトで使用していたVite設定をStorybookでも共有できるように構成しました。
framework: {
name: '@storybook/react-vite',
options: {},
},
viteFinal: async (config) => {
const { config: userConfig } = await loadConfigFromFile(
path.resolve(__dirname, '../vite.config.mts'),
);
return mergeConfig(config, {
...userConfig,
resolve: {
alias: {
'~': path.resolve(__dirname, '../src'),
},
},
});
},
このように構成することで、実際のプロジェクトで使用しているaliasやスタイルの処理方式などの設定を、Storybookでもできるだけ同じ状態に保つことができました。
また、Storybookのpreview設定では、実際のアプリケーションで使用していたProvider環境も合わせて構成しました。
例えば、テーマを提供するProvider、データリクエストを管理するProvider、通知メッセージを処理するProviderなどを、Storybook環境にも適用しました。
この作業は単にStorybookを起動するための設定というより、Storybookと実際のアプリケーションの差を減らすための作業でした。
UIを独立して確認する環境を作っても、実際のアプリケーションと大きく異なる環境であれば、結局は2回の検証が必要になるためです。
3.2 API依存性を減らすためのMockデータ構成
Storybookを導入するにあたって、最も重要だと考えたのはAPIへの依存を減らすことでした。
実際の画面の多くは、サーバーから返されるデータを基に構成されます。そのため、コンポーネントだけをStorybookでレンダリングする方法では、さまざまな状態を十分に再現することが困難でした。
最初は、各Storyファイルに必要なデータを直接記述する方法も検討できました。
export const Default = {
args: {
data: {
// Story마다 필요한 Mock 데이터 작성
},
},
};
しかし、画面が増え、状態が多様になるほど、同一または類似したMockデータが複数のStoryに重複する可能性が高くなります。
そこで、Mockデータを役割ごとに分離する方法を採用しました。
src
└── mocks
├── fixtures
└── handlers
fixturesには画面で使用する代表的なデータを整理し、handlersでは実際のAPIリクエストをインターセプトして、望むデータを返すように構成しました。
このようにすると、1つの画面についても複数の状態を比較的明確に分離できました。
例えば、次のように構成できます。
화면
├── 기본 상태
├── 빈 상태
├── 특정 조건 상태
└── 에러 상태
Storybookでは、それぞれの状態を独立したStoryとして作成し、すぐに確認できるようにしました。
結果として、実際のバックエンドが準備されていない状態でもUI作業を進められるようになり、特定の状態を確認するために毎回実際の環境でデータを作成しなければならない手間も減らすことができました。
4. 実際の導入過程で直面した問題
Storybookを導入したからといって、パブリッシングと開発の間にあるすべての問題が解決するわけではありませんでした。
むしろ、Storybookを実際のプロジェクトに適用することで、これまではあまり意識していなかった問題がより明確になることもありました。
4.1 コンポーネントのリネームによって発生したStoryのエラー
プロジェクトを進める中で、コンポーネントの役割を明確に区別するため、命名を整理する作業がありました。
既存のコンポーネント名に役割を区別できるサフィックスを追加したことで、複数のファイル名とimportパスが変更されました。
例えば、次のような変更が発生する可能性があります。
JobDetail
↓
JobDetailWkr
この過程で、すでに作成されていたStoryファイルのimportパスにも影響が及びました。
// 변경 전
import { JobDetail } from './JobDetail';
// 변경 후
import { JobDetail } from './JobDetailWkr';
1つのコンポーネント名を変更する作業自体は、大きなものではないかもしれません。しかし、そのコンポーネントを参照するStoryファイルが存在する場合、変更範囲は想定より広くなる可能性があります。
以前のようにパブリッシングと開発のリポジトリが分かれていた場合、このような変更は、私が作業成果物を再び取り込む時点になって初めて気づいていたでしょう。しかし、同じリポジトリ内でブランチを使って作業していたため、開発ブランチの変更がマージ時にすぐにコンフリクトとして明らかになり、その場ですぐに調整する必要がありました。
この経験を通じて、Storybookも実際のプロジェクトコードから分離された別個の成果物ではなく、プロジェクト構造の変化の影響を同じように受けるコードなのだと実感しました。
また、ファイル構造や命名のように複数の作業者に影響する変更は、可能な限り事前に共有することが重要だと感じました。
Storyファイルを保守する立場からすると、ちょっとした事前共有だけでも不要な修正作業を減らせるためです。
4.2 すべてをStorybookで確認できるわけではありませんでした
Storybookを導入して明確に感じたことの1つは、Storybookが実際のアプリケーションを完全に置き換えるツールではないということでした。
コンポーネント単位のUI状態を確認するには非常に効率的ですが、複数の画面がつながるフローや実際の環境に依存する機能については、別途確認が必要でした。
例えば、次のような領域です。
- 画面間の遷移
- 複数のアプリケーション間の切り替え
- 実際のAPIレスポンスとUIの連携
- 実行環境でのみ発生する問題
- 外部環境とのメッセージ処理
このような部分は、Storybookの独立した環境では確認が難しいか、別途Mockの実装が必要でした。
最初は、できるだけ多くの画面をStorybookで確認できるようにするのがよいと考えていました。しかし、実際に適用してみると、すべてをStorybookに移行すること自体を目的にしてはいけないと判断しました。
結局、役割を分けて考える方法のほうが効率的でした。
Storybook
→ 컴포넌트 및 화면의 상태 확인
→ UI 리뷰
→ 다양한 데이터 상태 재현
→ 퍼블리싱 결과 공유
실제 개발 환경
→ 화면 간 이동 확인
→ 실제 API 연동 확인
→ 전체 사용자 흐름 확인
→ 실제 실행 환경 검증
Storybookと実際のアプリケーションは競合するものではなく、それぞれ異なる目的を持つ検証環境として使うのが適切でした。
5. 導入前後の比較
|
区分 |
従来の方法 |
Storybook導入後 |
|---|---|---|
|
実行単位 |
開発環境全体の起動が必要 |
必要なコンポーネントまたは画面のみを起動 |
|
データの準備 |
実際のAPIとログイン状態の影響を受ける |
Mockデータで任意の状態を構成 |
|
状態の再現 |
特定の状態を手動で作成する必要がある |
Storyごとに状態をすぐに確認 |
|
UIの確認 |
目的の画面まで移動する必要がある |
該当するStoryに直接アクセス |
|
再確認の方法 |
毎回シナリオを最初からたどる必要がある |
保存しておいたStoryですぐに再現可能 |
|
適した領域 |
全体のフローと実際の連携の確認 |
UI状態およびコンポーネント単位の検証 |
Storybookを導入して最も大きく変わった点は、単に画面を見やすくなったことではありませんでした。
以前は、特定の画面状態を確認するには、環境を準備し、その状態を作成するプロセスを先に行う必要がありました。
しかし、Storybookでは必要な状態そのものを1つのStoryとして作成しておくことができました。
つまり、画面を確認するための準備プロセスが減り、一度定義した状態をその後も同じように再現できるようになりました。
6. この経験を通して学んだこと
6.1 Storybookの最大のメリットは、UIそのものよりも状態の再現にありました
最初は、Storybookをコンポーネントを独立して確認するためのツールだと考えていました。
しかし実際のプロジェクトに適用してみると、最大のメリットは特定の状態を繰り返し再現できる点にあると感じました。
正常な状態だけでなく、空の画面や例外状態のように、実際の環境では作りにくい状況もあらかじめ定義できました。
一度作成したStoryは、その後も同じ状態を維持できるため、UIの修正やレビューの過程でも繰り返し活用できました。
6.2 開発環境が整っていないときでも、作業を止めずに進められました
ブランチがまだマージされていなかったり、ひどい場合にはログイン自体ができず、開発環境では画面に入ることすらできなかったりする時期が、思った以上に頻繁にありました。
以前であれば、このような状況では開発側にデータやアカウントの状態を依頼したり、環境が正常化するまで作業を延期したりする必要があったでしょう。
しかし、Storybookとモックデータを併用するようになってからは、このような状況でもローカル環境で望む画面状態を自分で再現して確認できるようになりました。必要なデータを依頼したり、環境が復旧するのを待ったりすることなく、UI作業をそのまま続けられたのです。
この経験を通して感じたのは、Storybookの価値は画面をきれいに見せることにあるというよりも、自分の作業が他の側の準備状況にできるだけ依存しないようにしてくれる点にあるということでした。
6.3 すべての画面をStorybookで作ることが目標ではありませんでした
Storybookを導入する中で最も重要だと考えるようになったのは、どれだけ多く適用したかではなく、どこに適用したときに最も効果的かという点でした。
UIの状態が多く、実際のデータがなくても十分に確認できる画面では、Storybookの効果が大きく現れました。
一方で、複数の画面間のフローや実際の環境との連携が重要な機能については、既存の開発環境で確認するほうが適切でした。
そのため、今後はすべての画面を同じ方法で管理するのではなく、並行作業の中で確認コストが高い画面からStorybookに分離していく方向が効率的だと考えるようになりました。
7. おわりに
今回の経験を通して、Storybookは単にUIコンポーネントを表示するためのツールではなく、開発環境がまだ整っていない時点でも、自分が担当するUI作業を独立して検証できる確認環境として活用できることが分かりました。
特に、APIやログインなど開発側の環境が整っていない状況でMockデータと併用すると、開発側に依頼したり待ったりせずに、特定の状態を繰り返し再現しながら作業を続けられるため、効果的でした。
もちろん、Storybookだけですべての問題を解決できるわけではありません。実際のAPI連携や画面間のフロー、実行環境に依存する機能は、依然として実際の開発環境で確認する必要があります。
しかし、すべてを一つの環境で解決しようとするよりも、UIの状態確認はStorybookで、実際のフローや連携は実際のアプリケーションで確認するというように役割を分担するほうが効率的でした。
結局、今回の経験で最も強く感じたのは、Storybookという特定のツールそのものよりも、自分の作業を他の側の準備状況にどれだけ依存せずに進められるかのほうが、より重要な問題だということです。
これからも新しいプロジェクトを進める際には、すべての画面を最初からStorybookで構成するのではなく、開発環境やデータへの依存度が高く、UIの確認コストが大きい領域から優先的に適用していくつもりです。そうして積み重ねたStoryが、単なるテスト用画面ではなく、UIを確認・共有するための共通の基準として活用できると考えています。
自然