Skip to content
Dzunnurain A. Azhar← All work
01Banking · Internal platform

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
Vue 2Single-SPASystemJSVuexKeycloakDockerSonarQube

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

Key decisions

Decision

Single-SPA rather than one monolithic SPA

Why

Eight teams could then deploy on their own schedule, and a regression in one application could not hold the other seven hostage.

Trade-off

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.

Decision

Ship the style guide as a separate deployable

Why

27 components, resolved through the import map, kept eight applications built by different squads visually and behaviourally consistent.

Trade-off

Any breaking change became a cross-team negotiation. Consistency was bought with coordination cost.

Decision

Drive both menus and endpoint access from one RBAC source

Why

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.

Trade-off

A large configuration surface that is genuinely awkward to reproduce on a developer machine.

Decision

Modular Vuex per application, no shared global store

Why

State stayed isolated — the failure mode where one team's mutation breaks another team's screen simply cannot occur.

Trade-off

Shared reference data gets fetched more than once. Isolation was paid for in redundant requests.

Decision

Quality gates in the pipeline, not in review

Why

Bitbucket Pipelines and Jenkins with Docker multi-stage builds, Nginx and SonarQube meant standards were enforced mechanically rather than argued about per pull request.

Trade-off

Slower pipelines, and occasional friction when the gate was stricter than the fix.

Outcome

8
Independently deployable apps
27
Shared components
100+
Endpoints under RBAC
30–50%
Load time improvement

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.