Decouple the frontend
Independent builds and deploys without tying UI delivery to Java backend release cycles.
WorkCase study
From a monolithic AngularJS portal inside a FreeMarker shell to an 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.
End-of-life stack, coupled deploys, one SPA for four personas
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, and shared libraries 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 |
/, /sme, /location-group, /admin).@kv-frontend/shared for UI components, hooks, and theme tokens.@kv-frontend/data-access for Redux slices, API client, typed models, and auth utilities.@kv-frontend/grid for a reusable TanStack Table-based grid layer.| 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 |
Alongside the portal rebuild, embeddable review widgets were migrated from a Java FreeMarker renderer to a standalone Next.js application, enabling iframe-level cutover and instant rollback.
ResizeObserver for pixel-perfect iframe embedding.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.
Mail me at lotzgustav@gmail.com, or head back to the rest of my work.