Hydration Proof

Search documentation

Find a page or section

Disallow reading the current time while a component renders.

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.

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.*()

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:

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 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.

  • 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