# ESLint rules

> All 15 ESLint rules for React hydration in eslint-plugin-hydration-proof, grouped by the mismatch they catch, with their severity in each preset.

Source: https://hydration.jscrate.dev/docs/rules
Last updated: 2026-09-18

These are the ESLint rules for React hydration in
`eslint-plugin-hydration-proof`: 15 rules, each for one way the server render
and the browser's first render can differ. Every rule is on in all three
presets; the presets change only the severity and whether Server Components
are checked. [Install the plugin](https://hydration.jscrate.dev/docs/eslint) to use them.

## All ESLint rules for React hydration

| Rule | What it reports | recommended | strict |
| --- | --- | --- | --- |
| [no-date-in-render](https://hydration.jscrate.dev/docs/rules/no-date-in-render) | Disallow reading the current time while a component renders | Error | Error |
| [no-random-in-render](https://hydration.jscrate.dev/docs/rules/no-random-in-render) | Disallow random values while a component renders | Error | Error |
| [no-browser-global-in-render](https://hydration.jscrate.dev/docs/rules/no-browser-global-in-render) | Disallow reading browser-only globals such as window and document while a component renders | Error | Error |
| [no-storage-in-initial-render](https://hydration.jscrate.dev/docs/rules/no-storage-in-initial-render) | Disallow reading localStorage or sessionStorage while a component renders, including state initializers | Error | Error |
| [no-match-media-in-render](https://hydration.jscrate.dev/docs/rules/no-match-media-in-render) | Disallow evaluating media queries with matchMedia while a component renders | Error | Error |
| [no-locale-without-explicit-locale](https://hydration.jscrate.dev/docs/rules/no-locale-without-explicit-locale) | Require an explicit locale for locale-sensitive formatting during render | Warning | Error |
| [no-timezone-without-explicit-timezone](https://hydration.jscrate.dev/docs/rules/no-timezone-without-explicit-timezone) | Require an explicit timeZone when dates are formatted or split into parts during render | Warning | Error |
| [no-unstable-id](https://hydration.jscrate.dev/docs/rules/no-unstable-id) | Disallow ids built from random values, the clock or module-level counters | Error | Error |
| [no-global-render-counter](https://hydration.jscrate.dev/docs/rules/no-global-render-counter) | Disallow changing module-level variables while a component renders | Error | Error |
| [no-window-render-branch](https://hydration.jscrate.dev/docs/rules/no-window-render-branch) | Disallow rendering different output depending on whether the code runs on the server or in the browser | Error | Error |
| [no-invalid-interactive-nesting](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting) | Disallow HTML nesting that the browser repairs while parsing, such as <div> in <p> or <a> in <a> | Error | Error |
| [audit-suppress-hydration-warning](https://hydration.jscrate.dev/docs/rules/audit-suppress-hydration-warning) | Report suppressHydrationWarning where it has no effect or hides more than intended | Error | Error |
| [no-client-only-initial-state](https://hydration.jscrate.dev/docs/rules/no-client-only-initial-state) | Disallow initial state and refs computed from browser-only values | Warning | Error |
| [require-stable-server-snapshot](https://hydration.jscrate.dev/docs/rules/require-stable-server-snapshot) | Require useSyncExternalStore to have a getServerSnapshot that returns the same value on the server and during hydration | Error | Error |
| [require-deterministic-list-order](https://hydration.jscrate.dev/docs/rules/require-deterministic-list-order) | Require list ordering during render to be the same on the server and in the browser | Warning | Error |

Each rule page shows what it reports, why it breaks hydration, code the rule
reports and code it accepts, its options and its exact messages.

## What each group of rules catches

### Time and random values

[`no-date-in-render`](https://hydration.jscrate.dev/docs/rules/no-date-in-render) reports `Date.now()`,
`new Date()`, `performance.now()` and `Temporal.Now` in render code.
[`no-random-in-render`](https://hydration.jscrate.dev/docs/rules/no-random-in-render) reports
`Math.random()`, `crypto.randomUUID()`, uuid, nanoid and lodash's random
helpers. Both values change between the server render and hydration; see
[time](https://hydration.jscrate.dev/docs/causes/time) and [random values](https://hydration.jscrate.dev/docs/causes/random).

### Browser-only values

The server has no `window`, storage or screen, so anything read from them
renders differently on each side:

- [`no-browser-global-in-render`](https://hydration.jscrate.dev/docs/rules/no-browser-global-in-render):
  `window`, `document`, `navigator`, `location`, `innerWidth` and the other
  browser globals
- [`no-storage-in-initial-render`](https://hydration.jscrate.dev/docs/rules/no-storage-in-initial-render):
  `localStorage` and `sessionStorage`, including state initializers
- [`no-match-media-in-render`](https://hydration.jscrate.dev/docs/rules/no-match-media-in-render):
  `matchMedia()` media queries
- [`no-window-render-branch`](https://hydration.jscrate.dev/docs/rules/no-window-render-branch):
  `typeof window` checks and `isServer`-style flags that change the output
- [`no-client-only-initial-state`](https://hydration.jscrate.dev/docs/rules/no-client-only-initial-state):
  browser values in the initial value of `useState`, `useReducer`, `useRef`
  and class state

The matching fix guides are [browser-only APIs](https://hydration.jscrate.dev/docs/causes/browser-api),
[storage](https://hydration.jscrate.dev/docs/causes/storage) and
[media queries](https://hydration.jscrate.dev/docs/causes/media-query).

### Locale, time zone and list order

- [`no-locale-without-explicit-locale`](https://hydration.jscrate.dev/docs/rules/no-locale-without-explicit-locale):
  `toLocaleString()`, `localeCompare()` and `Intl.*` without a locale
- [`no-timezone-without-explicit-timezone`](https://hydration.jscrate.dev/docs/rules/no-timezone-without-explicit-timezone):
  date formatting without `timeZone`, and local-time getters such as
  `getHours()`
- [`require-deterministic-list-order`](https://hydration.jscrate.dev/docs/rules/require-deterministic-list-order):
  random or locale-dependent sorting, boolean comparators and `shuffle`

These are warnings in `recommended`, because the output only differs when the
server and the visitor use a different [locale](https://hydration.jscrate.dev/docs/causes/locale) or
[time zone](https://hydration.jscrate.dev/docs/causes/timezone).

### Ids and counters

[`no-unstable-id`](https://hydration.jscrate.dev/docs/rules/no-unstable-id) reports random, time-based or
counter ids where `useId()` belongs.
[`no-global-render-counter`](https://hydration.jscrate.dev/docs/rules/no-global-render-counter) reports
module-level variables changed during render, which keep counting on the
server across requests. See [generated ids that differ](https://hydration.jscrate.dev/docs/causes/unstable-id).

### Markup

[`no-invalid-interactive-nesting`](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting)
reports `<div>` in `<p>`, `<a>` in `<a>`, `<button>` in `<button>` and table
rows outside `<tbody>`: nesting the browser repairs before React hydrates
([invalid HTML](https://hydration.jscrate.dev/docs/causes/invalid-html)).
[`audit-suppress-hydration-warning`](https://hydration.jscrate.dev/docs/rules/audit-suppress-hydration-warning)
reports `suppressHydrationWarning` that does nothing or covers too much
([suppressed differences](https://hydration.jscrate.dev/docs/causes/suppressed)). Both also check Server
Components: React hydrates the elements a Server Component renders, and the
root layout, where `<html suppressHydrationWarning>` lives, is one.

### useSyncExternalStore

[`require-stable-server-snapshot`](https://hydration.jscrate.dev/docs/rules/require-stable-server-snapshot)
reports `useSyncExternalStore` without a `getServerSnapshot`, and a
`getServerSnapshot` that reads the browser, the clock or random values. It is
the rule behind the fix several other rules recommend.

## Presets

| Preset        | Severity                                                                                         | Server Components                                     |
| ------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
| `recommended` | Definite mismatches are errors; locale, time zone, initial state and list order are warnings.    | Checked (the setting defaults to `'none'`)            |
| `next`        | Same as `recommended`.                                                                           | Files under `app/` without `'use client'` are skipped |
| `strict`      | Every rule is an error, and every `suppressHydrationWarning` needs a reason (`reportAll: true`). | Checked unless you add the setting                    |

[Set up a preset](https://hydration.jscrate.dev/docs/eslint#set-up-a-preset) shows the config for each,
and [one report per problem](https://hydration.jscrate.dev/docs/eslint#one-report-per-problem) explains
which rule reports a construct when two could.

## Related

- [ESLint plugin: install and configure](https://hydration.jscrate.dev/docs/eslint)
- [What counts as render](https://hydration.jscrate.dev/docs/eslint#what-counts-as-render)
- [Common causes of hydration errors](https://hydration.jscrate.dev/docs/causes)
- [eslint-plugin-react-hooks compared](https://hydration.jscrate.dev/docs/compare/eslint-plugin-react-hooks)
- [Test the running app with hydration-proof](https://hydration.jscrate.dev/docs/quick-start)
