GoogleChrome / GoogleChrome/webstatus.dev
RFC: Architectural Investigation for Server-Side Rendering (SSR) in webstatus.dev
- Dominant language
- Go
- Stars
- 254
- Forks
- 62
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 64
Description
# Architectural RFC & Investigation: Server-Side Rendering (SSR) for webstatus.dev
**Status:** Proposed / Theoretical Investigation
**Primary Candidate:** Angular v22 (`@angular/ssr`)
**Comparisons Evaluated:** Angular SSR vs. Go HTML Templating vs. Lit Labs SSR
**Scope:** Measurable ROI, SEO & Social Unfurls, Component Libraries, `@lit/task` Evolution, Isomorphic Services, Google Charts, and Testing Strategy
---
## Executive Summary
This document evaluates the architectural, operational, and development implications of migrating [webstatus.dev](https://webstatus.dev) from its current **Client-Side Rendered (CSR) Lit Single Page Application** to a **Server-Side Rendered (SSR) architecture using Angular v22 (`@angular/ssr`)**.
Today, `webstatus.dev` relies on client-side JavaScript to fetch data from the Go API, parse JSON, and render web components in the browser. While this provides a simple static hosting model (Nginx in Cloud Run), it imposes **Time-to-Interactive (TTI) and First Contentful Paint (FCP) penalties**, prevents **rich social sharing previews (OpenGraph / Twitter Cards)**, and requires search engine bots to execute client-side JavaScript for indexing.
---
## 1. Latency Budget Breakdown: CSR vs. Uncached SSR vs. Edge-Cached SSR
You correctly identified a fundamental latency constraint: **SSR does not eliminate backend API requests; it changes *where* and *when* those requests occur.**
Below is the step-by-step physical latency breakdown comparing the current CSR application with Uncached SSR and Edge-Cached SSR:
```
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ STEP-BY-STEP LATENCY BUDGET │
├──────────────────────────────────────┬────────────────────────┬─────────────────────────────┤
│ Execution Step │ Current (Lit CSR) │ Proposed (Angular v22 SSR) │
├──────────────────────────────────────┼────────────────────────┼─────────────────────────────┤
│ 1. DNS + TCP + TLS Connection │ ~30 – 60 ms │ ~30 – 60 ms │
│ 2. Initial HTML Download │ ~30 ms (Blank shell) │ — │
│ 3. Client JS Bundle Download (1.8MB) │ ~350 – 700 ms │ — (Deferred / In Background)│
│ 4. Client JS Parse & Execution │ ~200 – 400 ms │ — (Deferred / In Background)│
│ 5. API Data Request Network Latency │ ~80 – 180 ms (Public) │ **< 3 ms (Internal GCP VPC)**│
│ 6. Go API + Spanner Query Execution │ ~20 – 60 ms │ ~20 – 60 ms │
│ 7. Server HTML Template Render │ — │ ~10 – 25 ms (Node engine) │
│ 8. Server HTML Payload Transmission │ — │ ~40 – 70 ms │
│ 9. Browser Initial Paint (FCP) │ ~100 ms (Post-fetch) │ **Instant upon HTML parse** │
├──────────────────────────────────────┼────────────────────────┼─────────────────────────────┤
│ **Total Real-World FCP (Uncached)** │ **~1.5s – 2.8s** │ **~250ms – 550ms** │
│ **Total Real-World FCP (Edge-Cached)│ **N/A** │ **< 100ms** (Cloud CDN Edge)│
└──────────────────────────────────────┴────────────────────────┴─────────────────────────────┘
```
### Why Uncached SSR Is ~4x Faster Than CSR Even With API Requests:
1. **Internal VPC Network (3ms vs 150ms)**:
- In CSR, the user's browser in Australia or Europe must send a second round-trip HTTP request over the public internet to `/v1/features`, incurring 100–200ms latency.
- In SSR, the Cloud Run Node server communicates with the Go backend API within the **same internal Google Cloud VPC subnet**, where latency is **< 3 milliseconds**.
2. **Elimination of the "Waterfall" (Parallelism vs. Sequential)**:
- **Current CSR Waterfall**: `Download HTML (50ms)` $\rightarrow$ `Download 2MB JS (500ms)` $\rightarrow$ `Parse JS (300ms)` $\rightarrow$ `Call API over Internet (150ms)` $\rightarrow$ `Render DOM (100ms)` = **~1.8s**.
- **SSR Pipeline**: `Call API via Internal VPC (25ms)` $\rightarrow$ `Compile HTML (15ms)` $\rightarrow$ `Stream HTML to User (60ms)` = **~300ms FCP**.
3. **When Does FCP Hit <100ms? (Edge Caching)**:
- For static feature detail pages (e.g. `/features/subgrid` which only updates when daily data ingestion runs), Google Cloud CDN / Nginx caches the compiled HTML.
- For cached requests, the SSR server is bypassed entirely and the edge CDN delivers the full HTML in **under 100ms**.
---
## 2. SEO & Rich Social Media Previews (Dynamic Baseline Cards)
When developers share links to web platform features (e.g. on Slack, Discord, Twitter/X, GitHub issues, or LinkedIn), standard CSR applications return a generic fallback image and empty description because the client JavaScript has not yet executed.
### Dynamic OpenGraph & Meta Tag Injection in SSR:
In the proposed SSR architecture, the Node server dynamically generates rich metadata in the HTML `` on every request:
```html
Anchor Positioning - Baseline 2024 Newly Available | webstatus.dev
```
```
┌────────────────────────────────────────────────────────────────────────┐
│ SOCIAL PREVIEW CARD (Slack, GitHub, Twitter/X, Discord) │
├────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ webstatus.dev │ │
│ │ CSS Anchor Positioning │ │
│ │ │ │
│ │ [ ✔ BASELINE: NEWLY AVAILABLE (2024) ] │ │
│ │ │ │
│ │ Chrome: 125 ✔ | Firefox: In Dev ⏳ | Safari: 18 ✔ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ Anchor Positioning - Web Platform Feature Status & WPT Metrics │
│ webstatus.dev/features/anchor-positioning │
└────────────────────────────────────────────────────────────────────────┘
```
---
## 3. Stateful Asynchronous Requests: Evolving from `@lit/task` to Angular v22 `resource()`
Currently, `webstatus.dev` uses Lit's `@lit/task` library to manage stateful asynchronous lifecycles (`Task`, `TaskStatus.INITIAL`, `TaskStatus.PENDING`, `TaskStatus.COMPLETE`, `TaskStatus.ERROR`).
### The Angular v22 Evolution: The `resource()` and `httpResource()` API
Angular v22 introduces the **Resource API**, which provides a native, Signal-based state container that directly replaces `@lit/task`:
```typescript
// Comparison: Lit Task vs. Angular v22 Resource API
// 1. Lit Task (Current):
this.featureTask = new Task(this, {
task: async ([featureId]) => await this.apiClient.getFeature(featureId),
args: () => [this.featureId],
});
// 2. Angular v22 Resource API (Proposed):
export class FeaturePageComponent {
private readonly api = inject(API_CLIENT);
readonly featureId = input.required();
// Resource automatically manages Loading, Error, Success, and Reload
readonly featureResource = resource({
request: () => ({ id: this.featureId() }),
loader: async ({ request }) => await this.api.getFeature(request.id),
});
}
```
### Template Consumption in Angular v22:
```html
@if (featureResource.isLoading()) {
} @else if (featureResource.error()) {
} @else if (featureResource.value(); as feature) {
{{ feature.name }}
}
```
#### Client Rehydration Behavior:
- **Server Phase**: The `resource()` loader executes on the Node server, renders the HTML, and serializes the JSON into Angular's `TransferState`.
- **Client Phase**: Upon client hydration, `resource()` reads the serialized state from `TransferState` without making an unnecessary second network call.
- **Client-Side Filtering / Point Clicks**: When a user clicks a point on a chart, changing a reactive Signal input (e.g. `selectedDate.set('2023-01-01')`) automatically triggers the resource loader on the client to fetch and update the island asynchronously.
---
## 4. Invisible Services Architecture: Moving from DOM Elements to Isomorphic Services
Currently, `webstatus.dev` uses "invisible" Lit components (e.g. ``, ``, ``) placed in the DOM to inject state and coordinate global background tasks.
In Angular v22, services are **standard TypeScript Singletons** (`@Injectable({ providedIn: 'root' })`). They operate in a **Hybrid / Isomorphic Model**:
```
┌─────────────────────────────┐
│ Angular Dependency Injector│
└──────────────┬──────────────┘
│
┌───────────────────────────────┴───────────────────────────────┐
▼ ▼
[Isomorphic Services] [Client-Only Services]
(Runs on Server AND Client) (Guarded by isPlatformBrowser)
- ApiClientService - FirebaseAuthService
- UrlRoutingService - BookmarkSyncService
- ThemeStateService - NotificationPushService
```
### 1. Isomorphic Services (Run on Both Server & Client)
* **`ApiClientService`**:
* **On Server**: Calls the internal Go API endpoint over VPC network (`http://backend-api:8080`).
* **On Client**: Calls the public API endpoint (`https://webstatus.dev/v1/...`).
* **`ThemeStateService`**:
* Reads the dark/light mode preference from request cookies on the server to prevent theme-flicker on initial render.
### 2. Client-Only Services (Guarded by `isPlatformBrowser`)
* **`FirebaseAuthService`**:
* Firebase Auth SDK relies on browser primitives (`indexedDB`, `window.localStorage`, `postMessage`).
* In Angular v22, client-only code is cleanly encapsulated:
```typescript
@Injectable({ providedIn: 'root' })
export class FirebaseAuthService {
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
constructor() {
if (this.isBrowser) {
this.initFirebaseClient();
}
}
}
```
* **Benefit**: Eliminates awkward invisible DOM elements. Services are injected wherever needed with zero DOM pollution.
---
## 5. Architectural Options Evaluation: Angular SSR vs. Go SSR vs. Lit Labs SSR
```
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ SSR CANDIDATE COMPARISON │
│ │
│ [Angular v22 @angular/ssr] [Go html/template / HTMX] [@lit-labs/ssr] │
│ - Full SPA + SSR (Zoneless) - Single Go Binary - Native to Lit │
│ - Signals & @defer Islands - Ultra Low CPU / RAM - EXPERIMENTAL │
│ - Native Material 3 Components - Hard to manage rich client state - Complex hydration │
│ - Official Production Tier - Duplicate UI logic (Go + JS) - Node DOM Shims │
│ ★ RECOMMENDED FOR RICH SPA ★ BEST FOR PURE STATIC PAGES ❌ NOT PRODUCTION │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
```
### Why Go Manual SSR Was Deprecated for this Use Case:
* `webstatus.dev` contains deep client-side interactivity (multi-column filtering, ANTLR search grammar evaluation, saved search bookmark editing, modal state, and interactive chart point click interop).
* Go templates with HTMX require duplicating validation and rendering logic in two languages (Go and JavaScript) and do not provide smooth SPA client-side page transitions.
### Why `@lit-labs/ssr` Was Rejected:
* `@lit-labs/ssr` remains officially labeled as **Experimental** by the Lit team, lacks stable production hydration guarantees, and requires brittle Node DOM shimming.
---
## 6. Safe Migration Strategy: Side-by-Side Coexistence (`/ui/v2/`)
Instead of a high-risk "big-bang" rewrite, the migration will follow the **Strangler Fig Pattern**:
```
┌───────────────────────────────┐
│ Google Cloud Load Balancer │
│ or Ingress Router │
└───────────────┬───────────────┘
│
┌───────────────────────────┴───────────────────────────┐
│ Path: /ui/v2/* (or /v2/*) │ Path: /* (Default)
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Angular v22 SSR Service │ │ Existing Lit CSR App │
│ (Cloud Run - Node.js) │ │ (Cloud Run - Nginx) │
└──────────────┬──────────────┘ └──────────────┬──────────────┘
│ │
└───────────────────────────┬───────────────────────────┘
▼
┌───────────────────────────────┐
│ Go Backend API (v1) │
│ Cloud Spanner DB │
└───────────────────────────────┘
```
---
## 7. Component & Design System Analysis
```
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ COMPONENT LIBRARY COMPARISON MATRIX │
├──────────────────────┬──────────────────────┬──────────────────────┬────────────────────────┤
│ Dimension │ Shoelace (Current) │ Web Awesome (New) │ Angular Material 3 (★) │
├──────────────────────┼──────────────────────┼──────────────────────┼────────────────────────┤
│ **Technology** │ Web Components / WC │ Web Components / WC │ Native Angular M3 │
│ **SSR Hydration** │ ⚠️ Shadow DOM quirks │ ⚠️ Custom Elements │ 🟢 100% Zero-Friction │
│ **Licensing** │ Free (MIT) │ Commercial License │ Free (MIT / Open) │
│ **Angular Signals** │ Requires Event Wraps │ Requires Event Wraps │ 🟢 Native Signals API │
│ **Accessibility** │ Good │ Good │ 🟢 Industry Benchmark │
└──────────────────────┴──────────────────────┴──────────────────────┴────────────────────────┘
```
### Recommendation: **Angular Material 3 (`@angular/material`)**
* Native Angular architecture eliminating custom element lifecycle friction.
* Flawless SSR hydration without Shadow DOM encapsulation barriers.
* Industry-standard accessibility (a11y) and Material Design 3 theming.
---
## 8. Zero-Dependency Google Charts Integration in SSR
Google Charts (`loader.js`) cannot execute on a Node.js server (as it requires a browser DOM and Canvas context).
### The Pure DOM Interop Pattern in Angular v22:
1. **Server Phase**: Angular renders a lightweight `
2. **Client Phase**: Angular v22's `@defer (on viewport; on idle)` triggers when the chart enters the screen.
3. The official Google Charts loader (`https://www.gstatic.com/charts/loader.js`) loads once, and standard Google Visualization API methods render SVG/Canvas charts directly into the element ref—**with zero third-party wrapper dependencies**.
---
## 9. Clean Dependency Injection for Mock Clients (Zero `NODE_ENV` Checks)
Angular's **Dependency Injection (DI)** decouples the API client completely:
```typescript
// 1. Injection Token (src/api/token.ts)
export const API_CLIENT = new InjectionToken('API_CLIENT');
// 2. Production Configuration (app.config.ts)
export const appConfig: ApplicationConfig = {
providers: [{ provide: API_CLIENT, useClass: HttpApiClient }],
};
// 3. Visual Test Configuration (app.config.test.ts)
export const testConfig: ApplicationConfig = {
providers: [{ provide: API_CLIENT, useClass: FixtureMockApiClient }],
};
```
* Components inject `API_CLIENT` with **zero `if (process.env.NODE_ENV === 'test')` checks**.
* Production builds contain **zero test fixture bytes**.
---
## 10. Pragmatic Caching Architecture: What We Should Do vs. What Is Over-Engineered
Caching in SSR introduces significant architectural trade-offs. Below is the pragmatic evaluation of what is worth doing versus what represents excessive complexity for webstatus.dev:
```
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ PRAGMATIC SSR CACHING SPECTRUM │
├─────────────────────────────────────────────────────────────────────────────────────────────┤
│ ❌ OVER-ENGINEERED (Avoid): │
│ • Cloud CDN Edge HTML Caching: Cache invalidation webhooks on daily scrapes, cache │
│ poisoning risks, and query parameter collision bugs. │
│ • Multi-Tier Valkey HTML Fragment Caching: Redis complexity for <50ms savings on a │
│ developer dashboard. │
│ │
│ 🟢 PRAGMATIC & BULLETPROOF (Recommended): │
│ • Dynamic On-Demand SSR on Cloud Run (HTML is always fresh, ~250–350ms, zero leak risk). │
│ • Content-Hashed Static Assets (Fingerprinting: js/index-[hash].js cached forever). │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
```
### A. The Over-Engineered Path (What We Won't Do & Why)
1. **Cloud CDN Edge HTML Caching**:
- **Why Avoid**: If HTML is cached at the CDN edge:
- **Daily Scrape Invalidation**: Whenever background ingestion updates WPT/BCD data, complex purge webhooks must invalidate thousands of edge URLs.
- **Authentication Leak Risk**: If an authenticated response accidentally omits `Cache-Control: private`, private bookmarks or notification channels could be cached and served to anonymous users.
- **Query String Variations**: URLs like `?q=baseline:widely` vs `?q=baseline:newly` create fragmented cache keys or risk cache collisions if CDNs are misconfigured.
2. **Valkey Server-Side Fragment Caching**:
- Storing rendered HTML DOM fragments in Valkey introduces cache synchronization bugs with zero noticeable user benefit on a dashboard with moderate traffic.
---
### B. The Pragmatic Path (What We Should Do)
1. **Dynamic On-Demand SSR (No HTML Caching)**:
- Cloud Run compiles HTML dynamically per request (~250–350ms).
- HTML responses always return:
```http
Cache-Control: private, no-cache, no-store, must-revalidate
Vary: Cookie, Authorization
```
- **Zero security risk**: Authenticated user data can never leak to another user.
- **Zero query string bugs**: Every search query gets exact, real-time results from the Go API.
2. **Bulletproof Static Asset Cache Busting (Content Hashing / Fingerprinting)**:
- All client JavaScript, CSS, images, and fonts are compiled with cryptographic content hashes in their filenames:
- `js/main.a8f9c2d1.js`
- `css/styles.e3b0c442.css`
- `img/logo.5d41402a.png`
- **Asset Cache Header**: `Cache-Control: public, max-age=31536000, immutable` (browser and CDN cache these forever).
- **How Cache Busting Works**:
- When a developer deploys a bug fix, the build generates a new content hash (`js/main.b910e3fa.js`).
- The SSR server delivers HTML pointing to the new URL.
- The browser sees a brand new URL and downloads the fresh code immediately—**guaranteeing 100% instant cache-busting without manual cache clearing**.
---
## 11. Granular, Reviewable PR Breakdown Plan (With Code & Test Estimates)
To prevent massive, unreviewable pull requests, the migration is structured into **16 atomic, reviewable PRs**. Each PR is scoped to **~200–450 lines of diff (max ~600 lines including tests)**, follows a strict **Shift-Left Testing** pattern (where every UI PR delivers its own component tests, SSR tests, and visual snapshots atomically), and keeps the repository 100% buildable and testable at every commit.
```
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ REVISED 16-PR MIGRATION STACK & TEST SCOPING │
├─────┬──────────────────────────────────────────┬───────────┬───────────┬───────────────────┤
│ PR# │ Title & Focus │ App (LOC) │ Test(LOC) │ Reviewable Scope │
├─────┼──────────────────────────────────────────┼───────────┼───────────┼───────────────────┤
│ 01 │ `infra: scaffold /ui/v2/ Cloud Run` │ ~150 lines│ ~60 lines │ Terraform config │
│ 02 │ `build: Angular SSR & Hydration Guard` │ ~260 lines│ ~140 lines│ SSR & Test Harness│
│ 03 │ `feat(api): ApiClient token & bindings` │ ~180 lines│ ~140 lines│ OpenAPI DI bindings│
│ 04 │ `feat(theme): Material 3 design & dark` │ ~180 lines│ ~110 lines│ M3 theme & cookie │
│ 05 │ `feat(charts): Google Charts DOM loader` │ ~170 lines│ ~150 lines│ Native loader/wrap│
│ 06 │ `feat(overview): filter bar & search` │ ~240 lines│ ~200 lines│ Signals filter bar│
│ 07 │ `feat(overview): table & sort/pagination`│ ~320 lines│ ~280 lines│ Overview table │
│ 08 │ `feat(overview): saved search bookmarks` │ ~250 lines│ ~210 lines│ Bookmarks & dialog│
│ 09 │ `feat(feature): detail metadata & SEO` │ ~280 lines│ ~240 lines│ OpenGraph & chips │
│ 10 │ `feat(feature): @defer WPT & UMA charts` │ ~260 lines│ ~220 lines│ Progressive charts│
│ 11 │ `feat(stats): Global Feature Support` │ ~220 lines│ ~180 lines│ Multi-line stats │
│ 12 │ `feat(stats): Missing One browser chart` │ ~290 lines│ ~260 lines│ Point-click table │
│ 13 │ `feat(auth): isomorphic FirebaseAuth` │ ~220 lines│ ~200 lines│ Firebase bridge │
│ 14 │ `feat(settings): channels & settings` │ ~340 lines│ ~290 lines│ Notifications/subs│
│ 15 │ `test(e2e): cross-engine matrix & parity`│ ~60 lines │ ~380 lines│ Chromium/FF/WebKit│
│ 16 │ `infra: production traffic cutover to /` │ ~60 lines │ ~60 lines│ Traffic switchover│
├─────┴──────────────────────────────────────────┴───────────┴───────────┴───────────────────┤
│ **Total Estimated Lines across 16 PRs** │ **~3,460**│ **~3,120**│ **Total: ~6,580** │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
```
### Detailed PR Specifications (Shift-Left Quality Model):
1. **PR 1: `infra(frontend): add /ui/v2/ Cloud Run service and routing in Terraform`**
- **Scope**: Define Cloud Run service definition for `frontend-v2` with direct VPC access and route `/ui/v2/*` path in load balancer.
- **Tests**: Terraform validation and dry-run plan tests.
- **Size**: ~150 LOC Infra + ~60 LOC Tests.
2. **PR 2: `build(frontend-v2): scaffold Angular v22 workspace, @angular/ssr, and Playwright Hydration Guard`**
- **Scope**: Add `package.json` scripts, `tsconfig.json`, `app.config.ts` (zoneless change detection), `server.ts` Express SSR entrypoint, and base `testWithHydrationGuard` sentinel.
- **Tests**: Build verification test and Express SSR smoke test asserting `NG0500` error interceptors trigger on hydration mismatch.
- **Size**: ~260 LOC App + ~140 LOC Tests.
3. **PR 3: `feat(api): implement ApiClient injection token and HttpApiClient (reusing existing satisfies typed fixtures)`**
- **Scope**: Define `API_CLIENT` InjectionToken matching OpenAPI schema types, implement `HttpApiClient` for production, and register `FixtureMockApiClient` directly consuming our pre-existing, strictly typed `e2e/fixtures/` stack.
- **Tests**: Compile-time unit tests asserting DI token resolution and mock provider swaps.
- **Size**: ~180 LOC App + ~140 LOC Tests.
4. **PR 4: `feat(theme): add Material 3 design tokens, typography, and dark/light theme service`**
- **Scope**: Configure Angular Material 3 palette, typography, CSS custom properties, and `ThemeStateService` reading cookies on SSR server.
- **Tests**: Unit test verifying theme cookie parsing on server and dual-theme visual snapshot of base layout.
- **Size**: ~180 LOC App + ~110 LOC Tests.
5. **PR 5: `feat(charts): add Google Charts loader service and @defer skeleton wrapper component`**
- **Scope**: Implement `GoogleChartsLoaderService` (loading `https://www.gstatic.com/charts/loader.js` once on browser) and `` wrapper with `afterNextRender` DOM isolation.
- **Tests**: Unit test verifying server skips Google script load while client loads on demand with 0 Cumulative Layout Shift (CLS).
- **Size**: ~170 LOC App + ~150 LOC Tests.
6. **PR 6: `feat(overview): implement Angular v22 Resource-based overview filter bar and search input`**
- **Scope**: Search input field, query chip parser, baseline status filters, and reactive search query Signal bindings.
- **Tests**: Component unit tests for query string parsing, debounce input behavior, and search bar visual snapshot.
- **Size**: ~240 LOC App + ~200 LOC Tests.
7. **PR 7: `feat(overview): implement Overview table component with multi-column sorting and pagination`**
- **Scope**: Material 3 table with browser implementation status badges, baseline icons, column customizer dialog, and async pagination signals.
- **Tests**: Component unit tests for column sorting state, pagination button interactions, and Overview page visual snapshots.
- **Size**: ~320 LOC App + ~280 LOC Tests.
8. **PR 8: `feat(overview): implement saved search bookmarks and sharing dialogs`**
- **Scope**: Bookmarks toolbar, save search modal, copy link toast, and saved search signal state management.
- **Tests**: Unit tests for saving and retrieving bookmarks via `ApiClient` and bookmark dialog visual snapshots.
- **Size**: ~250 LOC App + ~210 LOC Tests.
9. **PR 9: `feat(feature-detail): implement Feature Detail page metadata, baseline status, and dynamic OpenGraph SEO`**
- **Scope**: `/features/:id` page component, spec link chips, browser support breakdown, explicit HTTP 404 response headers for missing features, and server-rendered OpenGraph / Twitter meta tags.
- **Tests**: Server-side rendering test verifying ``, ``, and HTTP 404 status codes.
- **Size**: ~280 LOC App + ~240 LOC Tests.
10. **PR 10: `feat(feature-detail): implement WPT implementation progress and UMA adoption @defer Google Charts`**
- **Scope**: WPT multi-browser pass rate chart and UMA daily usage percentage chart wrapped in `@defer (on viewport; on idle)`.
- **Tests**: Component test verifying chart renders when viewport event triggers, with full Feature Detail visual snapshots.
- **Size**: ~260 LOC App + ~220 LOC Tests.
11. **PR 11: `feat(stats): implement Global Feature Support chart with multi-browser lines and Baseline series`**
- **Scope**: `/stats` Global Feature Support chart with Chrome, Firefox, Safari, and Total Baseline historical series.
- **Tests**: Unit test validating series calculation logic, date boundary offsets, and Stats page base visual snapshots.
- **Size**: ~220 LOC App + ~180 LOC Tests.
12. **PR 12: `feat(stats): implement Features Missing in One Browser chart with interactive point-selection table`**
- **Scope**: Missing in One Browser line chart and interactive point-click listener opening the filtered feature table.
- **Tests**: Component unit test verifying clicking a point dispatches the task and populates missing features list, with point-selected visual snapshot.
- **Size**: ~290 LOC App + ~260 LOC Tests.
13. **PR 13: `feat(auth): implement isomorphic FirebaseAuthService and user session cookie bridge`**
- **Scope**: Isomorphic auth service with browser-only Firebase SDK initialization, SSR request cookie extraction, and zero-flicker `TransferState` user state.
- **Tests**: Unit tests for authenticated user state transitions, sign-out cleanup, and authenticated header visual snapshot.
- **Size**: ~220 LOC App + ~200 LOC Tests.
14. **PR 14: `feat(settings): implement Notification Channels and Subscriptions management pages`**
- **Scope**: Email/Slack channel management table, feature subscription toggle switches, and verification modals.
- **Tests**: Component tests for channel creation, deletion, and subscription toggles, with Settings visual snapshots.
- **Size**: ~340 LOC App + ~290 LOC Tests.
15. **PR 15: `test(e2e): cross-engine matrix execution and dual-run parity verification against root /`**
- **Scope**: Multi-engine execution (Chromium, Firefox, WebKit) across visual, functional, and synthetic suites, with automated parity checks comparing `/` vs `/ui/v2/`.
- **Tests**: Full 3-tier E2E test matrix and data divergence verification.
- **Size**: ~60 LOC Infra + ~380 LOC Tests.
16. **PR 16: `infra(routing): cut over production traffic from /ui/v2/ to root / and deprecate Lit frontend`**
- **Scope**: Update Cloud Load Balancer / Nginx rules to point `/*` to `frontend-v2` and remove legacy CSR container.
- **Tests**: Live staging and production synthetic canary verification.
- **Size**: ~60 LOC Infra + ~60 LOC Tests.
---
## 12. Orchestrator & QA Expert Review Findings
### Review by Principal QA & Test Engineering Architect
```
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ MANDATORY CI QUALITY GATES PIPELINE │
├───────┬──────────────────────────────┬──────────────────────────────────────────────────────┤
│ Gate │ Quality Check │ Failure Condition (Blocks PR Merge) │
├───────┼──────────────────────────────┼──────────────────────────────────────────────────────┤
│ **1** │ OpenAPI Contract Typings │ Any fixture failing TypeScript `satisfies` check. │
│ **2** │ Hydration Sentinel │ Any `NG0500`–`NG0505` error in Playwright console. │
│ **3** │ Payload Budget Assertion │ SSR HTML > 150KB or `TransferState` > 50KB. │
│ **4** │ Containerized Visual Diffs │ Pixel mismatch > 0.1% on Docker Linux snapshot runs. │
│ **5** │ Dual-Run Parity Verification │ Feature count or data divergence between `/` & `/v2/`│
└───────┴──────────────────────────────┴──────────────────────────────────────────────────────┘
```
#### 1. Compile-Time Type Safety: The `satisfies` Operator
* In the existing codebase ([`e2e/fixtures/types.ts`](file:///usr/local/google/home/jamescscott/code/final/final2/webstatus.dev/e2e/fixtures/types.ts)), casting with `as unknown as` bypasses TypeScript checking entirely.
* **Mandatory Standard**: All fixtures must use the TypeScript `satisfies` operator:
```typescript
export const _typeCheckFeatureDetail =
featureDetailAnchorPositioning satisfies components['schemas']['Feature'];
```
If the backend OpenAPI schema changes, TypeScript immediately fails the build before CI runs.
#### 2. Automated Hydration Sentinel Fixture
* Playwright test runner will wrap all `/ui/v2/` page interactions with a zero-tolerance console listener catching Angular error codes:
* `NG0500`: Hydration node mismatch.
* `NG0501`: Hydration component mismatch.
* `NG0502`: Hydration attribute mismatch.
* `NG0503`: Direct DOM manipulation bypassing Angular.
#### 3. TransferState Size Budgeting
* **Hard Rule**: Keep `TransferState` (``) under **50KB** and total SSR HTML under **150KB**. Never serialize multi-year historical metric runs on the server; load them via client-side `@defer` islands.
#### 4. Soft 404 Prevention
* Missing features (e.g. `/features/invalid-id`) must explicitly set `response.status(404)` on the Node SSR server to prevent Googlebot "Soft 404" SEO penalties.
---
## 13. Summary & Living Documentation
This document serves as the permanent RFC and architectural reference for evaluating the SSR migration. As further exploration takes place, this document will be updated.
Contributor guide
Research direction
No implementation files, tests, or entry points are named. Start by reviewing the current Lit client-side application and Go API boundary described in the RFC, then compare the proposed SSR options and define the architectural decision, measurable criteria, and implementation scope needed for the investigation to be complete.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, go, typescript
- Domain
- backend, frontend, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100