Summary
Full hydration re-executes React for every node in the server-rendered tree — wasted work on static content. Selective, progressive, and lazy hydration reduce that cost. Islands architecture takes it further: ship a fully static page, then hydrate each interactive widget in isolation. The result is faster TTI and less main-thread blocking on content-heavy pages.
Jump to the interview angleHydration
Hydration is the step after SSR where the browser attaches React's event system to server-rendered HTML. Full hydration does this for the entire tree up-front — wasted work on static content.
Selective hydration (React 18): each <Suspense>-wrapped subtree hydrates independently; React prioritises whichever the user clicks first. Progressive/lazy hydration defers non-critical subtrees until idle or visible via IntersectionObserver + dynamic import(). Islands architecture (Astro, Fresh) goes further: the page is static HTML and only explicitly marked components carry their own JS bundle.
Hydration strategies at a glance
| Strategy | JS shipped | When hydrates | Framework | |
|---|---|---|---|---|
| Full hydration | Whole tree | Page load | React / Vue | |
| Selective hydration | Whole tree | Per Suspense boundary | React 18 | |
| Islands (client:visible) | Per island only | On viewport entry | Astro / Fresh | |
| RSC | Zero client JS | Never (server only) | Next.js App Router |
Astro island with client:visible
Zero JS ships by default in Astro. Add a client: directive to opt an island into hydration.
---
// src/pages/index.astro
import HeroImage from "../components/HeroImage.astro"; // static, zero JS
import CommentSection from "../components/CommentSection"; // React island
---
<html>
<body>
<HeroImage />
<!-- Hydrate only when the section scrolls into view -->
<CommentSection client:visible />
</body>
</html>client:visible uses IntersectionObserver; island JS loads only when it enters the viewport. HeroImage is plain Astro — no JS ships for it.
next/dynamic with ssr: false requires a Client Component
In Next.js App Router, dynamic(() => import('./Component'), { ssr: false }) is not allowed in Server Components. Wrap the dynamic call in a file marked "use client" and import that wrapper into your Server Component tree instead.
Interview angle
Frame islands as a cost-accounting decision: every interactive component pays a JS tax; static content should pay nothing. Contrast with RSC — islands ship client JS but hydrate lazily; RSC eliminates the component from the client bundle. Name the mismatch hazard.
Soundbite: "Islands ship JS only for interactive widgets; RSC eliminates the component from the client entirely — pick RSC first, island when you need browser APIs."
Key terms
- Hydration
- Attaching React's event system to server-rendered HTML so it becomes interactive.
- Islands architecture
- Static-HTML page with isolated interactive components that each carry their own JS bundle.
- client:visible
- Astro directive that hydrates an island only when it enters the viewport via IntersectionObserver.
- Hydration mismatch
- Server HTML differs from client render; React discards SSR work and re-renders from scratch.
- Selective hydration
- React 18 feature: Suspense subtrees hydrate independently; user interaction prioritises which hydrates first.