This hydration error occurs when React's useId() hook generates different IDs on the server versus the client, typically when components render conditionally or in loops during SSR.
This error appears during React's hydration process when the server-rendered HTML contains different `id` attributes than what React expects during client-side hydration. The error message shows the server value (often empty) and the client value (typically React's auto-generated IDs like ":R1:", ":R2:"). The root cause is almost always the `useId()` hook being called in components that render conditionally or differently between server and client. When the server renders a component with certain conditions (like a collapsed state), but the client hydrates with different conditions (like an expanded state), React's ID generation sequence gets out of sync. This is particularly common in Next.js applications using the App Router with Server Components, where the server and client rendering paths can diverge. The issue affects React 18.x but was resolved in React 19.
The most reliable fix is to ensure your component renders identically on both server and client during the initial render.
Avoid conditional rendering that depends on client-only state:
// ❌ BAD: Client-only state affects useId()
function MyComponent() {
const [isOpen, setIsOpen] = useState(true); // true on client, but server might differ
const id = useId();
return isOpen ? <div id={id}>Content</div> : null;
}
// ✅ GOOD: Same initial state on server and client
function MyComponent() {
const [isOpen, setIsOpen] = useState(false); // false on both
const id = useId();
return isOpen ? <div id={id}>Content</div> : null;
}For dynamic content, delay rendering until after hydration:
function MyComponent() {
const [isMounted, setIsMounted] = useState(false);
const id = useId();
useEffect(() => {
setIsMounted(true);
}, []);
if (!isMounted) {
return <div>Loading...</div>; // Same on server and client
}
return <div id={id}>Client-only content</div>;
}Call useId() at the top level of your component, not inside conditionally rendered sections:
// ❌ BAD: useId() called conditionally
function MyComponent({ showItems }: { showItems: boolean }) {
return (
<>
{showItems && items.map(() => {
const id = useId(); // Called different times on server vs client
return <Item key={id} id={id} />;
})}
</>
);
}
// ✅ GOOD: useId() always called consistently
function MyComponent({ showItems }: { showItems: boolean }) {
const baseId = useId();
return (
<>
{showItems && items.map((item, index) => {
const id = `${baseId}-${index}`; // Derive IDs from stable base
return <Item key={id} id={id} />;
})}
</>
);
}Or extract the component with useId() to a separate component:
function ItemWithId({ item }: { item: Item }) {
const id = useId(); // Always called when component renders
return <div id={id}>{item.name}</div>;
}
function MyComponent({ showItems }: { showItems: boolean }) {
return (
<>
{showItems && items.map(item => (
<ItemWithId key={item.id} item={item} />
))}
</>
);
}As a last resort, you can suppress the hydration warning on specific elements where the mismatch is intentional and safe:
function MyComponent() {
const id = useId();
return (
<div id={id} suppressHydrationWarning>
{/* Content that differs between server and client */}
</div>
);
}Important caveats:
- This only suppresses the warning one level deep (only direct children)
- It doesn't fix the underlying issue, just silences the error
- Use sparingly and only when you've verified the mismatch is truly safe
- Document why the suppression is needed for future maintainers
This is useful for browser-specific content like theme toggles or client-only features:
function ThemeToggle() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const id = useId();
useEffect(() => {
setTheme(localStorage.getItem('theme') as 'light' | 'dark' || 'light');
}, []);
return (
<button id={id} suppressHydrationWarning>
Theme: {theme}
</button>
);
}React 19 includes fixes for many useId() hydration issues. If you're on React 18 and encountering persistent problems, upgrading may resolve them:
npm install react@latest react-dom@latestFor Next.js projects:
npm install next@latest react@latest react-dom@latestAfter upgrading, test thoroughly as React 19 includes other breaking changes that may affect your application.
Check the React 19 upgrade guide for migration steps:
https://react.dev/blog/2024/12/05/react-19
Why useId() exists: The useId() hook was introduced in React 18 to generate stable, unique IDs for accessibility attributes in SSR environments. Before useId(), developers used libraries like uuid or nanoid, which caused hydration mismatches because UUIDs generated on the server differed from those on the client.
How React generates IDs: React's ID generation uses a hierarchical counter system based on the component tree structure. Each call to useId() increments a counter, and the final ID includes information about the component's position in the tree. This ensures consistency between server and client *as long as the component tree structure is identical*.
Next.js App Router considerations: In Next.js 13+ with the App Router, Server Components don't hydrate (they're server-only), but Client Components that import them can still cause mismatches. Be careful when passing props from Server Components to Client Components that use useId().
Third-party library issues: UI libraries like Radix UI, Headless UI, and MUI use useId() internally for accessibility. If you see ID mismatch errors from these libraries, the issue is likely in how you're conditionally rendering their components, not a bug in the library itself.
Performance impact: Hydration mismatches force React to re-render the entire subtree on the client, negating the performance benefits of SSR. Fix these errors even if the visual result appears correct.
Debugging tips: Use React DevTools to compare the server-rendered HTML (view page source) with the client-rendered tree. Look for structural differences in conditional rendering that might cause the ID sequence to diverge.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite