🥾 Trailhead — One Site, Six Ways

The same 3-page hiking-trail guide, built six times — identical content, identical visual design, identical four features (routing/transitions, a reusable trail card, shared state, a validated form with drag-to-reorder) — to compare what a framework actually buys you against doing it with nothing but the browser.

Vanilla

vanilla-static

Real separate .html files, full page loads. localStorage + the storage event for shared state. A Web Component for the trail card. Zero dependencies, zero build step.

3 files · no npm
Vanilla

vanilla-spa

One index.html shell. A ~20-line History API router with native View Transitions, the same Web Component, an in-memory pub/sub store persisted to localStorage.

1 shell + JS · no npm
Server

site-server

The same vanilla JS/CSS layer, served through a minimal Rust (Axum) server with a {{HEADER}}/{{CONTENT}}/{{FOOTER}} templating engine + page front matter, real server-rendered content, and generated sitemap.xml/robots.txt/llms.txt.

Rust · 70 crates, no npm
Framework

react

Vite + React Router (data router) + Context/useReducer. NavLink's viewTransition prop drives the native View Transitions API; aria-current comes for free.

78 packages · 66.3 MB node_modules
Framework

vue

Vite + Vue Router + Pinia. Vue's own <Transition> handles page transitions instead of the native API — the framework's idiomatic mechanism.

120 packages · 61.2 MB node_modules
Framework

angular

Angular CLI (standalone components) + Router + a signals-based injectable service. withViewTransitions() wires the router straight into the native API.

577 packages · 210.1 MB node_modules