ADR

ADR-008: Frontend Framework, Server-Side Rendering & Web Performance

Last updated: 2026-02-01 | Decisions

ADR-008: Frontend Framework, Server-Side Rendering & Web Performance

Status

Proposed — Pending engineering team review

Context

The platform’s frontend consists of two Angular 18 monorepos (peeq-mono and frontends) serving fan, expert, and admin audiences. ADR-002 proposes unifying them into a single Angular monorepo. This ADR challenges the framework assumption: should we stay with Angular, or consider React, Next.js, or another framework? It also addresses web performance — the user has identified slow first-paint times, large artifact downloads, and the need for server-side rendering (SSR).

Current Frontend Performance Issues

Problem Evidence
Large bundle sizes Two CSS frameworks (Tailwind + Bootstrap), 3 date libraries, multiple video SDKs
No SSR All rendering is client-side — blank screen until JS loads and executes
No code splitting strategy All routes likely bundled together (need Lighthouse audit to confirm)
Heavy third-party SDKs Mux player, Stream Chat, Stripe, Phenix RTS, 100ms, Socket.io — all loaded client-side
No image optimization No Next.js-style image component; images loaded at full resolution
Dead code ~17% of API gateways call non-existent services
Ionic overhead peeq-mono includes Ionic framework for mobile — adds weight to web bundle

Framework Comparison for This Platform

Criterion Angular 18 (Current) React + Next.js Vue + Nuxt SvelteKit
SSR/SSG Angular Universal (complex setup) Built-in (App Router) Built-in Built-in
Performance Good with optimizations Excellent (RSC, streaming) Very good Best (smallest bundle)
Ecosystem Large but declining in frontend market Largest ecosystem Growing Small but growing
Mobile Ionic/Capacitor (current) React Native (shared code) No strong mobile story No strong mobile story
Team skills Current team knows Angular Larger talent pool Moderate talent pool Smaller talent pool
Migration effort None (already Angular) XL (full rewrite) XL (full rewrite) XL (full rewrite)
GraphQL Apollo Angular (works) Apollo Client / urql (better DX) Apollo Vue (works) urql (works)
Component libs Angular Material, PrimeNG shadcn/ui, Radix, MUI Vuetify, PrimeVue Skeleton UI
Hiring Shrinking Angular pool Largest pool globally Moderate pool Growing but small
SSR streaming Experimental (Angular 19+) Stable (React 18+ Suspense) Stable Stable

Decision

Migrate from Angular to Next.js (React) with a phased strangler-fig approach, prioritizing the fan-facing web experience for SSR and performance. Keep Angular for admin/expert dashboards during transition.

Rationale for Framework Change

  1. SSR is table stakes for consumer-facing apps. Angular Universal exists but is significantly more complex to configure, less mature, and lacks React Server Components equivalent. Next.js has SSR, SSG, ISR, and streaming built in.

  2. Mobile strategy alignment (ADR-009). React enables React Native for the native mobile app, allowing shared business logic, types, GraphQL queries, and potentially shared components between web and mobile. Angular + Ionic doesn’t offer this level of code sharing with native.

  3. Performance. Next.js with React Server Components can render content-heavy pages on the server, sending HTML immediately. Combined with streaming SSR, users see meaningful content in <1 second instead of waiting for the full JS bundle.

  4. Talent and ecosystem. React dominates frontend hiring. The platform’s long-term maintainability benefits from a larger talent pool. The component library ecosystem (shadcn/ui, Radix) is richer and more actively maintained.

  5. Existing Angular code is not wasted. The backend API layer (24+ GraphQL gateways), business logic, auth flow, and design system patterns transfer to any frontend framework. The rewrite is component-level, not logic-level.

Migration Strategy: Strangler Fig

Do NOT rewrite everything at once. Use a phased approach where Next.js pages replace Angular pages incrementally:

