FSD ベースのフロントエンドアーキテクチャ
- Authors
- Name
- ごとれん
- X
- @ren510dev
目次
- 目次
- はじめに
- Feature-Sliced Design
- どのような環境に適しているか
- 特徴
- 古典的デザインとの比較
- シンプルなモジュラデザインとの比較
- アーキテクチャ
- Layer
- Slice
- Segment
- Public API
- 規約・制約
- オブジェクト指向プログラミング と FSD
- Next.js との競合
- Pages
- App
- まとめ
- 参考・引用

はじめに
近年、フロントエンドアーキテクチャとして Feature-Sliced Design(FSD) と呼ばれる設計手法が注目されています。 FSD は、特にマイクロサービスを採用しているような大規模かつ複雑なシステムの開発現場において、生産性の向上が期待されており、アジャイル法や DevOps との親和性も高いとされています。
FSD はシステムを構成する機能や特性(Feature)を スライス(Slice) と呼ばれる単位で細分化し、それぞれが独立性を持つことで全体の設計や変更を容易にするというものです。 スライスの考え方により、複雑な機能を把握しやすくなり、サービス規模拡大に伴うスケーラビリティを担保することが期待されています。
FSD は、言語問わずあらゆるフロントエンドアプリケーションに適用できますが、今回は特に Web フロントの設計に焦点を当てて紹介したいと思います。
Feature-Sliced Design

Feature-Sliced Design (FSD) is an architectural methodology for scaffolding front-end applications. Simply put, it's a compilation of rules and conventions on organizing code. The main purpose of this methodology is to make the project more understandable and structured in the face of ever-changing business requirements.
FSD はフロントエンドアプリケーションの構成に関する設計手法で、その目的は、常に変化するビジネス要件に対応するためにプロジェクトを理解しやすく、構造化されたものにすることです。 具体的には、FSD はコードの組織化に関するルールと規約のセットを提供します。
コードの組織化とは、ソフトウェア開発におけるコードベースを整理し、管理しやすい形にまとめることを指します。 一般に、サービスが成長すると、コードベースが大きくなるため管理が複雑化するという課題が生じます。 Web 開発における各ドメインで、開発のスケーラビリティを確保するアーキテクチャが提案されていますが、 FSD の場合は、スライスと呼ばれる考え方を採用することで各コンポーネントを独立して管理できるようにしようというものです。
どのような環境に適しているか
FSD は特に次の条件に当てはまるプロジェクトに適しているとされています。
- フロントエンド(Web、モバイル、デスクトップ等の UI)の開発現場
- ライブラリではなくアプリケーションを構築している
使用するプログラミング言語、UI フレームワーク、ステート管理に具体的な制限はなく、モノレポに導入したり、分割されたパッケージ内で個別に FSD を実装したりすることでスケーラビリティを実現できます。
例えば、既存の開発モデルにおいて、各コンポーネント間の相互接続が複雑化し、新機能を効率的に実装できない場合や、開発人数が多く、頻繁にメンバーが追加されるような現場においても、責務分割を明確化しやすい FSD は有効です。
一方で、他のフロントエンドアーキテクチャにより既に開発体制が安定している場合は、無理に FSD を採用しない方が良いことも言及されています。
If the current architecture works, maybe it's not worth changing.
FSD は、移行に関する ガイダンスドキュメント も用意されています。
特徴
FSD は React.js(Next.js)をはじめとするモダンなフレームワークと親和性が良く、コンポーネント化を推進する現代のフロントエンド開発の流れに適しています。
モジュール性
各機能は独立したスライスとして管理され、異なるチームおよび開発サイクルを適用できる。 これにより、全体の開発スピードを上げることが可能になる。
透明性
各スライスは目的と機能が明確であるため、開発者が担当スライスが何をするものなのかを理解しやすい。
スケーラビリティ
各スライスは個別にスケールできるため、システム全体の規模に対して柔軟に対応することができる。
メンテナンス性
各スライスは独立しているため、一部のスライスだけを修正したり拡張することが容易である。 これにより、全体の品質と安定性を維持しながら迅速なリリースを追求することができる。
古典的デザインとの比較
ここで、古典的デザイン(Classic design)は以下のような構造を例とします。
└── src/
├── api/
├── assets/
├── components/
├── ├──About/
│ │ ├── index.tsx/
│ │ └── about.css/
│ ├── ProductCard/
│ ├── UserCard/
│ ├── Signin/
│ └── Signup/
├── domain/
│ ├── hooks/
│ └── helpers/
├── pages/
│ ├── Home.tsx/
│ ├── Signin.tsx/
│ └── Signup.tsx/
├── store/
├── utils/
└── ui/
古典的デザインでは、コンポーネント間の暗黙的な関連性が課題になります。 例えば、A という部分が B という部分に影響を受けていたり、仮に B が変わった場合 A に影響が出るといった状況で、所謂、コンポーネント間の依存関係が管理されていないような状態です。
また、モジュールを追加しすぎると、どのモジュールが何の役割を果たしているのかを把握しずらくなるという課題も生じます。 これらの課題は、コード規模の拡大やプロジェクトが進行するにつれ一層顕在化し、結果としてプロジェクト保守の困難性や開発者のバーンアウトに繋がります。
これに対し、コード設計が明確化されている FSD の場合、設計からの逸脱を早期に発見して対処することができます。
しかし、FSD の場合はサービス設計に対する理解や一定のスキルレベルが求められため、チームのスキルセットを鑑みて導入する必要があります。
シンプルなモジュラデザインとの比較
単純なモジュラデザイン(Simple modular design)ではモジュールやコンポーネントにどの機能を配置すべきか明確な定義がないため、責務の捉え方が開発者によって異なる場合があります。 また、単一のモジュールを他でも使用すると、コード規模が拡大するにつれて DRY の違反 や依存関係が曖昧になってしまうという懸念が生じます。
FSD は複雑なプロジェクトにおいても、各コンポーネントにおける責務やルールが明確化されているため、シンプルなモジュラデザインと比較して、一貫した開発プロセスを適用することができます。
一方で、サービス規模が小さく、開発速度の方が重要視されるような場面では、FSD が必ずしも最良の選択とはならないことが言及されています。 特に最小限のビジネスモデル(MVP)が必要な状況や短期間での開発が求められるプロジェクトにおいては、シンプルなモジュラデザインの方が適している場合もあります。
アーキテクチャ
FSD では、プロジェクトは Layer で構成、各 Layer は Slice で構成、各 Slice は Segment で構成といった親と子の関係で成立します。

