Files
apf_portal/docs/decisions/0004-frontend-stack-angular-csr-zoneless-signals.md
T
Julien Gautier 0e58e32d29 chore: relocate ADRs from decisions/ to docs/decisions/ to consolidate documentation
Move the ADR folder under docs/ alongside the rest of the project
documentation. Convention (flat folder, globally-sequential 4-digit
numbering, tags-based categorization, MADR 4.0.0 format) is unchanged
- only the path moved.

- git mv decisions docs/decisions preserves history for all 18 ADRs +
  README + template (19 files renamed in this commit).
- ADR-0001 amended in-place with a dated note documenting the
  relocation. Status remains 'accepted' - the location detail
  changed, the decision did not.
- All cross-references updated:
  - CLAUDE.md (~17 ADR links + 3 mentions of decisions/ in the Project
    rules section)
  - docs/README.md (now references decisions/ as a sibling under docs/)
  - docs/setup/03-angular-nx-monorepo.md (paths shortened from
    ../../decisions/ to ../decisions/, since setup/ and decisions/ are
    now both inside docs/)
  - docs/decisions/0003 ../CLAUDE.md adjusted to ../../CLAUDE.md
    (one extra level of nesting)
  - docs/decisions/template.md mention of the README path
  - notes/asvs-level-decision-briefing-rssi.md mention of the index

Sanity verified: every ADR link in CLAUDE.md, docs/setup/03, and
docs/decisions/0001 resolves to an existing file. pnpm nx run-many
-t lint passes on 8 projects.
2026-04-30 18:57:59 +02:00

5.0 KiB

status, date, decision-makers, tags
status date decision-makers tags
accepted 2026-04-29 R&D Lead
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 NgModules. 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