Hydration Proof

Search documentation

Find a page or section

Disallow reading the current time while a component renders.

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.

Rulehydration-proof/no-date-in-render
What it reportsDisallow reading the current time while a component renders
recommended / nextError
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsNo
Optionsnone

What it reports

These calls in render code:

  • Date.now()
  • new Date() and Date() without arguments
  • performance.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:

app/sale/page.jsx
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).

  • no-unstable-id reports the clock when the value ends up in an id (or htmlFor, aria-*, name). Those calls are not reported by this rule.
  • no-timezone-without-explicit-timezone reports new Date().getHours() for the time zone; this rule reports the new Date() in it. They are different problems.
  • require-stable-server-snapshot reports the clock inside getServerSnapshot.
  • eslint-plugin-react-hooks: its purity rule (part of the React Compiler rules) also flags known impure calls such as Date.now() during render, because they make renders unpredictable. This rule explains the hydration consequence, skips Server Components, covers performance.now() and Temporal.Now, and hands ids to no-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