Claude Skill

feature-sliced-design

Official Feature-Sliced Design (FSD) v2.1 skill for applying the methodology to frontend projects. Use when the task involves organizing project structure with FSD layers, deciding where code belongs, placing static assets (images, icons, fonts, PDFs), grouping closely related sl

LLM Mart · 0 points · 9 views 29 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download feature-sliced-skills-feature-sliced-design-fd71da4.zip · 61 KB

Install

skills CLI npx skills add https://github.com/feature-sliced/skills/tree/master/feature-sliced-design
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install feature-sliced-skills@llmmart
Git git clone https://github.com/feature-sliced/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole feature-sliced/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Feature-Sliced Design (FSD) v2.1

Source: fsd.how | Strictness can be adjusted based on project scale and team context.

How to use this skill. For placement decisions, start with the decision tree in Section 2 and use the placement table in Section 3 as a quick reference. To check a structure for violations, use the rules in Section 4. To resolve same-layer cross-imports, use Section 7. For task-specific guidance, load only the relevant reference files from Section 10; do not preload the rest.

1. Core philosophy & layer overview

FSD v2.1 core principle: "Start simple, extract when needed."

The extraction rule

Place code in pages/ first. Duplication across pages is acceptable and does not by itself require extraction to a lower layer. Extract only when all three conditions hold:

  1. The same code is used in multiple places right now, not hypothetically.
  2. It has a reason to change that is independent of any one consumer.
  3. The boundary has a focused responsibility.

The six layers

Not all layers are required. Most projects can start with only shared/, pages/, and app/. Add features/ and entities/ only when they provide clear value. Do not create empty layer folders "just in case." The widgets/ layer is discouraged (see the callout below).

FSD uses 6 standardized layers, listed here from highest to lowest:

app/       → App initialization, providers, routing
pages/     → Route-level composition, owns its own logic
widgets/   → Reusable UI blocks (discouraged, see the callout below)
features/  → Reusable user interactions (see the extraction rule above)
entities/  → Reusable business domain models (see the extraction rule above)
shared/    → Infrastructure with no business logic (UI kit, utils, API client)

The official layer reference discourages using the Widgets layer, and this skill follows it. Widgets may seem useful for representing independent UI blocks. However, in real frontend code, UI blocks often include logic required for user flows, such as data fetching, state management, and event handling. In this case, the responsibilities of Features, which handle user flows, and Widgets, which handle UI blocks, can overlap, making the boundary between the two layers unclear.

Not creating a widget does not mean moving the block elsewhere untouched. A screen-specific composition stays in pages; a reused action and the UI to perform it go to features; context-free UI goes to shared; an app-wide layout goes to app.

Discouraged is not deprecated: an existing widgets layer stays valid. See references/layer-structure.md for that case and for layout placement.

The import rule

A module may only import from layers strictly below it. Cross-imports between slices on the same layer are forbidden, with one narrow exception in Section 7.

// Allowed
import { Button } from "@/shared/ui/Button"; // features → shared
import { useUser } from "@/entities/user"; // pages → entities

// Violation
import { loginUser } from "@/features/auth"; // entities → features
import { likePost } from "@/features/like-post"; // features → features

Note: The processes/ layer is deprecated in v2.1. For migration details, read references/migration-guide.md.

2. Decision framework

When writing new code, follow this tree:

Step 1: Where is this code used?

  • Used in only one page → keep it in that pages/ slice.
  • Used in 2+ pages but duplication is manageable → keeping separate copies in each page is also valid.
  • An entity or feature with a single consumer → keep it at the consumer (Steiger flags this as insignificant-slice).

Step 2: Is it reusable infrastructure with no business logic?

The official layer reference draws the line for Shared like this: no business logic, but business-themed is fine (a company logo, a page layout), and so is UI logic (autocomplete, a search bar). Exchanging data with the backend and CRUD boilerplate are not business logic either. Business logic is a rule the product enforces on its own data, such as applying a discount to an order. If the code fits none of the exclusions and still does not clearly enforce a product rule, the term does not decide; go back to Step 1 and place it by where it is used.

  • UI components → shared/ui/
  • Utility functions → shared/lib/
  • API client, route constants → shared/api/ or shared/config/
  • Auth tokens, session management → shared/auth/
  • CRUD once several slices call it → shared/api/ (a single caller keeps it, see Step 1)

Step 3: Is it a complete user action that several consumers share, with a focused responsibility and a reason to change of its own?

  • Yes → features/
  • Uncertain, single use, or speculative reuse → keep in the page.

Step 4: Is it a business domain model that several consumers share, with a focused responsibility and a reason to change of its own?

  • Yes → entities/
  • Uncertain, single use, or speculative reuse → keep in the page.

Step 5: Is it app-wide configuration?

  • Global providers, router, theme → app/

Golden Rule: When in doubt, keep it in pages/. Extract only when the extraction rule holds.

3. Quick placement table

Scenario Single use Confirmed multi-use
User profile form pages/profile/ui/ProfileForm.tsx features/profile-form/
Product card pages/products/ui/ProductCard.tsx entities/product/ui/ if the entity owns it
API request (read or CRUD) pages/product-detail/api/fetch-product.ts shared/api/ (no domain rules)
Auth token/session shared/auth/ shared/auth/
Auth login form pages/login/ui/LoginForm.tsx features/auth/
Generic Card layout shared/ui/Card/
Modal manager shared/ui/modal-manager/
Modal content pages/[page]/ui/SomeModal.tsx
Date formatting util shared/lib/format-date.ts

"Confirmed multi-use" means the extraction rule holds, not that a second consumer appeared: two similar copies that keep drifting apart stay in their pages (references/growth-walkthrough.md, Snapshot 1). Entity UI carries the Section 6 caution even when the rule does hold.

4. Architectural rules (MUST)

These rules are the foundation of FSD. Violations weaken the architecture. If you must break a rule, ensure it is an intentional design decision and document the reason in code (a comment or ADR).

4-1. Import only from lower layers

app → pages → widgets → features → entities → shared. Upward imports are forbidden. So are cross-imports between slices on the same layer, except through the other slice's public API as a last resort (Section 7, Strategy D).

4-2. Public API: every slice exports through index.ts

External consumers may only import from a slice's index.ts. Direct imports of internal files are forbidden.

// Correct
import { LoginForm } from "@/features/auth";

// Violation: bypasses public API
import { LoginForm } from "@/features/auth/ui/LoginForm";

Shared layer: Shared has no slices. Define a separate public API per segment (shared/ui/index.ts, shared/api/index.ts, etc.) rather than one top-level shared/index.ts. This keeps imports from Shared organized by intent.

Where one index over a segment's unrelated modules hurts bundling, give each component, library, or controller folder its own index instead (shared/ui/Button/index.ts as @/shared/ui/Button, shared/api/post/ as @/shared/api/post). That folder is then the boundary; reaching past it (@/shared/ui/Button/Button.tsx) is still a violation. See references/layer-structure.md for the shape.

Environment-specific entry points: a slice normally exposes one index.ts, and ad-hoc variations are not recommended. If a single index cannot preserve a runtime boundary, add an entry point such as index.server.ts. See references/framework-integration.md.

4-3. No cross-imports between slices on the same layer

If two slices on the same layer need to share logic, follow the resolution order in Section 7. Never reach into another slice's internals.

4-4. Domain-based file naming (no desegmentation)

Name files after what they are for, the domain or concern they serve, not after their technical role. Technical-role names like types.ts, utils.ts, helpers.ts mix unrelated concerns in a single file and reduce cohesion.

// BAD: technical-role naming
model/types.ts          ← Which types? User? Order? Mixed?
model/utils.ts

// GOOD: domain-based naming
model/user.ts           ← User types + related logic
model/order.ts          ← Order types + related logic
api/fetch-profile.ts    ← Clear purpose

4-5. No business logic in shared/

Shared contains only infrastructure: UI kit, utilities, API client setup, route constants, assets. Business calculations, domain rules, and workflows belong in entities/ or higher layers. Section 2, Step 2 says what counts.

// BAD: business logic in shared
// shared/lib/userHelpers.ts
export const calculateUserReputation = (user) => { ... };

// GOOD: move it to whoever owns the rule
// pages/profile/model/reputation.ts       ← while the profile page owns it
// entities/user/model/reputation.ts       ← once a user boundary is earned
export const calculateUserReputation = (user) => { ... };

5. Recommendations (SHOULD)

5-1. Pages first: place code where it is used

Place code in pages/ first. Extract to lower layers only when truly needed. Extraction is a design decision that affects the whole project, so the threshold should be high.

What stays in pages:

  • Large UI blocks used only in one page
  • Page-specific forms, validation, data fetching, state management
  • Page-specific business logic and API integrations
  • Code that looks reusable but is simpler to keep local

Evolution pattern: Start with everything in pages/profile/. Extract the shared model to entities/user/ when a second page consumes it and the extraction rule holds. A response type that several pages read is not one of those cases: it stays in shared/api. Keep page-specific API calls and UI in the page.

5-2. Be conservative with entities

The entities layer is highly accessible (almost every other layer can import from it), so changes propagate widely.

  1. Start without entities. shared/ + pages/ + app/ is valid FSD. Thin-client apps rarely need entities.
  2. Do not split slices prematurely. Keep code in pages. Extract to entities only when the extraction rule holds.
  3. Business logic does not automatically require an entity. Keeping types in shared/api and logic in the current slice's model/ segment may be sufficient.
  4. CRUD is infrastructure, not entities. Place it by the request placement rule: with its consumer while there is one, in shared/api/ once several slices call it.
  5. Place auth data in shared/auth/ or shared/api/. Tokens and login DTOs are auth-context-dependent and rarely reused outside authentication.

For detailed guidance on keeping the entities layer clean (when to skip it entirely, how to isolate business contexts, why CRUD belongs in shared/api), see references/excessive-entities.md.

5-3. Start with minimal layers

// Valid minimal FSD project
src/
  app/         ← Providers, routing
  pages/       ← All page-level code
  shared/      ← UI kit, utils, API client

// Add layers only when an actual use case requires them:
// + features/  ← User-action boundaries that need one shared home
// + entities/  ← Domain boundaries that need one shared home
// (widgets/ is discouraged; see Section 1 for where that code goes instead)

5-4. Validate with the Steiger linter

Steiger is the official FSD linter. Key rules:

  • insignificant-slice: Flags a slice with no references, or with one, and suggests merging it into the layer above. Pages may hold a single reference, and so may slices used only from app/.
  • excessive-slicing: Suggests merging or grouping when a layer has too many slices.
npm install -D @feature-sliced/steiger
npx steiger src

6. Anti-patterns (AVOID)

  • Do not create entities prematurely. Data structures used in only one place belong in that place.
  • Do not put CRUD in entities. Plain CRUD is shared/api/. An operation that carries business rules is placed by who owns the rule, which may be an entity, a feature, or the page running the workflow.
  • Do not create a user entity just for auth data. Tokens and login DTOs belong in shared/auth/ or shared/api/.
  • Do not abuse @x. It is a necessary compromise, not a recommended pattern. The notation is for the entities layer only, and only when boundary merge is genuinely impossible. Features and widgets handle cross-imports through strategies A through D (see Section 7).
  • Do not extract single-use code. A feature or entity used by only one page should stay in that page.
  • Do not use technical-role file names. Use domain-based names (see Rule 4-4).
  • Be cautious adding UI to entities. Entity UI tempts cross-imports from other entities. If you add UI segments to entities, only import them from higher layers (features, pages, app), never from other entities.
  • Do not create god slices. Slices with excessively broad responsibilities should be split into focused slices (e.g., split user-management/ into auth/, profile-edit/, password-reset/).
  • Do not create a top-level assets/ segment. Place static assets next to the code that uses them; global stylesheets and fonts go to app/. See references/asset-handling.md.

7. Cross-import resolution

Cross-imports are a code smell, not an absolute prohibition. The right strategy depends on the layer and the situation.

Entities layer: prefer boundary merge, @x is last resort

Cross-imports in entities are usually caused by splitting entities too granularly. Before reaching for @x, consider whether the boundaries should be merged.

@x is a necessary compromise, not a recommended approach. Use it only when boundaries genuinely cannot be merged, and document why. Overuse locks entity boundaries together and increases refactoring cost.

Features and widgets: four strategies (A, B, C, D)

In features and widgets, choose based on context:

  • Strategy A: slice merge. Two slices always change together → merge.
  • Strategy B: push to entities. A shared domain responsibility → move it to the entity that owns it, keep UI in the feature.
  • Strategy C: compose from upper layer (IoC). The parent (pages or app) imports both slices and connects them via render props, slots, or DI.
  • Strategy D: Public API access. When reuse is genuinely unavoidable, allow it only through the slice's index.ts. Never reach into model/, store/, or internal files.

The @x notation is for the entities layer only. Features and widgets use strategies A through D above.

Strictness depends on project context

Cross-imports are dependencies that are generally best avoided, but sometimes used intentionally. Strictness varies by project context:

  • Early-stage products with heavy experimentation: allowing some cross-imports may be a pragmatic speed trade-off.
  • Long-lived or regulated systems (fintech, large-scale services): stricter boundaries pay off in maintainability and stability.

If a cross-import is introduced, treat it as a deliberate choice and document the reasoning in code (a comment explaining why other strategies do not apply).

For detailed code examples of each strategy, read references/cross-import-patterns.md.

8. Segments & structure rules

Standard segments

Segments group code within a slice by technical purpose:

  • ui/: UI components, styles, display-related code
  • model/: Data models, state stores, business logic, validation
  • api/: Backend integration, request functions, API-specific types
  • lib/: Internal utility functions for this slice
  • config/: Configuration, feature flags

Layer structure rules

  • App and Shared: No slices, organized directly by segments. Segments within these layers may import from each other.
  • Pages, Widgets, Features, Entities: Slices first, then segments inside each slice.
  • Slice groups (optional): A group folder may contain related slices on the same layer for navigation purposes only. The group has no segments and no public API. See references/layer-structure.md for details.

File naming within segments

Always use domain-based names that describe what the code is about:

model/user.ts            ← User types + logic + store
model/order.ts           ← Order types + logic + store
api/fetch-profile.ts     ← Profile fetching
api/update-settings.ts   ← Settings update

If a segment has only one domain concern, the filename may match the slice name (e.g., features/auth/model/auth.ts).

9. Shared layer guide

Shared contains infrastructure with no business logic. It is organized by segments only (no slices). Segments within shared may import from each other.

Allowed in shared:

  • ui/: UI kit (Button, Input, Modal, Card)
  • lib/: Utilities (formatDate, debounce, classnames)
  • api/: API client, route constants, CRUD helpers, base types
  • auth/: Auth tokens, login utilities, session management
  • config/: Environment variables, app settings
  • Assets live with the code that uses them, not in an assets/ segment. See references/asset-handling.md.

Shared may contain application-aware code: route constants, API endpoints, branding assets, and transport types such as ProductDTO. It must never hold the business rules an entity or feature owns, nor import from those layers.

10. Conditional references

Read the following reference files only when the specific situation applies. Do not preload all references.

  • When reviewing or reorganizing folder and file structure that already exists, deciding what goes inside a layer or slice, deciding where a page layout belongs, routing widget-like code to another layer, or grouping closely related slices into a parent folder for navigation (e.g., "where does this folder go", "how do I group these payment entities"): → Read references/layer-structure.md

  • When setting up a new project from scratch (e.g., "set up an FSD project", "start a new app with FSD"), or when asked whether to add entities or features yet, or to show how a structure earns each layer over time rather than its finished shape: → Read references/growth-walkthrough.md

  • When resolving cross-import issues between slices on the same layer, evaluating the @x pattern, choosing between Strategy A/B/C/D for features and widgets, or deciding whether boundaries should be merged: → Read references/cross-import-patterns.md

  • When deciding whether to create or remove an entity, dealing with too many entities, evaluating whether to skip the entities layer entirely, placing CRUD operations, or isolating business contexts to avoid @x chains: → Read references/excessive-entities.md

  • When deciding where to place static assets (images, icons, fonts, PDFs, stylesheets) for a single slice, for sharing across slices, or globally: → Read references/asset-handling.md

  • When migrating from FSD v2.0 to v2.1, converting a non-FSD codebase to FSD, phasing out an existing widgets layer, or deprecating the processes layer: → Read references/migration-guide.md

  • When integrating FSD with a specific framework (Next.js with App Router or Pages Router, React Router, Nuxt, Vite, Astro) for wiring routes to FSD pages, placing proxy/middleware and instrumentation files, structuring API route handlers, or configuring path aliases: → Read references/framework-integration.md

  • When implementing authentication, type definitions, or API request handling as concrete code within FSD structure (token storage, login flow, DTO placement, where a request function lives): → Read references/auth-and-api.md

  • When wiring state management (Redux slices, TanStack Query / React Query, including query factories, infinite scroll, Suspense mode, and useMutationState) into FSD structure: → Read references/state-management.md

