Fix Next.js Hydration Mismatch Errors (2026 Complete Guide)
The error:
A tree hydrated but some attributes of the server rendered HTML didn't
match the client properties. This won't be patched up. This can happen
if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes
each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with
the HTML.
- Invalid HTML tag nesting.
TL;DR: the HTML your server rendered is different from what React rendered on the client. The most common 2026 causes are locale-aware date/number formatting, third-party scripts that mutate the DOM, and browser-specific globals (window, navigator, localStorage) called during render.
What causes Next.js hydration mismatch?
| Cause | How it shows up | Fix |
|---|---|---|
| Date formatting differs by locale | new Date().toLocaleString() returns one string on server, another on client | Pass locale explicitly, or render only after useEffect |
| Math.random() / Date.now() in render | Different value each render | Move to a useState initialized in useEffect |
| window, navigator, localStorage access during render | Server has no window, throws or undefined | Use useEffect, or typeof window !== "undefined" guard, or a useIsClient hook |
| Browser extensions injecting HTML | <body> gains data-extension-* attributes after page load | Add suppressHydrationWarning to <body> |
| Third-party scripts mutating DOM | Analytics, chat widgets, A/B testing | Load via next/script with strategy="afterInteractive" |
| Invalid HTML nesting (<div> inside <p>) | The browser silently rewrites the tree | Fix the markup |
| next-intl or react-i18next flashing | First paint shows source language, then swaps | See the next-intl flash fix or use a zero-flash i18n library |
| Server / client time zone differences | new Date().toLocaleString("en", { timeZone: "Europe/Paris" }) on server, browser default on client | Set explicit timeZone everywhere |
Step-by-step debugging
Step 1 — Read the React DevTools warning carefully
The browser console shows the exact element where the mismatch occurred. Expand the stack trace; React 18 highlights the component and prop that differed.
Step 2 — Disable browser extensions
Open the page in a private/incognito window with all extensions disabled. If the error disappears, an extension is mutating your HTML. Add suppressHydrationWarning to your <html> and <body> tags:
// app/layout.tsx
<html lang="en" suppressHydrationWarning>
<body suppressHydrationWarning>
{children}
</body>
</html>This is a safe targeted suppression — it only stops the warning for the attributes of these elements, not for their children.
Step 3 — Audit for browser globals during render
Search your codebase for these patterns:
grep -rn "typeof window" src/
grep -rn "localStorage" src/
grep -rn "Math.random()" src/
grep -rn "Date.now()" src/
grep -rn "new Date()" src/ | grep -v ".test."For each hit, ask: does this run during render? If yes, move it to useEffect.
Step 4 — Use a useIsClient hook
import { useEffect, useState } from "react";
export function useIsClient() {
const [isClient, setIsClient] = useState(false);
useEffect(() => setIsClient(true), []);
return isClient;
}
// Usage
function MyComponent() {
const isClient = useIsClient();
if (!isClient) return null; // or a server-safe placeholder
return <div>{new Date().toLocaleString()}</div>;
}Step 5 — Identify i18n flash specifically
If your error message includes attribute differences on text content (children), and you use next-intl, react-i18next, or any DOM-injection translation tool (e.g. Weglot), you have an i18n hydration mismatch. The page rendered in the source language on the server, then the i18n library swapped to the user's locale on the client.
Fixes:
- Set the locale before render. With
next-intl, passlocalefrom the URL segment or middleware header to theNextIntlClientProvider. Server must know the locale at render time, not detect it client-side. - Use a zero-flash React i18n library. Lovalingo renders the correct locale before paint by integrating into the React tree at the provider level. No client-side language swap.
- Use locale-prefixed URLs.
/en/page,/fr/page— never a single URL that detects locale at runtime.
Next.js-specific i18n hydration fix
If you use the Next.js App Router with next-intl:
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
export default async function LocaleLayout({
children,
params: { locale },
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}The critical detail: <html lang={locale}> is set from the server-known locale, not from navigator.language (which only exists on the client).
If you use Lovalingo instead:
// app/layout.tsx
import { LovalingoProvider } from "@lovalingo/lovalingo";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<LovalingoProvider
publicAnonKey={process.env.NEXT_PUBLIC_LOVALINGO_KEY!}
defaultLocale="en"
locales={["fr", "es", "de", "it", "pt", "zh", "ar", "ja"]}
routing="path"
>
{children}
</LovalingoProvider>
</body>
</html>
);
}Lovalingo handles the locale resolution server-side via the URL path, so there is no client-side flash to recover from.
When suppressHydrationWarning is OK
suppressHydrationWarning is appropriate when:
- A browser extension is the cause (you cannot control it)
- Date / time / locale formatting is intentionally per-user
- A third-party script mutates a specific element you control
It is NOT appropriate when:
- You have a real bug that will cause inconsistent UX
- You are using it on
<body>or a large subtree just to silence the warning
Related guides
- Fix next-intl translation flash
- Next.js i18n setup
- Best React translation libraries 2026
- Lovalingo + Next.js integration
- Official Next.js docs: nextjs.org/docs/messages/react-hydration-error
Updated 2026-05-22 by Charles Perret.