chore: agentic upgrade
dev / deploy (push) Successful in 2m15s
compute / deploy (push) Has been cancelled

This commit is contained in:
Daniil
2026-05-17 02:11:33 +03:00
parent c16fcba693
commit 21e936a827
106 changed files with 12094 additions and 291 deletions
@@ -0,0 +1,293 @@
# 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.
## 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. If reuse is needed across several pages,
move it to a 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`**: an entity or feature used by only one page.
This rule will suggest merging that entity or feature into the page
entirely.
- **`excessive-slicing`**: too many slices in a single layer.
For each flagged slice, decide:
- Reused in 2+ places → keep in features/entities.
- Used only in one page → mark for migration back into that page.
### Step 2. Move single-use code back to its consumer
Take single-use features and entities and inline them into the consuming
page (or widget if that is the single consumer):
```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
Code confirmed to be used in 2+ places 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 multiple
pages share workflow state, create a feature for it.
- **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 ← Used in 2+ pages
app/sync/background-sync.ts ← Global concern
```
### Post-migration verification
1. Run `npx steiger src`. All `insignificant-slice` warnings should be gone.
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.
## 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
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/ ← only objects that are reused
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, and selectors become the `model` segment.
- Thunks and mutations become the `api` segment.
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
Reused Redux slices typically describe business concepts (products, users)
or user actions (comments, likes):
- Business entities → **Entities layer**, one entity per folder.
- User actions → **Features layer**.
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
The `modules/` folder typically holds business logic, similar in nature to
the Features layer. Some modules describe large UI chunks (an app header)
which belong in the Widgets layer.
### 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. If business logic is not used in many
places, copy-pasting back to consumers 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/`, `entities/`, or
`widgets/` directories until there is content for them.
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).