Why time breaks hydration

Server rendering produces HTML at one moment; the browser hydrates it at a later one. Any component calling `Date.now()` or `new Date()` during render computes a different value in each place, so the markup differs and React reports a hydration mismatch.

It is worse than a console warning. React may discard the server HTML and re-render on the client, throwing away the benefit of server rendering for that subtree — you paid for SSR and got client rendering with extra steps.

The now prop

`Countdown` and `Clock` accept a `now` prop and never read the clock during render. You pass the reference time in, so the server and the client are given the same value and produce identical HTML. After hydration the component takes over and ticks normally.

This is a general pattern worth stealing: push non-deterministic values in as props rather than reading them inside render. The same applies to random values, which cause identical mismatches for identical reasons.

AnimatedCounter renders its final value

A counter that animates from zero to a target has an obvious naive implementation: render `0`, then animate up after mount. On a server-rendered page that means the HTML contains `0`, and crawlers or users with JavaScript disabled see zero rather than your actual metric.

`AnimatedCounter` renders its final value in the server HTML and animates only after hydration. The number is correct before any JavaScript runs, which matters for a stats section on a marketing page where the figures are the content.

RelativeTime and the locale trap

'3 hours ago' is doubly non-deterministic: it depends on the current time and on the viewer's timezone, neither of which the server knows. A server in UTC and a browser in Tokyo will disagree about what day a timestamp falls on.

The reliable approach is a stable absolute value in the server HTML — with a `<time dateTime>` attribute carrying the machine-readable timestamp — and localised relative formatting after hydration. `Intl` handles the formatting, which is also how `Calendar` localises without a date library and part of how the package keeps zero dependencies.

Finding these bugs before users do

Hydration mismatches are loud in development and silent in production, so a clean production build is not evidence they are absent. Watch the console in development specifically for hydration warnings and treat them as errors rather than noise.

A quick audit: grep your codebase for `Date.now()`, `new Date()` and `Math.random()` inside component bodies. Every occurrence in a component that server-renders is a mismatch waiting to happen. In effects they are fine, because effects only run on the client.

The general principle

Render must be a pure function of props and state. Time, randomness, `window` dimensions and storage are all inputs from outside that function, and reading them during render breaks the assumption server rendering depends on.

Pass them in as props, or read them in an effect after mount. This is the entire technique — every hydration-safe component in the library is an application of it, and it applies to your own components identically.

The one exception worth knowing is `useSyncExternalStore`, which exists precisely for reading external values safely and lets you supply a distinct server snapshot. When you genuinely need a browser-only value during render, that is the supported route rather than reaching for the global directly.

Key takeaways

  • Reading the clock during render makes server and client HTML differ, and React may discard the server output entirely
  • Countdown and Clock take a now prop so both environments are given the same reference time
  • AnimatedCounter renders its final value in server HTML, so crawlers never see 0
  • RelativeTime depends on time and timezone — emit an absolute <time dateTime> and localise after hydration
  • Audit by grepping for Date.now(), new Date() and Math.random() inside component bodies
  • Render must be pure: pass non-deterministic values as props, or read them in an effect

Conclusion

Every hydration bug involving time is the same bug: render read something the server could not know. Passing it in as a prop is a small constraint that removes a whole category of mismatches, and it costs nothing once it is the habit.

Enjoyed this article?

Vivek Kumar Singh

Vivek Kumar Singh

Technical Expert · Full Stack Cloud Engineer · Tokyo, Japan