graph TD
    subgraph "Phase 1: Foundation"
        A[Next.js app with shared<br/>auth, GraphQL, design tokens]
        B[SSR landing pages<br/>home, expert profiles, content]
        C[Lighthouse baseline<br/>measure current Angular perf]
    end

    subgraph "Phase 2: Fan Experience"
        D[Fan browsing flow<br/>content, events, search]
        E[Fan purchasing flow<br/>subscriptions, shoutouts]
        F[Video player integration<br/>Mux, 100ms]
    end

    subgraph "Phase 3: Expert Portal"
        G[Expert dashboard<br/>content management]
        H[Expert earnings/wallet<br/>Stripe, Dwolla]
    end

    subgraph "Phase 4: Admin"
        I[Admin dashboard<br/>user management, analytics]
        J[Admin tools<br/>content moderation, events]
    end

    A --> D
    B --> D
    C --> D
    D --> G
    E --> G
    F --> G
    G --> I
    H --> I
    I --> K[Angular fully retired]
    J --> K

Phase 1: Foundation (Priority — SSR for SEO & Performance)

Task Purpose
Create Next.js app with App Router Foundation for all future frontend work
Implement Keycloak auth (next-auth or custom) Preserve existing OAuth2/OIDC flow
Set up Apollo Client for GraphQL Connect to existing 24+ backend services
Define design system in Tailwind Port design tokens from peeq-mono
SSR for public pages (expert profiles, content, landing) Immediate SEO + performance win
Set up Lighthouse CI Automated performance tracking

Why public pages first: These are the pages that benefit most from SSR. They need SEO (Google indexing), fast first-paint (user acquisition), and social sharing (Open Graph). The current SPA renders a blank page until JS loads — search engines may not index content properly.

Web Performance Targets

Metric Current (Estimated) Target Technique
First Contentful Paint (FCP) >3s (SPA, no SSR) <1.2s SSR + streaming
Largest Contentful Paint (LCP) >5s (heavy bundles) <2.5s SSR + image optimization + code splitting
Time to Interactive (TTI) >6s (full JS parse) <3.5s Code splitting + dynamic imports
Cumulative Layout Shift (CLS) Unknown <0.1 Server-rendered layout + font preloading
Total Blocking Time (TBT) >500ms (estimated) <200ms Fewer client-side JS bundles via RSC
Bundle size (initial) >1MB (estimated) <200KB Tree shaking + RSC + lazy loading

Performance Optimization Techniques

Technique Implementation
Server-Side Rendering Next.js App Router with React Server Components for content pages
Streaming SSR loading.tsx fallbacks — send shell immediately, stream content as it loads
Code splitting Route-based splitting (automatic in Next.js) + dynamic imports for heavy SDKs
Image optimization Next.js <Image> component with automatic resizing, WebP/AVIF, lazy loading
Font optimization next/font for self-hosted Google Fonts with zero layout shift
Third-party SDK lazy loading Load Mux player, Stream Chat, Stripe only when needed (dynamic import)
Edge caching CDN caching for SSR pages with ISR (Incremental Static Regeneration)
Dead code elimination Remove 5 dead API gateways, unused components, unused libraries
Single CSS framework Tailwind only (remove Bootstrap, Angular Material)
Single date library date-fns or luxon (remove moment, remove the third)

Architecture: Next.js + Existing Backend

graph TB
    subgraph "CDN / Edge"
        CDN[Vercel / Cloudflare<br/>or GKE Ingress]
    end

    subgraph "Next.js (SSR)"
        NEXT[Next.js App Router<br/>React Server Components]
        API_ROUTES[API Routes<br/>BFF pattern if needed]
    end

    subgraph "Auth"
        KC[Keycloak 26.x<br/>OAuth2/OIDC]
    end

    subgraph "Backend (Unchanged)"
        GQL[24+ GraphQL Gateways<br/>Spring Boot / Spring GraphQL]
        RMQ[RabbitMQ]
        PG[(PostgreSQL)]
    end

    subgraph "External"
        STRIPE[Stripe]
        MUX[Mux]
        STREAM[Stream Chat]
    end

    CDN --> NEXT
    NEXT -->|GraphQL| GQL
    NEXT -->|OAuth2| KC
    API_ROUTES -->|Proxy if needed| GQL
    GQL --> PG
    GQL --> RMQ
    NEXT -->|Client SDK| STRIPE
    NEXT -->|Client SDK| MUX
    NEXT -->|Client SDK| STREAM

