Back to all writing

ARCHITECTURE/5 MIN READ

Migrating a frontend to Feature-Sliced Design

Feature boundaries, public interfaces, and a migration that starts with one feature.

Feature-Sliced Design organizes frontend code around business capabilities and limits dependencies between layers. Before adopting it, identify where changes currently spread across unrelated modules.

Start with the cost of change

Before moving a single file, ask a practical question: what kinds of changes keep touching unrelated parts of the application?

A checkout change that requires editing the profile page, a global store, and three generic utility folders suggests that responsibilities are leaking. The folder names might look tidy while the actual dependency graph is not.

Look at recent pull requests. Trace imports. Identify components that know too much about the product around them. These are stronger signals than the number of files in a directory.

Make business boundaries visible

A feature should communicate an action a user can take, not merely the implementation technology behind it. change-password says more about the product than forms.

src/
  app/                    # Composition and application setup
  pages/                  # Route-level screens
  widgets/                # Larger interface compositions
  features/
    change-password/      # One user-facing capability
      api/
      model/
      ui/
      index.ts            # Deliberate public interface
  entities/
    user/                 # Shared business concept
  shared/
    ui/                   # Product-agnostic primitives
    lib/

This is a possible destination, not a migration checklist. Empty layers do not make a codebase more maintainable. Introduce a layer when there is a responsibility that genuinely belongs in it.

The important rule is the direction of dependencies. Low-level primitives should not import business features. A user entity should not need to know which page renders it. Public interfaces keep consumers from depending on a feature’s internal file layout.

Migrate one vertical slice

A whole-codebase rewrite combines architectural risk with product-delivery risk. A safer alternative is to pick one feature with clear boundaries and move it end to end.

  1. Define the feature’s public interface.
  2. Move its UI, state, and API interactions together.
  3. Keep adapters at the old boundary where necessary.
  4. Verify its behavior before migrating the next feature.
  5. Document the decisions that another engineer will need to repeat.

During a migration, old and new structures will coexist. That is acceptable if the direction is clear and new dependencies do not quietly undo the progress.

Decide what belongs in shared

The easiest way to dissolve boundaries is to promote everything into shared.

A button is often shared. A component that knows how an organization approves a refund probably is not, even if it appears on two screens. Reuse alone does not make a component product-agnostic.

Sometimes two small implementations are cheaper than one abstraction with twelve configuration flags. Wait until the common behavior is understood before designing a reusable interface around it.

Measure the result in everyday work

Review the next few feature changes. Check how many unrelated modules they touch and whether consumers use the new public interfaces. Enforce import boundaries with linting so later changes do not reintroduce the dependencies you removed.

Questions or feedback?

Email me about this article

Gate of Gilvex

Survive 30 seconds. Avoid the blades and transform to dash. Near misses earn points.

Time
30.0s
Score
0
Shields
3
This arcade game needs a browser with Canvas support.

Survive 30 seconds.

Loading…

Best 0

WASD / arrows to move Space to dash

Options

Move with WASD, arrows, or the touch pad. Space or Dash transforms you briefly.

Your cyan core is the hitbox. Near misses earn points.

P pauses. Escape exits.