Hydration Proof

Search documentation

Find a page or section

Fix date and time hydration errors

The server renders at one moment, the browser at another.

A Next.js date hydration error happens when a component reads the current time during render. The server renders first; the browser renders later, so the values can differ. Pass the server's time as a prop, or read the clock after hydration.

Symptoms

React uses different wording depending on its version and whether you run a development or production build:

Hydration failed because the server rendered text didn't match the client.
Text content does not match server-rendered HTML.
Warning: Text content did not match. Server: "12:04:31" Client: "12:04:33"
Minified React error #418; visit https://react.dev/errors/418

hydration-proof reports a text difference as HP1001 or an attribute difference as HP1002. The likely cause is shown as Time-dependent value. Use the two values in the finding to check whether the clock is involved.

If it happens only sometimes, look for rounded values such as "3 minutes ago". The text changes only when the two renders fall on opposite sides of the rounding boundary.

Why it happens

These calls read the clock during render:

  • Date.now(), new Date() and Date() with no arguments
  • performance.now() and Temporal.Now.*()
  • relative formatting built on them: "3 minutes ago", countdowns, "today"

React compares the first browser render with the server HTML. A new Date() hydration mismatch or a Date.now() hydration mismatch occurs when the clock has moved enough to change the displayed value. A value rounded to a minute may match for a while; a millisecond timestamp will not.

How to fix it

Pass the server's time as a prop

Read the clock on the server and pass that value to the client. Both renders then start from the same number:

app/post/[slug]/page.tsx
// A Server Component: it only renders on the server.
export default async function Page() {
  const post = await getPost();
  return <PostDate publishedAt={post.publishedAt} renderedAt={Date.now()} />;
}
app/post/[slug]/post-date.tsx
"use client";
 
export function PostDate({
  publishedAt,
  renderedAt,
}: {
  publishedAt: number;
  renderedAt: number;
}) {
  const minutes = Math.round((renderedAt - publishedAt) / 60_000);
  return <p>Updated {minutes} minutes ago</p>;
}

Render the time after hydration

For a live clock or countdown, render a placeholder first. Fill it in from an effect after hydration, so the first browser render still matches the server:

countdown.tsx
"use client";
 
import { useEffect, useState } from "react";
 
export function Countdown({ endsAt }: { endsAt: number }) {
  const [now, setNow] = useState<number | null>(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 left</span>;
}

useEffect and two-pass rendering explains the pattern and when the flash of the placeholder matters.

Format a fixed date, not "now"

A stored date such as new Date(post.publishedAt) has the same underlying value on both sides. Formatting can still differ if the server and browser use different timezones or locales. Set those explicitly too.

suppressHydrationWarning stops React from reporting the difference, but it does not update the text. Use it only when the first value does not matter, such as a live clock that updates immediately. Otherwise, visitors may see stale text until the next render. See when suppressHydrationWarning is safe.

Catch it with ESLint

no-date-in-render reports Date.now(), new Date(), performance.now() and Temporal.Now in render code, and skips Server Components with the next preset:

npm install -D eslint-plugin-hydration-proof

no-timezone-without-explicit-timezone catches the formatting half.

Catch it in CI

hydration-proof test checks each route for text and attribute differences. Add --probe to reload a page with the browser clock changed. If its client value moves with the clock, the cause is proven. See probes.

npx hydration-proof test --probe