Calling Date.now() in render gives the server and browser different values:
they render at different times. no-date-in-render flags that call, along
with new Date(), performance.now() and Temporal.Now. Pass a time from
the server or read the clock after hydration.
| 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.*()
The rule also catches calls through window., self. and globalThis.. It
does not flag a local variable named Date or performance. A date made
from a known value, such as new Date(post.publishedAt), is fine.
Why Date.now() in render breaks hydration
The server and browser render at different times. Even a short delay can change the result:
server HTML: <p>Rendered at 1767225600000</p>
client render: <p>Rendered at 1767225600412</p>React sees different text and may re-render that part of the page. See date and time hydration errors for the fixes.
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
One fix is to read the time in a Server Component and pass it to the client.
The next preset skips this Server Component:
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 an effect or event handler is safe for hydration; those run after the first browser render.
Options
This rule has no options.
Messages
The exact ESLint messages:
<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
Disable this rule for a component that never renders on the server, such as
one loaded with next/dynamic and ssr: false.
For a deliberate live clock, consider suppressHydrationWarning on the
smallest element that holds the changing text.
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