この記事はAteam LifeDesign Advent Calendar 2025の24日目の記事です。
今回は、新規プロダクト開発においてフロントエンドのアーキテクチャとしてFeature-Sliced Design(FSD)を採用した経験について書きたいと思います。FSDとはなにか、実際に導入してみてどうだったかなどを共有できればと思います。
ことのきっかけ
新規サービスの立ち上げがあり、私もエンジニアとして参加することになりました。フロントエンドについては設計から開発まで、ほぼ一人で任されることになったのですが、一から設計するのは今回が初めての経験でした。自分で方針を決める必要があり、参考になるアーキテクチャや方法論を探していました。
そんな中で出会ったのがFeature-Sliced Design(FSD)です。
Feature-Sliced Design (FSD) とは、フロントエンドアプリケーションの設計方法論です。簡単に言えば、コードを整理するためのルールと規約の集大成です。FSDの主な目的は、ビジネス要件が絶えず変化する中で、プロジェクトをより理解しやすく、構造化されたものにすることです。
(引用元: 概要 | Feature-Sliced Design)
FSDを採用した理由
フロントエンドの設計手法を調べていく中で、KINTOテクノロジーズ Osaka Tech Labのスライド資料に出会い、FSDの存在を知りました。(わかりやすく情報がまとまっていてとても参考になりました。ありがとうございます。)
私でも問題なく扱えると感じ採用に至りましたが、具体的には以下の点が決め手になりました。
公式ドキュメントが充実している
FSDの公式ドキュメントは非常に丁寧に書かれており、日本語版も用意されています。概念の理解からチュートリアルで使い方まで、段階的に学べる構成になっています。
参考になるexampleが複数用意されている
実装例が複数用意されており、実際にどのようにディレクトリを分けているのか、どのレイヤーに何をしているかを参考にすることができます。抽象的な概念だけでなく、実際のコードを見ながら学べるのは非常に助かりました。実際に自分で設計・実装する際に悩んだ際はいくつかのリポジトリを見て参考にさせてもらいました。
Linterがあることで違反に気づける
SteigerというLintツールが提供されており、FSDのルールに違反していないかを自動でチェックできます。(ESLintプラグインも開発されていますが、現在はSteigerを使うことを推奨しているようです。)
慣れていない段階では無意識にルールを破ってしまうこともありますが、Linterがあることで早期に気づいて修正できます。なれるまで何度かSteigerに指摘を受けたので非常に助かりました。
導入当初は、後述するクロスインポートのファイル名がインポート先と一致していなくても検知してくれませんでしたが、アップデートされて検知できるようになりました。ただし、beta版でAPIが変更される可能性があったり、ここ最近更新がなく若干不安な部分もあります。
FSDとはなにか
FSDの説明については、公式ドキュメントや先ほど紹介したスライド資料を見ていただくのが一番わかり易いかと思いますが、軽くここでも記載しておきます。
公式の言葉を借りると、FSDはアプリケーションの機能に焦点を当てた設計手法で、ドメイン駆動設計やモジュラーアーキテクチャと概念的に類似しており、Reactエコシステムをある程度前提としています。
FSDでは、レイヤー、スライス、セグメントという3つの概念で構造化します。
レイヤー(Layers)
FSDでは、アプリケーションを以下の6つのレイヤーに分割します(下から上の順):
- shared - 再利用可能なユーティリティやUIコンポーネント
- entities - ビジネスエンティティ(ユーザー、商品など)
- features - ユーザーが実行できる機能(ログイン、商品をカートに追加するなど)
- widgets - 複数のfeaturesやentitiesを組み合わせた独立したUIブロック
- pages - アプリケーションのページ
-
app - アプリケーションの初期化やグローバル設定
※processesは非推奨になったため除外
重要なルールとして、下のレイヤーは上のレイヤーを参照できません。 例えば、entitiesはfeaturesやpagesをインポートできません。この制約により、依存関係が一方向に保たれます。
スライス(Slices)
スライスは、開発するアプリケーションに合わせて自分たちで作成する単位です。例えば、entitiesレイヤーであれば「user」「product」「order」といったスライスを作成します。
なお、appとsharedレイヤーにはスライスは存在しません。
外部からスライスを利用する場合、Public API を通じてのみアクセスが許可されます。
スライスは他のスライスを直接参照できません。 もし別のスライスの機能が参照したい場合は、クロスインポートという仕組みを使います。
Pubilc API
各スライスは、index.tsファイルを通じてPublic APIを提供します。スライス内部の実装詳細は隠蔽され、外部からは必要な機能だけが公開されます。これにより、カプセル化が実現され、影響範囲が限定されます。
entities/
user/
model/
user.ts
ui/
UserCard.tsx
index.ts # Public API
export { UserCard } from "./ui/UserCard";
export { userAtom } from "./model/user";
クロスインポート
ビジネスエンティは実際には互いに参照し合うことがあります。例えばECサイトであればカートエンティティは商品エンティティを参照するかもしれません。こういった場合には、エンティティレイヤーではクロスインポートを利用してあるエンティティから別のエンティティを参照することができます。
entities/
cart/
product/
@x/
cart.ts # cartエンティティ専用のPublic API
model/
product.ts
index.ts # 通常のPublic API
import { Product } from "entities/product/@x/cart";
セグメント(Segments)
各スライスの中は、セグメントと呼ばれる単位でさらに分割されます。独自のセグメントを作成できますが、いくつか標準化されているセグメント名があります。
- ui - UI関するすべてのもの
- model - データモデル
- api - バックエンドとのインタラクション
- lib - スライス内のモジュールに必要なライブラリコード
- config - 設定ファイル
実際どうだったか
ここからは、実際にFSDを導入して開発を進めて得た知見や考えを共有します。
まずはapp/pages/shared/entitiesから始める
FSDの公式ドキュメントでは、まずapp、pages、sharedの3つのレイヤーから始めることが推奨されています。必要に応じて段階的に他のレイヤーを追加していくアプローチです。
今回のプロジェクトでは、ビジネスエンティティが明確だったため、最初からentitiesレイヤーも作成しました。例えば、ユーザー情報や申し込み情報といったエンティティは、プロジェクト開始時点で確実に必要だと分かっていたためです。
その後、開発を進める中でwidgetsとfeaturesも導入し、最終的には全レイヤー(processesを除く)を使用する形になりました。
featuresの切り方が難しい
FSDを導入して最も悩んだのが、featuresの粒度です。
例えば、「申し込み」という大きな機能があったとします。この中には「申し込みフォームの入力」「入力内容の確認」「申し込みの送信」といった、さらに細かい機能が含まれています。
FSDでは、featuresは「ユーザーに提供する機能」単位で切ることが推奨されています。しかし、これらを全て細かく分けるべきなのでしょうか?それとも「申し込み」という大きな単位でまとめるべきなのでしょうか?
私自身は、「featureはユーザーがentitiesを使って行いたいこと」という原則に従い、細かく分けすぎないことにしました。entitiesでできることはentitiesに任せ、必要に応じてクロスインポートを活用しながら、featuresは適度な粒度に保つようにしました。
entitiesを細かく切っているとuiセグメントは使わない
entitiesを細かく切って開発していると、意外とuiセグメントを使う機会がないことに気づきました。
entitiesはビジネスエンティティを表現するレイヤーですが、それ専用のUIコンポーネントが必要になるケースは思ったより多くありませんでした。多くの場合、sharedのUIコンポーネントを組み合わせ、featuresやwidgetsでUIを構築する形になりました。
Next.jsでの導入時には一工夫が必要
Next.jsのApp Routerを使用している場合、FSDの導入には注意が必要です。
Next.jsのApp Routerでは、appディレクトリがルーティングに使用されます。一方、FSDでも最上位レイヤーとしてappを使用します。名前が衝突してしまうのです。
この問題については、公式ドキュメントで対応方法が紹介されています。具体的には、FSDの各レイヤーをsrcディレクトリ配下に配置し、Next.jsのpagesディレクトリにはREADMEファイルのみ置いておき、Next.jsのappディレクトリとは分離する方法です。
project/
src/ # FSDのレイヤー群
app/
pages/
widgets/
features/
entities/
shared/
app/ # Next.jsのApp Router
pages/ # 空のNext.jsのPage Router
README.md
FSDを採用してみて良かったこと
実際にFSDを使って開発を進めてみたところ、いくつかのメリットを実感しました。
entitiesで別れていてスライスごとに状態やスキーマ管理できるのが便利
- 実際に開発するうえで修正するさい、entitieのスライスごとに考えることが多く、近くのディレクトリにあるのがわかりやすい
- カプセル化されていて影響範囲もある程度わかりやすいのが良い
スライスのレイヤー移動が楽
- 実装していると、このスライスはwidgetsにすべきだなあと持ったときにそのままでディレクトリを移動させるだけですむ
- 最初のうちは悩みながら実装することもあったのでこれが楽だった
オンボーディングがしやすい
- FSDのドキュメントが充実している&標準化された構造おかげで構成が説明しやすい
終わりに
一からアーキテクチャを設計する際、何の指針もない状態は非常に不安でした。「このディレクトリ構成で本当に良いのだろうか」「後から破綻しないだろうか」といった迷いがありました。
FSDは、そんな無秩序な状態から秩序ある世界へと導いてくれました。明確なルールと豊富なドキュメント、そして実装例があったおかげで、ある程度自信を持って前に進むことができました。
チームでの開発を見据えている場合、FSDは有力な選択肢の一つだと思います。この記事が、フロントエンドのアーキテクチャ選定で悩んでいる方の参考になれば幸いです!



