A server component rendering a timestamp via `Date.now()`. Client hydrates with its own `Date.now()`. Times differ. React logs hydration mismatch, sometimes paints stale content.
The vulnerable diff
// app/page.tsx (Next.js server component)
export default function Page() {
// BUG: Date.now() differs between server render and client hydration
return (
<div>
<TimeStamp time={Date.now()} />
</div>
);
}What is wrong
Server components render once on the server, then the HTML is sent to the client where React re-runs the same render and reconciles. Anything non-deterministic — current time, random number, user-locale formatting that differs between server and client — produces different output on the two runs. React detects the mismatch, logs a warning, and in some cases throws away the server-rendered DOM and re-renders from scratch (a layout shift the user can see). The fix is to render dynamic values only on the client (`useEffect` after mount), or to pass a stable server-rendered value and live with it.
The attack
Reproducer:
# Browser console after navigation:
Warning: Text content did not match. Server: "1759872134821" Client: "1759872135244"
# Visible: timestamp briefly shows server time, then jumps to client time.Worse forms — locale-dependent currency formatting differing between server and client — produce visible content jumps every page load.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Language] [Hydration] [medium]
`Date.now()` inside a server component (or server-rendered prop)
produces a hydration mismatch. The server timestamp and client
timestamp differ by however long the HTTP roundtrip + parse took.
Two correct shapes. Pick by whether the value must be 'current' on
client or 'as-of-render' from server:
// Option A: client-only render (no SSR for this piece)
"use client";
export function TimeStamp() {
const [t, setT] = useState<number | null>(null);
useEffect(() => setT(Date.now()), []);
if (t === null) return null; // server renders nothing
return <span>{new Date(t).toLocaleString()}</span>;
}
// Option B: pass server time, accept the freeze
// Server: const t = Date.now(); return <TimeStamp time={t} />
// Client: <span>{new Date(props.time).toLocaleString()}</span>
// (formatting needs to match server's locale settings)
For locale-dependent rendering, pin the locale on the server and
pass it to the client component to ensure parity.The fix
// app/page.tsx — fixed (option A)
"use client";
import { useState, useEffect } from "react";
function TimeStamp() {
const [t, setT] = useState<number | null>(null);
useEffect(() => setT(Date.now()), []);
if (t === null) return null;
return <span>{new Date(t).toLocaleString()}</span>;
}Render nothing on server (initial state null). After mount, set state and render. No hydration mismatch because the server output and the pre-mount client output match (both empty). The timestamp appears after hydration is complete.
Why human review missed it
Hydration mismatches are a 2024-2026 Next.js-specific bug class that didn't exist in pure-client React. The bug looks like working code in dev — the warning is in the console, easy to miss. Mesrai catches `Date.now()`, `Math.random()`, and locale-dependent functions inside server-rendered trees.
Related rules + further reading
Mesrai rule pack: language/no-nondeterministic-ssr — flags non-deterministic calls in server components.
Next.js docs: Avoiding Hydration Mismatches.
React 18+ docs: useId for stable ids across SSR/CSR.
Takeaway
Server output must be deterministic. Render dynamic values only on the client. Mesrai catches every Date.now / Math.random inside SSR.