# Feature-Sliced Design in a monorepo

Published 2026-10-14 · Architecture · 3 min read · https://www.marcellefter.com/log/feature-sliced-design-in-a-monorepo

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

[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.

| Layer | Owns | Can import |
| --- | --- | --- |
| `app` | Providers, routing, global styles | *all layers below* |
| `pages` | Route-level composition | *widgets and below* |
| `widgets` | Large self-contained UI blocks | *features and below* |
| `features` | User actions with business value | *entities, shared* |
| `entities` | Business objects such as user, invoice, order | *shared* |
| `shared` | UI kit, API client, utils, config | *nothing* |

## Monorepo structure

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

```text title="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.

```ts title="entities/invoice/index.ts"
export { InvoiceCard } from "./ui/InvoiceCard";
export { useInvoice } from "./model/useInvoice";
export type { Invoice } from "./model/types";
```

```tsx title="features/pay-invoice/ui/PayButton.tsx" fade=2 mark=5
// avoid: reaches into the slice
import { useInvoice } from "@/entities/invoice/model/useInvoice";

// prefer: the public API
import { 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.]

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

> Boring architecture wins.
>
> — P/02, How I work

## What it changed

```metrics
5x | lower 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.]
