Problem
stx today hydrates the entire page on the client. Every component, even static content (a judge's name, a court's address, the article body), runs through processElement and gets reactive bindings. For a content site like bench-review, ~70% of the page is static after first render — the JS hydration is wasted bandwidth + execution time.
The article page on bench-review weighs ~300KB of JS (signals runtime + per-page scopes + components). Most of that exists to make ~12 interactive elements work (like button, comment composer, dropdown menus). The other 95% of the DOM doesn't need any of it.
Proposed: islands architecture
Components opt INTO client hydration. Default is SSR-only — render server-side, ship HTML, no JS hydration.
Two opt-in mechanisms:
Per-component directive (Astro model)
<BenchHeader client="load" /> <!-- hydrate on page load -->
<CommentsList client="visible" /> <!-- hydrate when scrolled into viewport -->
<LikeButton client="idle" /> <!-- hydrate during idle time -->
<SearchBox client="media:(min-width: 768px)" /> <!-- hydrate conditionally -->
<JudgeName /> <!-- static, never hydrates — ships zero JS -->Per-file marker (RSC model)
'use client' <!-- file is interactive, hydrates -->
<script client>
// ...
</script>Default (no marker) = server-only.
Streaming SSR
Pair with streaming response — flush HTML to the browser as the server resolves data, instead of waiting for the full page:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<!-- Header renders immediately (no fetch dependency) -->
<BenchHeader />
<!-- Article body waits for fetch — emit a placeholder, stream the real content when ready -->
<Suspense fallback="<Skeleton />">
<ArticleView reviewId={16} /> <!-- this fetches; renders when data arrives, streamed mid-response -->
</Suspense>
</body>
</html>The browser starts painting the header instantly. The article appears when the server has the data. Time-to-first-content drops dramatically for slow data sources.
Prior art
- Astro islands —
client:load,client:visible,client:idle,client:onlydirectives. Default is server-only. - React Server Components (Next.js App Router) — server components by default,
'use client'opts in. - Qwik resumability — different approach (no hydration, just resume from serialized state).
- Marko — similar islands approach.
- Fresh (Deno) — islands by default.
- Streaming SSR: Next 13+, Remix, SvelteKit.
Impact
- JS bundle size: bench-review's article page would drop from ~300KB to ~50KB (only interactive components ship JS).
- Time-to-interactive: dramatic improvement on slow networks/devices.
- Time-to-first-content (with streaming): the header paints in ~100ms instead of waiting for full page assembly.
- Lighthouse / Core Web Vitals: huge improvements across the board.
Phasing
- Phase 1: Per-file
'use client'marker. Files without it are server-only at SSR (no client scope IIFE emitted). - Phase 2: Per-component
client:*directives at use site. - Phase 3: Streaming SSR — emit HTML in chunks as data resolves.
- Phase 4: Resumability (Qwik-style — no hydration, just resume).
This is genuinely XL effort. Probably 6+ months of focused work. But the architectural payoff is huge — Stacks becomes a content-site-class framework, not just an SPA framework.
Note
Pairs heavily with P1.1 (SSR-side :if branch elimination) and P2.4 (<Suspense>). All three are part of the same architectural direction: render more on the server, ship less to the client.