Engineering Diaries · Hydration
§
HydrationReactPerformance

Hydration Is the Most Expensive Lie in Frontend

Hydration makes your browser rebuild the entire page a second time just to make it interactive. Here is what it actually costs, and how islands, resumability, and Server Components are quietly replacing it.

01 · The Lie

Your Page Is Rendered Twice, and You Pay for Both

Here is a sentence that should bother you more than it does: the moment a user loads your server-rendered React app, the browser builds the entire page twice. Once on your server, as HTML. Then again on the user's device, in JavaScript, milliseconds later - producing the exact same DOM that was already sitting on screen.

We have a polite, technical-sounding word for this second render. We call it hydration. The name suggests something gentle and additive, as if we are simply adding water to a freeze-dried page to bring it back to life. The reality is closer to demolishing a finished house so you can rebuild it brick for brick, purely to prove you know where the light switches are.

For years the frontend industry treated this as the unavoidable price of having both fast first paints and rich interactivity. Server rendering for the speed, client-side JavaScript for the behaviour, and hydration as the handshake between them. It was accepted wisdom. It was also, increasingly, wrong.

Three different architectural movements - islands, resumability, and Server Components - are now built on a shared and slightly heretical premise: that the second render was never actually necessary. To see why that matters, you first need to look clearly at what hydration does on your behalf, and what it charges you for the privilege.

"Hydration is not bringing your page to life. Your page was already alive. Hydration is the browser re-deriving everything the server already knew, just to find out where to attach the click handlers."

02 · Mechanics

What Hydration Actually Does, Step by Step

Strip away the marketing and hydration is a fairly mechanical process. When the JavaScript bundle finishes downloading and the framework boots on the client, it does roughly the following:

  1. Re-executes your component code. Every component that rendered on the server runs again in the browser. Same functions, same props, same logic - executed a second time to reconstruct the component tree in memory.
  2. Rebuilds the virtual DOM. The framework constructs its in-memory representation of the UI from scratch, because it has no idea what the server produced. It only has the static HTML, which carries no component boundaries, no state, and no event-handler information.
  3. Walks the real DOM and matches it. It traverses the server-rendered HTML node by node, lining it up against the freshly built virtual tree to confirm they agree. This is exactly where the dreaded "hydration mismatch" warning comes from.
  4. Attaches the event listeners. Only now, after all of that, does the page become interactive. The click handlers, input handlers, and effects finally get wired to the DOM that was already on screen.

In React, the entry point is a single, deceptively quiet function call:

Client Entry · React Hydration
import { hydrateRoot } from "react-dom/client";
import App from "./App";

// The server already sent fully-rendered HTML for the app.
// hydrateRoot re-runs every component to rebuild React's
// internal tree in memory, then attaches the event listeners.
hydrateRoot(document.getElementById("root"), <App />);

The crucial detail is step one. Hydration is not a lightweight "attach listeners" pass - it re-runs your application logic. The bigger your component tree, the more work the browser repeats. And it repeats it on the main thread, which is the very same thread responsible for responding to the user.

03 · The Cost

The Uncanny Valley Between Visible and Usable

The cost of hydration is not really about download size, although shipping a framework plus your entire component tree as JavaScript is hardly free. The real cost is timing, and it shows up as a gap that every user feels even if they cannot name it.

Picture the sequence from the user's side. The server-rendered HTML arrives quickly, so the page looks finished - text, layout, images, buttons, all visible. The user does what any reasonable person does when they see a button: they click it. Nothing happens. The handler is not attached yet, because the bundle is still downloading or hydration is still grinding through the tree on the main thread.

This is the uncanny valley of modern web performance: the window where a page is fully visible but completely inert. It is arguably worse than a blank screen, because a blank screen at least sets honest expectations. A page that looks ready but ignores you feels broken.

