Skip to content
All writing2 June 2026 · 4 min read

Shared packages, five engineers, eight months

How a monorepo with internal packages let a small team ship a national platform without spending its time colliding.

ArchitectureTeamsTypeScript

Eight months from empty repository to national launch, with five engineers across frontend and full-stack. The thing that made that possible was not velocity heroics. It was deciding, in about week two, that the shared surface between us would be packages rather than conventions.

Conventions do not survive contact with a deadline

Every team starts with a document. Components go here. API calls look like this. We name things this way. Everyone agrees, and for about six weeks it holds.

Then someone is under pressure, and the fastest path is to copy a pattern that is nearly right and change it slightly. That is entirely rational behaviour, and it happens on every project I have worked on including the ones I ran. Six months later you have four HTTP clients with different retry semantics, and nobody decided that — it accumulated.

A convention is a request. A package is a constraint. If the API client is @internal/api-client and it is the only thing that knows the base URL and the auth header, there is no fast path around it, so nobody takes one.

What we actually extracted

Turborepo with pnpm workspaces, and roughly these packages:

  • components — the design system, one implementation of every primitive
  • api-client — typed client, retry policy, auth, error normalisation
  • types — shared domain types, generated where possible from the API contract
  • hooks — the data-fetching and state hooks that wrap the client
  • feature-flags — flag definitions and evaluation, typed so a typo is a compile error
  • constants — the boring one that stops three files disagreeing about a limit

None of these are architecturally exciting. Their value is that there is exactly one of each.

The typed feature flags were worth more than expected

This is the one I would recommend to anyone regardless of scale. Flags defined in a package, as a typed union, with evaluation behind a hook:

export const flags = {
  opportunitySemanticSearch: { default: false, description: "Vector search on opportunities" },
  newOnboarding: { default: false, description: "Redesigned first-run flow" },
} as const;
 
export type FlagName = keyof typeof flags;

Two consequences. A misspelled flag name fails at compile time rather than silently evaluating false in production — which is the single worst failure mode a flag system has, because it looks exactly like the feature being off on purpose. And because every flag has a description in one file, the list of flags is also the list of things that need cleaning up, which is the only reason flag debt ever actually gets paid down.

Where it cost us

Honest accounting, because the monorepo advocacy online tends to skip this part.

Build orchestration is a real ongoing cost. Turborepo's caching is good, and you will still spend days over the project tuning task graphs, cache keys and CI concurrency. Budget for it.

Package boundaries can be drawn too early. We extracted one package that turned out to have exactly one consumer for the whole project. The abstraction cost was small but non-zero, and the honest answer is that it should have stayed in the app.

Versioning internally is a trap. We used workspace protocol and always-latest, deliberately. The moment you start semver-ing internal packages you have invented distributed development inside a single repository, and you get all of the coordination cost with none of the benefit.

The part that actually mattered

The packages were a mechanism. What they bought was that five engineers could work on five different features and the shape of the result was consistent, without anyone having to police it in review.

Code review stops being about "please use the shared button" and starts being about whether the logic is right. That is a better use of a senior engineer's attention, and on an eight-month deadline it is the difference between shipping and nearly shipping.

Working on something with the same shape? I’d be glad to help.

Start a project