HP2004 (root-client-rendered) means an error during hydration happened
outside every Suspense boundary, so React gave up on the whole root and
rendered the entire page on the client. In production this is minified React
error #423. Fix the error React reported, and wrap unstable parts in Suspense
boundaries so one failure cannot re-render the whole page.
| Code | HP2004 |
|---|---|
| Name | root-client-rendered |
| Default severity | Error |
| Group | Problems React reported |
| What it means | An error during hydration outside any Suspense boundary made React render the whole root on the client. |
What HP2004 (root-client-rendered) means
React recovers from a hydration failure at the nearest Suspense boundary above it. With none in between, that is the root: the server HTML of the whole page is discarded, every component renders again in the browser, and anything the user did before hydration is lost.
hydration-proof normally sees the discarded root in the DOM as well, and reports the concrete difference with React's message as evidence, or HP1011 when it cannot locate one. HP2004 is React's report when no DOM finding from that commit explains it.
The React error it matches
There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.This is React 18's message; production builds show minified React error #423. See there was an error while hydrating. When the failure is inside a boundary, only that boundary switches: HP2003.
Likely causes
A mismatch in a part of the page with no Suspense boundary above it, often in a layout every page shares: a theme class, a time value, browser storage or invalid HTML.
How to fix it
- Wrap unstable parts in Suspense boundaries so a hydration error does not re-render the whole page.
Fix the error itself first: the finding's evidence has React's message and the
component stack, and npx hydration-proof test --mode dev gives readable names
and source lines. A boundary then keeps the next failure local:
import { Suspense, type ReactNode } from "react";
import { AccountMenu } from "./account-menu";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<header>
<Suspense fallback={null}>
<AccountMenu />
</Suspense>
</header>
{children}
</body>
</html>
);
}If AccountMenu fails to hydrate, React now client-renders that boundary
only, and the finding becomes HP2003 instead.
Example
✖ / 1.2s 1 error
HP2004 The root switched to client rendering
There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
→ Wrap unstable parts in Suspense boundaries so a hydration error does not re-render the whole page.