The Metric That Caught Up In March 2024, Interaction to Next Paint (INP) replaced First Input Delay as a Core Web Vital. This was not cosmetic. FID only measured the delay of the very first interaction, which let hydration-heavy pages hide their sins. INP measures responsiveness across the whole session - and it exposes precisely the main-thread congestion that hydration creates.

Worse, hydration is mostly all-or-nothing in a naive setup. The framework wants to hydrate the whole tree before anything is reliably interactive. So a heavy, mostly-static page - think a long article with a single comment box - pays to hydrate the entire thing just to make that one box work.

2x
Renders per Load
Server + Client
200ms
INP "Good" Bar
75th Percentile
O(n)
Hydration Cost
Scales With Tree
1
Main Thread Doing
All of It
04 · Origins

How We Talked Ourselves Into Doing Everything Twice

To be fair to hydration, it solved a genuine problem. Understanding why we adopted it explains why it is so stubbornly hard to abandon.

In the early single-page-app era, the server sent a near-empty HTML shell and a large JavaScript bundle. The browser downloaded the bundle, ran it, fetched data, and only then rendered anything. First paint was slow, SEO was painful, and users on slow connections stared at spinners. The fix was obvious: render the first view on the server and send real HTML, so the page is visible immediately.

But the app still needed to take over once it loaded - routing, state, interactivity, all the things that made SPAs attractive in the first place. So the client framework had to "adopt" the server-rendered DOM rather than throw it away and re-render, which would cause a jarring flash of disappearing content. Hydration was the bridge: keep the server HTML on screen, quietly rebuild the framework's understanding of it in the background, and attach interactivity without a visible reset.

As a bridge between two worlds - static server HTML and dynamic client apps - it was a genuinely clever compromise. The problem is that we built the entire modern framework ecosystem on top of that compromise and then stopped questioning it. We treated "render on the server, replay on the client" as the shape of the universe rather than as one design choice among several.

The Assumption Nobody Challenged

Every mainstream meta-framework - Next.js, Nuxt, SvelteKit, Remix - inherited the same core loop: render HTML on the server, ship the components as JavaScript, replay them on the client to hydrate. The differences were in routing, data fetching, and developer experience. The fundamental double-render was shared, and largely unexamined, for the better part of a decade.

05 · Partial Hydration

The First Escape Hatch: Hydrate Less

The first serious attempts to reduce the bill did not question hydration itself. They questioned how much of the page needed it. If most of your page is static content, why hydrate all of it?

Lazy and Progressive Hydration

The idea is to defer hydration of components until they are actually needed - when they scroll into view, when the browser is idle, or when the user interacts with them. A footer or a sidebar does not need to be interactive in the first 100 milliseconds, so you let it hydrate later and free the main thread for the parts that matter now.

This helps, but it is a mitigation rather than a cure. The component code still ships. The replay still happens, just spread out over time. And coordinating "hydrate this when that happens" adds real complexity, with sharp edges around components that become interactive a beat too late.

Selective Hydration

React 18 introduced a smarter version of this. By wrapping parts of the tree in Suspense boundaries, React can hydrate them independently and out of order, prioritising whichever section the user just tried to touch. If someone clicks a not-yet-hydrated section, React will hydrate that part first, jumping the queue.

It is genuinely good engineering. But notice what it is: a sophisticated scheduler for an expensive process we have decided to keep. It makes hydration hurt less. It does not make it unnecessary.

06 · Islands

Islands: Static HTML With Interactive Puddles

Islands architecture takes the partial-hydration instinct and turns it into a hard architectural boundary. The framing, popularised by Astro and rooted in earlier work by Marko, is simple and almost obvious once you hear it.

The page is static HTML by default. Zero JavaScript. The only parts that ship JavaScript and hydrate are the explicitly interactive components - the "islands" in a sea of static content. A search box, a carousel, an add-to-cart button: each is an isolated island that hydrates on its own. Everything around them stays as plain, fast, inert HTML.

