Hydration Proof

Search documentation

Find a page or section

HP2005: An update arrived before hydration finished

HP2005 (update before hydration): a Suspense boundary or root got an update before it finished hydrating (React errors 421, 424). Fix it with startTransition.

HP2005 (update-before-hydration) means a state or context update reached a Suspense boundary, or the root, before React had finished hydrating it, and React switched it to client rendering (React errors #421 and #424). Wrap updates that run during hydration in startTransition.

CodeHP2005
Nameupdate-before-hydration
Default severityWarning
GroupProblems React reported
What it meansA Suspense boundary or root received an update before it finished hydrating (React errors 421/424).

What HP2005 (update-before-hydration) means

Hydration is not instant: React hydrates the root first and each Suspense boundary when its code and content are ready. If something updates state that a not-yet-hydrated boundary depends on (a context value set in an effect, a parent re-rendering on load), React has to render the new state right away, cannot use the server HTML for it, and renders the boundary on the client instead.

The page keeps working, but that part's server HTML is thrown away and rendered a second time.

The React error it matches

This Suspense boundary received an update before it finished hydrating.

React logs this for a boundary; production builds show #421, or #424 when the root itself received the update. See Suspense boundary received an update before it finished hydrating and the minified React error codes.

Likely causes

  • A provider that sets state in an effect on load, such as a user or theme read from browser storage, while boundaries below it are still hydrating.
  • A parent that re-renders with new props on mount, for example after reading a media query.

How to fix it

  • Wrap updates triggered during hydration in startTransition.

An update in an effect is urgent by default:

user-provider.tsx
"use client";
 
import { createContext, useEffect, useState, type ReactNode } from "react";
 
export const UserContext = createContext<string | null>(null);
 
export function UserProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<string | null>(null);
 
  useEffect(() => {
    setUser(localStorage.getItem("user"));
  }, []);
 
  return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
}

Marked as a transition, it can wait for the boundaries below to finish hydrating instead of forcing them to render on the client:

user-provider.tsx
"use client";
 
import {
  createContext,
  startTransition,
  useEffect,
  useState,
  type ReactNode,
} from "react";
 
export const UserContext = createContext<string | null>(null);
 
export function UserProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<string | null>(null);
 
  useEffect(() => {
    startTransition(() => {
      setUser(localStorage.getItem("user"));
    });
  }, []);
 
  return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
}

Example

  ⚠ /account 1.4s  1 warning
    HP2005 An update arrived before hydration finished
      This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.
      → Wrap updates triggered during hydration in startTransition.