FSD は、各機能をスライスと呼ばれる単位で分割・細分化するアプローチを採用しており、各スライスはそれぞれ独立して設計されるため、機能の柔軟性や拡張性が向上します。 スライスの考え方により、コンポーネント単位で管理するような Web フロントやモバイルクライアントとの親和性は高くなります。

Layer
FSD の第 1 階層を Layer と呼びます。 App / Processes(非推奨) / Pages / Widgets / Features / Entities / Shared の 7 つのレイヤがあり、責務と依存関係に基づいて分類されています。

├── app/
│ # Does not have specific slices,
│ # Because it contains meta-logic on the project and its initialization
├── processes/
│ # Slices implementing processes on pages
│ ├── payment
│ ├── auth
│ ├── quick-tour
│ └── ...
├── pages/
│ # Slices implementing application pages
│ # At the same time, due to the specifics of routing, they can be invested in each other
│ ├── profile
│ ├── sign-up
│ ├── feed
│ └── ...
├── widgets/
│ # Slices implementing independent page blocks
│ ├── header
│ ├── feed
│ └── ...
├── features/
│ # Slices implementing user scenarios on pages
│ ├── auth-by-phone
│ ├── inline-post
│ └── ...
├── entities/
│ # Slices of business entities for implementing a more complex BL
│ ├── viewer
│ ├── posts
│ ├── i18n
│ └── ...
├── shared/
│ # Does not have specific slices
│ # is rather a set of commonly used segments, without binding to the BL
App
アプリケーション全体に影響を与える機能を管理します。 Routing や Provider、Store のセットアップ等、アプリケーション全体で共有される機能が含まれます。
App レイヤは 基本的にスライスを持たず直接セグメントで構成 されています。
└── app/
├── providers/
├── model/
│ └── store.ts
└── ui/
└── App.tsx
Processes
This layer has been deprecated. The current version of the spec recommends avoiding it and moving its contents to features and app instead.
Process レイヤは非推奨 となっています。
これまで Process レイヤはアプリケーション内でユーザが複数のページ間で行う操作や動作(インタラクション)を管理しやすくするための一時的な解決策(避難口)として設けられていました。
しかし、複雑なアプリケーションではユーザが行う操作が一つのページ内だけで完結することは稀で、複数のページ間でのナビゲーションが必要となります。 そのようなインタラクションを管理しようとするとコードが複雑化し、管理の煩雑性を生むため、Process レイヤの機能は App レイヤおよび Features レイヤ(後述)で定義すべきとされています。
Pages
アプリケーションの個々のページを管理します。 Pages レイヤには特定のページでのみ使用されるロジックと UI コンポーネントが含まれます。
└── pages/
├── posts/
│ ├── api/
│ │ └── GetPosts.graphql
│ ├── model/
│ └── ui/
│ └── Post.tsx
└── post
Widgets
Entities や Features を意味のあるブロックに結合した UI を管理します。 Widgets レイヤは基本的にビジネスロジックを持たず、ジェスチャやキーボード操作等の非ビジネスロジックが含まれます。
└── widgets/
├── comment-list/
│ ├── model/
│ │ └── scroll.ts // 非ビジネスロジックを定義
│ └── ui/
│ └── CommentList.tsx
└── header/
Features
機能を実現するためのモジュールを管理します。 Features レイヤでは、データフェッチやステート管理等を、再利用可能な 1 つの機能として集約することで、アプリケーションの主要な機能を独立した単位として整理します。 他の部分との結合を最小限に抑えることで、保守性と拡張性が向上します。
└── feature/
├── submit-post/
│ ├── api/
│ │ └── SubmitPost.graphql
│ ├── model/
│ │ └── submit-post.ts
│ └── ui/
│ └── submit-post-button/
└── create-post/
Entities
アプリケーションのビジネスロジックやデータモデルを定義し、再利用可能なビジネスロジックやコンポーネントを管理します。 Entities の各スライスには、Entities に基づく静的な UI やデータストア、CRUD 操作が含まれます。
└── entities/
├── user/
│ ├── api/
│ │ └── User.fragment.graphql
│ ├── model/
│ │ └── store.ts
│ └── ui/
│ ├── user-card/
│ └── user-profiel/
└── article/
Shared
プロジェクトやビジネスの特殊性から切り離された、独立したモジュールや UI を管理します。 Shared レイヤは スライスを持たず直接セグメントで構成されている 点が App を除く他のレイヤと異なります。
└── shared/
├── ui/
│ ├── button/
│ └── selector/
└── lib/
└── date
これらレイヤ間の呼び出しスキームには制約があり、必ず上位レイヤから下位レイヤへの依存関係のみが許可されます。 例えば、features から shared、entity のモジュールを import することは可能ですが、widget のような features よりも上位のレイヤからモジュールを import することは禁止されています。
- // これは NG
- import { OrderHistoryList } from "@/widget/order-history";
+ // これは OK
+ import { Button } from "@/shared";
+ import { cartModel } from "@/entities/cart";
Slice
続く FSD の第 2 階層を Slice と呼びます。 スライスは、責務毎、機能毎といった意味のあるまとまりで分離するための階層で、命名はビジネスドメインによって直接決定されます。
- 例:ブログサイトにおける entities のスライス
└── entities/
├── user/ // ユーザ情報管理
├── article/ // 成果物管理
└── comment/ // コメント管理
関連性の高いスライス同士は、より使いやすくするために同じディレクトリにグループ化することもできます。 例えば「認証」と「ユーザ情報管理」等の機能が関係している場合、それらを「User」というディレクトリにまとめるといったことが可能です。
各スライスは互いに直接的に依存しないように分離する必要があります。 もしスライス間で共有したいロジックがあったとしても、同じスライスの階層にロジックを置くことは回避すべきです。 このような場面では、スライスの分類を見直すか、もしくは上位の shared レイヤにロジックを移行する等、他の方法を検討すると良いでしょう。

