Skip to content

Multi-region government platform

Frontend Architect · Lean Solutions · 2022–present

New features: copy-pasted into every regional repo → written once, shipped to all regions

  • Angular 22
  • Nx
  • TypeScript
  • Tailwind v4
  • Elf
  • Transloco
  • GitLab CI

The platform by numbers

regions, each with a portal and an admin panel
17
libraries
49
my commits
1,164
my merged MRs
436
Angular versions
6 → 22

Problem

Each region ran as a separate Angular project, copied from the previous one. A feature built for one region had to be copy-pasted into every other region by hand. There was no CI/CD.

What I did

  • Merged the regional apps into one Nx monorepo: 10+ public portals and admin panels built from shared libraries (ui, ui-widgets, data-access, feature-*), with dependency boundaries enforced
  • Built an internal UI library and design system on Tailwind CSS v4 tokens
  • Set up GitLab CI from scratch: one pipeline lints, tests, builds and deploys every staging app
  • Upgraded the stack from Angular 6 to 22 (signals, standalone components, the new control flow)

Results

of the code in shared libraries; each regional app is a thin shell (~18k of ~200k lines)
~90%
to launch a new region: a new app shell with its own texts, logo and favicon
~5 min
automated deploy to every staging app; before, there was no pipeline
~7 min
Where the code lives~200k lines in total
Shared libraries: ~90% 10+ app shells: ~18k lines
Before → after
Before After
One new feature Before: copy-pasted into every regional repo After: written once, shipped to all regions
New region Before: a copy of the previous project After: ~5 minutes: a new app shell
Staging deploy Before: no pipeline After: ~7 minutes, fully automated
Angular Before: 6 After: 22

Media

Architecture diagram, from the bottom up: ui, ui-widgets, data-access, feature-* libraries, then 10+ public portals and admin panels built from them.10+ public portals and admin panels(thin app shells) feature-* data-access ui-widgets ui
Apps are built from shared libraries; Nx enforces the dependency boundaries.
Simplified Nx project graph: app shells at the top use feature-* libraries, which use data-access and ui-widgets; ui-widgets use ui. Picking a library highlights every project that depends on it. apps feature-* data-access ui-widgets ui

Pick a library below

Change a library:
Simplified, not the real project graph: pick a library to see which projects depend on it. A change in ui or ui-widgets reaches every app, so most pipelines build them all.

On the blog

  1. From 10+ copied Angular projects to one Nx monorepo
  2. CI from scratch for an Nx monorepo: from “no pipeline” to 7 minutes for all staging apps
  3. Angular 6 → 22: the upgrade path and where you trip up
  4. Migrating Tailwind v3 → v4 in a large monorepo
  5. Sentry in an Angular monorepo: from “users don't complain” to 6889 events in one group
  6. Your own ESLint rules as a guard against repeated mistakes
  7. ESLint rules that fix things themselves: fixers, suggestions and Angular templates

© 2026 Abzal Khairullauly