Micro-frontend Platform
Eight independently deployable Vue 2 applications resolved at runtime through a SystemJS import map. An internal banking system, so this is an account of the architecture rather than of what the product does.
- Client
- Bank Central Asia
- Year
- 2022—2026
- Role
- Frontend Architect
- Team
- Multiple squads, one platform
Context
Eight teams, one product, and no appetite for a shared release train. Each team owned a distinct area of the platform and had no reason to wait on the others to ship.
A single deployable frontend would have made everyone move at the pace of the slowest team. The architectural question was never which framework — it was where to put the seams, and what those seams would cost.
Constraints
- Independent deploys
- Eight teams had to release on their own cadence without coordinating a shared deploy window.
- Vue 2 era
- The Composition API and Vite were not viable options when the platform started. Webpack and the Options API were the ground truth.
- Bank controls
- On-premise deployment, static analysis as a merge gate, and permissions auditable down to the individual endpoint.
- Parallel delivery
- Teams had to build simultaneously against a shared component surface without blocking each other.
Architecture
A Single-SPA root config resolves eight independently deployable Vue 2 applications through a SystemJS import map, over one shared style guide.
Root config + 9 remotes
- Micro-app 01
- Micro-app 02
- Micro-app 03
- Micro-app 04
- Micro-app 05
- Micro-app 06
- Micro-app 07
- Micro-app 08
- Style guide27 components, resolved once via the import map
- The root config owns routing and the import map — nothing else. It holds no business logic, so it almost never needs to ship.
- Each application carries its own Vuex modules. Nothing is hoisted into a global store, so one team's state shape is never another team's problem.
- The style guide is its own deployable, resolved through the same import map rather than bundled into each application.
Key decisions
Single-SPA rather than one monolithic SPA
Eight teams could then deploy on their own schedule, and a regression in one application could not hold the other seven hostage.
Runtime module loading through SystemJS added a class of failure that does not exist in a monolith: version skew between an application and the shared library it resolves at load time.
Ship the style guide as a separate deployable
27 components, resolved through the import map, kept eight applications built by different squads visually and behaviourally consistent.
Any breaking change became a cross-team negotiation. Consistency was bought with coordination cost.
Drive both menus and endpoint access from one RBAC source
Permissions across 100+ API endpoints resolved from the same Keycloak roles that built the navigation, so the UI could not offer something the API would reject.
A large configuration surface that is genuinely awkward to reproduce on a developer machine.
Modular Vuex per application, no shared global store
State stayed isolated — the failure mode where one team's mutation breaks another team's screen simply cannot occur.
Shared reference data gets fetched more than once. Isolation was paid for in redundant requests.
Quality gates in the pipeline, not in review
Bitbucket Pipelines and Jenkins with Docker multi-stage builds, Nginx and SonarQube meant standards were enforced mechanically rather than argued about per pull request.
Slower pipelines, and occasional friction when the gate was stricter than the fix.
Outcome
The performance gain came from code splitting, lazy loading, and letting SystemJS load modules at runtime instead of shipping everything on first paint.
What I’d revisit
Eight boundaries was too many. They followed team ownership rather than how the data actually clustered, so applications sharing reference data ended up talking across a seam that existed for organisational reasons rather than technical ones. Four would have been the right number — which is what the next platform got.
The shared library needed a stricter contract from day one. Runtime resolution without an enforced version policy means a consumer can discover a breaking change in production rather than at build time — and by the end a second repository was publishing under the same module name, which is exactly the failure that policy would have prevented.
Diagrams are drawn from scratch and module names describe the domain. No client assets, interfaces or internal naming appear here.