また、スライスは基本的にネストしないことがベストプラクティスとなりますが、pages レイヤはフレームワークの要件によってネストが必要になる場合もあります。 例えば Next.js における Pages Router や App Router はファイルルーティングに基づくため URL 構造と同様のネストが必要になります。
Segment
FSD の第 3 階層は Segment と呼ばれます。 セグメントは技術的な性質によってコードを分類するための階層で、基本的には ui
/ api
/ model
/ lib
/ config
のような要素を持ちます。
ui
:UI 表示に関連するコンポーネント(UI コンポーネント、日付フォーマッタ、スタイル)api
:API との通信部(リクエスト関数、データ型、マッパ、GraphQL クエリ)model
:データモデル(スキーマ、インターフェイス、ストア、ビジネスロジック)lib
:補助コードやインフラconfig
:構成ファイル や Feature Flag
Public API
FSD には Public API と呼ばれる、スライスを外部のレイヤに公開するインターフェース設計手法があります。 外部レイヤからアクセス可能なエントリポイントを提供し、スライス配下のモジュールをカプセル化することで内部構造を隠蔽しつつ必要な機能だけを公開する、所謂、スライスの抽象化を行います。 これにより、内部の変更が他の部分に影響を与えにくくなり、保守性と可読性が向上します。
Public API は、スライス配下に index.ts
を作成して下記のように外部に公開してよいロジックのみをエクスポートします。
// entities/auth/index.ts
export { AuthForm } from './ui'
export * as authFormModel from './model'
model セグメントにも、下記のように index.ts
を作成して外部公開可能なロジックをエクスポートします。
// entities/auth/model/index.ts
export { useAuth } from './use-auth'
スライス内部にあるロジックに直接アクセスすることはできないため、スライスを通じて外部公開されているロジックを import することで呼び出します。
- // これは NG
- import { AuthForm } from "entities/auth/ui/auth-form"
- import { useAuth } from "entities/auth-form/model/use-auth"
-
- useAuth()
+ // これは OK
+ import { AuthForm, authFormModel } from "features/auth-form"
+
+ authFormModel.useAuth()
規約・制約
アプリケーションのスライスとセグメントは Public API の index.ts
(インデックス)に定義されているスライスの機能とコンポーネントのみを使用します。 Public API で定義されていないスライスまたはセグメントの内部部分は隔離されていると見做され、スライス、あるいはセグメント内でしかアクセスできません。
オブジェクト指向プログラミング と FSD
FSD は疎結合(Zero coupling)で 高凝縮(High cohesion)という特徴があります。

