
Feature Sliced Design
- 53 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Helps with design & ui/ux tasks.
About
feature-sliced-design is a Claude Code skill for design & ui/ux. It helps solo builders move faster with AI-assisted coding.
- feature-sliced-design
- Design & UI/UX
- AI-coding skill
Feature Sliced Design by the numbers
- 53 all-time installs (skills.sh)
- Ranked #1,233 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill feature-sliced-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 53 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Helps with design & ui/ux tasks.
Files
Feature-Sliced Design ガイドライン
ディレクトリ構成
skills/feature-sliced-design/
SKILL.md
references/
README.md
import-rules.md
layers.md
public-api.md
slices-segments.md
samples/
README.md
project-structure.md
slice-public-api.md
layer-imports.md
cross-entity-import.md
slice-groups.md
composition-in-pages.md
nextjs-app-router.md
react-query-integration.md
scripts/
README.md
install.md
generate.md
lint.md
rules/
README.md
layer-dependency.md
public-api-enforcement.md
slice-isolation.md
segment-naming.md探索手順
タスクからカテゴリを引き、カテゴリの README.md で目的のページを特定する:
1. 下記マッピング表でタスクに対応するカテゴリを探す 2. そのカテゴリの README.md を参照して目的のページを特定する 3. 該当ページの .md を Read して詳細を確認する
タスク → カテゴリ マッピング
| タスク | カテゴリ | 参照 README |
|---|---|---|
| レイヤー構成・責務を知りたい | references | references/README.md |
| インポートルール・依存方向を確認したい | references | references/README.md |
| Public API パターンを理解したい | references | references/README.md |
| スライス・セグメントの定義を知りたい | references | references/README.md |
| 典型的な FSD プロジェクト構成の例を見たい | samples | samples/README.md |
| Next.js / React Query との組み合わせ例を見たい | samples | samples/README.md |
| @x 記法・クロスエンティティ依存の実例を見たい | samples | samples/README.md |
| pages / widgets での合成パターンを知りたい | samples | samples/README.md |
| ツールチェーンのインストール・CLI コマンドを知りたい | scripts | scripts/README.md |
| steiger でアーキテクチャ準拠チェックを実行したい | scripts | scripts/README.md |
| @feature-sliced/cli でコード生成したい | scripts | scripts/README.md |
| レイヤー依存方向のルールを自動適用したい | rules | rules/README.md |
| Public API 経由のみ許可するルールを確認したい | rules | rules/README.md |
| スライス分離・セグメント命名規約を確認したい | rules | rules/README.md |
コア概念の詳細は references/<topic>.md、チェックルールは rules/<rule>.md を参照。
必須ルール
- コードは 6 つの標準レイヤーに分割する:
app,pages,widgets,features,entities,shared - インポートは下位レイヤーへの一方向のみ許可。同一レイヤー内のスライス間インポートは禁止
- 各スライスは
index.tsで Public API を定義する。外部からは Public API 経由でのみインポートする export *は使用しない。公開するものを明示的に列挙する- セグメント名は「目的」で命名する(
ui,model,api)。「本質」で命名しない(components,hooks,types) appとsharedにスライスは持たない。セグメントのみで構成する
レイヤー構成(上位 → 下位)
app — ルーティング、プロバイダー、グローバル設定(スライスなし)
pages — 画面単位のスライス
widgets — 複数ページで再利用される大規模 UI ブロック
features — ビジネス価値をもたらすユーザーインタラクション
entities — ビジネスエンティティ(User, Product 等)
shared — 汎用的な再利用可能コード(スライスなし)標準セグメント
| セグメント | 内容 |
|---|---|
ui | UI コンポーネント、フォーマッタ、スタイル |
api | バックエンド連携、リクエスト関数、レスポンス型 |
model | データスキーマ、ストア、ビジネスロジック |
lib | スライスローカルのユーティリティ |
config | 設定定数、フィーチャーフラグ |
ディレクトリ構造例
src/
├── app/
│ ├── routes/
│ ├── store/
│ └── styles/
├── pages/
│ └── feed/
│ ├── ui/
│ ├── api/
│ └── index.ts
├── widgets/
│ └── header/
│ ├── ui/
│ └── index.ts
├── features/
│ └── search-articles/
│ ├── ui/
│ ├── model/
│ └── index.ts
├── entities/
│ └── user/
│ ├── ui/
│ ├── model/
│ ├── api/
│ └── index.ts
└── shared/
├── ui/
├── api/
├── lib/
└── config/よくある間違い
1. セグメント名に `components/`, `hooks/`, `types/` を使う → ui/, model/, lib/ を使用する 2. *`export でまとめてエクスポートする** → 明示的に export { Name } from で列挙する 3. **同一レイヤーのスライスを直接インポートする** → 上位レイヤーで合成するか、@x 記法を使用する 4. **再利用されないコードを features や widgets に早期移動する** → まず pages に置き、再利用が確定してから昇格する 5. **shared にビジネスロジックを置く** → ビジネスロジックは entities 以上に配置する 6. **独自レイヤーを追加する** → 標準の 6 レイヤーのみ使用する 7. **Public API なしでスライスを作成する** → 必ず index.ts を先に定義する 8. **型定義を types/ フォルダにまとめる** → ドメインごとに model/` セグメント内に配置する
Import Rules
FSD のインポートルールと依存関係の方向を定義する。
基本ルール
スライス内のモジュール(ファイル)は、厳密に下位のレイヤーに位置するスライスからのみインポートできる。
app ← pages, widgets, features, entities, shared をインポート可
pages ← widgets, features, entities, shared をインポート可
widgets ← features, entities, shared をインポート可
features ← entities, shared をインポート可
entities ← shared をインポート可
shared ← 外部ライブラリのみ(他レイヤーからインポート不可)例外
appとsharedはスライスを持たないため、内部セグメント間で自由に相互参照可能
同一レイヤー内のルール
同一レイヤーのスライス間で直接インポートは禁止:
// Bad — features 内のスライス間インポート
// features/cart/model/cart.ts
import { Product } from "features/product/model/product";
// Bad — pages 内のスライス間インポート
// pages/home/ui/HomePage.tsx
import { ProfileCard } from "pages/profile/ui/ProfileCard";クロスインポートの解決策
同一レイヤーの依存が必要になった場合の対処法:
1. 上位レイヤーでの合成
最も推奨される方法。上位レイヤーで組み合わせる:
// pages/post/ui/PostPage.tsx(pages レイヤーから features と entities をインポート)
import { CommentList } from "features/comment-list";
import { UserAvatar } from "entities/user";
export const PostPage = () => (
<div>
<CommentList renderAvatar={(userId) => <UserAvatar id={userId} />} />
</div>
);2. @x 記法(entities 限定)
エンティティ間の正当な依存に使用:
entities/
user/
@x/
post.ts ← entities/post に公開する専用 API
index.ts ← 汎用 Public API// entities/user/@x/post.ts
export { UserAvatar } from "../ui/Avatar";
// entities/post/ui/PostCard.tsx
import { UserAvatar } from "entities/user/@x/post";3. スライスの統合
常に一緒に変更されるスライスは 1 つに統合する。
4. 共有ロジックの下位レイヤーへの移動
features 間で共有されるビジネスロジックは entities に、entities 間で共有されるユーティリティは shared に移動する。
インポートスタイルのルール
| 状況 | スタイル |
|---|---|
| 同一スライス内 | 相対インポート("./ui/Button") |
| スライス境界を越える | 絶対インポート("entities/user") |
この使い分けにより循環インポートを防止する。
Steiger によるルール検証
Steiger は FSD の公式アーキテクチャ linter。インポートルール違反を CI で自動検出する。
警告サイン
- 他スライスのストアに直接依存している
- 他スライスの内部ファイルをインポートしている(Public API を迂回)
- 双方向の依存関係が存在する
- クロススライスの変更で頻繁に破壊が発生する
- 同一フォルダ内の複数ドメインを扱う汎用ファイル(
types.ts,utils.ts等)が存在する(デセグメント化の兆候)
Layers
FSD の第 1 階層。コードを責務に応じた 6 つの標準レイヤーに分割する。
レイヤー一覧(上位 → 下位)
app
アプリケーション全体の技術的・ビジネス的な関心事。スライスなし — セグメントのみ。
| セグメント | 目的 |
|---|---|
routes | ルーター設定 |
store | グローバル状態管理のセットアップ |
styles | グローバルスタイル |
entrypoint | フレームワーク固有のエントリーポイント |
pages
画面やアクティビティ。各ルートに対応するスライスを持つ。
- ページレイアウトと UI(
ui) - ローディング状態とエラーバウンダリ(
ui) - データ取得とミューテーション(
api) - 通常は 1 ルート = 1 スライス
- 他で再利用されないコードはここに留める(
widgetsへの早期移動を避ける)
widgets
entities と features で構成される大規模な自己完結型 UI ブロック。
- 複数ページで使用される複合 UI セクション
- 単一ページ内の主要な独立セクション(ネストルーティング / レイアウトで有用)
- ページ固有で再利用されないコンポーネントは含めない
features
ビジネスエンティティを伴い、複数ページで再利用されるユーザー向けインタラクション。
- インタラクション UI(フォーム、副作用を伴うボタン)(
ui) - 必要な API 呼び出し(
api) - バリデーションとローカル状態(
model) - フィーチャーフラグ(
config) - 何でも feature にしない — 乱用すると意味が薄れる
- 「このインタラクションは複数箇所で再利用される」ことを示すシグナル
entities
現実世界のビジネス概念: User, Post, Product 等。
- データストレージとバリデーションスキーマ(
model) - エンティティ関連の API 関数(
api) - 上位レイヤーで再利用される視覚表現コンポーネント(
ui) - エンティティ 間 のインタラクションは上位レイヤー(features, widgets, pages)に配置
- クロスエンティティ参照は
@x記法を使用
shared
基盤レイヤー。外部システム(バックエンド、ライブラリ、環境)への接続を確立。スライスなし — セグメントのみ。
| セグメント | 目的 |
|---|---|
api | バックエンドクライアントとリクエスト関数 |
ui | UI キットコンポーネント(ビジネス非依存) |
lib | 内部ライブラリ(日付、色、テキスト等) |
config | 環境変数とフィーチャーフラグ |
routes | ルートパターン定数 |
i18n | 翻訳セットアップ |
- ビジネスロジックを含めない
- コードベース全体で再利用されるインフラストラクチャのみ
ディレクトリ構造
フォルダ名は小文字:
src/
app/
pages/
widgets/
features/
entities/
shared/processes レイヤー(非推奨)
複数ページにまたがる複雑なシナリオを扱うレイヤーとして存在していたが、現在は非推奨。 features と app へ移行する。
注意点
- 独自レイヤーを追加しない — 標準セットはチーム間で共通の意味を持つ
featuresレイヤーは新メンバーの主要な発見ポイント — 意味のある状態を保つ- スライス名はドメイン固有で可変、レイヤー名は固定
- 全レイヤーが必須ではない。ほとんどのフロントエンドアプリは
shared,pages,appの最小構成から開始できる
Public API
スライス(またはセグメント)とコードベースの他の部分との間の契約。外部コードは指定されたエントリーポイントのみからインポートでき、内部ファイルパスからは直接インポートしない。
なぜ重要か
1. 構造変更からの保護 — スライス内部のリファクタリングがアプリの他の部分を壊さない 2. 振る舞い変更の明示 — スライスの振る舞いに対する破壊的変更は Public API の変更を必要とする 3. 露出の最小化 — 必要な表面のみをエクスポートし、実装詳細は非公開に
実装: index ファイル
スライスルートの index.ts(または index.js)で、公開する表面のみを re-export する:
// pages/auth/index.ts
export { LoginPage } from "./ui/LoginPage";
export { RegisterPage } from "./ui/RegisterPage";利用側はスライスルートからインポートし、内部パスからインポートしない:
// Good
import { LoginPage } from "pages/auth";
// Bad — 内部パスからのインポート
import { LoginPage } from "pages/auth/ui/LoginPage";アンチパターン: ワイルドカード re-export
export * を避ける — 実際のインターフェースが不明瞭になり、内部詳細を意図せず公開する:
// Bad
export * from "./ui/Comment";
// Good
export { Comment } from "./ui/Comment";
export type { CommentProps } from "./ui/Comment";@x 記法(クロススライス Public API)
正当なクロススライス依存(主に entities)には @x ディレクトリで専用 API を作成:
entities/
user/
@x/
post.ts ← entities/post 専用の公開 API
index.ts ← 汎用の公開 API// entities/user/@x/post.ts
export { UserAvatar } from "../ui/Avatar";
// entities/post/ui/PostCard.tsx
import { UserAvatar } from "entities/user/@x/post";よくある問題と解決策
循環インポート
相対インポートと絶対インポートの混在から発生しやすい。2 つのルールで防止:
- 同一スライス内では相対インポート(完全な内部パス)
- スライス境界を越える場合は絶対インポート
ツリーシェイキング / バンドル肥大化
shared/ui の単一 index ファイルが、すべての利用側バンドルに全エクスポートを含めてしまう。
解決策: コンポーネントごとに個別の index ファイルを作成:
shared/
ui/
button/
index.ts ← Button のみエクスポート
input/
index.ts ← Input のみエクスポートimport { Button } from "shared/ui/button";
import { Input } from "shared/ui/input";IDE の自動インポート違反
IDE は最も近いファイルから自動インポートし、Public API を迂回することがある。Steiger 等の linter で CI で検出する。
大規模プロジェクトでのパフォーマンス問題
大規模プロジェクトでは単一の barrel ファイルにより開発サーバーが低速化することがある。対策:
- セグメントレベルの index ファイルを避け、コンポーネントごとの粒度で個別 index を作成する
- 極端に大規模なコードベースではモノレポアーキテクチャへの分割を検討する
注意点
- Public API ルールはスライスのないレイヤー(
app,shared)のセグメントにも適用される @x記法は意図的なエスケープハッチであり、一般パターンではない — 可能なら上位レイヤーでの再構成を優先- 新しいスライスを作るときは、利用側を書く前に必ず
index.tsを定義する
References
| Name | Description | Path |
|---|---|---|
| Import Rules | FSD のインポートルールと依存関係の方向を定義する。 | import-rules.md |
| Layers | FSD の第 1 階層。コードを責務に応じた 6 つの標準レイヤーに分割する。 | layers.md |
| Public API | スライス(またはセグメント)とコードベースの他の部分との間の契約。 | public-api.md |
| Slices & Segments | FSD の第 2・第 3 階層。レイヤーの下にスライスとセグメントが位置する。 | slices-segments.md |
Slices & Segments
FSD の第 2・第 3 階層。レイヤーの下にスライスとセグメントが位置する。
スライス
ビジネスドメインの意味でコードをグループ化する。
- 使用するレイヤー:
pages,widgets,features,entities - 使用しないレイヤー:
app,shared(セグメントのみ)
スライス名はドメイン固有でプロジェクトごとに異なる:
| プロジェクト | スライス例 |
|---|---|
| フォトギャラリー | photo, effects, gallery-page |
| SNS | post, comments, news-feed |
| EC サイト | product, cart, checkout |
ゼロ結合・高凝集
同一レイヤーのスライスは互いに独立:
- 同一レイヤーのスライス間で直接インポートしない
- スライスの主要な関心事に関するコードはすべてスライス内に配置する
スライスグループ
関連スライスを構造的にフォルダでまとめることが可能:
features/
auth/
login/
register/
reset-password/制約: グループ内のスライスも完全な分離を維持する — 兄弟スライス間でコードを共有しない。
セグメント
スライス(または app / shared)内で技術的な目的でコードをグループ化する。
標準セグメント
| セグメント | 内容 |
|---|---|
ui | UI コンポーネント、フォーマッタ、スタイル |
api | バックエンド連携、リクエスト関数、レスポンス型 |
model | データスキーマ、インターフェース、ストア、ビジネスロジック |
lib | スライスローカルの再利用可能ユーティリティ |
config | 設定定数、フィーチャーフラグ |
カスタムセグメント名も許可。特に app と shared で一般的。
命名規則
セグメント名はコンテンツの目的(purpose)を記述する。技術的な本質(essence)ではない。
Bad:
components/
hooks/
types/
utils/Good:
ui/
model/
api/
lib/ディレクトリ構造例
features/
authenticate-user/ ← スライス
ui/ ← セグメント
LoginForm.tsx
api/ ← セグメント
loginMutation.ts
model/ ← セグメント
authStore.ts
authSchema.ts
index.ts ← Public API@x 記法(クロスインポート)
主に entities での正当なクロススライス依存に使用:
entities/
user/
@x/
post.ts ← entities/post に公開する API
ui/
Avatar.tsx
index.ts// entities/post/ui/PostCard.tsx
import { UserAvatar } from "entities/user/@x/post";「user crossed with post」— 明示的で監査可能なクロスエンティティ依存。
注意点
- セグメントは単一ファイル(
model.ts)でもフォルダ(model/)でも可 — 内容が増えたらフォルダに - 1 ファイルだけのためにセグメントを作らない — 小さなスライスではルート直下のフラットファイルで可
@xは主にentities向け。他レイヤーでの多用は設計上の問題のサイン
Layer Dependency
インポートは下位レイヤーへの一方向のみ許可する。
Rule
スライス内のモジュールは、厳密に下位のレイヤーに位置するスライスの Public API からのみインポートできる。上位レイヤーや同一レイヤーのスライスからインポートしてはならない。
レイヤーの順序(上位 → 下位): app > pages > widgets > features > entities > shared
Good
// features/search/ui/SearchBar.tsx
import { User } from "entities/user"; // features → entities (OK)
import { Input } from "shared/ui/input"; // features → shared (OK)
// pages/home/ui/HomePage.tsx
import { SearchBar } from "features/search"; // pages → features (OK)
import { UserCard } from "entities/user"; // pages → entities (OK)
// widgets/header/ui/Header.tsx
import { SearchBar } from "features/search"; // widgets → features (OK)Bad
// entities/user/model/user.ts
import { SearchBar } from "features/search"; // entities → features (NG: 上位)
// features/cart/model/cart.ts
import { Product } from "features/product"; // features → features (NG: 同一レイヤー)
// shared/ui/button.tsx
import { User } from "entities/user"; // shared → entities (NG: 上位)Why
一方向の依存関係により:
- 変更の影響範囲が予測可能になる(下位レイヤーの変更は上位にのみ波及)
- 循環依存を構造的に防止する
- 各レイヤーを独立してテスト・リファクタリングできる
- コードの見通しが良くなり、新メンバーが構造を理解しやすい
Public API Enforcement
外部からのインポートは index.ts 経由のみ許可する。
Rule
スライスの外部からは、スライスルートの index.ts(Public API)を通じてのみインポートする。内部ファイルパスへの直接インポートは禁止。export * は使用せず、公開するものを明示的に列挙する。
Good
// index.ts で明示的にエクスポート
// features/auth/index.ts
export { LoginForm } from "./ui/LoginForm";
export { useAuth } from "./model/useAuth";
export type { AuthState } from "./model/types";
// 利用側: スライスルートからインポート
import { LoginForm, useAuth } from "features/auth";
import type { AuthState } from "features/auth";// shared/ui はコンポーネントレベルの index で個別エクスポート(ツリーシェイキング対策)
// shared/ui/button/index.ts
export { Button } from "./Button";
export type { ButtonProps } from "./Button";
// 利用側
import { Button } from "shared/ui/button";Bad
// 内部パスへの直接インポート
import { LoginForm } from "features/auth/ui/LoginForm";
import { authStore } from "features/auth/model/authStore";
import { loginMutation } from "features/auth/api/loginMutation";// ワイルドカード re-export(内部詳細の意図しない公開)
// features/auth/index.ts
export * from "./ui/LoginForm";
export * from "./model/authStore";Why
Public API を強制することで:
- スライス内部のファイル構成を自由にリファクタリングできる(外部に影響しない)
- 公開するインターフェースが明示的に管理される
export *の回避により、意図しないエクスポートを防止する- IDE の自動インポートが内部ファイルを選択した場合に Steiger 等で検出できる
Rules
| Name | Description | Path |
|---|---|---|
| Layer Dependency | インポートは下位レイヤーへの一方向のみ許可 | ./layer-dependency.md |
| Public API Enforcement | 外部からのインポートは index.ts 経由のみ許可 | ./public-api-enforcement.md |
| Slice Isolation | 同一レイヤー内のスライスは互いに独立 | ./slice-isolation.md |
| Segment Naming | セグメント名は「目的」で命名し「本質」で命名しない | ./segment-naming.md |
Segment Naming
セグメント名は「目的」で命名し、「本質」で命名しない。
Rule
セグメント名はコンテンツの目的(purpose — なぜそのコードが存在するか)を記述する。技術的な本質(essence — それが何であるか)で命名しない。標準セグメント名 ui, api, model, lib, config を優先する。
Good
features/auth/
ui/ ← UIコンポーネント(目的: 画面表示)
api/ ← API連携(目的: バックエンド通信)
model/ ← データモデル(目的: ビジネスロジック)
lib/ ← ユーティリティ(目的: 内部補助)
config/ ← 設定(目的: フラグ・定数管理)
index.ts# model/ 内でドメインごとにファイルを分割
pages/delivery/
model/
delivery.ts ← DeliveryOption, formatDeliveryPrice
user.ts ← UserInfo, getUserInitialsBad
features/auth/
components/ ← 本質: 「コンポーネントである」
hooks/ ← 本質: 「フックである」
types/ ← 本質: 「型定義である」
utils/ ← 本質: 「ユーティリティである」
constants/ ← 本質: 「定数である」# 汎用ファイル名で複数ドメインを混在
pages/delivery/
model/
types.ts ← DeliveryOption + UserInfo が混在
utils.ts ← formatDeliveryPrice + getUserInitials が混在Why
目的ベースの命名により:
- ファイルの役割が名前から直感的にわかる
- FSD 標準に従うことでチーム間の一貫性を保てる
- ドメイン固有のファイル名(
delivery.ts,user.ts)を使うことで、凝集度が高く結合度が低い構造になる types.tsやutils.tsのような汎用名は、無関係なコードの混在(desegmentation)を招く
Slice Isolation
同一レイヤー内のスライスは互いに独立する。
Rule
同一レイヤーのスライス間で直接インポートは禁止。スライス間の依存が必要な場合は、上位レイヤーでの合成、@x 記法(entities 限定)、またはスライスの統合で解決する。
Good
// 上位レイヤーで合成(推奨)
// pages/post/ui/PostPage.tsx
import { CommentList } from "features/comment-list";
import { UserAvatar } from "entities/user";
export const PostPage = () => (
<CommentList
renderAvatar={(userId) => <UserAvatar id={userId} />}
/>
);// @x 記法で明示的なクロスエンティティ依存
// entities/user/@x/post.ts
export { UserAvatar } from "../ui/Avatar";
// entities/post/ui/PostCard.tsx
import { UserAvatar } from "entities/user/@x/post";Bad
// 同一レイヤーのスライスを直接インポート
// features/cart/model/cart.ts
import { Product } from "features/product/model/product";
// entities/comment/ui/Comment.tsx
import { UserAvatar } from "entities/user/ui/Avatar";// スライスグループ内でも兄弟スライス間は禁止
// features/auth/login/model/loginStore.ts
import { registrationSchema } from "features/auth/register/model/schema";Why
スライスの分離により:
- 各スライスを独立して開発・テスト・削除できる
- 変更が他のスライスに予期しない影響を与えない
- コードの所有権が明確になる(どのスライスがどのロジックを担当するか)
@x記法は依存を明示的・監査可能にするが、乱用はエンティティ境界を固定化するため注意
Composition in Pages
同一レイヤー間の直接依存を避け、上位レイヤー(pages/widgets)で features と entities を合成するパターン。
// pages/post/ui/PostPage.tsx
// features と entities を pages レイヤーで合成する(同一レイヤー間インポートを避ける)
import { CommentList } from "features/comment-list";
import { UserAvatar } from "entities/user";
import { Button } from "shared/ui/button";
export const PostPage = () => (
<div>
<h1>投稿詳細</h1>
{/* entities の UserAvatar を features/comment-list に props として渡す */}
<CommentList renderAvatar={(userId) => <UserAvatar id={userId} />} />
<Button>コメントする</Button>
</div>
);// features/comment-list/ui/CommentList.tsx
// UserAvatar を直接インポートせず、props 経由で受け取る
type CommentListProps = {
renderAvatar: (userId: string) => React.ReactNode;
};
export const CommentList = ({ renderAvatar }: CommentListProps) => {
const { data: comments } = useComments();
return (
<ul>
{comments?.map((comment) => (
<li key={comment.id}>
{renderAvatar(comment.authorId)}
<p>{comment.body}</p>
</li>
))}
</ul>
);
};Notes
features/comment-listがentities/userを直接インポートするのは同一レイヤー間インポートに当たるため禁止- render props / slots パターンで上位レイヤーに合成を委ねることで分離を維持する
- どうしても同一レイヤー間依存が必要な場合は、共有ロジックを下位レイヤーに移動するか、スライスを統合する
- widgets レイヤーでも同様のパターンを使い、複数ページで再利用できる自己完結型ブロックを作る
Cross-Entity Import
entities 間の正当なクロススライス依存を @x 記法で明示的に管理する。
entities/
user/
@x/
post.ts ← entities/post に公開する専用 API
ui/
Avatar.tsx
index.ts ← 汎用 Public API
post/
ui/
PostCard.tsx
index.ts// entities/user/@x/post.ts — post スライス専用の公開 API
export { UserAvatar } from "../ui/Avatar";// entities/post/ui/PostCard.tsx
// "user crossed with post" — 明示的で監査可能なクロスエンティティ依存
import { UserAvatar } from "entities/user/@x/post";
export const PostCard = ({ post }) => (
<div>
<UserAvatar userId={post.authorId} />
<h2>{post.title}</h2>
</div>
);Notes
@xは意図的なエスケープハッチであり、一般パターンではない- 可能であれば上位レイヤー(pages, widgets)で合成する方を優先する
- 他のレイヤーで
@xを多用する場合は設計上の問題のサイン entities/user/@x/post.tsはentities/postだけに向けた専用 API — 汎用index.tsとは別管理
Layer Imports
上位レイヤーは下位レイヤーからのみインポートできる一方向依存ルールの実例。
// pages/post/ui/PostPage.tsx
// pages → features, entities, shared は OK
import { CommentList } from "features/comment-list";
import { UserAvatar } from "entities/user";
import { Button } from "shared/ui/button";
export const PostPage = () => (
<div>
<CommentList renderAvatar={(userId) => <UserAvatar id={userId} />} />
<Button>投稿する</Button>
</div>
);// widgets/header/ui/Header.tsx
// widgets → features, entities, shared は OK
import { SearchBar } from "features/search";
import { UserAvatar } from "entities/user";
import { Logo } from "shared/ui/logo";// Bad — 上位レイヤーへのインポート(禁止)
// entities/user/model/user.ts
import { SearchBar } from "features/search"; // entities → features (NG)
// Bad — 同一レイヤーのスライス間インポート(禁止)
// features/cart/model/cart.ts
import { Product } from "features/product"; // features → features (NG)Notes
- インポート方向:
app>pages>widgets>features>entities>shared sharedは外部ライブラリのみインポート可(他レイヤーからインポート不可)- 同一スライス内では相対インポート、スライス境界を越える場合は絶対インポートを使う
- 双方向依存や同一レイヤー間依存が必要になった場合は設計を見直すサイン
Next.js App Router Integration
Next.js App Router と FSD を併用するディレクトリ構成とページ再エクスポートパターン。
project-root/
├── app/ # Next.js App Router(ルーティング担当)
│ ├── api/
│ │ └── get-example/
│ │ └── route.ts
│ └── example/
│ └── page.tsx
├── pages/ # 空フォルダ(ビルド競合防止のため必須)
│ └── README.md
└── src/ # FSD レイヤー群
├── app/
├── pages/
├── widgets/
├── features/
├── entities/
└── shared/// app/example/page.tsx — Next.js ルートから FSD ページを再エクスポート
export { ExamplePage as default, metadata } from "@/pages/example";// src/pages/example/ui/ExamplePage.tsx — FSD 側の実装
export const metadata = { title: "Example" };
export const ExamplePage = () => <main>Example</main>;// src/pages/example/index.ts — Public API
export { ExamplePage } from "./ui/ExamplePage";
export { metadata } from "./ui/ExamplePage";Notes
- Next.js の
app/フォルダがルートに必要なため、FSD のappレイヤーはsrc/app/に配置する - Next.js Pages Router の場合は
pages/_app.tsxからexport { App as default } from "@/app/custom-app"を行う - Middleware と Instrumentation はプロジェクトルートに置く必要がある
- API Routes のロジックは
src/app/api-routesセグメントに配置し Next.js ルートから再エクスポートする
Project Structure
FSD の基本ディレクトリ構成を示す最小構成例。
src/
├── app/ # アプリ全体の設定・エントリーポイント
│ ├── providers/
│ │ └── index.ts
│ ├── store/
│ │ └── index.ts
│ └── styles/
│ └── global.css
├── pages/ # 画面・ルート対応スライス
│ ├── home/
│ │ ├── ui/
│ │ │ └── HomePage.tsx
│ │ └── index.ts
│ └── profile/
│ ├── ui/
│ │ └── ProfilePage.tsx
│ └── index.ts
├── widgets/ # 複数ページで使う複合 UI ブロック
│ └── header/
│ ├── ui/
│ │ └── Header.tsx
│ └── index.ts
├── features/ # 再利用されるユーザーインタラクション
│ └── auth/
│ ├── ui/
│ │ └── LoginForm.tsx
│ ├── api/
│ │ └── loginMutation.ts
│ ├── model/
│ │ └── authStore.ts
│ └── index.ts
├── entities/ # ビジネスエンティティ
│ └── user/
│ ├── ui/
│ │ └── UserCard.tsx
│ ├── model/
│ │ └── types.ts
│ └── index.ts
└── shared/ # 基盤インフラ(ビジネスロジックなし)
├── api/
│ └── client.ts
├── ui/
│ └── button/
│ └── index.ts
└── lib/
└── format-date.tsNotes
appとsharedにスライスはなく、セグメントのみ直下に置く- 全レイヤーが必須ではない。最小構成は
shared,pages,app - 各スライスのルートに
index.ts(Public API)を必ず作成する - フォルダ名はすべて小文字のケバブケース
React Query Integration
entities の api セグメントにクエリファクトリを置き、pages から利用するパターン。
// src/entities/post/api/post.queries.ts — クエリファクトリ
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import { getPosts } from "./get-posts";
import { getDetailPost } from "./get-detail-post";
export const postQueries = {
all: () => ["posts"],
lists: () => [...postQueries.all(), "list"],
list: (page: number, limit: number) =>
queryOptions({
queryKey: [...postQueries.lists(), page, limit],
queryFn: () => getPosts(page, limit),
placeholderData: keepPreviousData,
}),
detail: (id?: number) =>
queryOptions({
queryKey: [...postQueries.all(), "detail", id],
queryFn: () => getDetailPost({ id }),
staleTime: 5000,
}),
};// src/pages/post/ui/PostPage.tsx — pages からクエリを利用
import { useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { postQueries } from "entities/post/api/post.queries";
export const PostPage = () => {
const { postId } = useParams<{ postId: string }>();
const { data: post, isLoading } = useQuery(
postQueries.detail(Number(postId))
);
if (isLoading) return <div>Loading...</div>;
return <div>{post?.title}</div>;
};// src/shared/api/query-client.ts — 共有 QueryClient
import { QueryClient } from "@tanstack/react-query";
export const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5 * 60 * 1000 } },
});// src/app/providers/query-provider.tsx — app レイヤーでラップ
import { QueryClientProvider } from "@tanstack/react-query";
import { queryClient } from "shared/api/query-client";
export const QueryProvider = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);Notes
- ミューテーションは
featuresのapiセグメントにuseMutationフックとして配置する - クリアなエンティティ分離がない場合は
shared/apiにクエリをまとめる選択肢もある QueryClientはshared/apiに置き、appプロバイダーで注入する- entities 間でクエリキーが重複しないよう、
all()をルートキーとするファクトリパターンを採用する
samples
| Name | Description | Path |
|---|---|---|
| Composition in Pages | 同一レイヤー間の直接依存を避け、上位レイヤー(pages/widgets)で features と entities を合成するパターン。 | composition-in-pages.md |
| Cross-Entity Import | entities 間の正当なクロススライス依存を @x 記法で明示的に管理する。 | cross-entity-import.md |
| Layer Imports | 上位レイヤーは下位レイヤーからのみインポートできる一方向依存ルールの実例。 | layer-imports.md |
| Next.js App Router Integration | Next.js App Router と FSD を併用するディレクトリ構成とページ再エクスポートパターン。 | nextjs-app-router.md |
| Project Structure | FSD の基本ディレクトリ構成を示す最小構成例。 | project-structure.md |
| React Query Integration | entities の api セグメントにクエリファクトリを置き、pages から利用するパターン。 | react-query-integration.md |
| Slice Groups | 関連スライスをフォルダでまとめつつ、スライス間の分離を維持するパターン。 | slice-groups.md |
| Slice Public API | スライスの index.ts で公開インターフェースを定義し、内部パスへの直接インポートを防ぐ。 | slice-public-api.md |
Slice Groups
関連スライスをフォルダでまとめつつ、スライス間の分離を維持するパターン。
features/
auth/ ← スライスグループ(フォルダ)
login/ ← スライス
register/ ← スライス
reset-password/ ← スライスfeatures/
auth/
login/
ui/
LoginForm.tsx
api/
loginMutation.ts
model/
loginStore.ts
index.ts ← login スライスの Public API
register/
ui/
RegisterForm.tsx
api/
registerMutation.ts
index.ts ← register スライスの Public API// features/auth/login/index.ts
export { LoginForm } from "./ui/LoginForm";
export { useLoginMutation } from "./api/loginMutation";
// features/auth/register/index.ts
export { RegisterForm } from "./ui/RegisterForm";// グループ内スライス間でのコード共有は禁止
// Bad — features/auth/register/ui/RegisterForm.tsx
import { useLoginMutation } from "features/auth/login"; // 兄弟スライス間インポート (NG)
// Good — 共有ロジックは entities または shared に移動する
import { authApi } from "shared/api";Notes
- スライスグループはあくまで構造的な整理であり、グループ内の各スライスは完全な独立を維持する
- 兄弟スライス間でコードを共有する必要が出た場合は、下位レイヤーへの移動を検討する
- グループ自体の
index.tsは不要。利用側は各スライスから直接インポートする - グループ化はどのレイヤーでも可能(
pages/,entities/など)
Slice Public API
スライスの index.ts で公開インターフェースを定義し、内部パスへの直接インポートを防ぐ。
// features/auth/index.ts — Public API: 公開するものを明示的に列挙
export { LoginForm } from "./ui/LoginForm";
export { useAuth } from "./model/useAuth";
export type { AuthState } from "./model/types";// 利用側: スライスルートからインポート(内部パスは直接参照しない)
import { LoginForm, useAuth } from "features/auth";
import type { AuthState } from "features/auth";
// Bad — 内部パスへの直接インポート
// import { LoginForm } from "features/auth/ui/LoginForm";// shared/ui はコンポーネントごとに個別 index(ツリーシェイキング対策)
// shared/ui/button/index.ts
export { Button } from "./Button";
export type { ButtonProps } from "./Button";
// 利用側
import { Button } from "shared/ui/button";Notes
export *は使わない — 内部詳細が意図せず公開され、インターフェースが不明確になる- スライス作成時は利用側を書く前に必ず
index.tsを定義する - IDE の自動インポートが内部ファイルを選ぶことがある — Steiger 等の linter で CI 検出する
appとsharedのセグメントにも同じ Public API ルールが適用される
Generate
@feature-sliced/cli を使ったレイヤー・スライス・セグメントのコード生成コマンド集。
複数ページスライスの一括生成
npx fsd pages feed sign-in article-read article-edit profile settings --segments uipages/feed/ui/、pages/sign-in/ui/ 等のフォルダと各 index ファイルが作成される。
Entities レイヤーへのスライス生成
fsd entities client --segments ui apifsd e client -s ui,api--segments(-s)でセグメントをカンマまたはスペース区切りで指定する。
Widgets レイヤーへのスライス生成(ルート指定あり)
fsd w bottom-bar -s ui api -r srcfsd widgets bottom-bar --segments ui,api --root src--root(-r)で生成先ルートディレクトリを指定する。
Features レイヤーへのネストスライス生成
fsd f employee/employee-recordfsd feat employee/employee-recordスラッシュ区切りでネストしたスライスパスを指定できる。
Entities レイヤーへのカスタムルート指定
fsd e user -r ./src/libfsd entity user --root ./src/libPages レイヤーへの複数スライス生成
fsd p edit-note note-list -s ui apifsd page edit-note,note-list -s ui apiShared レイヤーへのセグメント生成
fsd shared --segments api configfsd s ui apifsd shared ui -s apiレイヤー省略エイリアス一覧
| 省略形 | フルネーム |
|---|---|
e / entity | entities |
w / widget | widgets |
f / feat | features |
p / page | pages |
s | shared |
Install
Feature-Sliced Design ツールチェーンのインストールコマンド集。
@feature-sliced/cli のグローバルインストール
npm add -g @feature-sliced/cliグローバルインストールにより fsd バイナリが利用可能になる。
@feature-sliced/cli のローカルインストール
npm install --save-dev @feature-sliced/cliローカルインストール後は npx fsd で実行する。
steiger(アーキテクチャ linter)のインストール
npm install --save-dev steigerFSD 準拠チェック用 steiger プラグインのインストール
npm install --save-dev @feature-sliced/steiger-pluginsteiger と合わせてインストールする。
Lint
steiger を使ったアーキテクチャ準拠チェックのコマンド集。
FSD 準拠チェックの実行
npx steiger ./src./src 配下のファイル構造を FSD ルールで検証する。
ウォッチモードでの継続チェック
npx steiger ./src --watchnpx steiger ./src -wファイルシステムの変更を監視し、変更のたびに自動で再チェックする。
steiger 設定ファイルの作成(TypeScript)
# steiger.config.ts を手動で作成するimport { defineConfig } from 'steiger'
import fsd from '@feature-sliced/steiger-plugin'
export default defineConfig([
...fsd.configs.recommended,
])設定ファイル不要の場合はゼロコンフィグで動作する。
特定レイヤーのルールを無効化
import { defineConfig } from 'steiger'
import fsd from '@feature-sliced/steiger-plugin'
export default defineConfig([
...fsd.configs.recommended,
{
files: ['./src/shared/**'],
rules: {
'fsd/public-api': 'off',
},
},
])files でファイルグロブを指定し、rules でルールを 'off' にする。
特定ファイルをチェック対象から除外
import { defineConfig } from 'steiger'
export default defineConfig([
{ ignores: ['**/__mocks__/**'] },
])scripts
| Name | Description | Path |
|---|---|---|
| Generate | @feature-sliced/cli を使ったレイヤー・スライス・セグメントのコード生成コマンド集。 | generate.md |
| Install | Feature-Sliced Design ツールチェーンのインストールコマンド集。 | install.md |
| Lint | steiger を使ったアーキテクチャ準拠チェックのコマンド集。 | lint.md |