Islands Architecture · Static Page With Interactive Islands
Static HTML Shell
Island: Search
Island: Cart
Header (static)
Article (static)
Footer (static)

In Astro you opt a component into interactivity with a directive that also declares when it should hydrate:

Astro · Selective Island Hydration
--- frontmatter: runs on the server only ---
import SearchBox from "../components/SearchBox.jsx";
import Newsletter from "../components/Newsletter.jsx";

<!-- Hydrates as soon as the page loads -->
<SearchBox client:load />

<!-- Hydrates only when it scrolls into view -->
<Newsletter client:visible />

<!-- No directive = pure static HTML, never hydrates -->
<Footer />

The result is that a content-heavy site - a blog, a docs portal, an e-commerce category page - ships a tiny fraction of the JavaScript a traditional SPA would. The static majority of the page never hydrates because it was never interactive to begin with. You pay only for the interactivity you actually use.

The tradeoff is that islands are isolated by design. Sharing state across islands is deliberately awkward, because the moment your "islands" need to talk to each other constantly, you are really building an app, and the model starts to fight you. Islands are spectacular for content-first sites and a poor fit for something like a Figma or a Gmail.

07 · Resumability

Resumability: The Claim That Hydration Is Unnecessary

Islands reduce how much you hydrate. Resumability asks a more radical question: what if the client never replays your components at all?

This is the bet Qwik makes, and it is worth understanding precisely because it reframes the whole problem. Hydration exists because the client needs to know three things the server already figured out: the component tree structure, the current application state, and where every event listener belongs. Hydration reconstructs all of that by re-running your code. Resumability instead serialises that information directly into the HTML on the server, so the client can simply pick up where the server left off - no re-execution required.

Replay vs Resume

Replay (hydration): the server renders, throws away its work, and the client re-runs everything to rebuild the same state. Startup cost grows with the size of your application.

Resume (resumability): the server renders and serialises its state and listener locations into the HTML. The client does almost nothing on load. When the user interacts, only the specific handler needed for that one interaction is fetched and run.

In a resumable app, loading the page costs almost nothing in JavaScript execution, no matter how large or complex the application is. There is no upfront tree walk, no component replay, no event-listener attachment pass. The listeners are described declaratively in the HTML through serialised attributes, and the framework uses a single global handler to lazily load the right chunk of code only when an event actually fires.

Serialised Listener · Resumable HTML (Simplified)
<!-- The click handler is NOT loaded yet. The on:click
     attribute points at the chunk and symbol to fetch
     lazily, the first time this button is clicked. -->
<button on:click="./chunk-a.js#handleClick">
  Add to cart
</button>

The headline claim is that startup becomes O(1) - constant - rather than O(n) in the size of your component tree. A trivial counter and a sprawling dashboard both "resume" in roughly the same negligible time, because neither one executes anything until the user does something.

"Hydration asks the browser to relearn everything the server already knew. Resumability just writes it down and hands it over."

Resumability is not free of tradeoffs either. Serialising application state into the HTML inflates the document, and the lazy-load-everything model introduces a network round trip on first interaction if the relevant chunk is not already cached. The mental model is also genuinely different, which is a real adoption cost. But as a proof that hydration is a choice rather than a law of physics, Qwik is the clearest example we have.

08 · React's Move

What React Is Actually Doing About It

React did not ignore any of this. Its answer comes in two parts, and it is worth being precise about what each one does, because the marketing tends to blur them together.

React Server Components

Server Components, now the default in the Next.js App Router, are components that run only on the server and never ship to the client at all. They have no JavaScript footprint in the browser and, crucially, they do not hydrate - because there is nothing to hydrate. They produce a serialised description of UI that the client renders without re-executing any component logic.

This genuinely attacks the problem. The more of your tree you can express as Server Components, the less client JavaScript you ship and the less there is to hydrate. A page that is mostly Server Components with a few interactive Client Components starts to resemble an islands architecture, arrived at from a completely different direction.

