Hydration Proof

Search documentation

Find a page or section

Require an explicit locale for locale-sensitive formatting during render.

Calling toLocaleString without a locale formats the value with the default locale of whichever runtime renders it: often en-US on the server, the visitor's language in the browser. The text then differs during hydration. no-locale-without-explicit-locale reports locale-sensitive formatting in render code that has no explicit locale.

Rulehydration-proof/no-locale-without-explicit-locale
What it reportsRequire an explicit locale for locale-sensitive formatting during render
recommended / nextWarning
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsYes
OptionsdefaultLocale

What it reports

In render code, when the locale argument is missing, undefined, void 0 or []:

  • value.toLocaleString(), value.toLocaleDateString(), value.toLocaleTimeString()
  • a.localeCompare(b) (the locale is the second argument)
  • Intl.NumberFormat, Intl.DateTimeFormat, Intl.RelativeTimeFormat, Intl.PluralRules, Intl.Collator, Intl.ListFormat and Intl.DisplayNames, with or without new
  • Intl.X().resolvedOptions().locale, which reads the runtime's default locale

A variable as the locale (toLocaleString(locale)) is accepted: the rule cannot know its value, and passing the locale from the server is the fix.

The suggestion inserts the locale from the defaultLocale option ('en-US' by default). It is offered as a suggestion, not a fix, because the right locale is a product decision.

Why toLocaleString without locale breaks hydration

Without a locale, formatting uses the default locale of the JavaScript runtime. Servers usually run with en-US (or whatever the container sets); browsers use the visitor's language:

server HTML:   <p>Total: 1,234.5</p>   (en-US)
client render: <p>Total: 1.234,5</p>   (de-DE)

React reports the text mismatch and renders the page again on the client. Locale-dependent formatting covers the fixes, including where to get the visitor's locale on the server.

Incorrect

function Price({ amount }) {
  return <p>Total: {amount.toLocaleString()}</p>;
}
 
function Percent({ value }) {
  const format = new Intl.NumberFormat(undefined, { style: "percent" });
  return <span>{format.format(value)}</span>;
}
 
function Match({ a, b }) {
  return <p>{a.localeCompare(b) === 0 ? "same" : "different"}</p>;
}

Correct

function Price({ amount, locale }) {
  // The locale comes from the request (cookie, URL or Accept-Language) and is passed down.
  return <p>Total: {amount.toLocaleString(locale)}</p>;
}
 
function Percent({ value }) {
  const format = new Intl.NumberFormat("en-US", { style: "percent" });
  return <span>{format.format(value)}</span>;
}
 
function Match({ a, b }) {
  return <p>{a.localeCompare(b, "en") === 0 ? "same" : "different"}</p>;
}

Options

OptionTypeDefaultDescription
defaultLocalestring'en-US'Locale inserted by the suggestion.
eslint.config.mjs
{
  rules: {
    'hydration-proof/no-locale-without-explicit-locale': ['warn', { defaultLocale: 'en-GB' }],
  },
}
  • defaultLocale (string, default 'en-US'): the locale the suggestion inserts.

Messages

What ESLint prints for this rule, word for word:

  • <call> uses the default locale of whichever runtime renders it. The server's locale and the visitor's browser locale usually differ, so the text does not match during hydration. Pass an explicit locale that is the same on the server and in the browser.
  • <call> reads the runtime's default locale, which differs between the server and the browser. Pass the locale from the server instead.
  • Use the '<locale>' locale.

When not to use it

When every server and every visitor uses the same locale (an internal tool with a fixed locale, for example), or when the app sets the runtime's default locale explicitly on both sides.