“Your production console shows Minified React error #418, and support says the page flickers on load. What is going on and how do you fix it?” Hydration errors are one of the most common real-world Next.js bugs, so interviewers like them: the question is practical, and the answer separates people who have debugged server rendering from people who have only used it.
The interviewer wants three things: an explanation of what hydration is and why a mismatch matters, a systematic way to find the cause, and fixes that keep the server-rendered HTML useful instead of turning everything into client-only rendering.
Before you start
You should know that Client Components in the App Router are rendered to HTML on the server and then hydrated in the browser, and you should be comfortable with useState and useEffect. Knowing how browsers parse HTML, including which elements may not nest inside others, helps with one category of bugs. Examples use Next.js 15 with React 19.
The short answer
Hydration is React attaching event handlers and state to HTML that the server already rendered, which requires the first client render to produce exactly the same markup. When it differs, React 19 throws away the server HTML for that part of the tree and renders it again on the client, logging “Hydration failed because the server rendered HTML didn’t match the client” in development and error #418 in production. Typical causes are values that differ between server and browser (time, randomness, locale, time zone), reading window or localStorage during render, invalid HTML nesting, and browser extensions. Fix by rendering the same initial output on both sides and moving browser-only values into an effect.
How it works
On a first page load the browser receives HTML for the whole page, including Client Components rendered on the server. React then calls hydrateRoot and renders those components again, in the browser, walking the existing DOM instead of creating it. For each node it expects to find the element or text it would have created.
'use client';
export function Clock() {
return <p>{Date.now()}</p>; // server and browser run this at different moments
}The server rendered 1791297010399, the browser renders a later number, and the text nodes differ. In a production build of a Next.js 15.5 test app, the console shows exactly this:
Uncaught Error: Minified React error #418; visit https://react.dev/errors/418?args[]=text&args[]=
for the full message or use the non-minified dev environment for full errors and additional
helpful warnings.The args[]=text part says the mismatch was in text content rather than element structure. In development, the full message lists the usual causes and the Next.js overlay shows a diff of the server and client markup.
React does not patch the differing text in place. It discards the server-rendered DOM for the affected tree and renders it on the client, up to the nearest Suspense boundary. That is where the visible flicker, lost scroll position and slower interactivity come from. Hydration errors are not cosmetic warnings.
Step-by-step walkthrough
Step 1: Reproduce it with the full message
Production messages are minified. Run next dev, open the page and read the overlay, which names the component and shows the differing markup. If the bug only appears in production, decode the error at react.dev/errors/418 and compare curl output with what the browser renders: curl -s localhost:3000/page shows the server HTML exactly as sent.
Step 2: Put the cause in a category
Almost every hydration bug falls into one of five groups:
- Values that differ by environment:
Date.now(),new Date(),Math.random(),toLocaleString()with the default locale and time zone, generated ids. - Browser-only branches in render:
typeof window !== 'undefined',localStorage,matchMedia,navigator. - Invalid HTML nesting: a
divinside ap, apinside ap, anainside ana, or a missingtbody. The browser’s parser fixes the HTML before React sees it, so the DOM no longer matches. React 19 warns in development that such nesting “will cause a hydration error”. - Data that changed between render and hydration: for example, a client fetch that runs during render instead of reusing the server’s data.
- Browser extensions that inject attributes or elements into the page, often into
htmlorbody.
Step 3: Apply the fix that matches the category
For environment-dependent values, render something deterministic first and update after hydration:
'use client';
import { useEffect, useState } from 'react';
export function LocalTime({ iso }: { iso: string }) {
const [text, setText] = useState<string | null>(null);
useEffect(() => {
setText(new Date(iso).toLocaleTimeString()); // runs only in the browser, after hydration
}, [iso]);
return <time dateTime={iso}>{text ?? 'Loading time'}</time>;
}Or remove the difference entirely by formatting with an explicit locale and time zone on both sides:
const fmt = new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeStyle: 'short', timeZone: 'UTC' });
fmt.format(new Date('2026-10-06T18:45:00Z')); // '6 Oct 2026, 18:45' on every machineFor a browser-only widget, such as a map that touches window as soon as it is imported, load it without server rendering from a Client Component. In Next.js 15, ssr: false is rejected in Server Components.
'use client';
import dynamic from 'next/dynamic';
const Map = dynamic(() => import('./Map'), { ssr: false, loading: () => <p>Loading map</p> });
export function MapPanel() {
return <Map />;
}For invalid nesting, change the markup: use a div for a container that holds blocks, and do not nest interactive elements.
Step 4: Use suppressHydrationWarning only where a mismatch is expected
suppressHydrationWarning silences the warning for one element’s own text and attributes, one level deep. It is the right tool for a rendered timestamp you accept may differ, or for the html element when a theme script sets a class before React loads. It does not make children match and should never be a blanket fix.
Worked scenario
A dashboard shows “Last updated” and switches between light and dark themes. Users in India report a flicker on every load, and the console shows #418.
The “Last updated” component formats a timestamp with new Date(updatedAt).toLocaleString(), and both sides use an en-US locale. The server runs in UTC, so for 2026-10-06T18:45:00Z it renders 10/6/2026, 6:45:00 PM. A browser in Asia/Kolkata renders 10/7/2026, 12:15:00 AM, a different day. The theme toggle also reads localStorage.getItem('theme') during render, so the server renders the light icon and dark-mode users get the dark one on the client.
The fixes follow the categories. The timestamp is formatted with Intl.DateTimeFormat and an explicit timeZone, or shown in the user’s zone by an effect after hydration, with the ISO value in a time element for accessibility. The theme moves to a cookie that the server can read, so the server renders the right icon. For users without the cookie, a tiny inline script in the root layout applies the class before paint, and the html element gets suppressHydrationWarning because that one attribute is expected to differ. After the change, the console is clean and the page no longer re-renders on load.
Common mistake
- “It’s only a warning.” React re-renders the affected tree on the client, losing the benefit of server rendering for it.
- Wrapping the whole app in
suppressHydrationWarning. It only affects one level and hides real bugs. - Fixing with
typeof windowin render. That branch is exactly what creates a mismatch. Use an effect oruseSyncExternalStorewith a server snapshot. - Making everything client-only.
ssr: falseeverywhere removes the HTML that users and crawlers see first. Reserve it for components that cannot render on the server. - Ignoring extensions. If the diff shows attributes you never wrote, test in a clean browser profile before changing code.
Verify the behavior
Reproduce, fix, and lock it in with a test that fails on hydration errors.
npx next build && npx next start -p 3000 &
TZ=UTC node -e "console.log(new Date('2026-10-06T18:45:00Z').toLocaleString('en-US'))"
# 10/6/2026, 6:45:00 PM
TZ=Asia/Kolkata node -e "console.log(new Date('2026-10-06T18:45:00Z').toLocaleString('en-US'))"
# 10/7/2026, 12:15:00 AMThe two commands show why server and browser disagree even with the same locale. Then add an end-to-end test that collects console errors on page load:
// e2e/hydration.spec.ts (Playwright)
import { test, expect } from '@playwright/test';
test('dashboard hydrates without mismatches', async ({ page }) => {
const errors: string[] = [];
page.on('console', (msg) => msg.type() === 'error' && errors.push(msg.text()));
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
expect(errors.filter((e) => /418|Hydration/.test(e))).toEqual([]);
});Run it with the browser’s time zone set differently from the server (test.use({ timezoneId: 'Asia/Kolkata' })) to catch the date bug.
Follow-up questions
Why is reading window inside useEffect fine? Effects never run on the server and run after hydration, so their changes are ordinary updates, not part of the comparison.
Can Server Components cause hydration errors? They do not hydrate, but their HTML is part of the page. Invalid nesting in server output still breaks hydration of client components around it.
What does useSyncExternalStore add? It accepts a getServerSnapshot function, so you can render a server-safe value during hydration and then switch to the browser value, for example for online status or media queries.
Interview exercise
A header greets users with “Good morning” or “Good evening” based on the current hour. It renders in a Client Component with new Date().getHours(), and users in some countries see hydration errors. Give two fixes and say which you prefer.
Answer and reasoning
The server computes the hour in its own time zone, the browser in the user’s, so the text differs for anyone whose local hour falls in a different bucket. Fix one: render a neutral greeting such as “Welcome” on the server, then compute the time-based greeting in useEffect and update it. It is simple and always correct, at the cost of a small text change after load. Fix two: store the user’s time zone in a cookie or profile, compute the greeting on the server with Intl.DateTimeFormat and that timeZone, and pass it to the client. It renders correctly on first paint but needs a fallback for visitors without a stored zone. I would choose the second for signed-in users, where the profile has a time zone, with the first as the fallback. The reasoning to state is that both sides must agree on the first render, and the choice is about where the user’s time zone is known.
Continue learning
Practise more rendering questions in the Next.js interview questions and the Next.js MCQs. Read React hydration and matching initial content for the React side, and Next.js Server vs Client Components for where components render. The official references are the Next.js hydration error guide and React’s hydrateRoot documentation.