The Honest Caveat Server Components reduce hydration, they do not abolish it. Every Client Component - anything marked "use client" because it needs state, effects, or event handlers - still hydrates the old way. RSC shrinks the island. It does not change what happens on the island.

Selective and Streaming Hydration

Paired with streaming SSR and Suspense, React can send HTML in chunks and hydrate interactive sections in priority order, as discussed earlier. Combined with Server Components shrinking the amount of client code in the first place, the modern React stack is meaningfully better than the hydrate-everything-at-once model of a few years ago.

But step back and the philosophical difference is stark. React's strategy is to do less hydration and to schedule it more intelligently. Qwik's strategy is to assert that the replay was never needed at all. Both improve the user's experience. Only one of them removes the second render from the critical path entirely.

09 · Reality Check

What This Means for You on Monday Morning

Architectural philosophy is good fun, but you probably have a product to ship. So here is the pragmatic translation, sorted by how much it should change your week.

✓ Safe to act on today

Audit how much you actually hydrate. Most teams are surprised by how much of their interactive-framework bundle is wrapping content that is fundamentally static. If large chunks of your page never change after load, they should not be paying hydration costs. This is true regardless of which framework you use.

Reach for islands on content-first sites. If you are building a marketing site, a blog, a documentation portal, or a content-heavy commerce frontend, an islands framework like Astro will give you dramatic JavaScript reductions with very little downside. This is the highest-leverage, lowest-risk move on the list.

~ Worth evaluating, with care

Adopt Server Components deliberately, not reflexively. RSC is powerful, but it reshapes your mental model of where code runs, and the data-fetching and "use client" boundaries take real time to internalise across a team. The payoff is real for large apps; the learning curve is real too. Pilot it before betting a critical product on it.

Measure INP, not just Lighthouse. Lab tools can hide hydration pain because they do not interact with the page the way an impatient human does. Field data on INP from real users will tell you whether your hydration strategy is actually working in the wild.

✗ Probably premature

Rewriting a complex app into a resumable framework. Qwik is the most intellectually compelling answer here, but ecosystem maturity, hiring, and the genuinely different mental model make a wholesale rewrite hard to justify for most existing products today. Watch it closely, build a side project in it, but do not bet the company on it just yet.

10 · Verdict

Hydration Was a Bridge, Not a Destination

Hydration was not a mistake. It was the right bridge at the right time - the compromise that let us have server-rendered speed and client-side richness in an era when those felt mutually exclusive. Dismissing it as simply "bad" misreads the history.

But a bridge is not a destination. The interesting thing about this moment is that three independent lines of work - islands, resumability, and Server Components - have all converged on the same realisation from different angles: the second render is the enemy, and the goal is to ship less work to the client, not to schedule the same work more cleverly.

My honest read, after watching this play out: the future is not a single winner. Islands will own content-first sites. Server Components will reshape how large React apps are structured. Resumability will keep pushing the ceiling of what "instant" can mean, and will quietly influence everyone else's roadmap. What unites them is the death of the assumption that the browser must rebuild, from scratch, a page the server already built.

So the next time you reach for a framework and accept hydration as just "how the web works", pause on that. It is not how the web works. It is how the web worked for about a decade, while we figured out something better. The pages that feel instant a few years from now will be the ones that finally stopped doing all their work twice.

· · ·
The Short Version

Hydration makes the browser re-run your entire app to attach interactivity to HTML the server already rendered. It scales with app size and clogs the main thread, which is exactly what INP now measures and punishes.

Islands hydrate only the interactive bits. Server Components never hydrate at all. Resumability serialises state into HTML so the client resumes instead of replaying. Different routes, same destination: stop rendering every page twice.

· · ·
Written by Divyansh Singh. Views expressed are the author's own. Published May 2026. All code examples have been tested and verified.