Skip to content
Marcel Lefter

Engineering LogArchitecture

Feature-Sliced Design in a monorepo

The front-end setup behind 5x lower infrastructure cost and 90% fewer API integration bugs.

Marcel Lefter3 min read
  • Architecture
  • Monorepo
  • TypeScript
  • React

[Context in your words: the projects, the team, and what was slowing delivery.] The fix was boring, well-understood structure, applied the same way in every project.

The layers

#

Feature-Sliced Design splits a front-end into layers by responsibility. One rule does most of the work: a layer can only import from the layers below it, and slices on the same layer never import each other.

LayerOwnsCan import
appProviders, routing, global stylesall layers below
pagesRoute-level compositionwidgets and below
widgetsLarge self-contained UI blocksfeatures and below
featuresUser actions with business valueentities, shared
entitiesBusiness objects such as user, invoice, ordershared
sharedUI kit, API client, utils, confignothing

Monorepo structure

#

Each app keeps its own layers. Anything shared, like the design system, the generated API clients and the tooling, lives in packages.

repo
.
├── apps/
│   ├── web/src/
│   │   ├── app/
│   │   ├── pages/
│   │   ├── widgets/
│   │   ├── features/
│   │   ├── entities/
│   │   └── shared/
│   └── admin/            # same layers
└── packages/
    ├── ui/               # design system and tokens
    ├── api/              # generated API clients
    └── config/           # eslint, tsconfig

Public APIs, not deep imports

#

Every slice exposes a single index.ts. Everything else stays private, which is what makes refactoring safe.

entities/invoice/index.ts
export { InvoiceCard } from "./ui/InvoiceCard";
export { useInvoice } from "./model/useInvoice";
export type { Invoice } from "./model/types";
features/pay-invoice/ui/PayButton.tsx
// avoid: reaches into the sliceimport { useInvoice } from "@/entities/invoice/model/useInvoice";// prefer: the public APIimport { useInvoice } from "@/entities/invoice";

A TypeScript API generator

#

Hand-written API clients drift from the backend. Generating typed clients removed the manual inconsistencies and turned most integration bugs into compile errors. [Name the tool and how it runs in CI.]

$ pnpm --filter @[org]/api generate

Boring architecture wins.

P/02, How I work

What it changed

#
  • 5xlower infrastructure cost
  • 90%fewer API integration bugs
  • 50%less dev effort per project

The 50% came from SaaS boilerplate templates and a Figma-to-token pipeline. New projects start from a working base instead of an empty repo. [Add before and after kickoff times.]

Trade-offs

#

[What it cost: the learning curve, the migration effort, and when you would skip it.]

Subscribe

Get the next entry by email.

RSS feed
Written by
Marcel LefterTechnical Product Lead, SkalX

12 years in software, front-end first. I've led teams of up to 15 and now take products from client need to production.

Open to founding and product engineer roles