Files (skills)
  • evals
    • evals.json 19.6 KB
      {
        "$comment": "Placement cases for the feature-sliced-design skill, in the evals/evals.json layout that agent-skills-eval and skill-creator read. See README.md in this directory for how to run them. Every `source` path and `rule` fragment is checked by .github/scripts/validate-skills.mjs.",
        "skill_name": "feature-sliced-design",
        "evals": [
          {
            "id": "auth-token",
            "prompt": "Where do I put the auth token and session helpers?",
            "expected_output": "shared/auth/ (or shared/api/). Do not create a user entity to hold the token; whether an existing current-user entity may store it is a separate question.",
            "assertions": [
              "The output places the auth token and session helpers under shared/, in shared/auth or shared/api.",
              "The output does not create a new user entity to hold the token.",
              "If the output mentions an existing current-user entity, it treats whether that entity may store the token as a separate question rather than ruling it out."
            ],
            "why": "Tokens and session DTOs are infrastructure, not a domain model, and wrapping a login response in an entity is the common wrong turn.",
            "source": "feature-sliced-design/SKILL.md",
            "rule": "Section 2, Step 2; Section 6 anti-pattern on the user entity"
          },
          {
            "id": "small-project-entities",
            "prompt": "I am starting a project with two pages. Should I create the entities and features layers now?",
            "expected_output": "No. app/ + pages/ + shared/ is valid FSD. Add entities or features only when a shared responsibility has a stable boundary its consumers must agree on; reuse alone does not require a layer.",
            "assertions": [
              "The output answers no: the project should not create the entities or features layers now.",
              "The output states that app/, pages/, and shared/ alone is a valid FSD structure.",
              "The output conditions adding entities or features on a shared responsibility with a stable boundary, and does not treat reuse across pages as sufficient on its own."
            ],
            "why": "A common failure mode: small projects split into every layer up front.",
            "source": "feature-sliced-design/SKILL.md",
            "rule": "Section 5-2 'Start without entities'; Section 5-3"
          },
          {
            "id": "plain-reusable-request",
            "prompt": "getUserById just wraps GET /users/:id and every page calls it. Does it belong in entities/user/api/?",
            "expected_output": "No. shared/api/. Plain resource access stays infrastructure however many consumers call it. A request moves into an entity only when an established entity boundary owns that domain responsibility.",
            "assertions": [
              "The output answers no and places getUserById in shared/api.",
              "The output keeps plain resource access in shared regardless of how many pages call it.",
              "The output moves a request into an entity only when an established entity boundary owns that domain responsibility."
            ],
            "why": "Guards the two-question placement rule. The old bullet list sent this exact function to entities.",
            "source": "feature-sliced-design/references/auth-and-api.md",
            "rule": "Request placement rule, Question 2"
          },
          {
            "id": "single-page-request",
            "prompt": "Dashboard stats are fetched only on the dashboard page. Where does the request go?",
            "expected_output": "pages/dashboard/api/. It stays with its only consumer.",
            "assertions": [
              "The output places the dashboard stats request in pages/dashboard/api/.",
              "The output does not move the request to shared/api or to an entity while the dashboard page is its only consumer."
            ],
            "why": "Question 1 must be asked before Question 2, or pages-first breaks.",
            "source": "feature-sliced-design/references/auth-and-api.md",
            "rule": "Request placement rule, Question 1"
          },
          {
            "id": "same-layer-import",
            "prompt": "features/profile needs something from features/auth. Is that allowed?",
            "expected_output": "Generally no. Try strategies A to C first. If the documented last resort is genuinely necessary, import only through the other slice's public API, never its internals.",
            "assertions": [
              "The output says a direct import from features/auth into features/profile is generally not allowed.",
              "The output recommends resolving the dependency first by merging the slices, moving the shared responsibility into an entity, or composing from an upper layer.",
              "The output keeps a last resort reachable: if the direct import is necessary, it goes through the other slice's public API and never through its internals."
            ],
            "why": "The MUST rule reads as an absolute ban; the documented escape hatch must stay reachable.",
            "source": "feature-sliced-design/references/cross-import-patterns.md",
            "rule": "Strategy D; SKILL.md Rules 4-1 and 4-3"
          },
          {
            "id": "shared-ui-import",
            "prompt": "How do I import the Button from the shared UI kit?",
            "expected_output": "Through the Shared UI public API: @/shared/ui when the segment index exports Button, or @/shared/ui/Button when Button has been given its own index. Never an internal file such as @/shared/ui/Button/Button.tsx.",
            "assertions": [
              "The output imports Button through the shared UI public API: @/shared/ui from the segment index, or @/shared/ui/Button when Button has its own index.",
              "The output does not reject either @/shared/ui or @/shared/ui/Button as incorrect.",
              "The output never imports an internal file such as @/shared/ui/Button/Button.tsx."
            ],
            "why": "Rule 4-2 makes the segment index the default and a per-component index the fallback for tree-shaking, so both imports are correct and only reaching past the boundary is not.",
            "source": "feature-sliced-design/SKILL.md",
            "rule": "Rule 4-2"
          },
          {
            "id": "business-themed-in-shared",
            "prompt": "Can the company logo component and an autocomplete input live in shared/ui?",
            "expected_output": "Yes, as long as they encode no business rules and no slice-specific behavior. Business-themed presentation and generic UI interaction logic may live in shared/ui; business logic may not.",
            "assertions": [
              "The output answers yes: both the company logo component and the autocomplete input may live in shared/ui.",
              "The output conditions this on the components encoding no business rules and no slice-specific behavior.",
              "The output does not treat business-themed presentation or generic UI interaction logic as business logic that must leave shared."
            ],
            "why": "Rule 4-5 is easy to over-apply. Shared excludes business logic, but business-themed code and UI logic are explicitly allowed, and an agent that flattens the rule removes both.",
            "source": "feature-sliced-design/SKILL.md",
            "rule": "Section 2, Step 2"
          },
          {
            "id": "new-project-three-pages",
            "prompt": "Set up an FSD structure for a shop with home, product, and search pages. Home and search both call the same fetchProducts request, and every page reads the same ProductDTO.",
            "expected_output": "app/, pages/ with three slices, shared/. No entities or features. ProductDTO and the shared fetchProducts go in shared/api; a request only one page calls stays in that page's api/ segment.",
            "assertions": [
              "The output proposes app/, pages/ with home, product, and search slices, and shared/, with no entities, features, or widgets layer.",
              "The output places ProductDTO and fetchProducts in shared/api.",
              "The output keeps a request that only one page calls in that page's api/ segment."
            ],
            "why": "The finished-structure reflex. Fails if any layer beyond three appears unprompted.",
            "source": "feature-sliced-design/references/growth-walkthrough.md",
            "rule": "Snapshot 0 and Snapshot 1; auth-and-api.md request placement rule"
          },
          {
            "id": "diverged-rule",
            "prompt": "Two pages each compute whether a product is on sale, and one copy is now out of date. What should change?",
            "expected_output": "Move the rule to entities/product/model. Leave the ProductDTO in shared/api and the badge UI in the pages.",
            "assertions": [
              "The output moves the on-sale rule to entities/product/model.",
              "The output leaves ProductDTO in shared/api.",
              "The output leaves the badge UI in the pages rather than moving it into the entity."
            ],
            "why": "Reuse alone did not open entities; a rule that must agree with itself does. Also checks that the DTO does not follow the rule into the entity.",
            "source": "feature-sliced-design/references/growth-walkthrough.md",
            "rule": "Snapshot 2"
          },
          {
            "id": "app-header-placement",
            "prompt": "New project. Where does the app-wide header with navigation and the user menu go?",
            "expected_output": "The header composition goes in app/ as an application-level layout, not a new widgets/ slice. Its parts keep their own ownership: an already-established feature may supply a reused action, an established entity may supply domain UI, context-free UI comes from shared. A section only one page renders stays in that page.",
            "assertions": [
              "The output places the header composition in app/ as an application-level layout.",
              "The output does not create a widgets/ slice for the header.",
              "The output sources the header's parts from their existing owners (an established feature for a reused action, an established entity for domain UI, shared for context-free UI) without creating new slices for them.",
              "The output keeps a section that only one page renders in that page."
            ],
            "why": "The header is the textbook widgets example. Placement is decided by scope, and the skill discourages opening widgets for it.",
            "source": "feature-sliced-design/references/layer-structure.md",
            "rule": "Where should layouts be placed?; SKILL.md Section 1 widgets callout"
          },
          {
            "id": "top-level-assets-segment",
            "prompt": "Should I create src/assets/ to hold all the images and icons?",
            "expected_output": "No. Keep an asset with the slice that owns it. A presentation asset several slices must share goes to the shared UI module that owns it, global styles and imported fonts go with app-level code, and files served as-is go in the framework's configured public directory.",
            "assertions": [
              "The output answers no to a top-level src/assets/ folder.",
              "The output keeps each asset with the slice that owns it, and sends a presentation asset several slices share to the shared UI module that owns it.",
              "The output puts global styles and imported fonts with app-level code, and files served as-is in the framework's configured public directory."
            ],
            "why": "A type-based assets folder is the common default and the official guidance calls it not recommended.",
            "source": "feature-sliced-design/references/asset-handling.md",
            "rule": "Caution; Decision tree"
          },
          {
            "id": "nextjs-app-layer-name",
            "prompt": "Next.js App Router project. Where does the FSD app layer go, and what is it called?",
            "expected_output": "src/_app/, with src/_pages/ for the pages layer. The Next.js app/ routing folder stays at the project root and only re-exports from FSD pages.",
            "assertions": [
              "The output names the FSD app layer src/_app/ and the FSD pages layer src/_pages/.",
              "The output keeps the Next.js app/ routing folder at the project root and has it only re-export from FSD pages.",
              "The output does not move the Next.js routing folder or leave the FSD layers unprefixed under src/."
            ],
            "why": "An older pattern kept the FSD layers unprefixed under src/ and moved the routing folder instead. Both existed in the wild.",
            "source": "feature-sliced-design/references/framework-integration.md",
            "rule": "Next.js; Projects on the previously recommended pattern"
          },
          {
            "id": "widgets-not-deprecated",
            "prompt": "Our FSD 2.0 project already uses widgets. Does 2.1 require us to remove the layer?",
            "expected_output": "No. Widgets are discouraged for new adoption, not deprecated, so an existing layer stays valid and keeps working. processes/ is the layer 2.1 deprecates. Phasing widgets out is optional.",
            "assertions": [
              "The output answers no: FSD 2.1 does not require removing an existing widgets layer.",
              "The output distinguishes discouraged from deprecated, and names processes/ as the layer 2.1 deprecates.",
              "The output presents phasing widgets out as optional."
            ],
            "why": "Discouraged is not deprecated. The reader most likely arrived from 2.0 already using widgets, and the guide calls this migration optional.",
            "source": "feature-sliced-design/references/migration-guide.md",
            "rule": "Phasing out a small widgets layer (optional); SKILL.md Section 1 widgets callout"
          },
          {
            "id": "phase-out-widget-placement",
            "prompt": "We decided to remove our small widgets layer. We have an app shell, a section only one page renders, a stable user action reused on several pages that must behave the same in each, and a generic UI block. Where does each go?",
            "expected_output": "App shell to app; the one-page section inlines into that page; the reused action and its UI to features; the context-free UI block to shared/ui. A widget that only composes features moves up to the page or the route layout.",
            "assertions": [
              "The output moves the app shell to app/.",
              "The output inlines the section only one page renders into that page.",
              "The output moves the reused user action and its UI to features/.",
              "The output moves the generic UI block to shared/ui.",
              "The output moves a widget that only composes features up to the page or the route layout."
            ],
            "why": "Splits the routing table off the deprecated-versus-discouraged question so a failure says which half is wrong.",
            "source": "feature-sliced-design/references/migration-guide.md",
            "rule": "Phasing out a small widgets layer (optional)"
          },
          {
            "id": "entity-selector-root-state",
            "prompt": "In entities/todo/model/todo.ts I am writing selectTodos. RootState is exported from app/providers/store.ts. How do I type the selector?",
            "expected_output": "Type it against only the state it reads, e.g. (state: { todos: TodoState }) => state.todos.items. Must not import RootState from app/: entities cannot depend on a higher layer. Type against RootState at the app layer if that guarantee is needed.",
            "assertions": [
              "The output types the selector against only the state it reads, for example (state: { todos: TodoState }) => state.todos.items.",
              "The output does not import RootState from app/ inside entities/todo.",
              "The output explains that entities cannot depend on a higher layer, and offers typing against RootState at the app layer as the alternative."
            ],
            "why": "Regression case: the skill's own example once made this upward import. Redux tutorials type selectors with RootState, so an agent reaches for it unless the dependency rule is spelled out.",
            "source": "feature-sliced-design/references/state-management.md",
            "rule": "Business-entity slice in entities; SKILL.md Rule 4-1"
          },
          {
            "id": "similar-product-cards",
            "prompt": "Home and search both render a ProductCard. The home card shows a recommendation reason, the search card shows a query-match snippet. Should I extract ProductCard to entities/product/ui?",
            "expected_output": "No, not because both pages have one. They are not the same component, they change for their own reasons, and separate page-local copies are valid. Extract only if the two must agree and need one home.",
            "assertions": [
              "The output answers no: it does not extract ProductCard to entities/product/ui.",
              "The output does not treat two pages having a component with the same name as a reason to extract.",
              "The output keeps separate page-local copies as valid because the two cards change for their own reasons, and extracts only if they must agree and need one home."
            ],
            "why": "The whole skill decides extraction on a shared responsibility, not on a count or a matching filename, and every other case tests the positive direction.",
            "source": "feature-sliced-design/references/growth-walkthrough.md",
            "rule": "Snapshot 1, 'The second card is a copy, not an extraction'"
          },
          {
            "id": "single-use-user-action",
            "prompt": "Only the checkout page has a coupon form right now. Should I create features/apply-coupon?",
            "expected_output": "No, not yet. Keep the form, its request, and its state in the checkout page while it has one consumer. Extract when another consumer appears and the action has to behave the same in both.",
            "assertions": [
              "The output answers no: it does not create features/apply-coupon now.",
              "The output keeps the coupon form, its request, and its state in the checkout page while that page is the only consumer.",
              "The output conditions extraction on another consumer appearing and the action having to behave the same in both."
            ],
            "why": "Guards the verb-equals-feature reflex, the features counterpart of small-project-entities, which no case covered.",
            "source": "feature-sliced-design/SKILL.md",
            "rule": "Section 2, Step 3; Section 5-1"
          },
          {
            "id": "user-entity-does-not-own-token",
            "prompt": "We now have entities/user because profile identity is reused across the app. Should I move the access and refresh tokens out of shared/auth into entities/user?",
            "expected_output": "shared/auth remains the default. Entity-owned credentials are also valid, but only when an already-established user or session entity genuinely owns that authentication state; the entity existing, or profile data being reused, is not enough on its own.",
            "assertions": [
              "The output keeps shared/auth as the default home for the access and refresh tokens.",
              "The output does not move the tokens into entities/user merely because the entity exists or profile data is reused.",
              "The output allows entity-owned credentials only when an already-established user or session entity owns that authentication state."
            ],
            "why": "The line between reusable user-domain state and authentication infrastructure moved twice while these references were being aligned, so pin where it landed.",
            "source": "feature-sliced-design/references/auth-and-api.md",
            "rule": "When to use shared/auth vs a user entity"
          },
          {
            "id": "single-consumer-crud",
            "prompt": "Only the settings page calls updateNotificationSettings. It is a plain PATCH with no business rules. Should it go in shared/api because it is CRUD?",
            "expected_output": "No. Keep it in pages/settings/api/ while that page is its only consumer. CRUD not being entity material does not make it Shared material; it moves to shared/api once it is genuinely shared.",
            "assertions": [
              "The output answers no and keeps updateNotificationSettings in pages/settings/api/.",
              "The output does not send the request to shared/api on the grounds that it is CRUD.",
              "The output moves the request to shared/api once more than one consumer calls it."
            ],
            "why": "Guards pages-first request ownership against the shortcut that CRUD always goes to shared/api, which three passages stated before this alignment.",
            "source": "feature-sliced-design/references/auth-and-api.md",
            "rule": "Request placement rule, Question 1"
          }
        ]
      }
      
    • README.md 5.3 KB
      # Skill evaluation cases
      
      `evals.json` records the placement answers this skill is supposed to produce.
      The validator keeps the file honest; a person or a harness grades the answers.
      
      The file uses the `evals/evals.json` layout that Anthropic's `skill-creator`
      and [agent-skills-eval](https://github.com/darkrishabh/agent-skills-eval)
      read, so the same cases run in a judge-model harness without conversion.
      The `why`, `source`, and `rule` fields are this repository's additions;
      harnesses ignore them and the validator checks them.
      
      ## Why this exists
      
      The other checks in this repository verify that the documents are well formed:
      the body stays under the line limit, every reference path resolves, no
      reference is orphaned. None of them verify the thing the skill is for, which
      is whether an agent reading it places code correctly.
      
      That gap is not theoretical. Several rules in this skill were changed because
      two passages decided the same question on different grounds, and nothing in CI
      noticed. A case list is the cheapest way to catch the next one.
      
      ## Running the cases
      
      There is no semantic grader in CI, because comparing an answer to
      `expected_output` needs a model or a person.
      
      ### With a harness
      
      From the repository root, with an OpenAI-compatible API key in the
      environment:
      
      ```bash
      npx agent-skills-eval . --target <model> --judge <model> --baseline
      ```
      
      It runs every prompt twice, with and without the skill in context, grades
      both against the case's assertions, and writes a report under
      `agent-skills-workspace/`. Read the `--baseline` column first. A case that
      passes without the skill is guarding a mistake the model does not make, so
      consider dropping it. A case that passes only with the skill shows where the
      skill changes the answer.
      
      The harness puts `SKILL.md` and every file under `references/` into context
      at once, so a pass shows that the documents decide the case correctly.
      Whether `SKILL.md` sends an agent to the right reference is a separate
      question the harness cannot answer, because an agent reads the skill one
      file at a time. Check that by hand.
      
      ### By hand
      
      1. Start an agent session with only this skill installed.
      2. Send one `prompt` verbatim. Do not add context; the point is to see what
         the skill alone produces.
      3. Check the answer against each assertion. Judge the placement, not the
         wording.
      4. On a mismatch, read the file named in `source` and check whether the rule
         is absent, ambiguous, contradicted elsewhere, or whether the case itself
         no longer describes the behavior the skill intends. Fix whichever one is
         wrong. Never edit `expected_output` just to match what the model said.
      
      Start a fresh session per case. A previous answer in the same conversation
      will steer the next one.
      
      ## Adding a case
      
      Add an object to `evals` with all seven fields:
      
      | Field | Meaning |
      | --- | --- |
      | `id` | kebab-case, unique |
      | `prompt` | what the user types, verbatim |
      | `expected_output` | the placement, plus what must not happen if that matters |
      | `assertions` | `expected_output` split into conditions a judge can grade one at a time |
      | `why` | what regression this case guards against |
      | `source` | repo-relative path to the primary file that decides it |
      | `rule` | the passage that decides it, as `;`-separated fragments; name a passage from another file too when the decision leans on one |
      
      Each assertion states one condition on the answer, phrased as "The output
      ...". The first one names the placement. Each thing that must not happen
      gets an assertion of its own, so a failed run names the condition that
      broke. Two to four per case is usual; a routing case that asks about
      several items gets one per item.
      
      `source` names one file, the one to open first on a mismatch, even where
      the decision is settled by more than one passage. It must point at a file
      that exists, and `node .github/scripts/validate-skills.mjs` enforces that,
      so no case can cite a file that has been deleted.
      
      The validator also resolves every fragment of `rule` against the skill's
      documents, so a renamed heading or renumbered section fails the build
      instead of leaving a case pointing at nothing. A fragment resolves against
      `source` unless it names another file (`SKILL.md`, `auth-and-api.md`), and
      it must contain at least one of:
      
      - a numbered reference such as `Section 2, Step 3`, `Rule 4-2`,
        `Strategy D`, `Snapshot 1`, or `Question 2`
      - a phrase in single or double quotes that appears verbatim in the file
      - the text of a heading or bold label in the file, such as `Decision tree`
      
      `Section N` and `Rule N-M` always mean a numbered heading in `SKILL.md`.
      Free prose after a numbered reference is allowed and not checked, so
      `Section 6 anti-pattern on the user entity` is fine.
      
      Write a case when it guards a mistake that is actually likely: a rule two
      readings could defend, a regression that has already happened once, or a
      convention from outside FSD that an agent will reach for anyway. Several
      cases here are the third kind, where the rule is plain and the pull toward
      breaking it is what needs holding down.
      
      Keep a case on one architectural decision where you can. A prompt that
      checks several independent placements fails as one result, and then the
      failure does not say which rule broke. Split it instead.
      
      A case that restates an obvious rule without guarding a realistic mistake
      costs a run and catches nothing.
      
  • references
    • asset-handling.md 6.4 KB
      # Asset Handling
      
      How to place static assets (images, icons, fonts, PDFs, stylesheets) inside an
      FSD project. Assets follow the same ownership rules as code: keep each one
      with the module whose lifecycle it shares, rather than grouping them by
      file type or by how many places use them.
      
      > **Caution:** A custom top-level `assets` segment that aggregates all static
      > files is **not recommended**. It violates the FSD principles of high
      > cohesion and locality of changes. Place assets where they are used.
      
      ## Decision tree
      
      1. **Owned by one slice?** Keep it in that slice, next to the segment that
         consumes it. A presentation asset usually lands in `ui/`; one coupled to
         domain logic can live in `model/`.
      2. **Must several consumers share one authoritative copy (a logo, the
         placeholder icon)?** Put it with the shared module that owns it, which
         for a presentation asset is `shared/ui/`. Two assets that merely look
         alike today and will change for their own reasons stay local.
      3. **Global stylesheet, font, or app-level resource?** Place it in the
         `app/` layer, by convention `app/styles/` and `app/fonts/`.
      4. **Served as-is (favicon, robots.txt)?** Use the framework's
         `public/` folder. The `public/` folder is not part of FSD and does not
         conflict with FSD layers.
      
      ## Slice-specific assets
      
      When an asset belongs to one page, widget, or feature, keep it inside that
      slice. The asset lives next to the component that renders it:
      
      ```text
      pages/
        home/
          ui/
            hero-image.jpg          ← Used only by HomePage
            HomePage.tsx
          index.ts
      ```
      
      If a slice uses many static images, group them in a subfolder of `ui/`:
      
      ```text
      pages/
        home/
          ui/
            previews/
              cake.jpg
              pizza.jpg
              sushi.jpg
            HomePage.tsx
          index.ts
      ```
      
      ### Non-UI assets
      
      Some assets are not part of the UI but are coupled to business logic. For
      example, a PDF template used to generate invoices. Place these in the
      `model/` segment alongside the logic that consumes them, not in `ui/`:
      
      ```text
      features/
        billing/
          model/
            invoice-template.pdf    ← Coupled to create-invoice.ts
            create-invoice.ts
          index.ts
      ```
      
      The principle is locality of changes: if you delete the slice, every file it
      owns goes with it. An asset that lives in business logic should sit next to
      that logic.
      
      ## Shared assets
      
      When several slices must share one authoritative copy of an asset, move it
      out of the slices. A presentation asset goes to `shared/ui/`, in a topical
      subfolder or next to the single shared component that uses it; anything
      else goes with the shared module that owns it:
      
      ```text
      shared/
        ui/
          placeholders/             ← Reused placeholder images
            cake.jpg
            pizza.jpg
          Dropdown.tsx
          chevron.svg               ← Used only by Dropdown, kept next to it
      ```
      
      A single icon used by exactly one component in the UI kit stays next to that
      component. A library of icons or images reused across many components goes
      in a topical subfolder.
      
      ## Global assets
      
      Global stylesheets and fonts belong in the `app/` layer because they are
      imported by the application entrypoint, not by individual slices:
      
      ```text
      app/
        styles/
          reset.css
          global.css
        fonts/
          inter.woff2
        main.ts
      ```
      
      Theme variables, CSS resets, and font registrations are app-wide concerns.
      `styles/` and `fonts/` are conventional App folder names, not standardized
      ones; `references/layer-structure.md` covers how App segments are named.
      
      ## Public folder
      
      Most frameworks and build tools provide a static-asset directory at the
      project root. Files there are served as-is, without bundling or hashing.
      
      The default is `public/` at the project root in Vite, Next.js, Nuxt, and
      Astro. Whether it can be moved is framework-specific: Vite and Astro both
      expose a `publicDir` option, while Next.js documents the project root.
      
      `public/` is not part of FSD. It does not collide with FSD layers and does
      not need to live under `src/`. Use it for files that must be served at fixed
      URLs: favicon, `robots.txt`, `sitemap.xml`, OG images, and similar. A few of
      these have framework conventions of their own, such as Next.js metadata
      files, and those win over the generic rule.
      
      > **Where this comes from.** The official assets guide says Astro has no
      > option to move its public folder. Astro documents `publicDir` with a
      > default of `./public` and an example of changing it, so this section
      > follows the framework.
      
      ```text
      public/
        favicon.ico
        robots.txt
        og-image.png
      src/
        app/
        pages/
        shared/
      ```
      
      Where the framework lets that directory be configured, treat wherever it
      points as a framework boundary, not as an FSD segment.
      
      ## Summary table
      
      | Asset                                  | Location                                  |
      | -------------------------------------- | ----------------------------------------- |
      | Asset owned by one slice               | Inside that slice, next to its consumer   |
      | PDF or template tied to business logic | Inside the slice's `model/` segment       |
      | Presentation asset several must share  | `shared/ui/`, with the module owning it   |
      | Icon used by exactly one shared kit UI | Next to that component in `shared/ui/`    |
      | Global CSS reset, theme variables      | `app/styles/`                             |
      | Web fonts                              | App layer when bundled, else public dir   |
      | Favicon, robots.txt, sitemap           | Framework convention, else public dir     |
      
      ## Anti-patterns
      
      - **Do not create a top-level `assets/` segment** that holds all images,
        fonts, and icons. It breaks cohesion and forces consumers to import from a
        folder unrelated to the code they are working on.
      - **Do not extract a slice-owned asset to `shared/` "in case".** Move it
        when shared ownership is real and one authoritative copy should serve
        several consumers.
      - **Do not place CSS modules in an `assets/` folder.** A component's
        stylesheet belongs next to that component in `ui/`.
      - **Do not name an FSD segment `public`.** The framework's `public/` folder
        is reserved and lives outside `src/`.
      - **Do not separate an asset from its owning module without a boundary
        reason.** A page that ships a hero image keeps it, so removing the page
        removes the image. A fixed public URL is such a reason; convenience is not.
      
      ## See also
      
      - `references/layer-structure.md`: segment rules and layer organization
      - [Desegmentation](https://fsd.how/docs/guides/issues/desegmented/): why
        technical-role grouping (including a generic `assets/` segment) hurts
        cohesion
      
    • auth-and-api.md 15.4 KB
      # Authentication, Types, and API Requests
      
      Concrete code patterns for authentication, type definitions, and API
      request handling within FSD structure. State management patterns (Redux,
      TanStack Query) are in `references/state-management.md`. Code samples are
      React; the placement rules are framework-agnostic.
      
      ## Authentication
      
      Auth is one of the most common sources of confusion in FSD. The key question
      is: what goes in `shared/`, what goes in `features/` or `pages/`?
      
      ### Auth data: `shared/auth/` or `shared/api/`
      
      Credential storage, authentication-session plumbing, and the API-client
      helpers around them are **infrastructure**, not business logic. Keep them
      in shared. The login flow itself, its form state, validation and error
      handling, is a user action and does not come with them:
      
      ```typescript
      // shared/auth/token.ts
      const TOKEN_KEY = "auth_token";
      export const getToken = () => localStorage.getItem(TOKEN_KEY);
      export const setToken = (t: string) => localStorage.setItem(TOKEN_KEY, t);
      export const clearToken = () => localStorage.removeItem(TOKEN_KEY);
      
      // shared/auth/session.ts
      export interface Session { userId: string; email: string; role: "admin" | "user" }
      // useSession depends on the auth provider (React Context, Zustand, etc.)
      export const useSession = (): Session | null => { /* ... */ };
      ```
      
      The `shared/auth/index.ts` re-exports from these files following the
      standard public API pattern.
      
      Which of the two: the token can sit in `shared/api` next to the client,
      where every request function can reach it directly. When token management
      grows past that (refresh, expiry, invalidation), the official Auth guide
      separates the responsibilities: requests and the API client stay in
      `shared/api`, and the token store with its management logic moves to
      `shared/auth`.
      
      ### Auth UI: pages (single use) or features (multi-use)
      
      Place the login form in the slice that consumes it. Single-use (only on the
      login page) goes in `pages/login/`; multi-use (dedicated page + modal login)
      goes in `features/auth/`:
      
      ```text
      pages/login/                     ← Single-use
        ui/{LoginPage,LoginForm}.tsx
        model/login.ts                 ← Form state, validation
        api/login.ts                   ← POST /auth/login
        index.ts
      
      features/auth/                   ← Multi-use: signing in
        ui/LoginForm.tsx
        model/auth.ts
        api/login.ts
        index.ts
      features/register/               ← Signing up, its own use case
        ui/RegisterForm.tsx
        model/register.ts
        api/register.ts
        index.ts
      ```
      
      ### Dialog for login
      
      If you need a login dialog that can be reused across multiple pages, you can
      implement it as a **feature** responsible for the login user action and flow.
      A login dialog typically includes logic such as form state management, input
      validation, authentication requests and error handling. These responsibilities
      belong in the `features` layer because they handle user actions and flows.
      
      ```text
      features/
        auth/
          ui/LoginDialog.tsx
          model/
          api/
          index.ts
      ```
      
      When multiple pages need the login dialog, they can import and use the feature
      from each page or from the route configuration in `app`.
      
      A component responsible only for the common dialog UI and basic interactions
      can be placed in `shared/ui`. This component should not include login-specific
      logic such as authentication requests, input validation or authentication
      state management.
      
      The UI and logic required for login should be managed in `features/auth`.
      When necessary, `LoginDialog` can be implemented by composing the dialog
      component from `shared/ui`.
      
      ```text
      shared/
        ui/
          modal/
            Modal.tsx
            index.ts
      features/
        auth/
          ui/LoginDialog.tsx
          model/
          api/
          index.ts
      ```
      
      ### When to use shared/auth vs a user entity
      
      The official Auth guide presents two valid storage locations: **In Shared**
      (`shared/auth` or `shared/api`) and **In Entities** (a `user` entity).
      **In Pages/Widgets** is not recommended.
      
      `shared/auth` is the default for tokens, refresh and expiry handling, and
      the rest of the authentication session. Keep them there unless something
      else already owns that state.
      
      A `user` or `session` entity may own auth state, token included, when that
      entity is an established boundary that genuinely owns it. Ownership is what
      decides. An entities layer existing, a `user` entity existing, and profile
      data being reused are not reasons to move credentials.
      
      ```text
      // Path A: shared/auth (simpler default)
      shared/auth/session.ts         ← userId, email, role, token
      
      // Path B: an established user-domain boundary that owns this state
      entities/user/
        model/
          current-user.ts            ← Current authenticated user + token
          user.ts                    ← Generic user type
        api/get-current-user.ts
        index.ts
      ```
      
      For the entity approach, the API client in `shared/api` cannot import from
      `entities/`. The official guide describes three solutions: pass the token
      manually, expose it through a context with the key kept in `shared/api`,
      or inject the token into the API client when the entity store updates.
      
      A `user` entity created **only** to wrap a login response is premature.
      `references/excessive-entities.md` explains what that costs.
      
      ### In Pages/Widgets (not recommended)
      
      It is not recommended to place the token store in `pages` or in a specific
      `features` slice.
      
      Tokens are not state that belongs only to a specific page or a single user
      action. They are application-wide state used by multiple authenticated API
      requests and user flows.
      For example, if the token store is placed in `features/auth`, another feature
      cannot directly import it. Different feature slices on the same layer should
      remain independent from one another.
      
      Similarly, if the token store is placed in `pages`, modules on lower layers
      cannot access it. This makes it difficult to reuse the token store in
      authenticated API requests or other user flows.
      Place the token store in `shared` or in an `entities` slice representing the
      current user or session, according to the criteria described above.
      
      ### Logout and token invalidation
      
      Most applications do not provide a separate page exclusively for logout.
      Instead, logout functionality is made available wherever it is needed, such as
      in a header, settings screen, or user menu.
      
      Logout generally consists of the following steps.
      
      1. Send an authenticated logout request to the backend.
         For example, `POST /logout`.
      2. Reset the token store.
         Remove both the access token and refresh token.
      3. Reset the current user information and authentication state when necessary.
      4. Navigate to the login page or another screen when necessary.
      
      The location of the logout request should be determined by the project's API
      organization and the scope in which the request is reused.
      If all API endpoints are managed in `shared/api`, authentication-related
      requests such as login, logout, and token refresh can be placed together.
      
      ```text
      shared/
        api/
          client.ts
          endpoints/
            login.ts
            logout.ts
            refresh-token.ts
          index.ts
      ```
      
      If the logout request is used only as part of a specific logout flow, it can
      be placed in the `api` segment of `features/logout`.
      If logout is reused across multiple screens and represents an independent user
      flow that includes token cleanup, user state cleanup, and error handling,
      the flow can be extracted into `features/logout`.
      
      Navigation is the exception. `features/logout` must not import the router
      from `app/`, which would be an upward import (Rule 4-1). Let the page or
      route configuration navigate after the action resolves, or pass the
      navigation callback into the feature:
      
      ```typescript
      // pages/settings/ui/SettingsPage.tsx
      const logout = useLogout();
      
      const onLogout = async () => {
        await logout();
        navigate("/login");
      };
      ```
      
      ```text
      features/
        logout/
          api/logout.ts
          ui/LogoutButton.tsx
          index.ts
      ```
      
      If logout requires its own state or reusable processing logic, a `model`
      segment can be added. There is no need to create unused segments in advance
      for a simple logout feature.
      
      `features/logout` coordinates tasks such as sending the logout request and
      resetting the token store as a single user flow. The token store itself and
      the token management logic should remain in the previously selected location
      under `shared` or `entities`.
      
      On the other hand, if the logout logic is simple and used in only one or two
      places, it does not necessarily need to be extracted into a separate feature.
      It can be composed directly in the page or route configuration where it is
      used.
      
      > Slice names should be based on user actions and flows rather than the UI
      > location where they are displayed. Therefore, even when logout is triggered
      > from a header, `features/logout` is more appropriate than `features/header`
      > when the behavior is extracted as an independent user flow.
      
      ### Automatic logout
      
      The token store and current user state should be reset when the client's
      authentication state can no longer be maintained, such as in the following
      cases:
      
      - The user requests to log out.
      - The refresh token has expired or is invalid, causing the token refresh
        request to be rejected.
      
      If the authentication state is not reset, the UI may appear as though the user
      is still logged in while authenticated API requests continue to fail.
      Even if the logout request fails, the client can still reset the token store
      and current user state. However, the server-side session or refresh token may
      not have been invalidated, so the backend authentication policy should also be
      taken into account.
      
      > If tokens are managed in an entity representing the current user or session,
      > the token reset logic can be placed in the slice's `model` segment. If tokens
      > are managed on the Shared layer, they can be separated into a module
      > responsible for authentication, such as `shared/auth`.
      
      The refresh failure is usually detected by the API client in `shared/api`,
      which cannot reach an entity to clear its state: that would be an upward
      import. Report the failure upward instead, through the callback, event, or
      context the official guide already uses to hand the token down, and let the
      layer that owns the state do the resetting. The same wiring that gets the
      token into the client carries the failure back out.
      
      ## Type definitions
      
      ### Where to define types
      
      The location of type definitions follows the same rules as any other code:
      
      | Type scope | Location |
      | --- | --- |
      | API response/request shapes shared across the app | Domain-named files in `shared/api/` (e.g., `shared/api/product.ts`) |
      | Types for a specific entity's domain model | `entities/<name>/model/<name>.ts` |
      | Types used only within one page | `pages/<name>/model/<name>.ts` |
      | Types used only within one feature | `features/<name>/model/<name>.ts` |
      | Generic utility types (e.g., `Nullable<T>`) | Purpose-named files in `shared/lib/` (e.g., `shared/lib/nullable.ts`) |
      
      Per Rule 4-4 (domain-based file naming), avoid grouping all types in
      `types.ts` or `utils.ts`. A file named `types.ts` cannot answer "types
      for what?" without inspection; a file named `product.ts` can.
      
      ### Example: API types in shared
      
      ```typescript
      // shared/api/product.ts: raw API response shapes
      export interface ProductDTO {
        id: string;
        name: string;
        price: number;
        category: string;
        createdAt: string;
      }
      ```
      
      ### Example: domain types in entities
      
      ```typescript
      // entities/product/model/product.ts: the shape the domain works with
      import type { ProductDTO } from "@/shared/api";
      
      export interface Product {
        id: string;
        name: string;
        price: number;
        listPrice: number;
        inStock: boolean;
      }
      
      export const fromProductDTO = (dto: ProductDTO): Product => ({
        id: dto.id,
        name: dto.name,
        price: dto.price,
        listPrice: dto.listPrice,
        inStock: dto.stock > 0,
      });
      
      // a rule, not a stored flag: it follows the price rather than a snapshot
      export const isOnSale = (product: Product) =>
        product.price < product.listPrice && product.inStock;
      ```
      
      **Key principle:** Raw API shapes go in `shared/api/`. A domain model stays
      with its current consumer until an entity boundary has been earned; once it
      has, the types and rules that entity owns live in its `model/`. If you only
      need the raw shape, do not create an entity just for types.
      
      ## API request handling
      
      ### Basic pattern: API calls in the consuming slice
      
      ```typescript
      // pages/product-detail/api/fetch-product.ts
      import { apiClient, type ProductDTO } from "@/shared/api";
      
      export const fetchProduct = (id: string): Promise<ProductDTO> =>
        apiClient.get(`/products/${id}`).then((r) => r.data);
      ```
      
      ### Shared API client setup
      
      ```typescript
      // shared/api/client.ts
      import axios from "axios";
      import { getToken } from "@/shared/auth";
      
      export const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_URL });
      
      apiClient.interceptors.request.use((config) => {
        const token = getToken();
        if (token) config.headers.Authorization = `Bearer ${token}`;
        return config;
      });
      ```
      
      The `shared/api/index.ts` re-exports from these files, so consumers import
      `apiClient` and the DTO types from `@/shared/api` rather than reaching into
      `client.ts` or `product.ts` (Rule 4-2).
      
      ### CRUD helpers in shared
      
      ```typescript
      // shared/api/create-crud-api.ts
      import { apiClient } from "./client";
      
      export const createCrudApi = <T>(resource: string) => ({
        getAll: () => apiClient.get<T[]>(`/${resource}`).then((r) => r.data),
        getById: (id: string) => apiClient.get<T>(`/${resource}/${id}`).then((r) => r.data),
        create: (data: Partial<T>) => apiClient.post<T>(`/${resource}`, data).then((r) => r.data),
        update: (id: string, data: Partial<T>) => apiClient.put<T>(`/${resource}/${id}`, data).then((r) => r.data),
        remove: (id: string) => apiClient.delete(`/${resource}/${id}`),
      });
      
      // Usage: export const productsApi = createCrudApi<ProductDTO>("products");
      ```
      
      ### Request placement rule
      
      Two questions decide where a request function goes. Ask them in order.
      
      **Question 1: does one consumer own this request, or is it genuinely
      shared?**
      
      One consumer means the request stays with that consumer. This is the
      pages-first rule applied to `api/` segments.
      
      - Data fetching for a single page (e.g., dashboard stats) →
        `pages/<name>/api/`
      - An action owned by a single feature (e.g., `toggleLike`) →
        `features/<name>/api/`
      
      Genuinely shared between consumers: continue to question 2.
      
      **Question 2: does the request carry domain rules?**
      
      Domain rules are permission checks, status transitions, derived
      calculations, or a model the frontend composes from several responses.
      Knowing a resource's URL and response shape is not a domain rule.
      
      - No domain rules → `shared/api/`. Plain resource access is
        infrastructure no matter how many slices call it. Generic CRUD belongs
        here; build it from `shared/api/create-crud-api.ts`.
      - Domain rules → `entities/<name>/api/`, once the boundary is stable.
      
      A `getUserById` that only wraps `GET /users/:id` stays in `shared/api/`
      even when every page calls it. If it resolves the caller's permissions
      first, ask who owns that rule: an established user domain puts it in
      `entities/user/api/`, while a rule that only one screen applies stays with
      that page or feature.
      
      > **Where this comes from.** The official API requests guide defaults
      > request functions to `shared/api` or the consuming slice's `api`
      > segment, and warns against placing API calls in `entities` prematurely.
      > Question 2 follows that advice. Existing code placed under an earlier
      > reading is still not a violation: a request function already sitting in
      > `entities/<name>/api/` without domain rules keeps working. Relocate it
      > when you are already changing that slice, or when the entity has no
      > other reason to exist. Do not sweep a repository to move these
      > functions.
      
    • cross-import-patterns.md 14.3 KB
      # Cross-Import Resolution Patterns
      
      How to resolve cross-imports between slices on the same layer. Rule 4-3
      disallows them by default, so the first move is always to remove the
      dependency by changing a boundary or a composition. Strategies A to C do
      that. Strategy D and `@x` are the documented exceptions for a dependency
      that cannot reasonably be removed, not a second way of working.
      
      Treat a cross-import as a code smell rather than an impossible state:
      some are deliberate, and those should be explicit and rare.
      
      ## What is a cross-import?
      
      A cross-import is an import between different slices within the same layer.
      For example:
      
      - importing `features/apply-coupon` from `features/add-to-cart`
      - importing `widgets/sidebar` from `widgets/header`
      
      The `shared` and `app` layers do not have slices, so imports within those
      layers are not cross-imports.
      
      ## Why is this a code smell?
      
      Cross-imports blur domain boundaries and introduce implicit dependencies.
      Four concrete problems:
      
      1. **Unclear ownership and responsibility.** When `cart` imports from
         `product`, it becomes unclear which slice owns the shared logic. A
         change to `product`'s public contract now forces a change in `cart`,
         and a deep import couples `cart` to `product`'s internals as well.
         This makes bugs harder to localize and code harder to reason about.
      2. **Reduced isolation and testability.** A core benefit of sliced
         architecture is that a slice can be read, changed, and tested with
         little knowledge of its siblings. Cross-imports break that. Testing
         `cart` now requires setting up `product`, and a change in one slice
         can fail tests in another.
      3. **Increased cognitive load.** Working on `cart` now requires accounting
         for how `product` is structured. As cross-imports accumulate, tracing
         the impact of a change requires following more code across slice
         boundaries.
      4. **Path to circular dependencies.** Cross-imports often start as one-way
         dependencies but evolve into bidirectional ones (A imports B, B imports
         A). This locks slices together and makes refactoring increasingly costly.
      
      ## Entities layer: prefer boundary merge over @x
      
      Cross-imports in `entities` are usually caused by splitting entities too
      granularly. Before reaching for `@x`, consider whether the boundaries should
      be merged instead.
      
      The `@x` notation is available as a dedicated cross-import surface for
      `entities`, but it should be treated as a **last resort**, a **necessary
      compromise**, not a recommended approach. Think of `@x` as an explicit
      gateway for unavoidable domain references, not a general-purpose reuse
      mechanism. Overuse locks entity boundaries together and makes refactoring
      more costly over time.
      
      ### How @x works (when boundary merge is genuinely impossible)
      
      Each entity exposes a special `@x/` directory containing files named after
      the consuming entity. This makes the cross-import explicit and auditable.
      
      **Direction rule:** in the path `entities/A/@x/B`, **A is the producer and
      B is the consumer**. Read it as "A crossed with B": the file `A/@x/B.ts`
      is the public API that A exposes specifically for B. So in the example
      below, `entities/user/@x/order.ts` is what `user` exposes to `order`, and
      `order` imports from it.
      
      ```text
      entities/
        user/
          @x/
            order.ts          ← Exposed specifically for the order entity
          model/
            user.ts
          index.ts
        order/
          model/
            order-summary.ts  ← Imports from user/@x/order
          index.ts
      ```
      
      ```typescript
      // entities/user/@x/order.ts: exposes only what order needs
      export { getUserDisplayName } from "../model/user";
      
      // entities/order/model/order-summary.ts
      import { getUserDisplayName } from "@/entities/user/@x/order";
      ```
      
      ### Rules when using @x
      
      1. Document why `@x` is needed and why merging boundaries does not apply.
      2. Review periodically. Requirements change and `@x` may become unnecessary.
      3. Minimize the surface area of `@x` exports.
      4. Only between entities. Features and widgets should use Strategy C or D
         below, not `@x`.
      
      ## Features and widgets: four strategies
      
      In `features` and `widgets`, multiple strategies are available depending on
      project context. Cross-imports here are not always forbidden; they are
      dependencies that should be deliberate. The four strategies below are
      listed in preferred order, but each fits different situations.
      
      ### Strategy A: slice merge
      
      If two slices are not truly independent and always change together, merge
      them into a single larger slice.
      
      ```text
      // Before: two features that always change together
      features/edit-profile/
      features/edit-profile-privacy/
      
      // After: one cohesive feature
      features/edit-profile/
        ui/
          EditProfileForm.tsx
          PrivacyFields.tsx
        model/
          edit-profile.ts
          privacy.ts
        index.ts
      ```
      
      If two slices keep cross-importing each other and effectively move as one
      unit, they are likely one feature in practice. Merging is often the simpler
      and cleaner choice.
      
      ### Strategy B: move a shared domain responsibility into an entity
      
      If multiple features share a domain rule or domain state, move that
      responsibility into the entity that owns it. Key principles:
      
      - What moves down is an established domain responsibility, not feature UI
        or user-flow orchestration.
      - Interaction-specific UI and workflow logic stay in `features`.
      - Features import that responsibility through the entity's public API.
      
      For example, if `features/add-to-cart` and `features/buy-now` both need
      the rule for whether a product can be purchased, that rule belongs to the
      product domain, while each button remains its own user action.
      
      ```text
      entities/
        product/
          model/
            can-purchase.ts     ← the shared domain rule
          index.ts
      
      features/
        add-to-cart/
          ui/AddToCartButton.tsx
          model/add-to-cart.ts  ← imports canPurchase from @/entities/product
          index.ts
        buy-now/
          ui/BuyNowButton.tsx
          model/buy-now.ts      ← imports the same canPurchase
          index.ts
      ```
      
      ### Strategy C: compose from an upper layer (IoC)
      
      When several lower-layer modules have to take part in one composition,
      assemble them in a layer above all of them. A page can import multiple
      features and entities to compose a screen, and components can be passed
      through props or children where that helps. By default one feature does
      not import another; Strategy D below is the documented exception when
      that dependency cannot be removed.
      
      Instead of connecting slices within the same layer via cross-imports,
      compose them at a higher level (`pages` or `app`). The upper layer assembles
      and connects the slices; the slices themselves do not know about each other.
      
      Common Inversion of Control techniques:
      
      - **Render props (React)**: pass components or render functions as props.
      - **Slots (Vue)**: use named slots to inject content from parent components.
      - **Dependency injection**: pass dependencies through props or context.
      
      #### Basic composition (React)
      
      ```typescript
      // features/follow-user/index.ts
      export { FollowButton } from "./ui/FollowButton";
      
      // features/report-user/index.ts
      export { ReportUserButton } from "./ui/ReportUserButton";
      
      // pages/profile/ui/ProfilePage.tsx
      import { FollowButton } from "@/features/follow-user";
      import { ReportUserButton } from "@/features/report-user";
      
      export const ProfilePage = ({ userId }) => (
        <div>
          <FollowButton userId={userId} />
          <ReportUserButton userId={userId} />
        </div>
      );
      ```
      
      Following and reporting are two user actions that happen to sit on one
      screen. Neither knows the other exists; the page puts them there.
      
      #### Render props (React)
      
      When one feature's UI has to place another feature's control inside it,
      use a render prop to invert the dependency:
      
      ```typescript
      // features/manage-wishlist/ui/WishlistItems.tsx
      // RemoveButton is this feature's own ui/, not a cross-import.
      interface WishlistItemsProps {
        items: WishlistItem[];
        renderAddToCart?: (productId: string) => React.ReactNode;
      }
      
      export const WishlistItems = ({ items, renderAddToCart }: WishlistItemsProps) => (
        <ul>
          {items.map((item) => (
            <li key={item.id}>
              <span>{item.title}</span>
              <RemoveButton itemId={item.id} />
              {renderAddToCart?.(item.productId)}
            </li>
          ))}
        </ul>
      );
      
      // pages/wishlist/ui/WishlistPage.tsx
      import { WishlistItems } from "@/features/manage-wishlist";
      import { AddToCartButton } from "@/features/add-to-cart";
      
      export const WishlistPage = () => (
        <WishlistItems
          items={items}
          renderAddToCart={(productId) => <AddToCartButton productId={productId} />}
        />
      );
      ```
      
      `manage-wishlist` never imports `add-to-cart`. It leaves a hole per row,
      and the page fills it.
      
      #### Slots (Vue)
      
      Vue's slot system provides a natural way to compose features without
      cross-imports:
      
      ```vue
      <!-- features/manage-wishlist/ui/WishlistItems.vue -->
      <script setup lang="ts">
      defineProps<{ items: WishlistItem[] }>();
      </script>
      
      <template>
        <ul>
          <li v-for="item in items" :key="item.id">
            <span>{{ item.title }}</span>
            <slot name="add-to-cart" :productId="item.productId" />
          </li>
        </ul>
      </template>
      
      <!-- pages/wishlist/ui/WishlistPage.vue -->
      <script setup lang="ts">
      import { WishlistItems } from "@/features/manage-wishlist";
      import { AddToCartButton } from "@/features/add-to-cart";
      </script>
      
      <template>
        <WishlistItems :items="items">
          <template #add-to-cart="{ productId }">
            <AddToCartButton :productId="productId" />
          </template>
        </WishlistItems>
      </template>
      ```
      
      ### Strategy D: cross-feature reuse only via Public API
      
      If strategies A through C do not fit and cross-feature reuse is
      genuinely unavoidable, allow it only through an explicit Public API
      (exported hooks or UI components). Do not access another slice's
      `store`, `model`, or internal implementation.
      
      Unlike strategies A through C, which aim to eliminate cross-imports,
      this strategy accepts them while minimizing risk through strict
      boundaries.
      
      ```typescript
      // features/auth/index.ts
      export { useAuth } from "./model/use-auth";
      export { AuthButton } from "./ui/AuthButton";
      
      // features/edit-profile/ui/ProfileMenu.tsx
      import { useAuth, AuthButton } from "@/features/auth";
      
      export const ProfileMenu = () => {
        const { user } = useAuth();
        if (!user) return <AuthButton />;
        return <div>{user.name}</div>;
      };
      ```
      
      The boundary holds: `features/edit-profile` cannot import from
      `@/features/auth/model/internal/*`. Only what `features/auth` explicitly
      exposes through `index.ts` is reachable.
      
      The `@x` notation is for the entities layer only. Features and widgets use
      strategies A through D above; their access path is the standard public API
      (`index.ts`), not a dedicated cross-import surface.
      
      ## When to treat a cross-import as a problem
      
      After reviewing these strategies, the question is: when is a cross-import
      acceptable to keep, and when should it be treated as a code smell and
      refactored?
      
      Common warning signs:
      
      - Directly depending on another slice's `store`, `model`, or business logic
      - Deep imports into another slice's internal files (bypassing the public API)
      - Bidirectional dependencies (A imports B, and B imports A)
      - Changes in one slice frequently breaking another slice
      - Flows that should be composed in `pages` or `app`, but are forced into
        cross-imports within the same layer
      
      When these signals appear, treat the cross-import as a code smell and apply
      one of the strategies above.
      
      ## Strictness depends on project context
      
      The strictness of cross-import enforcement depends on the project:
      
      - In **early-stage products** with heavy experimentation, allowing some
        cross-imports may be a pragmatic speed trade-off.
      - In **long-lived or regulated systems** (fintech, large-scale services),
        stricter boundaries pay off in maintainability and stability.
      
      Cross-imports are not an absolute prohibition. They are dependencies that
      are generally best avoided, but sometimes used intentionally. If a
      cross-import is introduced:
      
      - Treat it as a deliberate architectural choice.
      - Document the reasoning in code (a comment explaining why other
        strategies do not apply).
      - Revisit it periodically as the system evolves; if requirements change,
        the cross-import may no longer be needed.
      
      ## Decision flow for AI agents
      
      ```text
      Two slices on the same layer need to share code.
        │
        ├─ ENTITIES layer?
        │   ├─ Are they one cohesive domain boundary?
        │   │   └─ YES → Merge. Stop.
        │   ├─ Does either entity really need to know the other, or can a
        │   │  page or feature hold both?
        │   │   └─ Compose above them. Stop.
        │   ├─ Is the shared part business-neutral infrastructure?
        │   │   └─ YES → Move that part to shared/. Stop.
        │   └─ Boundaries must stay separate and the domain dependency is real?
        │       └─ Use @x as last resort. Document why merge is not possible.
        │
        └─ FEATURES or WIDGETS layer?
            ├─ Strategy A: Do they always change together?
            │   └─ YES → Merge slices.
            │
            ├─ Strategy B: Is the shared part domain-only logic?
            │   └─ YES → Push down to entities. Keep UI in features.
            │
            ├─ Strategy C: Can the connection be assembled by a higher layer?
            │   └─ YES → Compose in pages or app via render props, slots, or DI.
            │
            └─ Strategy D: Is reuse genuinely unavoidable and the access surface
                           limited to a Public API?
                └─ YES → Allow, but only through index.ts. Never reach into
                         model/, store/, or internal files. Do not use @x in
                         features or widgets.
      ```
      
      ## Anti-patterns
      
      - **Reaching for `@x` in features or widgets.** `@x` is for entities only.
        Use Strategy C (compose) or D (Public API) instead.
      - **Treating `@x` as a clean solution.** It is a compromise. If you find
        yourself adding multiple `@x` files between the same entities, the
        boundaries are probably wrong. Merge them.
      - **Bypassing the Public API to access internals.** Even when Strategy D is
        in use, importing from `@/features/auth/model/internal/*` defeats the
        purpose. Restrict yourself to what `index.ts` exports.
      - **Bidirectional cross-imports.** A imports B and B imports A says the
        boundaries or the composition are wrong. Re-check whether the slices are
        one boundary, whether the shared part belongs lower, or whether the
        composition should move up.
      
      ## See also
      
      - `references/excessive-entities.md`: prevent the conditions that lead to
        entity-layer cross-imports in the first place.
      - `references/layer-structure.md`: layer rules and import directions.
      
    • excessive-entities.md 8.8 KB
      # Excessive Entities
      
      How to keep the `entities` layer clean and avoid over-extracting business
      logic into entities. Excessive entities cause ambiguity (what code belongs
      where), coupling, and constant import dilemmas as code scatters across
      sibling entities.
      
      An entity is not where every reusable business concept goes. It is a
      stable, low-level domain boundary that several consumers genuinely have
      to share.
      
      ## Why this matters
      
      The `entities` layer is one of the lower layers and is widely accessible.
      Every layer except `shared` can import from it. That global nature means
      changes to `entities` propagate widely, so the boundaries need care up
      front to avoid costly refactors. Adding an entity is cheap; removing one
      after many consumers depend on it is expensive.
      
      ## How to keep entities clean
      
      ### 0. Consider having no entities layer
      
      An FSD application without an `entities` layer is still FSD. Skipping the
      layer simplifies the architecture and keeps it available for future scaling.
      
      **Thin clients** (where the backend handles most data processing and the
      client mostly exchanges data) usually do not need an entities layer.
      **Thick clients** (significant client-side business logic) are better
      candidates for entities.
      
      The classification is not strictly binary. Different parts of the same
      application may behave as thick or thin clients.
      
      ```text
      // Thin client without entities layer (still valid FSD)
      src/
        app/
        pages/
          dashboard/
          profile/
        shared/
          api/
          ui/
      ```
      
      ### 1. Avoid preemptive slicing
      
      FSD v2.1 encourages **deferred decomposition** of slices. Place code in the
      `model` segment of the consuming page (widget, feature) first. Move it to
      `entities` later, when business requirements stabilize and reuse is
      confirmed across multiple consumers.
      
      The later code moves to `entities`, the less dangerous the refactor. Code
      in `entities` can affect every higher-layer slice that imports it.
      
      ```text
      // Iteration 1: code lives where it is used
      pages/profile/
        model/
          profile-validation.ts    ← page-specific for now
      
      // Iteration 2 (once several pages must share one copy of the rule):
      entities/profile/
        model/
          profile-validation.ts    ← moved once the rule needs one home
      ```
      
      ### 2. Avoid unnecessary entities
      
      Do not create an entity for every piece of business logic. Use types from
      `shared/api` and place logic in the `model` segment of the current slice.
      For genuinely reusable business logic, use the `model` segment within an
      entity slice while the transport types stay in `shared/api`. That means
      the API shapes, `OrderDto` here; a type that exists because of a business
      rule can belong to the entity model once the entity does.
      
      ```text
      shared/
        api/
          endpoints/
            order.ts              ← OrderDto type and request functions
      
      entities/
        order/
          model/
            apply-discount.ts     ← Business logic that uses OrderDto
          index.ts
      ```
      
      The DTO lives in `shared/api/endpoints/order.ts`. Once an `order`
      boundary has been earned, reusable rules that operate on it (calculating
      discounts, applying promotions) live in `entities/order/model/`. Until
      then they stay in the consuming slice's `model/`. Do not mirror every API
      endpoint with a corresponding entity.
      
      ### 3. Exclude CRUD operations from entities
      
      CRUD operations involve boilerplate code without significant business
      logic. Putting them in `entities` clutters the layer and obscures the code
      that genuinely matters. Where it goes instead is the request placement
      rule's answer, not a fixed path: a single consumer keeps it in that
      slice's `api/`, and plain resource access shared across consumers lands
      in `shared/api` (`references/auth-and-api.md`):
      
      ```text
      shared/
        api/
          client.ts
          endpoints/
            order.ts          ← getOrder, createOrder, updateOrder, deleteOrder
            products.ts       ← Standard CRUD for products
            cart.ts           ← Standard CRUD for cart
          index.ts
      ```
      
      For complex CRUD with atomic updates, rollbacks, or transactions, evaluate
      whether the operation carries business rules. Complexity alone does not
      send it to `entities`: decide by what owns the rule. A checkout or a
      cancellation is a use case and belongs to a feature or the page that runs
      it; a rule the domain owns belongs to the entity; anything else stays in
      `shared/api`.
      
      ### 4. Store authentication data in shared
      
      Prefer `shared/auth` (or `shared/api`) over a `user` entity for tokens and
      session DTOs. They are specific to authentication, rarely reused outside
      it, and wrapping a login response in a `user` entity tends to pull the
      entity into `@x` chains. A `user` entity earns its place when user-domain
      responsibilities hold a stable boundary outside the login flow, such as
      profile identity read across several product contexts (avatars in
      comments, names in posts). An entities layer that already exists is not
      itself a reason, and neither is profile reuse on its own. Tokens and the
      session stay in `shared/auth` unless an established entity genuinely owns
      that state.
      
      Both folder shapes, when to split `shared/auth` from `shared/api`, and the
      three ways to expose the token to the API client are in
      `references/auth-and-api.md`.
      
      ### 5. Minimize cross-imports
      
      FSD permits cross-imports between entities via `@x`, but they introduce
      technical issues including circular dependencies. Design entities within
      **isolated business contexts** so cross-imports become unnecessary.
      
      **Non-isolated context (avoid):**
      
      ```text
      entities/
        order/
          @x/
          model/
        order-item/
          @x/
          model/
        order-customer-info/
          @x/
          model/
      ```
      
      Three sibling entities all referencing each other through `@x`. This is a
      sign that the boundaries are wrong.
      
      **Isolated context (preferred):**
      
      ```text
      entities/
        order-info/
          model/
            order-info.ts    ← order, items, and customer info together
          index.ts
      ```
      
      One entity encapsulates the related logic, so there is no `@x` file and
      no way for the sibling slices to form a cycle.
      
      The general rule: when several entities have `@x` dependencies on each
      other, treat that as a signal to merge the boundaries, not as something to
      manage.
      
      ## Decision tree for AI agents
      
      ```text
      A new piece of domain-related code or state needs a home.
        │
        ├─ Is the project a thin client?
        │   └─ YES → Strong signal to start without entities. Keep reading
        │            the branches below rather than stopping here.
        │
        ├─ Is the logic used in only one place right now?
        │   └─ YES → Keep in the consuming slice's model/. Defer extraction.
        │
        ├─ Is it a CRUD operation without business meaning?
        │   ├─ One consumer → that slice's api/ segment
        │   └─ Shared across consumers → shared/api/endpoints/<resource>.ts
        │
        ├─ Is it auth data (tokens, session, login DTOs)?
        │   ├─ Does an established user or session entity already own this
        │   │  state, rather than merely existing?
        │   │   └─ YES → that entity's model/
        │   └─ Otherwise → shared/auth/ (the default).
        │       An entities layer existing is not a reason to move it.
        │       Avoid placing in a page, widget, or single feature slice.
        │
        ├─ Is it just a TypeScript type for an API response?
        │   └─ YES → shared/api/. No entity needed for types alone.
        │
        └─ Is it reused now, stable enough to name, and required to stay
           consistent across its consumers?
            └─ YES → Create entities/<name>/model/.
                     Verify the boundary is isolated and does not require @x
                     to communicate with sibling entities.
      ```
      
      ## Anti-patterns
      
      - **Creating entities preemptively.** Wait for reuse that is real, has a
        reason to change of its own, and needs one authoritative copy.
      - **Mirroring every API endpoint with an entity.** API endpoints belong in
        `shared/api`. Entities exist for business logic, not for paralleling the
        backend structure.
      - **Creating a `user` entity *only* to wrap a login response.** A `user`
        entity is justified when a stable user-domain responsibility is shared
        across non-auth flows (avatars in comments, names in posts). Until then
        `shared/auth` is simpler. The official Auth guide accepts a token store
        in Shared or in an entities slice for the current user or session
        (`references/auth-and-api.md`); what it rules out is a page, widget, or
        single feature slice.
      - **Splitting one domain into many entities (`order`, `order-item`,
        `order-customer-info`).** This produces `@x` chains. Merge into a single
        isolated context (`order-info` or `order`).
      - **Putting CRUD wrappers in entities.** They clutter the layer. Place
        them by the request placement rule: with their consumer while there is
        one, in `shared/api/endpoints/` once several slices call them.
      
      ## See also
      
      - `references/cross-import-patterns.md`: how to handle cross-imports when
        they appear, and why `@x` is a last resort.
      - `references/layer-structure.md`: layer responsibilities and the entities
        segment shape.
      
    • framework-integration.md 15.7 KB
      # Framework Integration
      
      How to set up FSD within specific frameworks. Covers directory placement,
      routing integration, and framework-specific path alias configuration.
      
      ## General principle
      
      Place FSD layers inside `src/` to avoid naming conflicts with framework
      directories. The FSD `app/` and `pages/` layers are **not** the same as
      framework directories with the same names (e.g., Next.js `app/`).
      
      Examples here import through `@/` pointing at `src/`. Whether that is one
      root alias or one entry per layer is a tooling choice, not an FSD rule,
      and it changes no layer semantics; how the resolver is wired differs by
      framework, which is what each section below shows. Configure an entry
      only for a layer the project actually has.
      
      The FSD layers inside `src/` keep the standard shape described in
      `references/layer-structure.md`. Each section below shows only what the
      framework adds or renames around them, not the layers' own internals.
      
      The Next.js, Nuxt, and Astro sections follow the official tech guides on
      fsd.how. React Router and Vite are this skill's own additions with no
      official guide behind them. SvelteKit and Electron have official guides
      and are not covered here; read those directly.
      
      ## Next.js
      
      FSD works with both the App Router and the Pages Router. Next.js uses the
      `app/` and `pages/` folder names for its own routing. Those names collide
      with the FSD `app/` and `pages/` layers. Rename the FSD layers to `_app/`
      and `_pages/` (with the underscore prefix). Do this even if you only use
      one router. Keep the Next.js routing folders at the project root so `src/`
      holds only FSD code. The FSD linter (Steiger) expects this naming.
      
      ### Projects on the previously recommended pattern
      
      An earlier version of this guide recommended a different layout. It kept the
      Next.js `app`/`pages` folders at the root and added an empty root `pages/`
      placeholder. The `src/app`/`src/pages` layers were not prefixed. Projects set
      up that way keep working. The empty `pages/` placeholder can break the build on
      Next.js 13.5 and later. That is why the prefix is now the default. Use
      `_app`/`_pages` for new projects. Move a project off the old pattern when you
      can.
      
      ### App Router
      
      Route files in `app/` re-export from the FSD `_pages/` layer.
      
      #### Directory structure
      
      ```text
      my-nextjs-project/
        app/                     ← Next.js App Router (routing only)
          layout.tsx
          page.tsx
          profile/
            page.tsx
          api/
            get-example/
              route.ts
        src/
          _app/                  ← FSD app layer
            providers/
              index.tsx          ← All providers (QueryClient, theme, etc.)
            styles/
              globals.css
            api-routes/          ← Route Handler implementations (see below)
              index.ts
              get-example-data.ts
          _pages/                ← FSD pages layer
            home/                ← slice; segments per layer-structure.md
            profile/
          widgets/               ← FSD widgets layer (when needed)
          features/              ← FSD features layer (when needed)
          entities/              ← FSD entities layer (when needed)
          shared/                ← FSD shared layer
            db/                  ← Database queries (see below)
      ```
      
      #### Wiring Next.js routes to FSD pages
      
      ```typescript
      // app/layout.tsx
      import { Providers } from '@/_app/providers';
      import '@/_app/styles/globals.css';
      
      export default function RootLayout({ children }) {
        return (
          <html lang="en">
            <body><Providers>{children}</Providers></body>
          </html>
        );
      }
      
      // app/example/page.tsx: re-export the FSD page (component + metadata)
      export { ExamplePage as default, metadata } from '@/_pages/example';
      ```
      
      Keep route files free of logic. Re-export the page component plus
      whatever route exports the framework needs and the FSD page provides,
      such as `metadata` or `generateMetadata` when the page has one.
      
      ### Pages Router
      
      The Pages Router uses `pages/` at the project root. Each route file should
      re-export the corresponding page module from the FSD `_pages/` layer.
      
      ```text
      my-nextjs-project/
        pages/                   ← Next.js Pages Router (routing only)
          _app.tsx
          api/example.ts         ← API route re-export
          example/index.tsx
        src/
          _app/
            custom-app/          ← Custom App component
            api-routes/          ← Route Handler implementations
          _pages/
            example/
              ui/example.tsx
              index.ts
      ```
      
      ```typescript
      // pages/example/index.tsx
      export { Example as default } from '@/_pages/example';
      
      // pages/_app.tsx: re-export the custom App from src/_app/custom-app
      export { App as default } from '@/_app/custom-app';
      ```
      
      The custom App implementation lives in `src/_app/custom-app/` and exposes
      only what the framework entry file imports. App has no slices, so treat
      this as a segment boundary inside the layer, not a slice.
      
      ### Proxy and instrumentation
      
      `proxy.ts` and `instrumentation.ts` sit at the framework boundary, not
      inside the FSD layers. Next.js looks for them in the project root, or in
      `src/` when the Next.js app itself uses `src/`, at the same level as its
      `app/` or `pages/` folder. Keep them there and out of `src/_app/`.
      
      Next.js 16 deprecated the `middleware` convention and renamed it `proxy`;
      older projects still use `middleware.ts`.
      
      With the layout above, the Next.js `app/` and `pages/` folders sit at the
      project root, so both files sit there too. The `src/` option applies to
      projects that keep Next.js routing under `src/`, and never means `src/_app/`.
      
      > **Where this comes from.** The official Next.js guide on fsd.how says
      > both files must be in the project root and that Next.js will not find
      > them under `src/`. That matched earlier Next.js versions and is no
      > longer what the framework documents, so this section follows the
      > framework.
      
      ### Route Handlers (API routes)
      
      Use a dedicated `api-routes` segment in the FSD `_app/` layer
      (`src/_app/api-routes/`) to host the actual request handlers. The Next.js
      `app/api/*/route.ts` (App Router) or `pages/api/*.ts` (Pages Router) files
      become thin re-exports.
      
      **App Router:**
      
      ```typescript
      // src/_app/api-routes/get-example-data.ts
      import { getExamplesList } from '@/shared/db';
      
      export const getExampleData = () => {
        try {
          const examplesList = getExamplesList();
          return Response.json({ examplesList });
        } catch {
          return Response.json(null, {
            status: 500,
            statusText: 'Ouch, something went wrong',
          });
        }
      };
      
      // src/_app/api-routes/index.ts
      export { getExampleData } from './get-example-data';
      
      // app/api/example/route.ts
      export { getExampleData as GET } from '@/_app/api-routes';
      ```
      
      **Pages Router:**
      
      ```typescript
      // src/_app/api-routes/get-example-data.ts
      import type { NextApiRequest, NextApiResponse } from 'next';
      
      const config = { api: { bodyParser: { sizeLimit: '1mb' } }, maxDuration: 5 };
      const handler = (req: NextApiRequest, res: NextApiResponse) =>
        res.status(200).json({ message: 'Hello from FSD' });
      
      export const getExampleData = { config, handler } as const;
      
      // pages/api/example.ts
      import { getExampleData } from '@/_app/api-routes';
      export const config = getExampleData.config;
      export default getExampleData.handler;
      ```
      
      Keep Route Handlers as framework-facing adapters and delegate domain rules
      to the FSD boundary that owns them. FSD is primarily a frontend
      methodology. If `api-routes` grows to many endpoints, consider moving the
      backend to a separate package in a monorepo.
      
      ### Database access
      
      Place database queries in a `db` segment in `shared/` (`src/shared/db/`).
      Co-locate caching and revalidation logic with the queries themselves.
      
      Plain data access is all that goes there. Rule 4-5 does not relax because
      code runs on the server: domain rules and use-case orchestration stay with
      the slice that owns them.
      
      ### Path aliases
      
      ```json
      // tsconfig.json
      {
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/_app/*": ["src/_app/*"],
            "@/_pages/*": ["src/_pages/*"],
            "@/shared/*": ["src/shared/*"]
          }
        }
      }
      ```
      
      Next.js reads `tsconfig.json` paths automatically. No `next.config.js`
      alias configuration is needed.
      
      ### Server and client public APIs
      
      In the Next.js App Router, a single slice can contain both client-usable modules
      and server-only modules.
      
      Keep `index.ts` free of server-only exports, such as Server Components or
      data-access functions that import `server-only`. When a Client Component imports
      the slice, those exports can enter the client module graph and cause build
      errors.
      
      Split only when this boundary is required. Put server-only exports in
      `index.server.ts`.
      
      ## Nuxt (v3-compatible layout)
      
      Nuxt keeps file routing in `pages/` at the project root, the name FSD
      reserves for the pages layer. The official Nuxt guide resolves this by
      moving Nuxt's routing folder inside the FSD `app` layer and giving `src/`
      a single `@` alias. This section mirrors that guide.
      
      The shape below is a Nuxt 3 project, which is what the official guide
      covers. Nuxt 4 changed the default `srcDir` to `app/` and moves `pages/`,
      `layouts/`, and `components/` under it, so its framework `app/` collides
      with the FSD app layer the way Next.js does. No official FSD guide covers
      that layout yet. A Nuxt 4 project can keep this shape, which Nuxt still
      auto-detects, and follow this section. Nuxt 3 itself reached end of life
      on 31 July 2026, so read the heading as the layout, not the version to
      start on.
      
      ### Directory structure
      
      ```text
      my-nuxt-project/
        nuxt.config.ts
        src/
          app/                   ← FSD app layer
            routes/              ← Nuxt file routing (dir.pages)
              index.vue
            layouts/             ← Nuxt layouts (dir.layouts)
          pages/                 ← FSD pages layer
            home/
              ui/home-page.vue
              index.ts
          shared/
      ```
      
      ### nuxt.config.ts
      
      ```typescript
      // nuxt.config.ts
      export default defineNuxtConfig({
        alias: {
          "@": "../src",
        },
        dir: {
          pages: "./src/app/routes",
          layouts: "./src/app/layouts",
        },
      });
      ```
      
      ### Wiring Nuxt routes to FSD pages
      
      ```vue
      <!-- src/app/routes/index.vue -->
      <script setup>
      import { HomePage } from "@/pages/home";
      </script>
      
      <template>
        <HomePage />
      </template>
      ```
      
      The official guide also shows config-based routing through
      `app/router.options.ts` instead of file routing. Either way, route
      definitions live in the `app` layer and page slices in `pages`.
      
      ## Vite + React
      
      ### Directory structure
      
      ```text
      my-vite-project/
        src/
          app/                   ← FSD app layer
            providers/
            router.tsx
            styles/
            main.tsx             ← Entry point
          pages/
          shared/
        index.html
        vite.config.ts
        tsconfig.json
      ```
      
      ### Path aliases
      
      Mirror the standard `tsconfig.json` mapping in `vite.config.ts` so the
      Vite resolver agrees with TypeScript:
      
      ```typescript
      // vite.config.ts
      import { defineConfig } from "vite";
      import react from "@vitejs/plugin-react";
      import { resolve } from "path";
      
      export default defineConfig({
        plugins: [react()],
        resolve: {
          alias: {
            "@/app": resolve(__dirname, "src/app"),
            "@/pages": resolve(__dirname, "src/pages"),
            "@/shared": resolve(__dirname, "src/shared"),
          },
        },
      });
      ```
      
      Create React App is no longer maintained. A project still on it follows
      this section; the only difference is that the aliases go into a `craco`
      config instead of `vite.config.ts`. Migrate to Vite when you can.
      
      ## React Router (framework mode)
      
      React Router v7 in framework mode owns an `app/` directory for its root
      layout, route config, and route modules. That name collides with the FSD
      app layer, so apply the same fix as for Next.js: React Router's `app/`
      stays at the project root, FSD lives in `src/`, and the FSD app layer is
      renamed to `_app/`. Only `app/` collides, so `pages/` keeps its name.
      
      Library mode (`createBrowserRouter` inside a plain Vite app) has no
      framework directory. The Vite + React section applies as is, with the
      router defined in the FSD app layer.
      
      ### Directory structure
      
      ```text
      my-router-project/
        app/                     ← React Router (routing only)
          root.tsx               ← Root layout, mounts providers from @/_app
          routes.ts              ← Route config
          routes/
            home.tsx             ← Thin wrapper around @/pages/home
            product.tsx
        src/
          _app/                  ← FSD app layer
            providers/
            styles/
          pages/
            home/
            product/
              ui/ProductPage.tsx
              api/fetch-product.ts
              index.ts
          shared/
        react-router.config.ts
        vite.config.ts
        tsconfig.json
      ```
      
      ### Wiring routes to FSD pages
      
      Route modules stay thin. They own the framework-required route exports and
      delegate the application work to what the FSD page's public API exposes.
      
      ```typescript
      // app/routes.ts
      import { type RouteConfig, index, route } from "@react-router/dev/routes";
      
      export default [
        index("routes/home.tsx"),
        route("products/:id", "routes/product.tsx"),
      ] satisfies RouteConfig;
      ```
      
      ```typescript
      // app/routes/product.tsx
      import type { Route } from "./+types/product";
      import { ProductPage, fetchProduct } from "@/pages/product";
      
      export const loader = ({ params }: Route.LoaderArgs) =>
        fetchProduct(params.id);
      
      export default function ProductRoute({ loaderData }: Route.ComponentProps) {
        return <ProductPage product={loaderData} />;
      }
      ```
      
      **Framework requirement:** `loader` and the default component are Route
      Module exports, so both live in the route file, and React Router hands
      the component its generated `Route.ComponentProps`.
      
      **This skill's recommendation:** let the generated types stop there. The
      wrapper makes one call into the page's `api` segment or `shared/api` and
      passes plain props down, so the FSD page stays framework-independent.
      
      ### Path aliases
      
      Keep the standard `tsconfig.json` mapping, with `@/_app` pointing at
      `src/_app`, and let `vite-tsconfig-paths` feed it to the React Router
      Vite plugin:
      
      ```typescript
      // vite.config.ts
      import { defineConfig } from "vite";
      import { reactRouter } from "@react-router/dev/vite";
      import tsconfigPaths from "vite-tsconfig-paths";
      
      export default defineConfig({
        plugins: [reactRouter(), tsconfigPaths()],
      });
      ```
      
      ## Astro
      
      Astro uses `src/pages/` for file-based routing, which collides with the FSD
      `pages/` layer. Move the FSD pages layer to `src/_pages/` (with the
      underscore prefix) and reserve `src/pages/` for Astro routes.
      
      ### Directory structure
      
      ```text
      my-astro-project/
        src/
          pages/                 ← Astro routing (thin entry points)
            404.astro
            index.astro
          _pages/                ← FSD pages layer
            home/
              ui/HomePage.astro
              index.ts
          features/              ← when needed
          entities/              ← when needed
          widgets/               ← existing projects that keep the layer
          shared/
      ```
      
      ### Wiring Astro routes to FSD pages
      
      The Astro route file imports and renders the FSD page, nothing else:
      
      ```astro
      ---
      // src/pages/index.astro
      import { HomePage } from '@/_pages/home';
      ---
      <HomePage />
      ```
      
      ### Path aliases (tsconfig.json)
      
      The official FSD Astro guide uses a single `@/*` alias pointing at `src/*`
      rather than one alias per layer, and this section follows it:
      
      ```json
      {
        "extends": "astro/tsconfigs/strict",
        "compilerOptions": {
          "paths": {
            "@/*": ["./src/*"]
          }
        }
      }
      ```
      
      Imports then reference the layer path directly: `@/_pages/home`,
      `@/shared/ui`, `@/entities/user`.
      
      ### Working with integrations
      
      Some Astro integrations (for example, Starlight) use content collections
      that expect content in fixed folders such as `src/content/docs/`. If the
      integration does not allow the path to be changed, leave it as-is. The
      content folder lives alongside FSD layers without collision:
      
      ```text
      src/
        _pages/                  ← FSD pages layer
        content/                 ← Integration content (Starlight, etc.)
          docs/
            getting-started.md
        shared/                  ← FSD shared layer
      ```
      
      Let the integration handle its own routing and rendering, while FSD layers
      manage application-specific code.
      
    • growth-walkthrough.md 7.9 KB
      # Growth Walkthrough
      
      One small shop through four snapshots, showing which moments earn a layer
      and which do not. Read it when starting a project, or when deciding
      whether entities are needed yet. Each snapshot gives the tree, what
      changed in the product, and which rule from `SKILL.md` decided the
      response. Every decision here comes from `SKILL.md`; this file adds no
      placement rules of its own. Not every product change earns a layer: a
      layer is earned by a stable responsibility that needs one home, not by a
      count of how many places use something.
      
      ## Snapshot 0: two pages, three layers
      
      The shop has a home page with a product list and a product detail page.
      The detail page shows an "on sale" badge.
      
      ```text
      src/
        app/
          providers/
          router.tsx
          styles/
        pages/
          home/
            ui/HomePage.tsx
            ui/ProductCard.tsx        ← list card, used only here
            api/fetch-products.ts     ← one consumer: this page
            index.ts
          product/
            ui/ProductPage.tsx
            ui/SaleBadge.tsx          ← badge, used only here
            model/is-on-sale.ts       ← the rule: price < listPrice
            api/fetch-product.ts      ← one consumer: this page
            index.ts
        shared/
          api/
            client.ts
            product.ts                ← ProductDTO, read by both pages
            index.ts
          ui/
            Button/
            Card/
      ```
      
      **What is absent on purpose.** No `entities/`, no `features/`, no
      `widgets/`. Each request has one consumer, so it sits in that page's
      `api/` segment: Question 1 of the request placement rule is asked before
      Question 2 (`references/auth-and-api.md`). `ProductDTO` is in
      `shared/api` because both pages read the same transport shape. The sale
      rule sits in the product page because only that page applies it (Step 1).
      This is complete, valid FSD (Section 5-3).
      
      ## Snapshot 1: a third page reuses product data, no layer appears
      
      A search page is added. It fetches products, shows them as cards, and
      marks the ones on sale.
      
      ```text
        pages/
          home/
            index.ts                  ← api/fetch-products.ts moved out
          search/                     ← new slice
            ui/SearchPage.tsx
            ui/ProductCard.tsx        ← a second card, copied from home
            model/is-on-sale.ts       ← a second copy of the rule, from product
            index.ts
        shared/
          api/
            product.ts                ← ProductDTO, and now fetchProducts
      ```
      
      **The request moved down, and no layer opened.** `fetchProducts` has two
      consumers now, so Question 1 stops keeping it in the home page.
      Question 2 asks whether it carries domain rules, and a URL with a
      response shape is not a rule, so it lands in `shared/api` next to the
      DTO. Note where it did not land: `entities/product/api` was never a
      candidate, and the official API requests guide warns against putting
      requests there prematurely. Moving code down a layer is not the same as
      opening one.
      
      `fetchProduct` did not move. The detail page is still its only consumer.
      
      **The second card is a copy, not an extraction.** Two `ProductCard`
      files look like a signal for `entities/product/ui`. They are not, yet.
      The search card shows a match snippet and the home card does not, so they
      are not the same code, and they will keep drifting apart for their own
      reasons. Step 1 covers this case directly: used in two pages but the
      duplication is manageable, so separate copies are valid. Extracting now
      would force two cards that want to differ into one component that has to
      serve both.
      
      **The rule is copied too.** The search card marks sale items, so
      `is-on-sale.ts` is copied out of the product page. Keeping both copies
      local is still cheaper than committing to a shared boundary before the
      two consumers are required to agree, and so far nothing requires it. A
      second copy is not a boundary on its own. What turns one into a boundary
      is the subject of Snapshot 2.
      
      ## Snapshot 2: a rule diverges, `entities/product` appears
      
      Marketing changes what "on sale" means: the price must be below the list
      price *and* the item must be in stock. The product page is updated. The
      copy in the search page, made in Snapshot 1, is not. Search now marks
      items on sale that the detail page says are not.
      
      This is the signal. The two copies are the same rule, they must agree,
      and they no longer do. Check the extraction rule (`SKILL.md`, Section 1):
      
      1. The same code is used in multiple places right now. Yes, two pages.
      2. It has a reason to change that is independent of any one consumer.
         Yes: the rule changes when marketing changes it, not when either page
         changes.
      3. The boundary has a focused responsibility. Yes: "is this product on
         sale" and nothing else.
      
      All three hold, so the rule gets one home (Step 4).
      
      ```text
        entities/                     ← new layer
          product/
            model/is-on-sale.ts       ← replaces both copies; the one home
            index.ts
        pages/
          product/
            ui/SaleBadge.tsx          ← stays; now calls isOnSale from
                                         @/entities/product
          search/
            ui/ProductCard.tsx        ← stays; calls the same isOnSale
        shared/
          api/
            product.ts                ← ProductDTO stays here
      ```
      
      **What did not move.** The transport type `ProductDTO` stays in
      `shared/api`. The official excessive-entities guide moves the logic into
      the entity's `model` and leaves the API shape where it was (Section 5-2,
      item 3). A transport type does not follow business logic into an entity
      just because that logic reads it. The badge and the cards stay in their
      pages: they are UI, and Section 6 warns against adding UI to entities
      until there is a reason. The entity is one file and an index. That is
      enough.
      
      ## Snapshot 3: an action is reused, `features/add-to-cart` appears
      
      Between Snapshots 2 and 3 the product page gains an "Add to cart" button,
      with its request and an optimistic cart update in the page's `api` and
      `model` segments. Search results now need the same action.
      
      Step 3 asks whether this is a complete user action, used in multiple
      places, with a stable boundary. The button, the request, and the cart
      update form one action; two pages use it; adding to the cart means the
      same thing from either page. Extract it.
      
      The request goes with the feature because it is the add-to-cart use case
      itself, not a generic cart CRUD wrapper. A plain reusable cart request
      would have stayed in `shared/api` however many slices called it.
      
      ```text
        features/                     ← new layer
          add-to-cart/
            ui/AddToCartButton.tsx
            api/add-to-cart.ts        ← the use case itself, not cart CRUD
            model/add-to-cart.ts      ← pending and optimistic state for it
            index.ts
        pages/
          product/
            ui/ProductPage.tsx        ← renders <AddToCartButton />
          search/
            ui/ProductCard.tsx        ← renders <AddToCartButton />
      ```
      
      **What did not appear.** A `cart` entity. The state here exists only to
      run the add-to-cart interaction and carries no cart-domain responsibility
      anyone else could reuse, so Step 4 keeps it inside the feature. A cart a
      checkout page and an order summary both had to agree on would be a
      different question. A `widgets/` layer. Nothing in four snapshots needed
      one, and the callout in Section 1 says not to reach for it.
      
      ## What the walkthrough shows
      
      | Moment | Trigger | Response | Rule |
      | --- | --- | --- | --- |
      | 0 | Two pages | `app/`, `pages/`, `shared/` | Section 5-3 |
      | 1 | Third page reads product data | No layer; `fetchProducts` moves to `shared/api` | Question 1, Question 2 |
      | 2 | Same rule, two copies, one stale | `entities/product/model` | The extraction rule, Step 4 |
      | 3 | Same complete action on two pages | `features/add-to-cart` | Step 3 |
      
      Reuse alone opened no layer. A domain rule that had to stay consistent
      across its consumers earned `entities`; a complete user action with one
      shared behavior earned `features`. Everything else stayed where it was
      used, and one request moved down without earning anything.
      
      Once a layer exists, `references/layer-structure.md` shows the full shape
      of its slices and segments. This file only shows the moment it appears.
      
    • layer-structure.md 19.3 KB
      # Layer Structure Reference
      
      Detailed folder structures, code examples, and naming conventions for each
      FSD layer. Use this reference when creating, reviewing, or reorganizing
      project structure.
      
      ## App layer
      
      App-wide initialization: providers, routing, global styles, entry point.
      Organized by segments only, no slices.
      
      The methodology does not formally standardize App segment names. The
      common convention list (`ui`, `api`, `model`, `lib`, `config`) applies to
      all layers but is rarely a good fit here. In practice, projects use names
      that describe purpose: `routes`, `store`, `styles`, `providers`,
      `entrypoint`, etc. Choose names that match your stack (for example,
      `providers` for React/Vue provider components that wrap Redux,
      QueryClient, or theme contexts):
      
      ```text
      app/
        routes/          ← Route configuration (or router.tsx for single file)
        store/           ← Global state store (Redux configureStore, Zustand root)
        styles/          ← Global CSS, reset, theme variables
        providers/       ← Provider components (Redux Provider, QueryClientProvider)
        entrypoint.tsx   ← Application entry point (main.tsx, index.tsx)
      ```
      
      A smaller project may collapse some of these into single files:
      
      ```text
      app/
        router.tsx       ← Route configuration
        store.ts         ← Store configuration
        styles/
          global.css
        providers.tsx    ← All providers in one file
        index.tsx        ← Entry point
      ```
      
      ```typescript
      // app/router.tsx
      import { HomePage } from '@/pages/home';
      import { ProfilePage } from '@/pages/profile';
      
      export const router = createBrowserRouter([
        { path: '/', element: <HomePage /> },
        { path: '/profile/:id', element: <ProfilePage /> },
      ]);
      ```
      
      **Belongs in app:** Global providers (Redux store, QueryClient, theme),
      routing setup, global styles, error boundaries, analytics initialization.
      
      **Does not belong:** Feature-specific code, business logic, page-level UI.
      
      ## Pages layer
      
      Route-level composition. In v2.1, pages **own substantial logic**: they are
      not thin wrappers. In early project stages, most code lives here.
      
      ```text
      pages/
        home/
          ui/
            HomePage.tsx
            HeroSection.tsx
            FeaturesGrid.tsx
          model/
            home-data.ts          ← Page-specific state + logic
          api/
            fetch-home-data.ts    ← Page-specific API calls
          index.ts
        profile/
          ui/
            ProfilePage.tsx
            ProfileForm.tsx
            ProfileStats.tsx
          model/
            profile.ts            ← Profile state + validation logic
          api/
            update-profile.ts
            fetch-profile.ts
          index.ts
      ```
      
      **Belongs in pages:** Page-specific UI, forms, validation, data fetching,
      state management, business logic, API integrations. Even code that looks
      reusable stays here if it is simpler to keep local.
      
      **Does not belong:** Code that is already reused across multiple pages,
      has a stable focused responsibility, and must have one shared home.
      Extract when all three hold, not when reuse alone appears.
      
      ### Page layout patterns
      
      A typical page composes features and entities from lower layers, plus its own
      local UI components:
      
      ```typescript
      // pages/product-detail/ui/ProductDetailPage.tsx
      import { AddToCart } from '@/features/add-to-cart';
      import { ProductCard } from '@/entities/product';
      import { PageHeader } from './PageHeader'; // local to this page
      
      export const ProductDetailPage = ({ productId }) => {
        const product = useProductDetail(productId); // local hook in this page
      
        return (
          <>
            <PageHeader />
            <ProductCard data={product} />
            <AddToCart productId={productId} />
            <RelatedProducts products={product.related} /> {/* local component */}
          </>
        );
      };
      ```
      
      For pages that only need shared + page-local code (no extracted layers):
      
      ```typescript
      // pages/about/ui/AboutPage.tsx
      import { Card } from '@/shared/ui/Card';
      import { TeamSection } from './TeamSection';  // local to this page
      import { MissionStatement } from './MissionStatement';
      
      export const AboutPage = () => (
        <main>
          <MissionStatement />
          <Card><TeamSection /></Card>
        </main>
      );
      ```
      
      ## Widgets layer (discouraged)
      
      Widgets are a layer for placing reusable UI blocks. They can be composed from
      multiple UI elements into a meaningful section of a screen and then used in
      upper layers such as Pages or App.
      
      > **The official layer reference discourages using the Widgets layer**, and
      > this skill follows it. The reasoning, and where each kind of UI block
      > goes instead, is in `SKILL.md`, Section 1.
      
      One edge case: multiple flows from the Features layer may need to be composed
      together, the kind of case that previously would have been placed in Widgets.
      In most cases this can be resolved by taking a different approach to
      composition. The parent (`pages` or `app`) imports the features and connects
      them, which is Strategy C in `references/cross-import-patterns.md`. Still,
      there may be edge cases that are genuinely hard to resolve. When that happens,
      document the situation in
      [feature-sliced/skills#7](https://github.com/feature-sliced/skills/issues/7).
      
      Discouraging the layer does not mean removing it entirely. It means
      recommending against actively adopting it. Projects already using widgets can
      keep using them as before, and the standard slice/segment and public API rules
      apply just as on any other layer:
      
      ```text
      widgets/
        header/
          ui/
            Header.tsx
            Navigation.tsx
            UserMenu.tsx
          model/
            header.ts              ← Widget state
          api/
            fetch-notifications.ts
          index.ts
      ```
      
      **If you still use widgets:** Navigation bars, sidebars, dashboards, and
      footers are the typical examples. Simple UI primitives belong in `shared/ui/`,
      and single-use page sections stay in the page.
      
      ## Where should layouts be placed?
      
      Layout components often need to compose data handling, state management,
      access control and user actions that are shared across multiple routes.
      
      In React Router nested child routes may share a common URL path such as
      `/users`, `/users/:id` and `/users/:id/settings`. Instead of repeating the
      same handling in each page you can use the router's nesting capabilities to
      apply a common layout and route-level logic in one place.
      
      The location of a layout should be determined based on its **scope and
      responsibility** rather than its structural complexity.
      
      - Layouts responsible for the entire application or routing structure should
        be placed in `app`.
      - Layouts specific to a particular page or route group should be placed in
        `pages`.
      - Layout UI that is reusable without business context can be placed in
        `shared/ui`.
      - Layouts centered around a specific user action or user flow and reused
        across multiple pages can be implemented in the corresponding `features`
        slice.
      
      A layout in `shared` that directly imports from `features`, `entities` or
      `pages` violates the layer import rule. Modules in `app` and `pages` can
      import modules from lower layers to compose a screen.
      
      > A module can only import modules from layers below the layer it belongs to.
      
      Before extracting a layout into a separate module consider the following:
      
      - Is this layout actually reused across multiple routes?
      - Is it specific to a particular page or route structure?
      - Is the layout itself the reusable unit or is it only the user action used
        within the layout?
      
      A layout used by only a small number of pages and tied to a particular screen
      structure may be simpler to define directly in the corresponding `page` or
      route configuration.
      
      1. **Configure a route layout in the App layer**
         You can group multiple routes with a common URL path using the router's
         nesting capabilities and assign a single layout in `app`.
         A layout located in `app` can compose modules from `pages`, `features`,
         `entities` and `shared` without violating the layer import rule.
      
      2. **Pass feature UI through render props or slots**
         In React you can use the render props pattern. In Vue you can use slots.
         In this approach the layout in `shared` provides only the common UI
         structure while the required feature UI is passed from `app` or `pages`.
         This allows the layout to compose the required screen without directly
         depending on a specific feature.
      
      3. **Define it directly in a page**
         A layout used only by a specific page can be defined directly in the
         corresponding `page` without introducing a separate abstraction.
         When there is little duplicated code and the layout is unlikely to change
         frequently there is no need to extract it into a shared module.
      
      ## Features layer
      
      Independent, reusable user interactions. Create a feature when an
      interaction is already reused across consumers, has a focused
      responsibility, and needs one shared implementation. A second consumer on
      its own does not require one (the extraction rule in `SKILL.md`).
      
      ```text
      features/
        auth/                     ← Signing in
          ui/
            LoginForm.tsx
          model/
            auth.ts               ← Session state + logic
          api/
            login.ts
          index.ts
        register/                 ← Signing up, a separate use case
          ui/
            RegisterForm.tsx
          model/
            register.ts
          api/
            register.ts
          index.ts
        add-to-cart/
          ui/
            AddToCartButton.tsx
          model/
            cart.ts
          index.ts
        like-post/
          ui/
            LikeButton.tsx
          model/
            like.ts
          api/
            toggle-like.ts
          index.ts
      ```
      
      **Feature composition**: features consume entities and are composed in
      higher layers:
      
      ```typescript
      // pages/feed/ui/PostCard.tsx  (composition lives in the page that uses it)
      import { UserAvatar } from '@/entities/user';
      import { LikeButton } from '@/features/like-post';
      import { CommentButton } from '@/features/comment-create';
      
      export const PostCard = ({ post }) => (
        <article>
          <UserAvatar userId={post.authorId} />
          <h2>{post.title}</h2>
          <p>{post.content}</p>
          <div>
            <LikeButton postId={post.id} />
            <CommentButton postId={post.id} />
          </div>
        </article>
      );
      ```
      
      ## Entities layer
      
      Reusable business domain models. Create an entity when domain logic or
      state is already reused across consumers, has a focused responsibility,
      and needs one authoritative home (the extraction rule in `SKILL.md`).
      **Starting without this layer is completely valid.**
      
      ```text
      // Minimal entity: model only (most common form)
      entities/user/
        model/
          user.ts                  ← Types + domain logic
        index.ts
      
      // Entity with UI (use with caution)
      // Caution: adding UI to entities increases cross-import risk.
      // Other entities may want to import this UI, leading to @x dependencies.
      // Entity UI should only be imported from higher layers (features, pages,
      // app), never from other entities.
      entities/product/
        model/
          product.ts
        ui/
          ProductCard.tsx
        index.ts
      ```
      
      ## Shared layer structure
      
      Infrastructure with no business logic. Organized by segments only (no slices).
      Segments may import from each other.
      
      ```text
      shared/
        ui/                ← UI kit: Button, Input, Modal, Card
        lib/               ← Utilities: formatDate, debounce, classnames
        api/               ← API client, route constants, CRUD helpers, base types
        auth/              ← Auth tokens, login utilities, session management
        config/            ← Environment variables, app settings
      ```
      
      There is no `assets/` segment here. Assets live with the code that uses
      them, and a shared presentation asset goes to `shared/ui/` with the
      component that owns it (`references/asset-handling.md`).
      
      The official API requests guide groups request functions under
      `shared/api/endpoints/` and re-exports them from `shared/api/index.ts`.
      The examples in this skill also show flat domain-named files (`client.ts`,
      `product.ts`) and per-controller folders (`example/get-example.ts`); treat
      those as variations of the same idea, not competing standards. Whatever the
      internal shape, consumers import from the segment index, or from a
      component folder's own index where Rule 4-2 allows one.
      
      ```typescript
      // shared/ui/Button/Button.tsx
      export const Button = ({ children, onClick, variant = 'primary' }) => (
        <button className={`btn btn-${variant}`} onClick={onClick}>
          {children}
        </button>
      );
      
      // shared/ui/Button/index.ts
      export { Button } from './Button';
      export type { ButtonProps } from './Button';
      ```
      
      Shared **may** contain application-aware code: route constants, API
      endpoints, branding assets, and transport types such as `ProductDTO`.
      It must **never** hold the business rules an entity or feature owns, nor
      import from those layers.
      
      For asset placement specifically (images, icons, fonts, PDFs), see
      `references/asset-handling.md`.
      
      ## Segments
      
      A segment groups related code within a slice (or within App/Shared). The
      standard segments cover the most common technical purposes:
      
      - **`ui`**: UI display (components, date formatters, styles).
      - **`api`**: backend interactions (request functions, data types, mappers).
      - **`model`**: data model (schemas, interfaces, stores, business logic).
      - **`lib`**: library code that other modules in this slice need.
      - **`config`**: configuration files and feature flags.
      
      Custom segments are allowed when needed (for example, `routes` and `i18n`
      in the Shared layer, or `auth` for token storage when split out from
      `shared/api`).
      
      ### Group by what it is *for*, not by what it *is*
      
      Segment names describe **purpose**, not the kind of code they hold. This
      is the desegmentation principle:
      
      ```text
      // BAD: grouping by technical kind (what the code is)
      shared/
        components/         ← What kind of components?
        hooks/              ← Which feature do they serve?
        types/              ← Which domain do they describe?
        utils/              ← Utility for what?
        helpers/            ← Same problem
        actions/            ← Redux actions for what?
      
      // GOOD: grouping by purpose (what the code is for)
      shared/
        ui/                 ← For displaying UI
        api/                ← For talking to the backend
        lib/                ← For library code that supports the slice
        config/             ← For configuration
      ```
      
      A segment named `types/` cannot answer "types for what?" without inspecting
      the contents. A segment named `model/` says: this is the data model.
      Inside `model/`, files are named by domain (`user.ts`, `order.ts`), not by
      technical role.
      
      This rule applies everywhere: in `shared/`, in slices, and when designing
      new custom segments.
      
      ## Naming conventions
      
      ### Domain-based file naming
      
      Within a segment, name files after what they are for, the concern or
      domain they serve, not after their technical mechanism:
      
      ```text
      // BAD: technical-role naming mixes domains
      model/types.ts          ← Which types? User? Order?
      model/utils.ts
      api/endpoints.ts        ← Every domain's requests in one file
      model/selectors.ts
      
      // GOOD: domain-based naming, each file owns one domain
      model/user.ts           ← User types + logic + store
      model/order.ts          ← Order types + logic + store
      api/fetch-profile.ts    ← Clear what this API does
      model/todo.ts           ← Redux slice + selectors + thunks
      ```
      
      The fault in `api/endpoints.ts` is the single file, not the word. An
      `endpoints/` directory holding one file per domain is the shape the
      official API requests guide uses.
      
      ### Single-concern segments
      
      If a segment contains only one domain concern, the filename may match the
      slice name:
      
      ```text
      features/auth/
        model/
          auth.ts          ← Single concern, matches slice name
      ```
      
      ### Index files as public API
      
      Every slice must have an `index.ts` that re-exports its public interface:
      
      ```typescript
      // entities/user/index.ts
      export { UserAvatar } from "./ui/UserAvatar";
      export { useUser, type User } from "./model/user";
      ```
      
      ## Slice groups
      
      A **slice group** is a folder that contains related slices on the same
      layer, used purely to make the structure easier to navigate as the number
      of slices grows. A slice group is **not** a slice itself: it has no
      segments (`model/`, `ui/`, `api/`), no public API (`index.ts`), and no
      shared code. Slice isolation rules apply unchanged inside a group: sibling
      slices in the same group cannot import from each other.
      
      Slice groups are optional. Use them only when the layer has grown large
      enough that a flat structure becomes hard to scan and there is an obvious
      grouping criterion.
      
      ### When to use
      
      - Several slices share the same business context and are scattered across
        the layer.
      - The slice names clearly suggest they belong to the same topic.
      - The layer has grown to the point where it is hard to scan at a glance.
      
      ### When NOT to use
      
      - Names alone are enough for quick navigation.
      - There is no natural grouping criterion.
      - The group would hold too few slices to make the layer easier to scan.
      
      ### Example: grouping payment-related entities
      
      ```text
      entities/
        payment/                  ← Slice group (no public API)
          invoice/                ← Slice
            model/
            ui/
            index.ts
          receipt/                ← Slice (model/, ui/, index.ts)
          transaction/            ← Slice (model/, ui/, index.ts)
        user/                     ← Slice (not in any group)
        product/                  ← Slice
      ```
      
      Imports go through the full path:
      
      ```typescript
      import { Invoice } from "@/entities/payment/invoice";
      import { Receipt } from "@/entities/payment/receipt";
      ```
      
      The same pattern applies to the Pages layer. For example, grouping
      `pages/order/{list,detail,create}` when there are multiple pages on the same
      topic such as list, detail, create, and edit. This is one possible example
      and does not represent the default structure for the Pages layer.
      
      ### Features: use with caution
      
      Slice groups can be applied to Features, but features often span multiple
      entities and lack a natural grouping criterion. A group like
      `features/cart/` tends to attract everything cart-related (DTOs, mappers,
      helpers) until it stops being a navigation aid and starts acting as the
      home for the entire cart domain, which weakens the principle that
      features are split by use case. Before grouping features, check that the
      group contains only feature slices and that the grouping earns its keep in
      navigation.
      
      ### Anti-patterns
      
      - **Do not put `index.ts` on the group folder.** That promotes the group
        to a slice and breaks the layer's contract.
      - **Do not put shared `utils.ts`, `constants.ts`, or `types.ts` files
        inside the group.** A slice group has no shared code. Move reusable
        infrastructure to `shared/`. If the layer is `entities` and the shared
        logic is genuinely domain logic, consider whether the boundaries are
        too granular and the slices should be merged into one isolated entity
        (see `references/excessive-entities.md`). The `@x` notation does not
        apply to slice groups. It is a cross-import surface between entity
        slices, not a sharing mechanism for siblings within a group.
      - **Do not relax slice isolation inside the group.** Grouping is
        navigation; it creates no sharing boundary. Siblings that need to depend
        on each other are resolved the same way as ungrouped slices, through
        `references/cross-import-patterns.md`, not with a `_common/` file.
      
      ## Path aliases
      
      Configure path aliases so imports follow the `@/layer/slice` pattern. Add
      an entry only for a layer the project actually has; an alias for a layer
      that does not exist invites someone to fill it.
      
      ```json
      // tsconfig.json
      {
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/app/*": ["src/app/*"],
            "@/pages/*": ["src/pages/*"],
            "@/shared/*": ["src/shared/*"]
          }
        }
      }
      ```
      
      That is the three-layer project from Section 5-3. Add `@/features/*`,
      `@/entities/*`, or `@/widgets/*` when those layers appear, not before.
      
      For framework-specific alias configuration (Vite, Next.js, Nuxt, Astro),
      see `references/framework-integration.md`.
      
    • migration-guide.md 13.3 KB
      # Migration Guide
      
      How to migrate to FSD v2.1 from either FSD v2.0 or a custom (non-FSD)
      architecture. This guide reflects the official `from-custom` step order:
      **pages first**, then everything else.
      
      The steps reveal where code already belongs; they are not a folder rename
      script. Where a step names a destination, the placement rules in
      `SKILL.md` still decide whether the code goes there.
      
      ## Part 1: FSD v2.0 → v2.1 (non-breaking)
      
      The v2.1 update emphasizes **"pages first"**: most logic stays in pages,
      reusable foundation in Shared. When a stable responsibility is genuinely
      shared by several consumers and needs one authoritative home, move that
      responsibility to the layer below. The migration is non-breaking and simplifies
      the codebase by relocating single-use code back to where it is consumed.
      
      Another addition in v2.1 is the standardization of cross-imports between
      entities with the `@x` notation. See `references/cross-import-patterns.md`.
      
      ### Step 1. Audit existing slices
      
      Use Steiger to detect slices that are used in only one place:
      
      ```bash
      npm install -D @feature-sliced/steiger
      npx steiger src
      ```
      
      Look for these rules:
      
      - **`insignificant-slice`**: a slice with no references, or with one,
        which it suggests merging into the layer above. Pages may have a single
        reference without being flagged, and so may slices used only from `app`.
      - **`excessive-slicing`**: too many slices in a single layer.
      
      For each flagged slice, decide:
      
      - Several consumers, a stable and focused responsibility, and a reason to
        change of its own → leave it where it is.
      - One consumer → mark it to move back into that consumer.
      - Several consumers, but the copies would rather drift apart → local
        copies may still be the better answer
        (`references/growth-walkthrough.md`, Snapshot 1).
      
      ### Step 2. Move single-use code back to its consumer
      
      Move a single-reference feature or entity back into its sole consumer
      when the boundary no longer earns its own slice. That consumer is usually
      a page; it can be another feature, or an existing widget in a project that
      keeps its widgets layer:
      
      ```text
      // Before (v2.0): feature used by only one page
      features/user-profile-form/
        ui/ProfileForm.tsx
        model/profile-form.ts
        api/update-profile.ts
        index.ts
      pages/profile/
        ui/ProfilePage.tsx       ← Thin wrapper, just composes
      
      // After (v2.1): code lives in the page that owns it
      pages/profile/
        ui/{ProfilePage,ProfileForm}.tsx
        model/profile.ts          ← Merged form logic
        api/update-profile.ts
        index.ts
      ```
      
      For each moved slice:
      
      1. Copy all files into the consuming page.
      2. Update the page's `index.ts` to export what is needed externally.
      3. Update all imports across the codebase to point to the new location.
      4. Delete the now-empty feature/entity directory.
      5. Run tests.
      
      ### Step 3. Keep genuinely reused code in place
      
      A slice that passed the check above stays in features/entities. Do not
      move it. The point of v2.1 is reducing premature extraction, not removing
      reuse.
      
      ### Step 4. Deprecate the processes layer
      
      The `processes` layer is deprecated. Migrate its code:
      
      - **Multi-page workflows** (checkout, onboarding wizard): move
        orchestration logic to the page that initiates the workflow. If the
        workflow is a stable user-action boundary that several pages reuse, it
        may become a feature.
      - **Background processes** (polling, sync): move to `app/` if global, or
        to the relevant page/feature if scoped.
      
      ```text
      // Before
      processes/
        checkout/model/checkout-flow.ts
        sync/model/background-sync.ts
      
      // After
      features/checkout/model/checkout-flow.ts    ← Stable flow, reused
      app/sync/background-sync.ts                  ← Global concern
      ```
      
      ### Post-migration verification
      
      1. Run `npx steiger src` and work through what it reports. For each
         single-reference slice, decide whether the boundary is still
         intentional or whether it survived only because it predates the
         migration.
      2. Verify import directions. No upward or same-layer cross-imports.
      3. Check that no empty layer directories remain.
      4. Update documentation to reflect the new structure.
      
      ## Phasing out a small widgets layer (optional)
      
      The widgets layer is discouraged, but this migration is optional. A project
      with an established widgets layer that works well can keep it as is (see
      `references/layer-structure.md`). This section is for projects with only a
      few widgets that want to align with the recommended structure.
      
      Route each widget individually by its actual responsibility:
      
      1. **Used by one page only** → inline it into that page's slice. This is the
         most common case: the block was extracted prematurely and never reused.
      2. **Contains a user action reused across pages** (a form, a dialog, a
         toolbar with behavior) → move both the action and its UI to `features/`.
      3. **Pure presentational UI with no business context** → move to `shared/ui/`.
      4. **App-wide shell or layout** (header, footer, navigation frame) → move to
         `app/`, and compose it in the route configuration.
      5. **Composes multiple features** → move the composition up into the page or
         the route layout in `app` instead of keeping a middle layer.
      
      Migrate one widget per commit, updating its imports as you go. Delete the
      `widgets/` directory (and its path alias) only when the last widget is gone.
      A half-empty widgets layer is fine in the meantime; import rules keep
      working throughout.
      
      ## Part 2: custom architecture → FSD
      
      This part follows the official `from-custom` migration order. The core
      philosophy is **pages first**: start by dividing the code by pages, then
      work outward.
      
      ### Before you start
      
      The most important question to ask the team is: *do you really need it?*
      Some projects are perfectly fine without FSD. Reasons to consider the
      switch:
      
      1. New team members struggle to reach a productive level.
      2. Modifications to one part of the code **often** break unrelated parts.
      3. Adding new functionality is difficult due to the volume of context to
         hold in mind.
      
      **Avoid switching to FSD against the will of teammates**, even as a lead.
      Convince the team that the benefits outweigh migration and learning costs.
      Explain the migration plan to management; architectural changes are not
      immediately observable to them.
      
      If the decision is made, set up a path alias for `src/` first. This guide
      uses `@` as an alias for `./src`.
      
      ### Step 1. Divide the code by pages
      
      If `pages/` already exists, skip this step. Otherwise, create `pages/` and
      move as much component code as possible from `routes/` (or equivalent) into
      it. Aim for tiny route files that just re-export from page slices.
      
      ```text
      // Route file (thin)
      src/routes/products.[id].js
        export { ProductPage as default } from "@/pages/product"
      
      // Page slice
      src/pages/product/
        ui/ProductPage.jsx
        index.js                ← export { ProductPage } from "./ProductPage.jsx"
      ```
      
      Pages may reference each other for now. Tackle that later. Focus on
      establishing a prominent division by pages.
      
      ### Step 2. Separate everything else from pages
      
      This step is a staging move, not the final ownership rule. Business
      code will land in `shared/` here, and Step 4 pulls it back out. Do not
      read "does not import pages" as a reason for code to live in Shared.
      
      Create `src/shared/` and move everything that does **not** import from
      `pages/` or `routes/` there. Create `src/app/` and move everything that
      **does** import the pages or routes there, including the routes themselves.
      
      The Shared layer has no slices, so segments may import from each other.
      
      ```text
      src/
        app/
          routes/
            products.jsx
            products.[id].jsx
          App.jsx
          index.js
        pages/
          product/
            ui/ProductPage.jsx
            index.js
          catalog/
        shared/
          actions/, api/, components/, containers/, constants/,
          i18n/, modules/, helpers/, utils/, reducers/, selectors/, styles/
      ```
      
      ### Step 3. Tackle cross-imports between pages
      
      Find all cases where one page imports from another. Resolve each in one of
      two ways:
      
      1. **Copy-paste** the imported code into the depending page to remove the
         dependency.
      2. **Move to a Shared segment**:
         - UI kit code → `shared/ui/`
         - configuration constants → `shared/config/`
         - backend interaction → `shared/api/`
      
      Copy-pasting is **not architecturally wrong**. Sometimes it is more correct
      to duplicate than to abstract into a new reusable module, because the
      shared parts of pages can drift apart over time. Still, the DRY principle
      holds for business logic: avoid copy-pasting code that must stay in sync
      across multiple places.
      
      ### Step 4. Unpack the Shared layer
      
      The Shared layer can become bloated after Step 2. Find every object used in
      only one page and move it to that page's slice. **This applies to actions,
      reducers, and selectors too.** There is no benefit in grouping all actions
      together, but there is benefit in colocating relevant actions close to
      their usage.
      
      ```text
      src/
        pages/
          product/
            actions/, reducers/, selectors/, ui/   ← moved from shared
            index.js
          catalog/
        shared/                                    ← shared infrastructure only
          actions/, api/, components/, ...
      ```
      
      ### Step 5. Organize code by technical purpose (segments)
      
      In FSD, division by technical purpose is done with **segments**. The common
      ones are:
      
      - **`ui`**: everything related to UI display (components, date formatters,
        styles).
      - **`api`**: backend interactions (request functions, data types, mappers).
      - **`model`**: the data model (schemas, interfaces, stores, business
        logic).
      - **`lib`**: library code that other modules in the slice need.
      - **`config`**: configuration files and feature flags.
      
      Custom segments are allowed when needed. **Do not create segments that
      group code by what it is**, like `components`, `actions`, `types`, or
      `utils`. Group code by what it is **for**, not by what it is. This is the
      desegmentation principle.
      
      Reorganize each page to separate code by segments:
      
      - The existing page UI files become the `ui` segment.
      - Actions, reducers, selectors, and the rest of the state wiring become
        the `model` segment.
      - Request functions become the `api` segment, and so does a thunk that
        carries its own request. Once the request is separated out, the Redux
        wiring left behind belongs with the reducer in `model`
        (`references/state-management.md`).
      
      Reorganize the Shared layer too:
      
      - `components/`, `containers/` → most of it becomes `shared/ui/`.
      - `helpers/`, `utils/` → group by function (dates, type conversions, etc.)
        and move groups to `shared/lib/`.
      - `constants/` → group by function and move to `shared/config/`.
      
      ## Optional steps
      
      ### Step 6. Form entities/features from Redux slices used on several pages
      
      A slice's subject does not settle its layer. Reused Redux slices typically
      describe business concepts (products, users) or user actions (comments,
      likes), and the official step says such a slice **can** move:
      
      - A stable, reusable business-domain responsibility may become an entity,
        one entity per folder.
      - A complete, reusable user interaction with a stable boundary may become
        a feature.
      - Anything single-use or still unclear stays with the page that uses it.
      
      Entities and features are meant to be independent. If your business domain
      contains inherent connections between entities (a song belongs to an
      artist), see the
      [business entities cross-references guide](https://fsd.how/docs/guides/examples/types#business-entities-and-their-cross-references).
      
      API functions related to these slices can stay in `shared/api`.
      
      ### Step 7. Refactor your modules
      
      Do not map `modules/` onto `features/`. A legacy `modules/` folder usually
      holds several kinds of code at once, so read each module by its
      responsibility and its consumers: a screen-specific block stays in
      `pages/`, a reusable action plus its UI goes to `features/`, a stable
      reused domain rule goes to `entities/`, infrastructure goes to `shared/`,
      and an app-wide shell (header, footer) goes to `app/`. Some modules
      describe large UI chunks; a project that keeps its widgets layer can
      migrate those there.
      
      ### Step 8. Form a clean UI foundation in `shared/ui`
      
      `shared/ui` should contain UI elements with no encoded business logic.
      Refactor components from `components/` and `containers/` to extract their
      business logic to higher layers. Where no stable shared boundary has
      appeared and the copies can evolve apart, keeping the behavior local to
      each consumer is an acceptable choice.
      
      ## Common pitfalls during migration
      
      1. **Extracting too early.** Wait for real reuse, not anticipated reuse.
         The v2.1 philosophy is "pages first, extract later".
      2. **Creating empty layers.** Do not create `features/` or `entities/`
         directories until there is content for them, and do not actively adopt
         the discouraged `widgets/` layer.
      3. **Refactoring while migrating.** Separate relocation from refactoring.
         Move files first, improve them in separate commits.
      4. **Ignoring import direction.** Enforce import rules from day one with
         ESLint or Steiger.
      5. **Big-bang migration.** Migrate page by page, verifying each step. A
         hybrid structure (partly FSD, partly legacy) is acceptable during
         transition.
      6. **Grouping by technical role.** `components/`, `actions/`, `utils/` as
         segment names defeat the purpose of FSD. Group by what code is for.
      
      ## Migrating from FSD v1 to v2
      
      This guide does not cover v1 → v2. See the official
      [v1 to v2 migration guide](https://fsd.how/docs/guides/migration/from-v1).
      The v1 → v2 transition introduced the entities and processes layers
      (processes was later deprecated in v2.1).
      
    • state-management.md 13.7 KB
      # State Management
      
      Concrete code patterns for Redux and TanStack Query (React Query) within
      FSD structure. Authentication, type, and API request patterns are in
      `references/auth-and-api.md`. Code samples are React; the placement
      rules are framework-agnostic.
      
      ## State management: Redux
      
      FSD has no Redux guide of its own. The placement rule below is Step 6 of
      the official `from-custom` migration guide, and the only official Redux
      code is in the business entities guide. The rest of this section is
      Rules 4-1, 4-2, and 4-4 applied to Redux Toolkit.
      
      ### Where a Redux slice belongs
      
      **Redux does not decide the layer; ownership does.** Work out which slice
      owns the state with Section 2 of `SKILL.md`, then put the Redux code in
      that slice's `model/` segment. A `todo` noun does not make an entity, and
      a `toggle-todo` verb does not make a feature.
      
      Once a boundary has been earned, Step 6 gives two destinations: a stable
      reusable business-domain responsibility may move to Entities, a stable
      reusable user-interaction boundary may move to Features. Both assume the
      slice is already reused across pages. A slice used by a single page stays
      in that page's `model/` segment.
      
      ### Business-entity slice in entities
      
      The request is plain resource access, so it lives in `shared/api` with
      its DTO (Request placement rule in `references/auth-and-api.md`). The
      entity imports it; `model/` holds only the Redux wiring. The entity uses
      the transport type as it is here; convert to a separate domain type only
      when a business rule needs a shape the backend does not send.
      
      ```typescript
      // shared/api/todo.ts
      import { apiClient } from "./client";
      
      export interface TodoDto { id: string; title: string; completed: boolean }
      
      export const getTodos = (): Promise<TodoDto[]> =>
        apiClient.get("/todos").then((r) => r.data);
      ```
      
      ```typescript
      // entities/todo/model/todo.ts
      import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
      import { getTodos, type TodoDto } from "@/shared/api";
      
      interface TodoState { items: TodoDto[]; loading: boolean }
      
      export const fetchTodos = createAsyncThunk("todos/fetch", getTodos);
      
      const todoSlice = createSlice({
        name: "todos",
        initialState: { items: [], loading: false } as TodoState,
        reducers: {
          setCompleted: (state, { payload }: { payload: { id: string; completed: boolean } }) => {
            const todo = state.items.find((t) => t.id === payload.id);
            if (todo) todo.completed = payload.completed;
          },
        },
        extraReducers: (builder) => {
          builder
            .addCase(fetchTodos.pending, (state) => { state.loading = true; })
            .addCase(fetchTodos.fulfilled, (state, action) => {
              state.items = action.payload;
              state.loading = false;
            });
        },
      });
      
      export const { setCompleted } = todoSlice.actions;
      export const selectTodos = (state: { todos: TodoState }) => state.todos.items;
      export const todoReducer = todoSlice.reducer;
      ```
      
      A thunk that still carries its own request belongs in the `api` segment
      (`references/migration-guide.md`, Part 2 Step 5). Once the request lives
      in `shared/api`, the thunk is only Redux wiring and stays in `model/`
      next to the reducer that handles it, which is the shape of the official
      business entities guide.
      
      The selector takes only the state it reads, not `RootState`. `RootState` is
      declared in `app/`, so an entity importing it would depend on a higher layer
      (Rule 4-1). The trade-off is that the selector no longer type-checks against
      the whole store. Type it at the `app/` layer when you need that guarantee.
      
      The slice's public API re-exports what consumers need:
      
      ```typescript
      // entities/todo/index.ts
      export { todoReducer, selectTodos, setCompleted, fetchTodos } from "./model/todo";
      ```
      
      **Key:** Do not split Redux code by Redux mechanism into `reducers.ts`,
      `selectors.ts`, and `thunks.ts`. That is the technical-role naming Rule
      4-4 rules out. Keep a reducer, its selectors, and its thunks together in
      one domain-named file, and when the model outgrows it, split by domain
      concern (`todo.ts`, `todo-filter.ts`) rather than by mechanism.
      
      ### User-action slice in features
      
      Assuming the action is already reused across pages and has earned a
      feature boundary, it consumes the entity through the entity's public API
      and exposes its own hook through the feature's:
      
      ```typescript
      // features/toggle-todo/model/use-toggle-todo.ts
      import { useDispatch } from "react-redux";
      import { setCompleted } from "@/entities/todo";
      
      export const useToggleTodo = () => {
        const dispatch = useDispatch();
        return (id: string, current: boolean) =>
          dispatch(setCompleted({ id, completed: !current }));
      };
      ```
      
      ### Registering slices in app
      
      ```typescript
      // app/providers/store.ts
      import { configureStore } from "@reduxjs/toolkit";
      import { todoReducer } from "@/entities/todo";
      import { userReducer } from "@/entities/user";
      
      export const store = configureStore({
        reducer: {
          todos: todoReducer,
          user: userReducer,
        },
      });
      
      export type RootState = ReturnType<typeof store.getState>;
      ```
      
      The store imports each slice's reducer through its public API
      (`index.ts`), never reaching into `model/` directly (Rule 4-2). Do not
      let individual slices create their own stores.
      
      ## State management: TanStack Query (React Query)
      
      This section follows the official React Query guide. Guidance applies to
      `@tanstack/react-query` v5 (formerly React Query). The package name is
      `@tanstack/react-query`.
      
      ### Where to store query keys
      
      Three placements are valid. Choose by project size and by which slice owns
      the request, not by which folders happen to exist.
      
      **Option 1: Flat in `shared/api/queries/`** (small projects, few endpoints):
      
      ```text
      shared/api/
        queries/
          example.ts
          another-example.ts
        index.ts          ← export { exampleQueries } from './queries/example';
      ```
      
      **Option 2: Per controller in `shared/api/<controller>/`** (many endpoints):
      
      ```text
      shared/api/example/
        index.ts          ← export { exampleQueries } from './example.query';
        example.query.ts  ← Query factory: keys + functions
        get-example.ts
        create-example.ts
        update-example.ts
        delete-example.ts
      ```
      
      **Option 3: Per entity in `entities/<entity>/api/`** when the request
      carries domain rules, the entity boundary already exists, and each request
      corresponds to a single entity. Generic CRUD and plain resource access
      stay in `shared/api` however many slices call them (`auth-and-api.md`,
      request placement rule, Question 2). When entities reference each other,
      see `references/cross-import-patterns.md` for `@x` as a last resort.
      
      > **Where this comes from.** Two official guides differ here. The React
      > Query guide calls the per-entity split the cleanest option once a
      > project has entities, and shows CRUD files inside `entities/*/api/`.
      > The excessive-entities guide excludes CRUD from entities and keeps it
      > in `shared/api/endpoints/`. This skill follows the second, and
      > Question 2 is how it decides: an existing entities folder is not by
      > itself a reason to move a request into it.
      
      ### Where to store mutations
      
      Mixing mutations with queries is not recommended. Two patterns are
      accepted:
      
      1. **A mutation hook in the `api/` segment near the place of use.** Use
         `setQueryData` for cache updates:
      
         ```typescript
         // src/pages/example/api/use-update-example.ts
         export const useUpdateExample = () => {
           const queryClient = useQueryClient();
           return useMutation({
             mutationFn: ({ id, newTitle }) => apiClient.patch(`/posts/${id}`, { title: newTitle }).then((r) => r.data),
             onSuccess: (newPost, { id }) => queryClient.setQueryData(POST_QUERIES.detail({ id }).queryKey, newPost),
           });
         };
         ```
      
      2. **A `mutationFn` defined in `shared/` or `entities/`** and called from
         `useMutation` in the component.
      
      ### Query factory pattern
      
      A query factory is an object whose values return query keys. Each key is
      wrapped in `queryOptions`, a built-in helper from `@tanstack/react-query` v5
      that lets you share `queryKey` and `queryFn` between `useQuery`,
      `useSuspenseQuery`, `prefetchQuery`, `setQueryData`, and similar APIs
      without rewriting them:
      
      ```typescript
      // src/shared/api/post/post.queries.ts
      import { queryOptions } from "@tanstack/react-query";
      import { getPosts, getDetailPost, type DetailPostQuery } from "./get-posts";
      
      export const POST_QUERIES = {
        all: () => ["posts"],
        lists: () => [...POST_QUERIES.all(), "list"],
        list: (page: number, limit: number) => queryOptions({
          queryKey: [...POST_QUERIES.lists(), page, limit],
          queryFn: () => getPosts(page, limit),
          placeholderData: (prev) => prev,
        }),
        detail: (query?: DetailPostQuery) => queryOptions({
          queryKey: [...POST_QUERIES.all(), "detail", query?.id],
          queryFn: () => getDetailPost({ id: query?.id }),
        }),
      };
      ```
      
      Consume with `useQuery(POST_QUERIES.detail({ id }))`. For pagination,
      `placeholderData: prev => prev` prevents UI flicker when navigating pages.
      
      **Benefits of a query factory:** the keys and query definitions for a
      domain are reachable through one object, so consumers share them instead
      of rebuilding keys. Refetching and cache updates become a one-line call
      (`queryClient.invalidateQueries({ queryKey: POST_QUERIES.all() })`). The
      request functions themselves stay in their own files; the factory wires
      them to keys.
      
      ### Infinite scroll
      
      Use `infiniteQueryOptions` with `initialPageParam` and `getNextPageParam`.
      Add the infinite key to the same factory shown above:
      
      ```typescript
      import { infiniteQueryOptions } from "@tanstack/react-query";
      
      // Inside POST_QUERIES:
      infinite: (limit: number) => infiniteQueryOptions({
        queryKey: [...POST_QUERIES.lists(), "infinite", limit],
        queryFn: ({ pageParam }) => getPosts(pageParam, limit),
        initialPageParam: 0,
        getNextPageParam: (lastPage) => lastPage.skip + lastPage.limit < lastPage.total ? lastPage.skip / lastPage.limit + 1 : undefined,
      }),
      ```
      
      Consume with `useInfiniteQuery` and flatten via `data?.pages.flatMap(...)`.
      
      ### Suspense mode
      
      `queryOptions` and `useSuspenseQuery` are compatible, and the factory does
      not change. Components use `useSuspenseQuery` instead of `useQuery` and skip
      `isLoading` entirely. Wrap interested subtrees with an `ErrorBoundary` +
      `Suspense` provider in the App layer:
      
      ```tsx
      // src/app/providers/suspense-provider.tsx
      import { Suspense } from "react";
      import { ErrorBoundary } from "react-error-boundary";
      
      export const SuspenseProvider = ({ children }) => (
        <ErrorBoundary fallback={<div>Something went wrong</div>}>
          <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
        </ErrorBoundary>
      );
      ```
      
      ### Reading mutation state with useMutationState
      
      `useMutationState` lets any component read the state of a mutation without
      passing props, useful for global save indicators. Store mutation keys next
      to the query factory:
      
      ```typescript
      // src/shared/api/post/post.queries.ts
      export const POST_MUTATIONS = {
        updateTitle: () => ["post", "update-title"],
        create: () => ["post", "create"],
      };
      ```
      
      Tag the mutation with `mutationKey`, then read its state from any component:
      
      ```tsx
      // src/features/update-post/api/use-update-post-title.ts
      export const useUpdatePostTitle = () =>
        useMutation({
          mutationKey: POST_MUTATIONS.updateTitle(),
          mutationFn: ({ id, newTitle }) => apiClient.patch(`/posts/${id}`, { title: newTitle }),
        });
      
      // src/app/ui/save-indicator.tsx   (app-wide; page-local if one page)
      import { useMutationState } from "@tanstack/react-query";
      import { POST_MUTATIONS } from "@/shared/api/post";
      
      export const SaveIndicator = () => {
        const isPending = useMutationState({
          filters: { mutationKey: POST_MUTATIONS.updateTitle(), status: "pending" },
          select: (m) => m.state.status,
        }).length > 0;
        return isPending && <span>Saving...</span>;
      };
      ```
      
      ### QueryProvider in the app layer
      
      ```tsx
      // src/app/providers/query-provider.tsx
      import { QueryClient, QueryClientProvider, MutationCache, QueryCache } from "@tanstack/react-query";
      import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
      import { toast } from "sonner";
      
      const queryClient = new QueryClient({
        queryCache: new QueryCache({ onError: (e) => toast.error(e.message) }),
        mutationCache: new MutationCache({ onError: (e) => toast.error(e.message) }),
        defaultOptions: { queries: { staleTime: 5 * 60 * 1000, gcTime: 5 * 60 * 1000 } },
      });
      
      export const QueryProvider = ({ children }) => (
        <QueryClientProvider client={queryClient}>
          {children}
          <ReactQueryDevtools />
        </QueryClientProvider>
      );
      ```
      
      `QueryCache.onError` and `MutationCache.onError` give one place to wire up
      global toast notifications instead of repeating error handling on every hook.
      
      ### Code generation
      
      The official guide notes that OpenAPI/Swagger generators are less flexible
      than the hand-written factory above. Whichever you pick, generated clients
      are transport code: keep them in `@/shared/api/`, or in a separate
      generated package. Do not move generated endpoints into entities because
      they happen to name business resources.
      
      ### Custom API client
      
      Standardize base URL, headers, and JSON handling in a single class in
      `shared/api/`:
      
      ```typescript
      // src/shared/api/api-client.ts
      export class ApiClient {
        #baseUrl: string;
        constructor(url: string) { this.#baseUrl = url; }
      
        async #handle<T>(response: Response): Promise<T> {
          if (!response.ok) throw new Error(`HTTP ${response.status}`);
          return response.json();
        }
      
        get = <T>(path: string) => fetch(`${this.#baseUrl}${path}`).then((r) => this.#handle<T>(r));
        // post, put, delete follow the same pattern with method/headers/body.
      }
      
      export const apiClient = new ApiClient(API_URL);
      ```
      
      **Key principle:** Place query and mutation hooks with the slice that owns
      the responsibility. Page-specific queries stay in the page. Plain resource
      access and generic CRUD stay in `shared/api/`. Reach for
      `entities/<name>/api/` only when an established entity boundary owns that
      request; an Entities layer existing is not a placement rule.
      
  • SKILL.md 21.8 KB
    ---
    name: feature-sliced-design
    description: >
      Official Feature-Sliced Design (FSD) v2.1 skill for applying the methodology
      to frontend projects. Use when the task involves organizing project structure
      with FSD layers, deciding where code belongs, placing static assets (images,
      icons, fonts, PDFs), grouping closely related slices, defining public APIs
      and import boundaries, resolving cross-imports or evaluating the @x pattern,
      deciding whether to create or remove an entity, evaluating whether the
      entities layer is needed at all, deciding where page layouts belong or
      whether to use the widgets layer (discouraged), deciding whether logic
      should remain local or be extracted, migrating from FSD v2.0 or a non-FSD
      codebase, integrating FSD with frameworks (Next.js App Router and Pages
      Router, React Router, Nuxt, Vite, Astro), or implementing common patterns
      such as authentication, API handling, Redux, and TanStack Query
      (React Query) within FSD.
    ---
    
    # Feature-Sliced Design (FSD) v2.1
    
    > **Source**: [fsd.how](https://fsd.how) | Strictness can be adjusted based on
    > project scale and team context.
    
    **How to use this skill.** For placement decisions, start with the decision
    tree in Section 2 and use the placement table in Section 3 as a quick
    reference. To check a structure for violations, use the rules in Section 4.
    To resolve same-layer cross-imports, use Section 7. For task-specific
    guidance, load only the relevant reference files from Section 10; do not
    preload the rest.
    
    ## 1. Core philosophy & layer overview
    
    FSD v2.1 core principle: **"Start simple, extract when needed."**
    
    ### The extraction rule
    
    Place code in `pages/` first. Duplication across pages is acceptable and
    does not by itself require extraction to a lower layer. Extract only when
    all three conditions hold:
    
    1. The same code is used in multiple places right now, not hypothetically.
    2. It has a reason to change that is independent of any one consumer.
    3. The boundary has a focused responsibility.
    
    ### The six layers
    
    **Not all layers are required.** Most projects can start with only `shared/`,
    `pages/`, and `app/`. Add `features/` and `entities/` only when they provide
    clear value. Do not create empty layer folders "just in case." The `widgets/`
    layer is **discouraged** (see the callout below).
    
    FSD uses 6 standardized layers, listed here from highest to lowest:
    
    ```text
    app/       → App initialization, providers, routing
    pages/     → Route-level composition, owns its own logic
    widgets/   → Reusable UI blocks (discouraged, see the callout below)
    features/  → Reusable user interactions (see the extraction rule above)
    entities/  → Reusable business domain models (see the extraction rule above)
    shared/    → Infrastructure with no business logic (UI kit, utils, API client)
    ```
    
    **The official layer reference discourages using the Widgets layer**, and
    this skill follows it. Widgets may seem useful for representing independent
    UI blocks. However, in real frontend code, UI blocks often include logic
    required for user flows, such as data fetching, state management, and event
    handling. In this case, the responsibilities of Features, which handle user
    flows, and Widgets, which handle UI blocks, can overlap, making the boundary
    between the two layers unclear.
    
    Not creating a widget does not mean moving the block elsewhere untouched.
    A screen-specific composition stays in `pages`; a reused action and the UI
    to perform it go to `features`; context-free UI goes to `shared`; an
    app-wide layout goes to `app`.
    
    Discouraged is not deprecated: an existing widgets layer stays valid. See
    `references/layer-structure.md` for that case and for layout placement.
    
    ### The import rule
    
    A module may only import from layers strictly below it. Cross-imports
    between slices on the same layer are forbidden, with one narrow exception
    in Section 7.
    
    ```typescript
    // Allowed
    import { Button } from "@/shared/ui/Button"; // features → shared
    import { useUser } from "@/entities/user"; // pages → entities
    
    // Violation
    import { loginUser } from "@/features/auth"; // entities → features
    import { likePost } from "@/features/like-post"; // features → features
    ```
    
    **Note**: The `processes/` layer is **deprecated** in v2.1. For migration
    details, read `references/migration-guide.md`.
    
    ## 2. Decision framework
    
    When writing new code, follow this tree:
    
    **Step 1: Where is this code used?**
    
    - Used in only one page → keep it in that `pages/` slice.
    - Used in 2+ pages but duplication is manageable → keeping separate copies
      in each page is also valid.
    - An entity or feature with a single consumer → keep it at the consumer
      (Steiger flags this as `insignificant-slice`).
    
    **Step 2: Is it reusable infrastructure with no business logic?**
    
    The official layer reference draws the line for Shared like this: no
    business logic, but business-themed is fine (a company logo, a page
    layout), and so is UI logic (autocomplete, a search bar). Exchanging data
    with the backend and CRUD boilerplate are not business logic either.
    Business logic is a rule the product enforces on its own data, such as
    applying a discount to an order. If the code fits none of the exclusions
    and still does not clearly enforce a product rule, the term does not
    decide; go back to Step 1 and place it by where it is used.
    
    - UI components → `shared/ui/`
    - Utility functions → `shared/lib/`
    - API client, route constants → `shared/api/` or `shared/config/`
    - Auth tokens, session management → `shared/auth/`
    - CRUD once several slices call it → `shared/api/` (a single caller keeps
      it, see Step 1)
    
    **Step 3: Is it a complete user action that several consumers share, with
    a focused responsibility and a reason to change of its own?**
    
    - Yes → `features/`
    - Uncertain, single use, or speculative reuse → keep in the page.
    
    **Step 4: Is it a business domain model that several consumers share, with
    a focused responsibility and a reason to change of its own?**
    
    - Yes → `entities/`
    - Uncertain, single use, or speculative reuse → keep in the page.
    
    **Step 5: Is it app-wide configuration?**
    
    - Global providers, router, theme → `app/`
    
    **Golden Rule: When in doubt, keep it in `pages/`. Extract only when the
    extraction rule holds.**
    
    ## 3. Quick placement table
    
    | Scenario                   | Single use                                  | Confirmed multi-use                   |
    | -------------------------- | ------------------------------------------- | ------------------------------------- |
    | User profile form          | `pages/profile/ui/ProfileForm.tsx`          | `features/profile-form/`              |
    | Product card               | `pages/products/ui/ProductCard.tsx`         | `entities/product/ui/` if the entity owns it |
    | API request (read or CRUD) | `pages/product-detail/api/fetch-product.ts` | `shared/api/` (no domain rules)       |
    | Auth token/session         | `shared/auth/`                              | `shared/auth/`                        |
    | Auth login form            | `pages/login/ui/LoginForm.tsx`              | `features/auth/`                      |
    | Generic Card layout        |                                             | `shared/ui/Card/`                     |
    | Modal manager              |                                             | `shared/ui/modal-manager/`            |
    | Modal content              | `pages/[page]/ui/SomeModal.tsx`             |                                       |
    | Date formatting util       |                                             | `shared/lib/format-date.ts`           |
    
    "Confirmed multi-use" means the extraction rule holds, not that a second
    consumer appeared: two similar copies that keep drifting apart stay in
    their pages (`references/growth-walkthrough.md`, Snapshot 1). Entity UI
    carries the Section 6 caution even when the rule does hold.
    
    ## 4. Architectural rules (MUST)
    
    These rules are the foundation of FSD. Violations weaken the architecture.
    If you must break a rule, ensure it is an intentional design decision and
    document the reason in code (a comment or ADR).
    
    ### 4-1. Import only from lower layers
    
    `app → pages → widgets → features → entities → shared`.
    Upward imports are forbidden. So are cross-imports between slices on the
    same layer, except through the other slice's public API as a last resort
    (Section 7, Strategy D).
    
    ### 4-2. Public API: every slice exports through index.ts
    
    External consumers may only import from a slice's `index.ts`. Direct imports
    of internal files are forbidden.
    
    ```typescript
    // Correct
    import { LoginForm } from "@/features/auth";
    
    // Violation: bypasses public API
    import { LoginForm } from "@/features/auth/ui/LoginForm";
    ```
    
    **Shared layer:** Shared has no slices. Define a separate public API per
    segment (`shared/ui/index.ts`, `shared/api/index.ts`, etc.) rather than
    one top-level `shared/index.ts`. This keeps imports from Shared
    organized by intent.
    
    Where one index over a segment's unrelated modules hurts bundling, give
    each component, library, or controller folder its own index instead
    (`shared/ui/Button/index.ts` as `@/shared/ui/Button`, `shared/api/post/`
    as `@/shared/api/post`). That folder is then the boundary; reaching past
    it (`@/shared/ui/Button/Button.tsx`) is still a violation. See
    `references/layer-structure.md` for the shape.
    
    **Environment-specific entry points:** a slice normally exposes one
    `index.ts`, and ad-hoc variations are not recommended. If a single index
    cannot preserve a runtime boundary, add an entry point such as
    `index.server.ts`. See `references/framework-integration.md`.
    
    ### 4-3. No cross-imports between slices on the same layer
    
    If two slices on the same layer need to share logic, follow the resolution
    order in Section 7. Never reach into another slice's internals.
    
    ### 4-4. Domain-based file naming (no desegmentation)
    
    Name files after what they are for, the domain or concern they serve, not
    after their technical role. Technical-role names like `types.ts`,
    `utils.ts`, `helpers.ts` mix unrelated concerns in a single file and
    reduce cohesion.
    
    ```text
    // BAD: technical-role naming
    model/types.ts          ← Which types? User? Order? Mixed?
    model/utils.ts
    
    // GOOD: domain-based naming
    model/user.ts           ← User types + related logic
    model/order.ts          ← Order types + related logic
    api/fetch-profile.ts    ← Clear purpose
    ```
    
    ### 4-5. No business logic in shared/
    
    Shared contains only infrastructure: UI kit, utilities, API client setup,
    route constants, assets. Business calculations, domain rules, and workflows
    belong in `entities/` or higher layers. Section 2, Step 2 says what counts.
    
    ```typescript
    // BAD: business logic in shared
    // shared/lib/userHelpers.ts
    export const calculateUserReputation = (user) => { ... };
    
    // GOOD: move it to whoever owns the rule
    // pages/profile/model/reputation.ts       ← while the profile page owns it
    // entities/user/model/reputation.ts       ← once a user boundary is earned
    export const calculateUserReputation = (user) => { ... };
    ```
    
    ## 5. Recommendations (SHOULD)
    
    ### 5-1. Pages first: place code where it is used
    
    Place code in `pages/` first. Extract to lower layers only when truly needed.
    Extraction is a design decision that affects the whole project, so the
    threshold should be high.
    
    **What stays in pages:**
    
    - Large UI blocks used only in one page
    - Page-specific forms, validation, data fetching, state management
    - Page-specific business logic and API integrations
    - Code that looks reusable but is simpler to keep local
    
    **Evolution pattern:** Start with everything in `pages/profile/`. Extract
    the shared model to `entities/user/` when a second page consumes it *and*
    the extraction rule holds. A response type that several pages read is not
    one of those cases: it stays in `shared/api`. Keep page-specific API calls
    and UI in the page.
    
    ### 5-2. Be conservative with entities
    
    The entities layer is highly accessible (almost every other layer can import
    from it), so changes propagate widely.
    
    1. **Start without entities.** `shared/` + `pages/` + `app/` is valid FSD.
       Thin-client apps rarely need entities.
    2. **Do not split slices prematurely.** Keep code in pages. Extract to
       entities only when the extraction rule holds.
    3. **Business logic does not automatically require an entity.** Keeping types
       in `shared/api` and logic in the current slice's `model/` segment may
       be sufficient.
    4. **CRUD is infrastructure, not entities.** Place it by the request
       placement rule: with its consumer while there is one, in `shared/api/`
       once several slices call it.
    5. **Place auth data in `shared/auth/` or `shared/api/`.** Tokens and login
       DTOs are auth-context-dependent and rarely reused outside authentication.
    
    For detailed guidance on keeping the entities layer clean (when to skip
    it entirely, how to isolate business contexts, why CRUD belongs in
    `shared/api`), see `references/excessive-entities.md`.
    
    ### 5-3. Start with minimal layers
    
    ```text
    // Valid minimal FSD project
    src/
      app/         ← Providers, routing
      pages/       ← All page-level code
      shared/      ← UI kit, utils, API client
    
    // Add layers only when an actual use case requires them:
    // + features/  ← User-action boundaries that need one shared home
    // + entities/  ← Domain boundaries that need one shared home
    // (widgets/ is discouraged; see Section 1 for where that code goes instead)
    ```
    
    ### 5-4. Validate with the Steiger linter
    
    [Steiger](https://github.com/feature-sliced/steiger) is the official FSD
    linter. Key rules:
    
    - **`insignificant-slice`**: Flags a slice with no references, or with one,
      and suggests merging it into the layer above. Pages may hold a single
      reference, and so may slices used only from `app/`.
    - **`excessive-slicing`**: Suggests merging or grouping when a layer has too
      many slices.
    
    ```bash
    npm install -D @feature-sliced/steiger
    npx steiger src
    ```
    
    ## 6. Anti-patterns (AVOID)
    
    - **Do not create entities prematurely.** Data structures used in only one
      place belong in that place.
    - **Do not put CRUD in entities.** Plain CRUD is `shared/api/`. An
      operation that carries business rules is placed by who owns the rule,
      which may be an entity, a feature, or the page running the workflow.
    - **Do not create a `user` entity just for auth data.** Tokens and login DTOs
      belong in `shared/auth/` or `shared/api/`.
    - **Do not abuse `@x`.** It is a necessary compromise, not a recommended
      pattern. The notation is for the entities layer only, and only when
      boundary merge is genuinely impossible. Features and widgets handle
      cross-imports through strategies A through D (see Section 7).
    - **Do not extract single-use code.** A feature or entity used by only one
      page should stay in that page.
    - **Do not use technical-role file names.** Use domain-based names
      (see Rule 4-4).
    - **Be cautious adding UI to entities.** Entity UI tempts cross-imports from
      other entities. If you add UI segments to entities, only import them from
      higher layers (features, pages, app), never from other entities.
    - **Do not create god slices.** Slices with excessively broad responsibilities
      should be split into focused slices (e.g., split `user-management/` into
      `auth/`, `profile-edit/`, `password-reset/`).
    - **Do not create a top-level `assets/` segment.** Place static assets next
      to the code that uses them; global stylesheets and fonts go to `app/`.
      See `references/asset-handling.md`.
    
    ## 7. Cross-import resolution
    
    Cross-imports are a code smell, not an absolute prohibition. The right
    strategy depends on the layer and the situation.
    
    ### Entities layer: prefer boundary merge, @x is last resort
    
    Cross-imports in `entities` are usually caused by splitting entities too
    granularly. Before reaching for `@x`, consider whether the boundaries
    should be merged.
    
    `@x` is a **necessary compromise, not a recommended approach**. Use it only
    when boundaries genuinely cannot be merged, and document why. Overuse locks
    entity boundaries together and increases refactoring cost.
    
    ### Features and widgets: four strategies (A, B, C, D)
    
    In `features` and `widgets`, choose based on context:
    
    - **Strategy A: slice merge.** Two slices always change together → merge.
    - **Strategy B: push to entities.** A shared domain responsibility → move
      it to the entity that owns it, keep UI in the feature.
    - **Strategy C: compose from upper layer (IoC).** The parent (pages or app)
      imports both slices and connects them via render props, slots, or DI.
    - **Strategy D: Public API access.** When reuse is genuinely unavoidable,
      allow it only through the slice's `index.ts`. Never reach into `model/`,
      `store/`, or internal files.
    
    The `@x` notation is for the entities layer only. Features and widgets use
    strategies A through D above.
    
    ### Strictness depends on project context
    
    Cross-imports are dependencies that are generally best avoided, but
    sometimes used intentionally. Strictness varies by project context:
    
    - **Early-stage products** with heavy experimentation: allowing some
      cross-imports may be a pragmatic speed trade-off.
    - **Long-lived or regulated systems** (fintech, large-scale services):
      stricter boundaries pay off in maintainability and stability.
    
    If a cross-import is introduced, treat it as a deliberate choice and
    document the reasoning in code (a comment explaining why other strategies
    do not apply).
    
    For detailed code examples of each strategy, read
    `references/cross-import-patterns.md`.
    
    ## 8. Segments & structure rules
    
    ### Standard segments
    
    Segments group code within a slice by technical purpose:
    
    - **`ui/`**: UI components, styles, display-related code
    - **`model/`**: Data models, state stores, business logic, validation
    - **`api/`**: Backend integration, request functions, API-specific types
    - **`lib/`**: Internal utility functions for this slice
    - **`config/`**: Configuration, feature flags
    
    ### Layer structure rules
    
    - **App and Shared**: No slices, organized directly by segments. Segments
      within these layers may import from each other.
    - **Pages, Widgets, Features, Entities**: Slices first, then segments inside
      each slice.
    - **Slice groups (optional)**: A group folder may contain related slices on
      the same layer for navigation purposes only. The group has no segments and
      no public API. See `references/layer-structure.md` for details.
    
    ### File naming within segments
    
    Always use domain-based names that describe what the code is about:
    
    ```text
    model/user.ts            ← User types + logic + store
    model/order.ts           ← Order types + logic + store
    api/fetch-profile.ts     ← Profile fetching
    api/update-settings.ts   ← Settings update
    ```
    
    If a segment has only one domain concern, the filename may match the slice
    name (e.g., `features/auth/model/auth.ts`).
    
    ## 9. Shared layer guide
    
    Shared contains infrastructure with **no business logic**. It is organized by
    segments only (no slices). Segments within shared may import from each other.
    
    **Allowed in shared:**
    
    - `ui/`: UI kit (Button, Input, Modal, Card)
    - `lib/`: Utilities (formatDate, debounce, classnames)
    - `api/`: API client, route constants, CRUD helpers, base types
    - `auth/`: Auth tokens, login utilities, session management
    - `config/`: Environment variables, app settings
    - Assets live with the code that uses them, not in an `assets/` segment.
      See `references/asset-handling.md`.
    
    Shared **may** contain application-aware code: route constants, API
    endpoints, branding assets, and transport types such as `ProductDTO`.
    It must **never** hold the business rules an entity or feature owns, nor
    import from those layers.
    
    ## 10. Conditional references
    
    Read the following reference files **only** when the specific situation applies.
    Do **not** preload all references.
    
    - **When reviewing or reorganizing folder and file structure** that already
      exists, deciding what goes inside a layer or slice, deciding where a page
      layout belongs, routing widget-like code to another layer, or grouping
      closely related slices into a parent folder for navigation (e.g., "where
      does this folder go", "how do I group these payment entities"):
      → Read `references/layer-structure.md`
    
    - **When setting up a new project from scratch** (e.g., "set up an FSD
      project", "start a new app with FSD"), or when asked whether to add
      entities or features yet, or to show how a structure earns each layer
      over time rather than its finished shape:
      → Read `references/growth-walkthrough.md`
    
    - **When resolving cross-import issues** between slices on the same layer,
      evaluating the `@x` pattern, choosing between Strategy A/B/C/D for
      features and widgets, or deciding whether boundaries should be merged:
      → Read `references/cross-import-patterns.md`
    
    - **When deciding whether to create or remove an entity**, dealing with too
      many entities, evaluating whether to skip the entities layer entirely,
      placing CRUD operations, or isolating business contexts to avoid `@x`
      chains:
      → Read `references/excessive-entities.md`
    
    - **When deciding where to place static assets** (images, icons, fonts,
      PDFs, stylesheets) for a single slice, for sharing across slices, or
      globally:
      → Read `references/asset-handling.md`
    
    - **When migrating** from FSD v2.0 to v2.1, converting a non-FSD codebase to
      FSD, phasing out an existing widgets layer, or deprecating the processes
      layer:
      → Read `references/migration-guide.md`
    
    - **When integrating FSD with a specific framework** (Next.js with App Router
      or Pages Router, React Router, Nuxt, Vite, Astro) for wiring routes to
      FSD pages, placing proxy/middleware and instrumentation files,
      structuring API
      route handlers, or configuring path aliases:
      → Read `references/framework-integration.md`
    
    - **When implementing authentication, type definitions, or API request
      handling** as concrete code within FSD structure (token storage, login
      flow, DTO placement, where a request function lives):
      → Read `references/auth-and-api.md`
    
    - **When wiring state management** (Redux slices, TanStack Query / React
      Query, including query factories, infinite scroll, Suspense mode, and
      `useMutationState`) into FSD structure:
      → Read `references/state-management.md`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related