Browser & Frontend Runtime
RSC, per-route strategy, and production observability
A product manager asks: the marketing homepage, the product pages that update pricing a few times a day, the logged-in account dashboard with a slow order-history query, and the checkout flow — should they all use the same rendering strategy? The answer is no, and making the wrong choice costs either TTFB, freshness, INP, or SEO. The correct answer requires a per-route decision, an understanding of RSC’s role, and production telemetry that catches problems before users do.
RSC and SSR are orthogonal axes. React Server Components control which components ever ship JavaScript to the client. A Server Component runs on the server, renders to HTML (or the RSC wire format), and contributes zero bytes to the client bundle — its code never reaches the browser, so it never hydrates. A Client Component ('use client' at the top of the file) does ship code to the client, is SSR’d to HTML for the initial render (if SSR is enabled), and then hydrates. SSR controls when and where the HTML is generated — at request time on the server (SSR), at build (SSG), or on a schedule with caching (ISR). RSC controls how much of that HTML needs to become interactive. A page that is 90% Server Components ships a tiny client bundle and has minimal hydration cost — even with full SSR. The common confusion is thinking RSC replaced SSR; it did not. A well-architected page uses both: Server Components deliver server-computed HTML with zero client cost, and small 'use client' islands handle the genuinely interactive parts.
RSC vs SSR — two independent axes
The per-route decision frame. The correct approach is never one strategy for the whole application. Every route has a different freshness requirement, interactivity demand, and data latency profile — and those three dimensions determine the strategy:
-
Marketing homepage — content is identical for everyone, changes on marketing’s schedule (days, not minutes). Strategy: SSG. Served directly from the CDN edge, TTFB is tens of milliseconds, it costs zero per-request server work, and a new build on marketing’s deploy refreshes it.
-
Product pages — same content for every user, but prices update a few times a day and SEO requires fast LCP. Strategy: ISR with a revalidation interval (e.g. 4 hours) plus on-demand revalidation triggered by the price feed webhook. The page is a cached static document for the overwhelming majority of requests — excellent LCP and SEO — and freshness is bounded: no request waits more than one interval behind a price update.
-
Account dashboard — per-user, must be fresh, but has one slow query (order history). Strategy: streaming SSR. Render per request for freshness, but wrap the order-history query in a Suspense boundary. The shell and fast sections stream immediately (~50 ms TTFB); the order-history boundary streams in when its query resolves. The user sees content fast; the slow query does not block it.
-
Checkout — per-user, correctness is paramount, high interactivity. Strategy: SSR per request. Keep it mostly Server Components with small
'use client'islands for the form fields and payment inputs. The hydration surface stays minimal — only the interactive form parts hydrate, which keeps INP low even under heavy main-thread pressure.
Minimising hydration cost across all routes. The universal principle: push 'use client' as far down the component tree as possible. A layout component, a card, a data table — these are almost always Server Components. Only the leaf that actually needs onClick, useState, or browser APIs needs to be a Client Component. When 'use client' is pushed to leaves, the client bundle shrinks, the hydration pass is tiny, and INP stays comfortably under 200 ms.
lesson.inset.insight
ISR on the edge: each CDN POP holds its own cache entry. On-demand revalidation (Next.js revalidatePath / Vercel’s purge API) invalidates the entry globally — but there is a brief window (seconds to low tens of seconds) during which some POPs serve the old version and others serve the new one. For price-sensitive pages, the bounded staleness of ISR is a conscious trade: all POPs stale by at most one interval, with on-demand revalidation as the escape valve. If true consistency across all POPs at the moment of an update is required, full SSR per request is the only option.
Production observability. A render strategy only works if you know when it breaks. Three categories of signals:
-
TTFB and LCP per route — SSG routes should have TTFB under 50 ms from CDN hits; streaming SSR routes should flush the shell in ~50–100 ms. LCP should be under 2.5 s at p75. These are detectable in field data (CrUX) and synthetic monitoring (Lighthouse CI).
-
INP per page — the hydration long task is the most common INP regression. If INP at p75 exceeds 200 ms on a page that recently added a Client Component or increased bundle size, the hydration cost grew. Correlate with code changes. Real User Monitoring (RUM) tools segment INP by page.
-
Hydration mismatch rate — React logs mismatch warnings in development, but in production they are silent unless you instrument them. Wire
window.addEventListener('error', ...)to catch React’s error boundary reports, or use a framework integration (Next.jsinstrumentation.ts, Sentry’s React plugin) that captures hydration errors and sends them to your error tracker. Gate on CI: zero new hydration-mismatch errors per deploy. A mismatch in production means a CLS event and a re-render — they compound when the page has many components.
Which React DOM server API renders a tree as a stream, flushing the shell first and streaming Suspense boundaries as their data resolves?
Why are RSC and SSR described as orthogonal rather than competing?
An e-commerce product page: same content for every user, prices update 4–6 times a day, must rank in search and load fast. Pick the rendering strategy.
Design the rendering strategy for an e-commerce site: a marketing homepage, category pages, product pages with daily-changing prices, a logged-in account dashboard, and a checkout flow. Optimise first paint, freshness, and interactivity per route.
- Marketing homepage: identical for everyone, changes on marketing's schedule.
- Product pages: same for everyone, price updates a few times a day, must load fast and rank in search.
- Account dashboard: per-user, must be fresh, has one slow 'order history' query.
- Checkout: per-user, correctness over everything, interactive.
- Hydration cost must not block early interactions — INP under 200 ms.
- No hydration-mismatch errors in production.
- Pick the strategy per route by data freshness and interactivity, never one strategy for the whole app.
- SSG for build-frozen content, ISR for bounded staleness, streaming SSR for per-request with a slow dependency.
- Wrap slow data in Suspense boundaries so fast content streams without waiting.
- Minimise hydration: Server Components by default, 'use client' pushed down to small leaves.
- Enforce render determinism and wire hydration-mismatch errors to telemetry + a CI gate.
- 01A page is server-rendered and paints in 400 ms, but users report that buttons do not respond for another ~2 seconds. Explain the mechanism and name three ways to reduce the dead window.
- 02RSC, SSR, and streaming are often conflated. Lay out what each one independently controls, and how a single Next.js App Router page uses all three.
- 03What three categories of production signals should you monitor for a rendering strategy, and what is the CI gate for hydration mismatches?
RSC and SSR solve different problems on different axes. SSR controls when HTML is generated (build, request, cached interval); RSC controls which components ship JavaScript at all — Server Components contribute zero client bytes and never hydrate. The combination: mostly Server Components keeps the bundle small and hydration cost low, SSR or streaming delivers fast TTFB and LCP, and small 'use client' leaves hydrate quickly. The per-route decision frame: marketing content → SSG; shared content with bounded staleness → ISR with on-demand revalidation; per-user with a slow dependency → streaming SSR with Suspense around the slow query; per-user high-interactivity → SSR with minimal 'use client' surface. Push 'use client' to the smallest leaf that actually needs it. In production: monitor TTFB and LCP per route, INP per page, and hydration-mismatch rate with a CI gate of zero new mismatches per deploy. Hydration is a statefulness question — the server captured a state snapshot that the client must reconstruct identically; every strategy here is a different decision about which state is frozen when and what it costs to carry it across the wire.
appears again in145
- Why GraphQL gets N+1junior
- DataLoader mechanics: tick-boundary batchingmiddle
- Batch function contracts: ordering, shapes, errorsmiddle
- Federation and lookahead: batching beyond DataLoadermiddle
- Query complexity defences: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling contract, tenant isolation, observabilitysenior
- Why idempotency: making retries safejunior
- Server-side state machine: four states of an idempotency keymiddle
- Outbox and inbox: effectively-once across the dual-write boundarymiddle
- Concurrency and cache architecture for idempotency at scalesenior
- Observability, production failures, and global-scale designsenior
- Seeing the system: RED metrics, the p99 tail, and breaker statesenior
- Production readiness: the launch checklist that is the whole tracksenior
- What is a cache stampede and why it makes things worsejunior
- Lock and single-flight: bounding concurrent rebuildsmiddle
- XFetch: coordination-free probabilistic early expirationmiddle
- Stale-while-revalidate and CDN request coalescingmiddle
- Detecting stampedes and designing TTL for productionmiddle
- Metastable failure, fencing tokens, and production postmortemssenior
- What a relation is: tables, rows, keys, and constraintsjunior
- Constraints, keys, and Postgres data typesmiddle
- Normal forms, denormalization, and why schemas stickmiddle
- JSONB, arrays, and when a side table winsmiddle
- Heap storage, TOAST, and column alignmentsenior
- Schema integrity: deferral, versioning, and production failure modessenior
- Relational vs document, wide-column, graph, and key-valuesenior
- Index-only scans, the Visibility Map, and INCLUDEsenior
- Production failure modes and the index audit playbooksenior
- pg_statistic, ANALYZE, and production observabilitymiddle
- Production failure modes and plan stabilitysenior
- MVCC: why readers and writers never wait for each otherjunior
- Row versions and snapshots: the on-disk mechanicsmiddle
- HOT updates and isolation levels: what you gain and what you paymiddle
- Vacuum and bloat: keeping the storage tax boundedmiddle
- CLOG, XID wraparound, and MultiXact: deep visibility internalssenior
- SSI internals and production autovacuum tuningsenior
- Real-world MVCC failures, deployment patterns, and distributed snapshotssenior
- Connection pools: amortising the cost of a Postgres backendjunior
- PgBouncer session, transaction, and statement modesmiddle
- Pool sizing: the (cores × 2) + spindles formula and the two-layer stackmiddle
- Pool exhaustion and idle-in-transaction: the 3 AM failure modemiddle
- Migrating to transaction mode: rollout playbook and PgBouncer 1.21 prepared statementsmiddle
- The Postgres process model and why raising max_connections degrades throughputsenior
- Pooler landscape 2026, serverless connection storms, and the full failure-mode taxonomysenior
- What a schema migration is and why it replaces ad-hoc DDLjunior
- ADD COLUMN: instant in PG 11+ vs rewrite in older Postgresjunior
- The lock-queue failure mode: why instant DDL can freeze the databasemiddle
- Safe DDL patterns: NOT VALID, CONCURRENTLY, and unsafe-op fixesmiddle
- Expand-contract: zero-downtime for breaking schema changesmiddle
- Advisory locks, migration tools, and deploy coordinationsenior
- Migration failure taxonomy and production disciplinesenior
- Why sharding exists: the single-Postgres ceilingjunior
- Shard-key selection: hash, range, list, and directory strategiesmiddle
- Partitioning vs sharding: same word, two different thingsmiddle
- Co-location and Citus: the invariant that makes sharding usablemiddle
- The hot-shard failure mode: detection, isolation, and durable policymiddle
- Schema-based sharding and multi-tenancy alternativessenior
- Online resharding, 2PC, and the operational cost of shardingsenior
- The seven acts: from CREATE TABLE to Citusjunior
- Acts 1–3 in depth: schema, indexes, and planner statisticsmiddle
- Acts 4–6 in depth: MVCC bloat, connection pooling, and safe migrationsmiddle
- Act 7 in depth: sharding, co-location, and the seven-tier tradeoff cascademiddle
- Observability, anti-patterns, and production triagesenior
- Raft roles, terms, and why majority quorums prevent split brainjunior
- How Raft replicates a log entry and decides it is safe to commitmiddle
- Raft leader election: timeouts, voting rules, and the four safety propertiesmiddle
- Raft in the real world: partitions, slow disks, and client routingmiddle
- Raft extensions: pre-vote, learners, snapshots, and linearizable readssenior
- Raft in production: membership changes, Multi-Raft, and observabilitysenior
- Where data fetching happens — and why it decides LCPjunior
- Fetch waterfalls — diagnosis and the Promise.all curemiddle
- React Server Components and Suspense streamingmiddle
- Client-side cache: TanStack Query, SWR, and stale-while-revalidatemiddle
- LCP, prefetch, and race conditions in interactive fetchingmiddle
- Senior internals: RSC payload, caching layers, and production failure modessenior
- The three-way handshakejunior
- Sequence numbers and connection statemiddle
- DNS: what it does and why it existsjunior
- The resolver walk: referrals, record types, and gluemiddle
- TTL, caching, and DNS propagationmiddle
- The 1-RTT handshake: key shares and ECDHEmiddle
- Session resumption and 0-RTTmiddle
- WebSocket: the HTTP upgrade handshakejunior
- WebSocket frame format: opcodes, masking, fragmentationmiddle
- WebSocket backpressure: when clients can''''t keep upmiddle
- Reconnection: jittered backoff, thundering herd, message resumptionsenior
- WebSocket at scale: HTTP/2 multiplexing, permessage-deflate, C10Msenior
- WebSocket in production: proxies, security, and distributed architecturesenior
- What reverse proxies dojunior
- Health checks, connection draining, and slow startmiddle
- Session affinity, consistent hashing, and the right fixmiddle
- Retry storms, circuit breakers, and load sheddingsenior
- Resilient LB architecture: anycast, zone-aware routing, and observabilitysenior
- Why QUIC and not TCP+TLSjunior
- Connection IDs and network migrationmiddle
- 0-RTT resumption and packet encryptionsenior
- DDoS: what it is and why it worksjunior
- Amplification attacks and state exhaustionmiddle
- Rate limiting: algorithms and architecturemiddle
- WAFs, firewalls, mTLS, and HSTSmiddle
- DNS cache poisoning and BGP hijackingsenior
- Defense-in-depth architecture and attack economicssenior
- DNS, TCP, TLS in sequence: where the milliseconds gomiddle
- Proxy intercepts and security gates: rate limiters, WAF, mTLSmiddle
- Alternate paths: QUIC 0-RTT, WebSocket upgrade, connection migrationmiddle
- Observability: distributed traces, USE/RED, and samplingsenior
- Resilience: cascading retries, circuit breakers, and error budgetssenior
- What the three signals are: logs, metrics, and tracesjunior
- Why structured logs exist: the diary vs the spreadsheetjunior
- The production log schema: fields every line must carrymiddle
- PII redaction and log injectionsenior
- OTel Logs Data Model and audit logs as a subsystemsenior
- SLI, SLO, and the error budget: reliability by the numbersjunior
- Error budget policy, latency SLOs, and composite journeysmiddle
- Production SLO failures, self-observability, security, and the big picturesenior
- The incident loop: from pager to postmortem to preventionmiddle
- Cache lines, struct layout, and false sharingmiddle
- SIMD, SoA vs AoS, and memory bandwidthmiddle
- Cache-oblivious algorithms, PGO, and production failuressenior
- GC in production: observability, security, edge cases, and fleet governancesenior
- Batching: amortize fixed cost per operationjunior
- The batching window: size and wait timemiddle
- Batching in Kafka and Postgresmiddle
- io_uring and observability of batchingmiddle
- From Nagle to io_uring: evolution of batchingmiddle
- Backpressure, failure isolation, and batch security in productionsenior
- CI enforcement and RUM: making budgets stickmiddle
- V8 JIT pipeline, HTTP priorities, and bundle securitysenior
- The performance loop: discipline, not a projectjunior
- Classify and fix: matching bottleneck families to remediesmiddle
- Observability stack and CI gates: catching regressions before they shipmiddle
- Incident to enforcement: SLO burn to verified fix in 35 minutesmiddle
- Culture, economics, and org-scale performancesenior
- At-most-once, at-least-once, exactly-once: the three delivery contractsjunior
- The three failure legs — where duplicates and losses actually happenmiddle
- Consumer-side dedup: the cheapest path to exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer and transactionsmiddle
- SQS visibility timeout, DLQ, and the outbox patternmiddle
- Exactly-once in production: impossibility proof, hybrid patterns, and real incidentssenior
- What OAuth is and why passwords are not the answerjunior
- Authorization code flow with PKCEmiddle
- ID token validation and JWKS cache managementmiddle
- Refresh token rotation and scope-based least privilegemiddle
- Sender-constrained tokens: DPoP and mTLSsenior
- OAuth in production: audience attacks, observability, and real failuressenior