Calling Date.now() in render makes the output depend on the moment the
component runs: the server renders at one time, the browser hydrates at
another, and the text no longer matches. no-date-in-render reports
Date.now(), new Date(), performance.now() and Temporal.Now in render
code, and tells you where to read the time instead.
| Rule | hydration-proof/no-date-in-render |
|---|---|
| What it reports | Disallow reading the current time while a component renders |
| recommended / next | Error |
| strict | Error |
| Server Components | Skipped with the next preset (they never hydrate) |
| Suggestions | No |
| Options | none |
What it reports
These calls in render code:
Date.now()new Date()andDate()without argumentsperformance.now()Temporal.Now.*()
It also finds them through window., self. and globalThis., and ignores
local variables that happen to be called Date or performance. Dates built
from explicit values (new Date(post.publishedAt)) are fine.
Why Date.now() in render breaks hydration
The server renders the component at one moment, and the browser renders it again when it hydrates, a few hundred milliseconds (or, with a cached page, days) later:
server HTML: <p>Rendered at 1767225600000</p>
client render: <p>Rendered at 1767225600412</p>React finds different text, reports a hydration error and, in React 19, throws the server HTML away and renders the page again on the client. Date and time hydration errors covers every fix in detail.
Incorrect
function Footer() {
return <p>© {new Date().getFullYear()}</p>;
}
function Countdown({ endsAt }) {
const left = endsAt - Date.now();
return <span>{Math.round(left / 1000)}s</span>;
}
function Timer() {
const [start] = useState(() => performance.now());
return <Elapsed since={start} />;
}Correct
Pass the time from a Server Component, so both renders use the same value.
With the next preset, this file is a Server Component and is not checked:
export default async function Page() {
return <Countdown endsAt={deadline} now={Date.now()} />;
}Or render a placeholder and read the clock after hydration:
function Countdown({ endsAt }) {
const [now, setNow] = useState(null);
useEffect(() => {
setNow(Date.now());
const id = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(id);
}, []);
if (now === null) return <span>–</span>;
return <span>{Math.round((endsAt - now) / 1000)}s</span>;
}
// Explicit dates are deterministic.
export function Published({ at }) {
return <time dateTime={at}>{new Date(at).toISOString().slice(0, 10)}</time>;
}Reading the clock in event handlers, effects and useCallback callbacks is
fine: they run after hydration.
Options
This rule has no options.
Messages
What ESLint prints for this rule, word for word:
<source>reads the clock during render. The server renders one time and the browser another while hydrating, so the output does not match. Pass the time from the server (props or data), or read it in useEffect after hydration.
When not to use it
When the component is never server-rendered (a client-only app, or a
component loaded with next/dynamic and ssr: false),
or when the output is wrapped in an element with suppressHydrationWarning on
purpose (for example a live clock).
Related
no-unstable-idreports the clock when the value ends up in anid(orhtmlFor,aria-*,name). Those calls are not reported by this rule.no-timezone-without-explicit-timezonereportsnew Date().getHours()for the time zone; this rule reports thenew Date()in it. They are different problems.require-stable-server-snapshotreports the clock insidegetServerSnapshot.- eslint-plugin-react-hooks: its
purityrule (part of the React Compiler rules) also flags known impure calls such asDate.now()during render, because they make renders unpredictable. This rule explains the hydration consequence, skips Server Components, coversperformance.now()andTemporal.Now, and hands ids tono-unstable-id. With both enabled you see two reports for the same call; turn one off if you prefer a single report. - Text content does not match server-rendered HTML, the error this rule prevents