Hydration and speed in React and Next.js

A server-rendered React page can look ready and still ignore taps. How hydration works, what it costs, and how Server Components, streaming and splitting help.

AdvancedExplainer9 min readUpdated October 30, 2026

In this guide
  1. What hydration is
  2. Four ways to build a page
  3. Why a page can look ready and not respond
    1. The same work twice
  4. What has changed since the warnings
  5. Fix 1: send less JavaScript to hydrate
  6. Fix 2: split what is left, and load it late
  7. Fix 3: stream the page, and hydrate in pieces
    1. Keep the main content out of the streamed parts
  8. When you do not need hydration at all
  9. How to check your own site
  10. Questions to put to your developer
  11. Check your own page
  12. Sources

What hydration is

Many React sites make their HTML on the server, so that the visitor sees content before any script has run. That HTML is only a picture. Nothing in it responds to a tap yet. The React documentation says that hydration attaches your components' logic to the HTML from the server and turns that snapshot into a fully interactive app. The function that does it is hydrateRoot, and a framework such as Next.js usually calls it for you.

Hydration needs the browser to do real work: download the component code, run it, build React's own picture of the page, and attach the event handlers. The React docs add that the result must match the server's HTML, and that mismatches are bugs to fix.

Four ways to build a page

web.dev's article on rendering describes the choices. Each has a cost:

ApproachHTML is madeWhat it is good forWhat to watch
Static renderingAhead of time, once per URLFast first paint and a consistently fast first byte. Little script is needed if the page is mostly content.Every URL needs its own file, which is hard for large or unpredictable sites
Server-side renderingOn the server, for each requestFast first paint, and less script if the server does the workBuilding the page takes time, so the first byte can be later
Client-side renderingIn the browser, by JavaScriptApps that behave like softwareThe script grows with the app, which hurts weak phones and interaction speed
Server rendering plus hydrationOn the server, then the browser repeats the workFast first paint, and a page that works once the scripts runThe gap before it works, and the duplicated work

Most popular frameworks use the last row by default, as web.dev notes, so it helps to know which one your site uses. One test from web.dev: switch JavaScript off and load the page. A statically rendered page keeps most of its function. A page that needs hydration will mostly sit there.

Why a page can look ready and not respond

Here is the order of events on a server-rendered React page:

  1. The HTML arrives and the browser paints it. The visitor sees the page. Largest Contentful Paint can be good.
  2. The scripts download, then the browser parses and runs them.
  3. React hydrates the components and attaches the handlers.
  4. Only now does the page respond to everything.

Between steps 1 and 4, the page can ignore a tap or answer it late. web.dev puts it plainly: server-rendered pages can deceptively appear to be loaded and interactive, but cannot respond to input until the scripts have run and the handlers are attached, and it says this can take "seconds or even minutes" on mobile. It calls the state an uncanny valley, where interactivity feels absent although the page appears ready.

The measurements show it as long tasks while the page loads. In a lab test that is Total Blocking Time. In real visits it is a slow Interaction to Next Paint for people who tap early. A page can therefore pass the loading metric and fail the interaction one.

The same work twice

web.dev describes a second cost. To let the browser pick up where the server stopped, without asking for the data again, the server usually puts that data into the page as well. The document then holds the finished HTML, a copy of the data behind it, and the script that builds the same page again. That is more bytes to send and more work to do, for a page that already looks finished.

What has changed since the warnings

The Chrome team's article was written in 2019 and updated in 2023. It encouraged server-side or static rendering over full hydration. It also named two ideas as the way forward: streaming the HTML in chunks, and hydrating the page in pieces instead of all at once.

React has since built both into its design:

Next.js builds on both. In its App Router, layouts and pages are Server Components by default, and you add interactivity with Client Components.

None of this removes the cost by itself. It gives you ways to reduce it, and the next three sections show them.

Fix 1: send less JavaScript to hydrate

