Decouple the frontend
Independent builds and deploys without tying UI delivery to Java backend release cycles.
From a monolithic AngularJS portal inside a FreeMarker shell to a modern Nx-based, multi-app React architecture, plus a parallel Next.js widget rebuild that replaces dozens of legacy templates.
Decoupled deployments, strict typing, shared libraries, and a platform structure that scales with teams and tenants.
Kiyoh served thousands of businesses with review collection, moderation, analytics, and publication. But the frontend powering it was a single aging portal that had accumulated nearly a decade of coupling and tooling debt.
The legacy client-portal ran on AngularJS 1.5 + TypeScript 2.0, bundled via Gulp 3 + Webpack 1, rendered inside a Java-served FreeMarker shell. Four distinct personas were implemented as conditional runtime route registration inside one monolithic SPA.
The stack was end-of-life, onboarding was slow, and UI changes were tightly coupled to backend release cycles. We needed a rebuild that decoupled deployments, enforced structure, and preserved full feature parity.
Rebuild the platform with independent frontend deployment, persona separation, shared libraries, and modern tooling while maintaining full feature parity.
Independent builds and deploys without tying UI delivery to Java backend release cycles.
Tenant admins, business owners, group managers, and super-admins become first-class applications rather than conditional branches.
A shared library layer provides UI, hooks, models, and data access while keeping applications independently buildable.
An Nx monorepo containing four React applications and shared libraries. Each persona has its own entrypoint and deploy pipeline, while sharing a common component and data foundation.
kv-frontend/
├── apps/
│ ├── kv-frontend → Admin Portal (Tenant Administrators)
│ ├── kv-location → Business Portal (Location Owners)
│ ├── kv-location-dashboard → Group Portal (Multi-location Managers)
│ ├── kv-super-admin → Platform Admin (Super Administrators)
│ ├── routes → Unified route tree (shared across apps)
│ └── proxy → Development proxy server
├── libs/
│ ├── shared → UI components, hooks, utilities, theme
│ ├── data-access → Redux store, API layer, models
│ └── grid → Data table components (TanStack Table)| Dimension | Legacy (client-portal) | New (kv-frontend) |
|---|---|---|
| Framework | AngularJS 1.5 | React 19 |
| Language | TypeScript 2.0 | TypeScript 5.9 (strict) |
| Build system | Gulp 3 + Webpack 1 | Nx 22 + Vite 7 |
| Routing | angular-ui-router | React Router v6 |
| State management | $rootScope + services | Redux Toolkit 2 |
| Styling | LESS (per-tenant) | SCSS with shared tokens |
| Forms | AngularJS directives | React Hook Form + Yup |
| Tables/grids | Custom directives | TanStack Table + shared grid lib |
| i18n | angular-translate | i18next (HTTP backend) |
| CI/CD | Coupled to backend deploy | Docker + Kubernetes + Nx Cloud |
| Metric | Legacy | New |
|---|---|---|
| Applications | 1 monolithic SPA | 4 dedicated apps |
| Shared libraries | Ad-hoc | 3 typed Nx libraries |
| Build & dev | Minutes, full reloads | Seconds, Vite HMR + Nx cache |
| Deploy coupling | Tied to Java backend | Independent Docker/K8s |
| Code splitting | None | Route-level + chunked features |
The platform moved from end-of-life tooling and a monolithic SPA to a structure that supports long-term maintainability: strict typing, enforced boundaries, independent deploys, and shared libraries that scale across teams and tenants.
UI ships without requiring backend deployments.
Personas split into first-class apps with shared foundations.
Quality gates and boundaries reduce regressions.
Vite HMR + Nx cache improves iteration and confidence.