How a web page actually shows up.
Live demo: rendermodes.com
An interactive walkthrough of how a page reaches the screen, in two modules:
- The browser rendering lifecycle — twelve animated phases from URL bar to interactive page.
- Next.js rendering modes — six live demos where each route actually uses the technique it explains, so you can watch timestamps freeze on SSG, change per request on SSR, regenerate every 10s on ISR, and so on.
Twelve phases, in order:
address → connection → http → html-parsing → css-parsing → javascript → dom → render-tree → layout → paint → composite → hydration
Each phase has its own animated diagram and a dedicated route under /lifecycle/[phase]. The phase definitions live in app/_lib/phases.tsx; the per-phase animations are in app/lifecycle/[phase]/_components/.
Six routes, each demonstrating one strategy:
| Route | What it shows |
|---|---|
/rendering/csr |
Client-side rendering — the server ships an empty shell, the browser fetches the data and paints. |
/rendering/ssr |
Server-side rendering — the page is rendered fresh on every request. |
/rendering/ssg |
Static generation — rendered once at build time, frozen forever. |
/rendering/isr |
Incremental static regeneration — static, but regenerated every 10 seconds. |
/rendering/rsc |
React Server Components — server-only components, with "use client" islands. |
/rendering/hydration |
Hydration — HTML lands first, JS attaches event listeners afterward. |
The CSR demo is backed by app/api/now/route.ts.
- Next.js 16.2.4 (App Router, Turbopack)
- React 19.2.4
- Tailwind CSS v4 — theme tokens in
@theme inlineinside app/globals.css; notailwind.config.js. - TypeScript (strict)
cacheComponents: trueis enabled in next.config.ts. Everything is dynamic by default; static content is opted in with'use cache'+cacheLife(). Route segment configs (export const dynamic,revalidate, etc.) don't apply under this flag.
Requires Node 20 (matching CI).
npm install
npm run devOpen http://localhost:3000.
Caveat:
next devbypasses caching, so the SSG and ISR demos won't show their distinguishing behaviour againstnpm run dev. To actually see timestamps freeze on SSG and tick every 10s on ISR, run a real production build:npm run build && npm start
| Command | What it does |
|---|---|
npm run dev |
Dev server (caching bypassed). |
npm run build |
Production build. |
npm start |
Serve the production build. |
npm run lint |
ESLint. |
npm run typecheck |
tsc --noEmit. |
npm run format / npm run format:check |
Prettier write / check. |
npm run test:e2e |
Playwright across all profiles. |
npm run test:e2e:dev |
Playwright against the dev server. |
npm run test:e2e:prod |
Playwright against a production build. |
npm run test:e2e:ui |
Playwright UI mode (dev profile). |
Playwright e2e suites live in e2e/, with two profiles:
- dev — runs against
next dev. Fast feedback, used on every PR. - prod — runs against
npm run build && next start. The webServer is started by Playwright. This is the only profile where SSG/ISR behave realistically, so the slower wall-clock tests live here.
Suites cover smoke, rendering (the six modes), lifecycle (phase navigation), a11y (via @axe-core/playwright), prefs (preference handling), and failure (error paths).
Two GitHub Actions workflows in .github/workflows/:
ci.ymlruns on every PR and onmainpushes:quality— typecheck, lint, prettier check.build— full Next.js build. The build is the gate for the class ofcacheComponentsprerender errors that neither tsc nor eslint catch.e2e-dev— Playwright (dev profile).
nightly.ymlruns daily at 06:00 UTC (and onworkflow_dispatch):e2e-prod— Playwright (prod profile) against a real production build, kept off the PR path so the slow ISR wall-clock test doesn't gate every push.
app/
page.tsx landing
lifecycle/ module 1: 12 prerendered phase pages
[phase]/
_components/ per-phase animations
rendering/ module 2: hub + six rendering-mode routes
csr/ ssr/ ssg/ isr/ rsc/ hydration/
api/now/ dynamic JSON for the CSR demo
_components/ _lib/ app-wide shared UI and helpers
e2e/ Playwright suites and fixtures
.github/workflows/ ci.yml, nightly.yml
Project-specific conventions — Suspense boundaries under cacheComponents, params/searchParams as Promises, no request-time APIs inside 'use cache', pure SVG/CSS animations, prefers-reduced-motion honoured globally, Husky pre-push that runs typecheck + lint + build — are documented in AGENTS.md.
This is a personal showcase project. Issues are disabled and PRs are not actively reviewed, feel free to fork if you want to build on it. Security reports are welcome via SECURITY.md.
Released under the MIT License.