Key: The entire backend remains unchanged. Next.js replaces only the frontend rendering layer. All 24+ GraphQL gateways, all backend services, all infrastructure continue as-is.

Hosting Options

Option Pros Cons
Vercel Best Next.js support, edge functions, image optimization built-in Vendor lock-in, cost at scale
GKE (self-hosted) Stay in existing infrastructure, full control More ops overhead, must configure CDN separately
Cloud Run Serverless, auto-scale, GCP-native Less Next.js optimization than Vercel
Cloudflare Pages Edge rendering, competitive pricing Requires OpenNext adapter

Recommendation: Start with GKE deployment (stays within existing infrastructure) using the existing Helm chart patterns. Evaluate Vercel if Edge/ISR features prove critical.

Hypothesis Background

Primary: Migrating from Angular to Next.js (React) with SSR will reduce Time-to-First-Contentful-Paint by 60%+ and improve SEO, while enabling code sharing with the native mobile app (ADR-009).

Alternative 1: Stay with Angular, add Angular Universal for SSR. - Not fully rejected — viable but suboptimal. Angular Universal is more complex to configure than Next.js SSR, lacks React Server Components equivalent, and doesn’t enable mobile code sharing. If the team strongly prefers Angular, this is the fallback.

Alternative 2: Stay with Angular SPA, optimize without SSR. - Rejected for fan-facing pages: Code splitting and lazy loading can improve TTI but cannot fix FCP — the browser still needs to download, parse, and execute JavaScript before showing any content. SSR is necessary for sub-second FCP. - Acceptable for admin/expert dashboards where SEO doesn’t matter and users expect login-first experiences.

Alternative 3: Use Astro or similar static-first framework. - Rejected: The platform is highly interactive (video playback, real-time chat, live payments). Astro’s “islands” architecture works for content sites with interactive sprinkles, not for full applications. Next.js handles both static and dynamic content well.

Falsifiability Criteria

Evidence Quality

Evidence Assurance
No SSR in current frontend L2 (verified — no Angular Universal setup in either repo)
Frontend dead code (~17%) L2 (verified — API gateways to non-existent services)
Dual CSS frameworks bloating bundle L1 (Tailwind + Bootstrap confirmed in separate repos)
3 date libraries in frontends repo L2 (verified — luxon, moment, date-fns in package.json)
SSR improves FCP by 60%+ L1 (industry benchmarks, well-documented)
React Native enables mobile code sharing L1 (documented architecture pattern)
Actual Lighthouse scores for current site L0 (need production measurement)
Team’s React vs Angular preference L0 (need team assessment)

Overall: L1 (WLNK capped by actual performance measurements L0 and team assessment L0)

WLNK Warning: This decision is capped at L1 due to unverified assumption that current Lighthouse scores are poor. To promote to L2: Run Lighthouse CI against production fan-facing pages and measure FCP, LCP, TTI, CLS.

Bounded Validity

Consequences

Positive: - Sub-second FCP for fan-facing pages (SSR) - SEO indexable content (expert profiles, events, articles) - Shared code with native mobile app (ADR-009) - Modern development experience (React ecosystem, component libraries) - Smaller initial bundle (RSC + code splitting + dead code removal) - Single CSS framework (Tailwind) - Larger hiring pool (React developers)

Negative: - Full frontend rewrite (phased, but still substantial) - Team must learn React/Next.js (training investment) - Dual frontend during transition (Angular + Next.js running in parallel) - Keycloak integration needs reimplementation in React - All 24+ GraphQL gateway integrations need Apollo Client React migration - Testing starts from zero again (current coverage is near-zero, so this is less impactful)

Mitigated by: Strangler-fig approach means both frameworks coexist during transition. Backend is completely unchanged. GraphQL operations (queries/mutations) are framework-agnostic — only the client wrapper changes. Near-zero existing test coverage means no test migration burden.


Decision date: 2026-01-31 Review by: 2026-07-31