What hydration actually does
When a server-rendered page arrives, the browser parses the HTML and paints it. At that point the page is a picture of the app: links work because they are plain HTML, but buttons wired to JavaScript, menus, forms with client validation and anything else driven by component code do nothing yet.
Hydration is the step where the framework takes over. It downloads the component code, runs it to rebuild the component tree in memory, walks the existing DOM to match it with that tree, and attaches event handlers and state. Google's rendering guide calls the overall approach rehydration and notes two costs: pages can appear ready while they cannot respond, and the same UI is effectively sent twice, once as HTML and again as the data and code needed to rebuild it on the client (source 1).
Why it hurts on real devices
Hydration is main-thread work. While it runs, the browser cannot handle input or paint. If it runs as one long block, any click or tap during that block waits, and the wait counts as input delay in Interaction to Next Paint (source 2).
On a fast laptop, hydrating a typical page may take tens of milliseconds and nobody notices. On a mid-range phone, the same work can take several times longer. Lighthouse, for example, simulates mobile with a CPU slowdown of four times. A hydration pass that becomes a 300 ms long task on a phone sits right in the window when users first try to tap, because people act as soon as content appears.
There are smaller costs too. The serialized data a framework embeds in the HTML for hydration can be large, which delays the HTML itself. And if the client's output does not match the server's, frameworks warn and may re-render or patch parts of the page, doing work twice and sometimes causing flicker.
Measuring hydration
You cannot fix what you cannot see. Three ways to see hydration:
- A Performance trace on a throttled CPU. Record a page load in Chrome DevTools with 4x or 6x CPU slowdown. Look for the long tasks that follow the main bundle's evaluation; with source maps, the call stacks show your framework's hydrate functions and your components.
- Framework tooling. React 19.2 adds Scheduler and Components tracks to the Chrome DevTools Performance panel, showing which components mount and when work is blocked (source 3). Vue and Angular devtools show component trees and timings.
- Field data with attribution. If your INP attribution shows many slow interactions early in the page's life with large input delay, hydration is a likely cause. The
web-vitalsattribution build reports the phases of each slow interaction and the Long Animation Frames that overlapped it (source 4).
A useful single number is the gap between first contentful paint and the end of the last hydration task on a throttled profile. That is the period when the page looks ready but may not respond.
Ways to hydrate less
Do not hydrate what is not interactive
The cheapest hydration is none. Article bodies, footers, static hero sections and marketing copy often live inside components that hydrate simply because the whole page is one app. Moving them out of the hydrated tree removes their cost entirely.
React Server Components make this the default in frameworks that support them: components are Server Components unless they sit in a module marked 'use client' or are imported by one, and Server Component code is never sent to the browser (source 5). Astro takes the same view from the other side: components render as HTML unless you give them a client directive (source 6).
Hydrate in pieces
Instead of one long task, hydrate the page in sections so the browser can handle input between them. React does this with Suspense boundaries, each of which can hydrate on its own; it can also prioritize a boundary the user interacts with. Streaming server rendering pairs well with this, because sections arrive and hydrate independently.
Hydrate later
Some components are never seen by most visitors, or only after scrolling. Lazy hydration delays their code until a trigger fires.
- Vue 3.5 and later provides built-in strategies for async components in server-rendered apps:
hydrateOnIdle,hydrateOnVisible,hydrateOnMediaQueryandhydrateOnInteraction, plus custom strategies. The interaction strategy replays the triggering event after hydration completes (source 7). - Nuxt exposes delayed hydration as props on Lazy components, such as
hydrate-on-visible,hydrate-on-idle,hydrate-on-interaction,hydrate-after,hydrate-whenandhydrate-never(source 8). - Angular supports hydrate triggers on
@deferblocks, and since version 22, incremental hydration is enabled by default when client hydration is provided (source 9). - Astro uses
client:idleandclient:visibledirectives on islands (source 6).
// Vue 3.5+: hydrate a below-the-fold widget only when it scrolls into view
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
const Reviews = defineAsyncComponent({
loader: () => import('./Reviews.vue'),
hydrate: hydrateOnVisible({ rootMargin: '200px' })
})Send less data
Look at the serialized state in your HTML. If it includes whole API responses when components use a few fields, trim it on the server. Data that only the server needs should not cross to the client at all.
When lazy hydration backfires
Delaying hydration is not free:
- Delayed controls that look ready. A button that renders before its handler is attached invites taps that do nothing. Either hydrate such controls early, or make them work as plain HTML first, for example a real link or a form that submits normally.
- Hydrating on interaction can add latency to that interaction. If the code must download and run when the user taps, that tap waits for it. For controls people use immediately, hydrate on idle or visibility instead.
- Layout shifts. If a component renders differently once hydrated, the change can shift content.
- Testing gaps. End-to-end tests that click as soon as elements appear may pass or fail depending on hydration timing.
Hydration mismatches
A mismatch happens when the HTML the server sent differs from what the client renders during hydration. Frameworks warn in development and may fall back to re-rendering affected parts. Common causes:
- Dates and times formatted in the server's time zone or locale, then again in the browser's.
- Random values or IDs generated separately on server and client.
- Code that checks
window,localStorageor screen size during render. In React 19.3 and later,use(browser())fromreact-domis a cleaner way to render such a component only on the client, with its Suspense fallback in the server HTML (source 10). - Browser extensions or third-party scripts that change the DOM before hydration.
- Invalid HTML nesting, such as a
divinside ap, which the browser's parser rearranges.
Fix mismatches before tuning hydration speed. A route that silently re-renders after a mismatch makes every other measurement harder to interpret. Our Vue and Nuxt guide covers mismatches in more depth.
A short checklist
- Measure hydration on a throttled CPU and note the gap between paint and the end of hydration.
- List components that hydrate but have no interactivity, and move them out of the hydrated tree.
- Split hydration with boundaries so no single task is very long.
- Delay hydration for below-the-fold or rarely used components, but not for controls used immediately.
- Trim serialized state.
- Clear hydration mismatch warnings.
- Confirm the result in field data, especially INP input delay early in page life.
Sources
- Rendering on the Web, web.dev Primary source (Google)
- Interaction to Next Paint (INP), web.dev Primary source (Google)
- React 19.2, React blog Primary source (React team)
- Measuring Interaction to Next Paint (INP) codelab, web.dev Primary source (Google)
- 'use client' directive, React reference Primary source (React team)
- Islands architecture, Astro Docs Primary source (Astro)
- Async Components: Lazy Hydration, Vue.js guide Primary source (Vue team)
- Components directory: delayed hydration, Nuxt docs Primary source (Nuxt team)
- withIncrementalHydration API, Angular docs Primary source (Angular team)
- React 19.3, React blog (September 9, 2026) Primary source (React team)