Hydration Proof

Search documentation

Find a page or section

eslint-plugin-react-hooks vs hydration-proof

Purity keeps re-renders stable. Hydration needs the server and the browser to agree.

The eslint-plugin-react-hooks purity rule reports calls to known impure functions, such as Date.now() and Math.random(), while a component renders. It protects re-renders and memoization. eslint-plugin-hydration-proof targets hydration: 15 rules for values that differ between the server and the browser, including code the purity rule accepts. The two overlap on the clock and random values, and run fine together.

At a glance

The purity ruleeslint-plugin-hydration-proof
GoalComponents and hooks are pure, so re-renders and memoization are safeThe server render and the hydration render produce the same output
Comes withThe recommended preset of eslint-plugin-react-hooks 7, and eslint-config-nextIts own recommended, next and strict presets
Clock and random valuesDate.now(), Math.random(), performance.now() in 7.1.1Those, plus new Date(), crypto.randomUUID(), uuid, nanoid, lodash and more
Lazy useState initializersAcceptedReported: the initializer runs on the server and again during hydration
Locale and timezone formattingNot checkedtoLocaleString(), Intl.* and date getters without an explicit locale or timezone
Browser APIs, storage, matchMediaNot checkedReported in render and initial state
Invalid HTML nestingNot checked<div> in <p>, <a> in <a>, table structure
suppressHydrationWarningNot checkedAudited
Server ComponentsNo special handlingSkipped by the next preset, where the clock cannot cause a mismatch
MessageWhy the call is impureWhat the server and the browser render, and the fix

What the eslint-plugin-react-hooks purity rule does well

react-hooks/purity is one of the React Compiler's rules, now shipped in eslint-plugin-react-hooks. React's docs say it "validates that components/hooks are pure by checking that they do not call known-impure functions" (purity). It reports:

Error: Cannot call impure function during render
 
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render.
  • It is on by default. purity is in the recommended preset of eslint-plugin-react-hooks 7, and eslint-config-next includes that preset, so Next.js apps that use it already run the rule.
  • It sees data flow. It runs the compiler's analysis, not a pattern match.
  • It covers more than hydration. Impure renders break memoization and the React Compiler, not only server rendering. Its docs name hydration mismatches as one of the bugs impurity causes.

The React Compiler's lint rules used to live in a separate package, eslint-plugin-react-compiler. React's Compiler 1.0 announcement says to remove it and use eslint-plugin-react-hooks@latest instead.

Where hydration-proof differs

Pure is not the same as hydration-safe. Purity asks whether a render gives the same result every time it runs in one place. Hydration asks whether the server and the browser give the same result. The purity docs show this as the valid way to create an id:

components/item.tsx
import { useState } from "react";
 
function Item() {
  // Stable across re-renders, so react-hooks/purity accepts it.
  // The server and the browser each run the initializer once, and get
  // different UUIDs: the hydration render does not match the server HTML.
  const [id] = useState(() => crypto.randomUUID());
  return <div id={id}>Content</div>;
}

eslint-plugin-hydration-proof reports it (no-unstable-id), because lazy initializers run during the first render on both sides. The same goes for useState(() => Date.now()), which the purity docs use for a clock: fine for re-renders, a guaranteed mismatch during hydration.

More causes than the clock. Many hydration mismatches never call an impure function:

new Date(). In eslint-plugin-react-hooks 7.1.1, the compiler marks Date.now, Math.random and performance.now as impure. A reported gap was that new Date().getTime() and new Date().getFullYear() were not flagged; the fix, which treats new Date() without arguments as impure, was merged on September 15, 2026 and is not in a stable release yet. no-date-in-render reports new Date(), Date() and Temporal.Now today.

Server Components. With the next preset, eslint-plugin-hydration-proof skips Server Components, where reading the clock cannot cause a mismatch because the browser receives the result, not the code.

Use both

Keep the purity rule for what it protects, and add eslint-plugin-hydration-proof for hydration:

eslint.config.mjs
import { defineConfig } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import hydrationProof from "eslint-plugin-hydration-proof";
 
export default defineConfig([...nextVitals, hydrationProof.configs.next]);

A Date.now() or Math.random() in render is then reported twice, once by each plugin, with different reasons. Keep both reports, or turn one of the overlapping rules off. The ESLint plugin guide covers presets and settings.

npm install -D eslint-plugin-hydration-proof

A linter reads one file at a time. Values passed through props or context, nesting across components, CSS-in-JS and anything that happens outside your code need hydration-proof test, which checks the running app.

Sources

As of September 2026: