Reducing Layout Shift During React Hydration

A server-rendered React page can look perfect on first paint and then jump a moment later, when hydration mounts components that were not in the server markup β€” a personalized header, an ad slot, a client-only carousel. Each unreserved mount pushes content down and adds to Cumulative Layout Shift, a Core Web Vital that influences ranking. This guide eliminates hydration-driven CLS and complements the hydration strategies comparison.

Layout shift from an unreserved hydrated widget Before hydration the content sits high; after a widget mounts without reserved space, the content is pushed down, causing layout shift. Reserving space keeps it stable. No reserved space widget appears content pushed down CLS spikes ↑ Reserved space slot held open content stays put CLS β‰ˆ 0
Reserve the widget's dimensions before hydration so mounting it does not move surrounding content.

Step-by-step fix

  1. Reserve space for client-only widgets. Give the container fixed dimensions in the server markup so the mount fills a held slot instead of pushing content.

    // ❌ Widget mounts into a zero-height container, shoving content down
    <div id="promo">{mounted && <Promo />}</div>
    
    // βœ… Reserve the final size up front; no shift when it mounts
    <div id="promo" style={{ minHeight: 90 }}>{mounted && <Promo />}</div>
  2. Render auth-dependent UI consistently between server and client. Rendering a logged-out header on the server and a logged-in one after hydration shifts the layout; render a stable shell and fill details in place.

    // βœ… Same height server and client; only the label swaps
    <nav style={{ height: 56 }}>{user ? <AccountMenu /> : <SignInLink />}</nav>
  3. Size skeletons to match final content. A 200px skeleton replaced by 400px of content shifts everything below it β€” match the dimensions.

  4. Pin web fonts with font-display: optional or preload to avoid a font swap reflow during hydration.

Each of these fixes removes one contributor to a score that otherwise stacks up quickly β€” three unreserved mounts alone can push a page past the limit.

How unreserved hydration mounts accumulate into a failing CLS Three unreserved mounts each add to Cumulative Layout Shift and together exceed the 0.1 threshold, while reserving space for all of them keeps the score near zero. No reserved space ad +0.06 menu +0.05 skeleton +0.04 = 0.15 fails 0.1 threshold Space reserved CLS ≈ 0 Β· every slot held open
Each unreserved mount stacks onto the score; reserving space for all of them collapses the total to near zero.

Validation

  • Lighthouse β†’ Cumulative Layout Shift under 0.1 on lab runs.
  • Performance panel β†’ Experience lane shows no shift markers after load.
  • Web Vitals field data (CrUX / RUM) confirms CLS at the 75th percentile under 0.1.
  • DevTools β€œLayout Shift Regions” overlay highlights nothing during hydration.

The number those tools report is graded against fixed bands, and validation passes only when the field score lands in the good one.

The Cumulative Layout Shift rating thresholds A scale marking the good, needs-improvement, and poor CLS bands at 0.1 and 0.25, with the validation target sitting inside the good band. Good Needs work Poor 0.1 0.25 validation target field CLS at the 75th percentile must stay in the good band
Validation passes only when the 75th-percentile CLS sits in the good band, to the left of the 0.1 boundary.

Configuration reference

/* Reserve space for hydration-mounted regions so CLS stays flat */
[data-hydrate] { min-height: var(--reserved-height, 0); }
.skeleton      { height: var(--final-height); }     /* match real content */
@font-face     { font-family: Inter; font-display: optional; } /* no swap reflow */
What each CSS rule in the reference neutralizes Three rules β€” a reserved min-height for hydration-mounted regions, a skeleton height matched to final content, and font-display optional β€” each remove one source of hydration layout shift. Three rules, three sources of hydration shift removed CSS rule Shift it prevents [data-hydrate]{min-height:…} reserve the mounted region's height a client-only widget pushing content down .skeleton{height:var(--final-height)} size the skeleton to its final content a skeleton swap resizing the layout @font-face{font-display:optional} avoid a late font swap a web-font swap reflow during hydration
Each rule in the reference cancels one specific source of hydration layout shift, keeping Cumulative Layout Shift flat.

Frequently Asked Questions

Why does my page shift right after it loads? Hydration mounts components that were not in the server HTML β€” ads, auth-dependent menus, client-only widgets β€” and they push existing content down. Each unreserved mount contributes to Cumulative Layout Shift. Reserving space before hydration eliminates the shift.

Does Cumulative Layout Shift affect rankings? Yes. CLS is a Core Web Vital and part of Google’s page experience signals. A CLS above 0.1 is flagged as needing improvement and can weigh against a page in competitive rankings.

← Back to Incremental & Streaming SSR for SEO