System Design Cases
SPA vs SSR vs SSG vs RSC
SPA vs SSR vs SSG vs ISR vs RSC streaming — где и когда мы превращаем данные в HTML. Build-time → CDN edge → origin Node → client browser. Trade-offs: TTFB, SEO, dynamic data freshness, infra cost. 5 сценариев: SPA blank-then-fill, SSR per-request render + hydrate, SSG/ISR pre-built на CDN, RSC streaming через Edge с Suspense, anti-pattern (SPA для marketing landing). 2 ADR: дефолтный выбор в 2026 (Next.js App Router + RSC) и правило близости рендеринга к user.
SPA, SSR и static rendering: выбор по маршруту
CSR, SSR и static rendering описывают место и момент получения начального HTML, а не три взаимоисключающих типа продукта. Один сайт может предварительно сгенерировать документацию, рендерить персональную страницу на запросе и после загрузки выполнять клиентские переходы.
SSR или prerendering не отменяют JavaScript автоматически: интерактивный UI может гидратироваться и занять main thread. Поэтому решение принимают по freshness, personalization, cacheability, resilience и client-work budget каждого маршрута.
Проверяемые утверждения
- SSR формирует HTML по запросу; static rendering формирует HTML заранее; CSR строит DOM в браузере. Гибриды и streaming меняют границы, но не отменяют эти определения.
- Hydration связывает клиентскую логику с уже созданным HTML и требует согласованного начального снимка. Большой hydration workload может ухудшить responsiveness даже при раннем контенте.
- Персонализированный ответ нельзя помещать в shared cache без корректного cache key и политики приватности. Наличие CDN само по себе не делает SSR безопасно кэшируемым.
- Rendering mode не гарантирует SEO, Core Web Vitals или доступность: итог зависит от HTML semantics, bytes, main-thread work, network path и реальных пользователей.
Границы и компоненты
| Компонент | Роль в модели |
|---|---|
| Browser Navigation | navigation and assets; execute interactive code; sampled field metrics |
| Edge Cache and Route Policy | navigation and assets; static miss; dynamic miss |
| Versioned Static Output | static miss |
| Request-time Renderer | dynamic miss; request-scoped data |
| Authorized Data API | request-scoped data; client data request |
| Client Runtime and Hydration | execute interactive code; client data request |
| Real-user Telemetry | sampled field metrics |
Сценарии
Serve an immutable static route
A versioned documentation route is generated before traffic and served through the edge. A miss reads immutable output; the response can be shared because it has no user-specific state.
Проверяемый исход: The cache key names a deploy version, invalidation is explicit, and origin availability is not required for an edge hit.
Render a personalized request safely
An authenticated dashboard bypasses shared HTML caching. The request-time renderer fetches authorized data and returns private HTML with a request-scoped initial snapshot.
Проверяемый исход: No other user can receive the personalized response, and downstream deadlines bound request-time fan-out.
Hydrate without duplicating authority
The browser receives server-created HTML, then the client runtime attaches interaction using the same serialized initial version. Later server data remains server-owned and is revalidated explicitly.
Проверяемый исход: The first client snapshot matches the HTML, event handlers become active, and stale server data is not silently promoted to durable client truth.
Measure the route-level trade-off
Field telemetry separates TTFB, content rendering and interaction delay. The team changes one route only after comparing cache hit rate, transferred JavaScript and main-thread work by device class.
Проверяемый исход: The rendering decision is based on measured distributions and correctness constraints rather than a universal SPA-versus-SSR slogan.
Failure, concurrency и evolution checklist
- Bound request-time rendering with deadlines and explicit degraded output; do not let one optional API hold the entire HTML indefinitely.
- Version static assets and HTML coherently. A new HTML shell referencing removed chunks creates a deploy race even when both were cached successfully.
- Treat hydration mismatch as a correctness defect: locale, time, random values and request data need a deterministic initial snapshot.
- Test origin outage, CDN bypass, stale cache, disabled JavaScript and slow main-thread devices independently.
Security и privacy
- Private or authorization-dependent HTML requires private/no-store policy or a proven partitioned cache key; never vary only on an untrusted client hint.
- Serialize initial data with an escaping strategy that cannot terminate its script/data context, and enforce output encoding plus CSP as defense in depth.
- Rendering on a server does not make downstream authorization optional. The data API still authorizes the subject, tenant and object.
Метрики, формулы и допущения
origin_render_rps = navigation_rps × (1 − safe_cache_hit_ratio)for the measured route and cache policy.build_time ≈ generated_routes × mean_render_seconds / effective_parallelism; validate memory and API quotas before assuming linear parallel speed-up.- Navigation latency is a path distribution, not one constant: record TTFB, content paint and interaction delay separately. A lower TTFB does not prove a lower LCP or INP.
client_cost = transfer_bytes + parse_compile_work + execute_render_work; bytes and CPU use different units and must not be added into one synthetic number.
Числа и bounds выше действуют только при названных units, population и assumptions. Ни паттерн, ни browser API сами по себе не задают SLA, capacity или correctness.
Решения для production review
- Classify every route by personalization, freshness, cache scope, interactivity and acceptable stale behavior.
- Prefer generated/cacheable HTML for stable public content, request-time rendering where request context is essential, and client rendering for interaction that truly needs browser state.
- Keep the choice reversible with route-level boundaries and field telemetry; do not force a single rendering mode across the product.
Первичные и официальные источники
- https://web.dev/articles/rendering-on-the-web
- https://web.dev/articles/defining-core-web-vitals-thresholds
- https://www.rfc-editor.org/rfc/rfc9111.html
Scope note
Диаграмма показывает delivery и hydration boundaries. Она не моделирует конкретный framework, React Server Components, edge vendor или поисковый ranking algorithm и не обещает performance автоматически.