--- status: accepted date: 2026-04-29 decision-makers: R&D Lead tags: [frontend] --- # Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest ## Context and Problem Statement The portal's frontend (`portal-shell`) is a single-page application that aggregates access to existing applications and progressively integrates re-developed features. It sits fully behind authentication: no public content. We need to fix the framework, the rendering mode, the change-detection model, and the test runner now, so subsequent ADRs (auth flow, observability, accessibility, performance budgets) can build on a stable foundation. What frontend stack maximizes alignment with our backend (NestJS), gives us first-class accessibility and performance hooks, and is enterprise-stable for a long-lived project? ## Decision Drivers * Architectural alignment with NestJS (DI, decorators, modules, RxJS) — minimizes cognitive distance for the team. * Performance and accessibility as first-class concerns from day one. * Long-term enterprise stability (no pre-1.0, no exotic stack). * End-to-end type safety. * No public surface — all content gated by authentication, which removes the usual SSR drivers (SEO, anonymous LCP). ## Considered Options * **Angular (latest LTS) — standalone, zoneless, Signals, CSR, Vitest.** (Chosen.) * Angular with SSR (`@angular/ssr`). * React + Next.js (or Vite + React Router). * Vue + Nuxt. * Svelte + SvelteKit. ## Decision Outcome Chosen option: **Angular at the latest LTS major**, with the following modern defaults: * **Standalone APIs** — no `NgModule`s. Configuration is composed via providers; routing is functional. * **Zoneless change detection** — no `Zone.js`. Change detection is driven by Signals and explicit `markForCheck` where needed. * **Signals** — primary reactive primitive for component state. RxJS retained for async streams (HTTP, events). * **Vitest** — unit and component test runner (Karma is deprecated upstream). * **CSR only — no SSR** for v1. Reconsider only if a measured performance metric requires it. * **SCSS** for styles. Design-token strategy to be defined in a future ADR. * **Strict TypeScript** across the workspace (`strict: true`, `noUncheckedIndexedAccess: true`). ### Consequences * Good, because the team works with a single mental model across front and back (DI, decorators, RxJS). * Good, because zoneless + Signals removes Zone.js overhead and gives finer-grained, more predictable reactivity. * Good, because Vitest is significantly faster than Karma and shares the Vite toolchain Nx already uses. * Good, because CSR keeps the BFF free of SSR concerns (no auth-aware rendering, no double-fetch, no hydration debugging) — simpler operational surface. * Bad, because Angular's release cadence is brisk; the project commits to staying on LTS rather than skipping versions. * Bad, because zoneless and Signals are recent — though stable, the broader Angular ecosystem still includes Zone-dependent libraries that must be vetted before adoption. * Bad, because CSR yields a minimal initial HTML payload; this must be paired with strong performance budgets (covered by ADR-0015, future). ### Confirmation * `package.json` pins Angular to the current LTS major. * `app.config.ts` registers `provideZonelessChangeDetection()` (or its current stable equivalent) and standalone routing. * `tsconfig.base.json` sets `strict: true` and `noUncheckedIndexedAccess: true`. * CI runs `pnpm nx test portal-shell` (Vitest). * No `import 'zone.js'` anywhere in the codebase. * `@angular/ssr` is not installed. ## Pros and Cons of the Options ### Angular CSR (chosen) * Good, because alignment with NestJS minimizes context-switching for the team. * Good, because mature, enterprise-supported (Google + community). * Good, because Signals + zoneless make Angular's reactivity competitive with React/Vue. * Bad, because verbosity is higher than minimalist frameworks. ### Angular SSR * Good, because better TTFB and accessibility on first paint for unauthenticated content. * Bad, because we have no unauthenticated content — the gain is essentially zero. * Bad, because SSR-with-auth introduces non-trivial complexity (cookie-aware fetch, hydration with secured data). ### React + Next.js * Good, because large ecosystem, fast iteration. * Bad, because no architectural alignment with NestJS — different DI/state paradigms. * Bad, because Next.js's defaults push toward server components and edge rendering, which conflict with our on-prem, all-authenticated profile. ### Vue + Nuxt / Svelte + SvelteKit * Good, because lighter syntax, strong DX. * Bad, because no DI alignment with NestJS. * Bad, because smaller enterprise-grade community than Angular or React. ## More Information * Angular zoneless change detection: https://angular.dev/guide/experimental/zoneless * Angular Signals: https://angular.dev/guide/signals * Vitest with Nx: https://nx.dev/nx-api/vite * Related ADRs: [ADR-0005](0005-backend-stack-nestjs.md), ADR-0008 (auth flow, future), ADR-0014 (accessibility, future), ADR-0015 (performance budgets, future).