Fix Next.js "Hydration failed because the server rendered HTML didn't match the client
The problem
Every page load threw:
Hydration failed because the server rendered HTML didn't match the client.
As a result this tree will be regenerated on the client.
The component rendered new Date(iso).toLocaleDateString() — the server (UTC) and the browser (America/Los_Angeles) produced different strings, so React's hydration comparison failed.
What didn't work
suppressHydrationWarningon the wrapper — silences one attribute mismatch, says nothing about the subtree, and the user still sees a date that flips on screen.Math.random()/Date.now()"moved into a memo" — still non-deterministic between server render and client hydration.- Returning
nulluntil mounted and rendering inuseEffect— fixes the error but flashes empty content and loses the SSR value of that text.
The fix
Make the FIRST client render byte-identical to the server's, then upgrade after mount:
'use client';
import { useEffect, useState } from 'react';
export function Timestamp({ iso }: { iso: string }) {
const [time, setTime] = useState<string | null>(null);
useEffect(() => {
setTime(new Date(iso).toLocaleString('en-US', { timeZone: 'America/New_York' }));
}, [iso]);
// Pass 1 (server + first client render): both output the ISO string -> identical.
// Pass 2 (after mount): locale-aware string swaps in -> no mismatch is possible.
return <time dateTime={iso}>{time ?? iso}</time>;
}
Pin locale AND timeZone explicitly — omitting either lets each environment's defaults differ, which is the root cause.
Why it works
Hydration compares the server HTML against the client's first render, so the only real fix is making that render deterministic; explicit locale/timeZone arguments stop the browser's own settings from generating a different string than the server did.