David John Thammineni
Tooling11 Nov 20212 min read

Structuring a Frontend Monorepo with Nx

Structuring a Frontend Monorepo with Nx

I've now led two Nx monorepo migrations. The tooling is good; the structure is what determines whether you get code sharing or a distributed mud-ball. Here's the shape that worked.

Apps thin, libs fat

Applications should be deployment shells — routing, config, composition. Everything real lives in libraries:

apps/
  customer-portal/
  admin-dashboard/
libs/
  shared/ui/            # design system components
  shared/data-access/   # API clients, auth
  portal/feature-billing/
  admin/feature-reports/

The naming convention (scope/type-name) isn't cosmetic — it drives boundary enforcement.

Boundaries as lint rules

Tag each library in project.json (scope:shared, scope:admin, type:ui, type:feature), then enforce the dependency rules:

"@nrwl/nx/enforce-module-boundaries": ["error", {
  "depConstraints": [
    { "sourceTag": "scope:shared",
      "onlyDependOnLibsWithTags": ["scope:shared"] },
    { "sourceTag": "type:ui",
      "onlyDependOnLibsWithTags": ["type:ui", "type:util"] }
  ]
}]

Now a shared UI component importing from an app-specific feature is a lint error, not a code-review argument. This rule set is the single highest-value thing Nx gives you.

Affected-only CI

Nx knows the dependency graph, so CI only builds and tests what a change touches:

nx affected --target=test --base=origin/main
nx affected --target=build --base=origin/main

A change to admin/feature-reports doesn't run the customer portal's e2e suite. Combined with computation caching (identical inputs return cached outputs, and Nx Cloud shares that cache across machines), our CI went from 24 minutes to about 6 for a typical PR — and 40% builds finished in seconds because the cache hit.

The honest costs

Generators and executors have a learning curve; upgrading Nx itself is a project-wide event (nx migrate helps); and a monorepo makes undisciplined sharing easier too — without the boundary tags, you'll couple everything to everything within a quarter. Set the tags on day one.

Keep Reading