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.
| Code | HP2005 |
|---|---|
| Name | update-before-hydration |
| Default severity | Warning |
| Group | Problems React reported |
| What it means | A 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:
"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:
"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.