skip to content

A server-rendered React component renders <span>{new Date().toLocaleTimeString()}</span>, and the browser console reports a hydration mismatch on every page load. Explain the cause and how you would show the local time without the mismatch.

level: middleimportance: must knowfreq 72%

answer

  1. two clock reads, two environments
  2. server is UTC, browser is not
  3. effects never run on the server
  4. deterministic first render, upgrade after mount
  5. relative time and random ids have the same shape

basics

~20 s

The server and browser call new Date() at different moments, in different timezones and locales, so the two renders produce different text. Render a stable server-provided value first and switch to the localized time in an effect after mount.

solid answer

~50 s

Two things vary between the runs. First, time: the server formats the clock at request time and the browser formats it milliseconds or seconds later during hydration, so the strings differ. Second, environment: `toLocaleTimeString` depends on the runtime's locale and timezone, and a server in UTC will not format the same instant the way a browser in Europe/Berlin does — so even a fixed instant can mismatch. The fix is to make the first client render agree with the server by construction. Pass the instant down from the server as a stable ISO string, render that (or a neutral placeholder) initially, and compute the localized text in `useEffect`, which runs only in the browser after hydration succeeds. If the exact rendered string genuinely must be browser-local from the start, mark that element `suppressHydrationWarning` — that is an acknowledgement, not a repair.

go deeper

for a junior

Know that time and random values differ between the two runs, and that browser-only work belongs in an effect rather than in the render body.

for a middle

Separate the two causes — the clock advancing and the runtime's locale/timezone default — and write the two-pass fix, explaining why an effect cannot cause a mismatch.

for a senior

Weigh the options in a real app: an explicit server-side locale and timezone versus post-mount formatting, and manage the visual cost of the swap with reserved space so the correction does not shift layout.

for a principal

Set the standard that render is a pure function of props and server-known data, and decide how per-user locale and timezone reach the server at all — request headers, a user preference, or an accepted post-mount correction — so teams stop rediscovering this bug per component.

## Two independent sources of divergence It is worth separating them, because candidates usually name only the first. **Non-determinism in time.** `new Date()` reads the clock. The server reads it while building the response; the browser reads it again during hydration, some hundreds of milliseconds or several seconds later. If the format includes seconds, the two strings differ almost always; with minute precision they differ only sometimes, which is worse — an intermittent mismatch is much harder to diagnose. `Math.random()` and module-level id counters have the same shape of problem. **Environment-dependent formatting.** `toLocaleTimeString()` with no arguments uses the *runtime's* default locale and timezone. Node on a server typically runs in UTC with whatever ICU locale the image ships; the user's browser reports their real timezone and language. So even if you froze the instant, the formatted output can still differ: `14:05:00` versus `4:05:00 PM`, or a completely different clock hour. This is why passing a preformatted string from the server is not always enough — the whole point of the UI is usually to show *the user's* local time, which the server does not know. ## Why React cannot smooth this over Hydration adopts existing DOM rather than creating it. React walks the server's nodes claiming them for the tree it is building now, so it needs the two descriptions to line up. When the text differs, React reports a hydration error and falls back to client-rendering the subtree, discarding that piece of server HTML. ## The fix that scales: deterministic input, browser-only formatting Render something both environments can agree on, then upgrade after mount: ```jsx function LocalTime({ iso }) { // iso comes from the server render const [text, setText] = useState(iso); // identical on server and first client render useEffect(() => { setText(new Date(iso).toLocaleTimeString()); }, [iso]); return <time dateTime={iso}>{text}</time>; } ``` Why this works: effects do not run during server rendering, and on the client they run *after* hydration has committed. So the first client render is a pure function of `iso` — exactly what the server rendered — and the localized string appears in a second render that React performs normally, with no adoption involved. The cost is a brief flash of the server value and one extra render. Reserve a stable width or render a `<time>` element with the machine-readable `dateTime` attribute so the swap does not shift layout. ## The variant with a mounted flag A common generalization is to gate any browser-only rendering behind a flag: ```jsx const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); if (!mounted) return <span className="skeleton" />; ``` This is correct but blunt: it opts the whole subtree out of server rendering, so use it for genuinely client-only widgets, not as a reflex whenever a mismatch appears. If you apply it broadly you have re-implemented client-side rendering while still paying for SSR. ## When the ticking clock is the point If the component is a live clock that updates every second anyway, there is no value in server-rendering the digits at all: render a placeholder or the server instant, and let an interval in an effect own the display from there. The interval is browser-only by definition. ## Where suppressHydrationWarning fits If you truly want the browser's formatting on the very first paint and accept that the server string was a guess, put `suppressHydrationWarning` on that element. React will not report the difference for that node's text. It does not make the two renders agree and it reaches only one level deep, so treat it as a narrow, deliberate acknowledgement on a leaf element — never as a blanket wrapper around a region that mismatches for reasons you have not diagnosed. ## The generalization interviewers are listening for Anything whose value depends on *when* or *where* the code runs is unsafe to render on both sides: clocks, relative time ("3 minutes ago"), random ids, currency and number formatting, and locale-dependent collation. The rule that covers them all is that render must be a pure function of props and data the server also has. Where the value is genuinely per-user, get it after mount rather than guessing at it during render.

  • You pass the instant down as an ISO string and still format it with toLocaleTimeString() during render. Does the mismatch go away?
    No. Freezing the instant removes the clock-drift half of the problem, but `toLocaleTimeString()` still uses the runtime's default locale and timezone, and the server's differ from the user's. Either format on the server with an explicit locale and timezone and render that exact string on both sides, or move the localized formatting into an effect after mount.
  • How would you render "3 minutes ago" style relative timestamps in a server-rendered React app?
    Send the absolute instant from the server and render it as an absolute, locale-independent value initially — or a placeholder — then compute the relative string in an effect and refresh it on an interval. Relative time is a function of the current clock, so it is nondeterministic by nature; there is no server value that stays correct long enough to hydrate against.
  • Why does moving the browser-only work into useEffect make hydration safe, rather than just moving the bug later?
    Because effects do not run during server rendering, and on the client React runs them only after the commit that finishes hydration. The render React compares against the server HTML never sees the browser value, so there is nothing to disagree about; the localized value arrives in a subsequent, ordinary render where React updates the DOM instead of adopting it.

saying these in an interview costs you the question

  • Suggests wrapping it in suppressHydrationWarning without addressing the cause
  • Thinks passing an ISO string alone removes locale and timezone divergence
  • Says useEffect runs on the server too, so it would not help
  • Proposes disabling SSR for the whole page to silence one timestamp
  • Claims React re-syncs the text automatically after hydration

context