name: rendering-models
# description: routing-facing summary of when this skill activates and what it covers.
description: "Use when reasoning about how a web UI is produced and delivered: client-side rendering, server-side rendering, static-site generation, incremental static regeneration, React Server Components, streaming SSR, edge rendering, and partial prerendering. Covers the time × place grid (build/request/stream/interaction × server/edge/client), the trade-offs between first-paint latency and time-to-interactive, the relationship between rendering and hydration, and how a route's content profile (dynamic / static / personalized) maps to a model. Do NOT use for organizing the frontend codebase (use frontend-architecture), the serialization frontier between server and client code (use client-server-boundary), the wire protocol itself (use http-semantics), or specific deploy-platform composition patterns (use vercel-composition-patterns)."
# license: SPDX-compatible license identifier for the skill content.
license: MIT
allowed-tools: Read Grep
# metadata: Skill Metadata Protocol fields encoded under Agent Skills-compatible frontmatter.
metadata:
# === v8 Classification (subject + public; polyhierarchy via subjects[]) — see ADR-0017 ===
# subject: primary browse shelf — what the skill teaches. One of twelve closed values:
# backend-engineering / frontend-engineering / software-architecture / data-engineering / agent-ops / ai-engineering /
# quality-assurance / design / reasoning-strategy / software-engineering-method / knowledge-organization / product-domain.
subject: frontend-engineering
# public: publishability/private-data gate. Boolean.
# true = publishable/shareable; false = private and excluded from public export.
# Project anchoring is carried separately by non-empty `project[]` plus `grounding`.
public: true
# scope: free-text PRD-style statement of what the skill teaches and what it excludes.
# (v8 required; not an enum). Mirrors Coverage + Do NOT Use When at frontmatter level.
scope: "Reasoning about how a web UI is produced and delivered across the time × place grid (build / request / stream / interaction × server / edge / client): the named rendering models (CSR, SSR, SSG, ISR, RSC, streaming SSR, edge SSR, PPR), their trade-offs across FCP/LCP/TTI/INP, server cost, cache behavior, and crawlability, the per-route mapping from a content profile (static / dynamic / personalized) to a model, and the relationship between rendering and hydration. Portable across any web framework; principle-grounded, not repo-bound. Excludes frontend codebase organization (frontend-architecture), the server/client serialization frontier (client-server-boundary), HTTP wire semantics (http-semantics), and deploy-platform composition patterns (vercel-composition-patterns)."
# taxonomy_domain: optional hierarchical sub-path within `subject`. Slash-delimited
# lowercase kebab-case segments. rename of the original v8 `domain`. Remove when the flat
# `subject` is sufficient.
taxonomy_domain: engineering/frontend
# stability: lifecycle marker. One of:
# experimental (active development) / stable (production-ready) /
# frozen (no further changes expected) / deprecated.
# When `deprecated`, schema's allOf REQUIRES `superseded_by: <real-skill-name>`.
stability: experimental
# keywords: semantic phrases for fuzzy router activation. v8 cap: max 10.
# Keep terms a user would actually type when starting a task in this skill's domain.
keywords: ["rendering model","CSR","SSR","SSG","ISR","React Server Components","RSC","streaming SSR","edge rendering","partial prerendering"]
# triggers: explicit-match activation phrases the router fires on literally.
# Use when label-based routing is intended; usually keywords + examples are enough.
triggers: ["should this page be server-rendered","static or dynamic","what's the difference between SSR and RSC","why is this page slow to first paint","should this be a client component"]
# examples: 2-5 realistic user prompts the skill SHOULD activate for.
# Written in the user's voice. Improves retrieval recall beyond keywords alone.
examples: ["decide whether a product page should be SSG with revalidation or SSR","explain why a marketing page is fast but a dashboard is slow despite both 'server rendering'","choose between streaming SSR and a loading skeleton","diagnose why a server component re-renders on every navigation"]
# anti_examples: near-miss prompts that should route ELSEWHERE.
# Pair with relations.suppresses (or legacy boundary alias) to name the confusable territory's owner.
anti_examples: ["organize the folder structure of a frontend codebase (use frontend-architecture)","decide what types can cross the network boundary (use client-server-boundary)","design HTTP cache headers (use http-semantics)"]
# relations: typed graph edges to sibling skills. Current fields:
# related (adjacency for browse / co-routing expansion) /
# suppresses (exclude listed skills from co-routing when THIS skill wins; write reason
# as "I own this exclusively over X", not "use X instead") /
# boundary (DEPRECATED alias of suppresses, retained for unmigrated skills) /
# verify_with (cross-check; co-loaded as one-hop expansion) /
# depends_on (composition; transitive — A→B→C loads all three) /
# broader / narrower (SKOS-style generalization) /
# disjoint_with (mutual exclusion for incompatible ownership).
relations: "{\"related\":[\"frontend-architecture\",\"client-server-boundary\",\"http-semantics\",\"performance-engineering\",\"vercel-composition-patterns\"],\"suppresses\":[{\"skill\":\"frontend-architecture\",\"reason\":\"frontend-architecture owns how the codebase is organized; rendering-models owns where and when the UI is produced. A route's architecture and its rendering model are independent decisions.\"},{\"skill\":\"client-server-boundary\",\"reason\":\"client-server-boundary owns the serialization frontier (what can cross between server and client code). rendering-models owns the staging of work across build/request/stream/interaction.\"}],\"verify_with\":[\"performance-engineering\",\"frontend-architecture\"]}"
# === Understanding fields (when comprehension_state: present) ===
# mental_model: the primitives of the concept and how they relate. One paragraph.
mental_model: |
A rendering model is a staging decision on a two-axis grid: WHEN the work of producing the UI happens (build / request / stream / interaction) × WHERE it executes (server / edge / client). The named models (CSR, SSR, SSG, ISR, RSC, streaming SSR, edge SSR, PPR) are particular cells or paths through this grid. Each cell makes different trade-offs across four user-facing performance numbers (FCP, LCP, TTI, INP), three operational properties (server cost, cache behavior, crawlability), and one content profile (static / dynamic / personalized). The model choice is per-route, not per-application — a site picking one model for everything will be wrong for most of its routes.
# purpose: the problem this concept solves and why the field exists. One paragraph.
purpose: |
Replaces "framework default" with deliberate trade-off navigation. Without rendering-models as a discipline, every route uses whatever default the framework picks (often SSR), which makes marketing pages slow (paying SSR cost for content that never changes), dashboards slow-to-interactive (paying full HTML + hydration cost for personalized content), and admin panels expensive (paying request-time server cost for routes hit once a week). The alternative — "just SSR everything" — fails because the four performance numbers respond differently to each model, and the right choice for a marketing page (SSG) is the wrong choice for a search-results page (SSR or streaming SSR). The discipline is to read each route's content profile and pick the model whose trade-offs match.
# concept_boundary: what this concept is NOT. Distinguishes from adjacent skills by naming
# the MECHANISM that differs, not just the label. Canonical replacement for top-level `boundary`.
concept_boundary: |
Distinct from frontend-architecture, which owns how the codebase is organized (folder structure, routing convention, build config) — rendering-models owns where and when the UI is produced, an orthogonal decision. Distinct from client-server-boundary, which owns the serialization frontier (what can cross between server code and client code) — rendering-models owns the staging of work across the build/request/stream/interaction timeline; the two compose (every model has a boundary). Distinct from http-semantics, which owns the wire protocol (caching headers, status codes, content negotiation) — rendering-models is upstream, deciding what HTML and JS exist; http-semantics decides how they are delivered and cached. Distinct from performance-engineering, which owns measurement and optimization across the stack — rendering-models is one input that bounds what performance numbers are achievable.
# analogy: one-sentence metaphor preserving the core mechanism.
analogy: "Rendering models are to web pages what cooking styles are to restaurant kitchens — the same ingredients (data, components, markup) get plated differently depending on whether the kitchen pre-cooks at dawn (SSG), cooks to order during service (SSR), streams courses out as they finish (streaming SSR), or hands raw ingredients to the diner to assemble themselves (CSR), and no one style is right for every menu item."
# misconception: the wrong mental model people bring; corrected explicitly.
misconception: |
The wrong mental model is that "SSR vs CSR" is the complete dichotomy. It is not. The space is at least 6-8 named models on a two-axis time × place grid, and the meaningful comparisons are not SSR-vs-CSR but rather SSG vs ISR vs SSR for a dynamic catalog route, streaming SSR vs RSC for a slow-data dashboard, PPR vs full SSR for a marketing-plus-personalization mix. Collapsing the space to a binary makes the trade-off invisible — a team that thinks they're "doing SSR" might actually want SSG (static content), RSC (server-only data, less client JS), or streaming SSR (slow data with a fast shell), and the wrong choice is invisible until measured.
Rendering Models
Concept of the skill
A rendering model is the strategy by which a web user interface is produced and delivered, defined by two axes that together form a grid: WHEN the work happens (build time, request time, response stream, or user interaction) and WHERE it executes (server, edge, or client). The full work of producing a UI is constant — interpret data, compose a component tree, emit DOM, attach behavior — but moving each step between those time-and-place cells has dramatic consequences for what the user sees first, how the server scales, whether the content is crawlable, and how soon the page becomes interactive. The named models in current use (CSR, SSR, SSG, ISR, RSC, streaming SSR) plus two recent additions (edge SSR, PPR) are particular cells or paths through this grid, each making a different bargain across four user-facing performance numbers (FCP, LCP, TTI, INP) and three operational properties (server cost, cache behavior, crawlability). The decisive insight is that the choice is per-route, not per-application: a site that picks one model for everything is wrong for most of its routes, because a marketing page (static content), a search-results page (per-query dynamic content), and a logged-in dashboard (personalized content) each have a different content profile that maps to a different model. This skill exists to make those trade-offs legible — to read each route's content profile and pick the model whose bargain matches — rather than to crown a single winner.
Coverage
The taxonomy of how a web user interface is produced and delivered. Covers the time × place grid (build / request / stream / interaction × server / edge / client), the six named models in current use (CSR, SSR, SSG, ISR, RSC, streaming SSR) plus two recent additions (edge SSR, PPR), their trade-offs in FCP, LCP, TTI, INP, server cost, and crawlability, and the relationship between rendering and the downstream concerns of bundling, hydration, and HTTP delivery.
Philosophy of the skill
A rendering model is a staging decision: at what moment, and in what location, does the work of producing the UI happen. The full work is the same — interpret data, compose a component tree, emit DOM, attach behavior — but moving each step between build, request, stream, and interaction has dramatic consequences for what the user experiences first, how the server scales, and whether the content is crawlable.
The model choice is per-route, not per-application. A site that picks one model for everything will be wrong for most of its routes. The correct mental model is a grid of trade-offs, with each route landing at the position that matches its content profile (static / dynamic / personalized) and its performance constraints (FCP / TTI / cost).
The goal of this skill is to make the trade-offs legible, not to pick a winner. The right model in 2026 for a marketing page is not the right model for a logged-in dashboard, and neither is the right model for a streaming chat UI.
The Time × Place Grid
The grid below positions the named models on the two-axis space. Read each cell as "work happens at this time, in this place."
| Server (origin) | Edge | Client |
|---|
| Build | SSG | SSG (with edge cache) | — |
| Request | SSR, RSC | Edge SSR, Edge RSC | — |
| Stream | Streaming SSR, PPR | Edge streaming | — |
| Interaction | — | — | CSR, hydration |
The Client column is sparse because client-only models are rare in production — most pages mix at least one server-produced step (HTML or RSC payload) with client interaction.
Trade-off Profile
The four user-facing performance numbers respond differently to each model.
| Model | FCP | LCP | TTI | INP | Server cost | Crawl-friendly |
|---|
| CSR | Worst — empty shell | Worst — depends on client fetch | Worst — full JS execute | Worst — depends on bundle | Lowest | Conditional (depends on crawler JS support) |
| SSR | Good — HTML served | Good | Worse — hydration cost | Worse if bundle is large | Highest | Yes |
| SSG | Best — CDN cache | Best | Worse — hydration cost | Worse if bundle is large | Lowest at request | Yes |
| ISR | Best (hot) / Good (cold) | Best (hot) / Good (cold) | Worse — hydration cost | Worse if bundle is large | Low (background regen) | Yes |
| RSC | Good — server tree | Good | Better — less client JS | Better — less client JS | High | Yes |
| Streaming SSR | Excellent — shell first | Good | Better — interactive in chunks | Same as SSR | High | Yes |
| Edge SSR | Excellent — proximity | Good | Same as SSR | Same as SSR | Medium | Yes |
| PPR | Excellent — static shell | Good — streamed | Better | Better | Low for shell, high for dynamic | Yes |
"Worse if bundle is large" means hydration cost dominates: the model produced HTML quickly, but the page is not interactive until the client JS loads, parses, and executes.
When to Choose Each Model
A heuristic matrix. Use it as a starting point; always validate with real measurements.
| Route profile | First choice | Second choice |
|---|
| Marketing page (rarely changes, no personalization) | SSG | ISR if content updates daily |
| Blog post (occasional updates) | SSG with on-demand revalidation | ISR with TTL |
| Product detail (catalog + inventory) | ISR or PPR | SSR if catalog changes hourly |
| Search results (per-query) | SSR or streaming SSR | Edge SSR if global users |
| Logged-in dashboard (per-user data) | SSR or RSC | Streaming SSR if data is slow |
| Real-time chart (high update rate) | CSR with server data | RSC + client island for the chart |
| Admin panel (rarely visited, personal) | RSC | SSR |
| Documentation site (static content + search) | SSG + client search | PPR if search is server-side |
Hydration — The Cost the Model Cannot Hide
Every model except pure CSR-from-scratch produces HTML that the client must hydrate to be interactive. Hydration walks the existing DOM, reconciles it against the React (or other framework) component tree, and attaches event handlers. The cost is:
- Proportional to the number of components.
- Paid before the page is interactive.
- Visible in INP and TTI metrics.
Strategies that reduce hydration cost:
- RSC — server components are omitted from the client bundle; only client components hydrate.
- Islands architecture (Astro, Marko, Qwik) — only the marked-interactive parts hydrate; the rest is static HTML forever.
- Progressive hydration — hydrate components as they enter the viewport or as the user interacts.
- Resumability (Qwik) — the framework serializes the application state into HTML so the client never needs to re-execute initialization code.
A page that uses any model with a 2MB client JS bundle will hydrate slowly regardless of how its HTML was produced. The HTML production speed and the hydration cost are independent.
Edge Rendering
Edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy, Fastly Compute@Edge) move SSR closer to the user. The trade-off:
- Pro — geographic proximity reduces TTFB. Cold starts are faster than serverless functions (often single-digit ms).
- Con — the runtime is constrained: typically Web Standard APIs (fetch, Request, Response, streams) but not full Node.js. Direct database connections are usually unavailable; data access happens via HTTP to an origin.
- Hybrid — edge for the rendering step, origin for the data step. Works well when the data fetch is cacheable; less well when every request requires a fresh database round-trip from the edge.
Edge SSR is most useful when (1) the audience is geographically distributed, (2) per-request rendering is needed, (3) the data layer is HTTP-accessible.
Partial Prerendering (PPR)
PPR is the most recent addition to the taxonomy. It produces:
- A static shell at build time (the parts of the page that don't change per request).
- Streamed dynamic content at request time (the parts that do — user data, personalized blocks, inventory).
The user sees the shell instantly (cache-served), and the dynamic holes fill in via streaming. The model is well-suited for routes where most of the layout is static but small regions are personal (a product page with a "recommended for you" block, a dashboard wrapper with per-user widgets).
PPR is currently first-class in Next.js (App Router); the underlying pattern is general and can be implemented in any framework with streaming SSR and a CDN.
Verification
After applying this skill, verify:
- [ ] The rendering model for each route is documented (per route, not per app).
- [ ] The choice matches the route's content profile — static content uses build-time models; per-user content uses request-time models.
- [ ] FCP, LCP, INP are measured for representative routes in real-user conditions (not lab-only Lighthouse scores).
- [ ] Hydration cost is acknowledged separately from rendering cost — a "fast SSR" page with a 1MB bundle is not actually fast for the user.
- [ ] Routes mixing server and client work use the appropriate marker (
'use client' / 'use server' in React + Next.js, or the equivalent in the chosen framework). - [ ] SSG and ISR caches are validated to invalidate correctly on content updates (stale-while-revalidate behavior matches expectations).
- [ ] Edge-rendered routes confirm their data access path works at the edge (HTTP-accessible, not direct DB).
Do NOT Use When
| Instead of this skill | Use | Why |
|---|
| Organizing the frontend codebase folder layout | frontend-architecture | frontend-architecture owns module boundaries and component layering; rendering-models owns where and when the UI is produced |
| Deciding what types and values can cross between server and client code | client-server-boundary | client-server-boundary owns the serialization frontier and marker directives |
| Designing HTTP caching headers, status codes, or content negotiation | http-semantics | http-semantics owns the wire protocol; rendering-models is upstream |
| Setting performance thresholds and failure consequences | performance-budgets | performance-budgets owns the threshold-and-consequence contract; rendering-models is one input to what budgets are achievable |
| Profiling a specific slow page and deciding what to fix | performance-engineering | performance-engineering owns the diagnostic and optimization activity |
| Composing build pipelines, deploy configs, or platform-specific features | vercel-composition-patterns | platform composition is downstream of model choice |
Key Sources
- React team. React Server Components RFC. Proposed Dec 2020; integrated into React 18+ and Next.js App Router. The canonical specification of RSC's component-tree serialization model.
- Vercel. Next.js App Router documentation — Rendering. Reference for how the named models map to the framework's primitives.
- Google Chrome Team. web.dev — Rendering on the Web. Jason Miller and Addy Osmani's matrix of CSR / SSR / SSG / pre-rendering / streaming — the foundational document for the taxonomy.
- Remix team. Remix loaders and the server-first model. Reference for the request-time data-loading pattern that informs SSR design beyond React.
- Astro team. Astro Islands. The canonical statement of islands architecture and selective hydration.
- Google. Core Web Vitals. The user-experience metrics (LCP, INP, CLS) that the model choice directly affects.
- Google. The RAIL Performance Model. Older but still load-bearing: the interaction-class taxonomy (Response / Animation / Idle / Load) that frames why TTI matters.
- Misko Hevery. "Hydration is Pure Overhead". 2022 essay arguing that hydration cost is the dominant factor in TTI for modern SSR — motivates resumability and islands as alternative architectures.