従来、オブジェクト指向プログラミングではこれらを達成するために、ポリモフィズム(Polymorphism)、カプセル化(Encapsulation)、継承(Inheritance)、抽象化(Abstraction) の概念が取り入れられてきました。 これらは、コードの独立性、再利用性、多機能性を保証し、コンポーネントや機能の使用方法によって得られる結果を柔軟に変更します。
FSD ではこれらの原則がフロントエンドアプリケーションに適用しやすくなっています。
例えば、ポリモフィズムや抽象化は FSD のレイヤによって達成されます。 下位のレイヤは抽象化されるため、上位レイヤでの再利用が可能となり、指定した parms
や props
に基づいてコンポーネントや機能の動作を変えることができます。
また、カプセル化は Public API によって達成されており、スライスとセグメントを用いることで、外部から必要な機能のみを呼び出すことができます。 これは、Java や PHP における Public クラス と Private / Protected クラスの使い分けに相当します。
クラスの継承は、レイヤ間の依存関係及び上位レイヤから下位レイヤを呼び出すことによって再利用性を担保します。
Next.js との競合
最近では、Next.js と FSD を併用する傾向が高まっています。 Next.js は FSD と相性が良いとされていますが、2 つの点で競合する可能性があります。
Pages
Next.js pages は Next.js 12 以前で主に使用されるファイルルーティングの仕組みです。
pages はディレクトリで定義したページがそのままルーティングに反映されます。 FSD にとって、pages はフラットなページリストを持つレイヤとなります。 これにより、Next.js の pages と FSD の pages をどのように組み合わせるかというコンフリクトが生じます。
Next.js と FSD を併用する場合、Next.js の pages をアプリケーションのルート([root]/pages/
)に、FSD の pages を src フォルダ([root]/src/pages/
)に格納する ことができます。
また、FSD から改名した pages のフラットリスト(例:pages-flat)と、Next.js のネストしたルート(pages)の 2 つのディレクトリを維持することで、pages-flat にページコードを格納し、pages にエクスポートします。
個人的には FSD の命名規則を遵守できる前者の方が良いと思っています。
App
Next.js では app レイヤの基本的な機能をすべて処理します。
しかし、ページとは独立してアプリケーション全体で何かを実行する必要がある場合は、Layout Pattern を使用してアプリケーション全体のレイアウトを作成することができます。
Layout Pattern では、全てのページや画面で共有される部分(例:ヘッダ、フッタ、サイドバー)を一箇所にまとめます。 つまり、ページに依らずにアプリケーション全体に影響を及ぼすような機能や設定をレイアウトとして定義し、それをアプリケーション全体で使用するため、機能の細分化を図る FSD の設計思想とコンフリクトする場合があります。
まとめ
今回のブログでは、フロントエンドのスケーラビリティを実現する Feature-Sliced Design(FSD)というデザイン手法について紹介しました。
責務が明確化されている FSD を採用することで、コード規模の拡大に対して、一貫した設計手法を適用できるというメリットがあります。 また、オブジェクト指向プログラミングの思想を Layer / Slice / Segment を使って実現することで、複雑な原理を隠蔽できます。
一方でプロジェクトの規模やチームメンバーのスキルセットにも依存するため、決して万能でないことも念頭に置いておく必要があります。
FSD の考え方は以前からありましたが、マイクロサービス指向や DevOps に注目が集まったことで、その優位性が表に出てきはじめたのかなと思います。