ADR-008: Frontend Framework, Server-Side Rendering & Web Performance
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
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.
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.
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.
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.
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).
- Evidence: The current frontend is entirely client-side rendered (no SSR). Industry data consistently shows SSR reduces FCP from 3-5s to <1.5s for similar applications.
- Evidence: The platform serves consumer content (expert profiles, videos, events) that benefits from SEO indexing. SPAs without SSR are poorly indexed by search engines.
- Evidence: React + React Native enables shared types, GraphQL operations, and business logic between web and mobile. Angular + Ionic does not offer equivalent code sharing with a truly native mobile experience.
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
- If Next.js migration doesn’t achieve <1.5s FCP on fan-facing pages within Phase 1 → investigate bottlenecks (backend GraphQL latency, image sizes, third-party SDKs)
- If React team hiring proves harder than Angular in the platform’s market → reconsider framework choice
- If the strangler-fig approach causes unacceptable UX inconsistency between Angular and React pages → accelerate migration or revert
- If GraphQL client migration (Apollo Angular → Apollo Client React) reveals incompatibilities with existing backend schemas → use BFF (Backend-for-Frontend) proxy layer
- If Next.js on GKE proves operationally too complex → evaluate Vercel or Cloud Run
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
- Scope: Fan-facing web experience (Phase 1-2 priority). Admin and expert dashboards migrate in Phase 3-4 or remain Angular if migration pressure is low.
- Expiry: Re-evaluate framework choice if Angular introduces equivalent of React Server Components (Angular 19+ signals-based SSR), or if a new framework emerges with significantly better characteristics.
- Review trigger: If Phase 1 Lighthouse scores don’t show >50% improvement over Angular baseline, or if migration velocity is <2 pages per sprint.
- Monitoring: Lighthouse CI in PR pipeline. Core Web Vitals tracked via CrUX (Chrome User Experience Report). Bundle size tracked per deploy.
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