Use Server Components for anything that does not need the browser. The Next.js docs suggest them for fetching data and for reducing the JavaScript sent to the browser. The React docs give an example. A page that loads a Markdown converter and an HTML cleaner in the browser, only to show text that never changes, makes visitors download about 75 KB more (gzipped) and then wait for a second request. Rendered once on the server, those libraries are not in the bundle at all.

Mark only the interactive parts as client code. In Next.js the "use client" directive marks a boundary. Everything the file imports, and every component it renders directly, goes into the client bundle. So the docs say to put the directive on the files that define interactive components instead of marking large parts of the page. Their example is a layout that is mostly a logo and links, with one search box. Only the search box is a Client Component.

Move heavy display work to the server. The Next.js bundling guide points to libraries that only turn data into markup, such as syntax highlighters, chart renderers and Markdown parsers. If the work needs no browser feature or tap, it can run in a Server Component. Then the library does not ship.

Trim packages with many exports. For libraries that export hundreds of items, such as icon sets, Next.js has an optimizePackageImports option (an experimental setting) that loads only the modules you use. Use the bundle analyzer to find the big items first. The general method is in JavaScript bundle size and code splitting.

Fix 2: split what is left, and load it late

Lazy-load client components that are not needed at once. Next.js offers next/dynamic, which is built on React.lazy() and Suspense. A modal that opens on a click, a chart below the fold, or a map can load when needed.

'use client'
import dynamic from 'next/dynamic'

const Chart = dynamic(() => import('../components/Chart'))

The ssr: false option skips the server render for a component. It keeps the code out of the first HTML, so use it only for parts that are not needed on first paint. A component that skips the server render adds nothing to the first view, and can add a layout shift when it arrives. The docs also say ssr: false works only inside Client Components.

Load scripts from other companies late. The Next.js <Script> component has four strategies: beforeInteractive (before any Next.js code and before hydration), afterInteractive (the default, after some hydration), lazyOnload (in the browser's idle time) and worker (experimental, in a web worker). The docs suggest afterInteractive for tag managers and analytics, and lazyOnload for background and low-priority scripts. Keep beforeInteractive for the few that must run first, such as cookie consent. See third-party scripts.

Fix 3: stream the page, and hydrate in pieces

In plain server rendering the server waits for all the data before it sends any HTML. Streaming uses chunked responses to send parts as they become ready. The Next.js docs explain the result: the first byte arrives after the server renders your layouts and the placeholders, and the first paint no longer waits for the slowest data request.

React decides what goes first. The part of the page outside every <Suspense> boundary is called the shell. The shell is sent first, and each boundary's content follows when it is ready. Two details matter:

Keep the main content out of the streamed parts

Streaming has costs of its own. The Next.js docs list them for the metrics:

When you do not need hydration at all

A brochure site, a blog or a documentation page is mostly text. Hydrating it adds cost and little else. web.dev says it is perfectly fine to ship mostly HTML with little JavaScript, and to use different approaches on different pages. Frameworks such as Next.js can make some pages static and others server-rendered. Choose static rendering where the content is the same for everyone, and keep scripts for the parts that really need them.

How to check your own site

  1. View the source. Use your browser's View Source, not the Elements panel. If your main text and heading are in it, the server made them. If the body is nearly empty, the browser makes everything.
  2. Do the tap test. On a mid-range phone, load the page and tap a menu button the moment the page looks ready. A delay or no response means hydration finishes late. Repeat with the processor slowed down in DevTools on a computer.
  3. Record the load in the Performance panel and look at the long tasks after the first paint. If your own bundle fills them, this guide is for you.
  4. Watch the real-visitor numbers. A good Largest Contentful Paint with a poor Interaction to Next Paint is the classic sign. See how to fix INP.

Questions to put to your developer

Check your own page

Run the free audit. If Largest Contentful Paint is fine and Total Blocking Time is high, look at the Reduce JavaScript execution time and Reduce unused JavaScript findings, and at the longest tasks. A large bundle that runs right after the first paint is the hydration cost.

Sources

Start here

Free website audit

Live Google data for any public page: mobile and desktop.

Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it

Rather have this done for you? Speed optimization · Book a call

Related guides

All guides →