A toLocaleString hydration mismatch happens when the server and browser use different default locales. The same number can render as "1,234.5" on one side and "1.234,5" on the other. Choose a locale from the URL, a cookie, or request data, then pass it to both renders.
Symptoms
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: "Total: 1,234,567.891" Client: "Total: 1.234.567,891"
Minified React error #418; visit https://react.dev/errors/418A toLocaleTimeString hydration error can show "5:00:00 PM" on the server and "17:00:00" in the browser. React also lists locale-dependent date formatting among possible causes.
hydration-proof reports changed text as HP1001. The same digits with different separators are a clue that formatting changed.
Why a toLocaleString hydration mismatch happens
When you omit a locale, each runtime chooses its own default. The server's container and the visitor's browser may not choose the same one.
These use the default locale:
value.toLocaleString(),toLocaleDateString()andtoLocaleTimeString()a.localeCompare(b)without its second argumentIntl.NumberFormat,Intl.DateTimeFormat,Intl.RelativeTimeFormat,Intl.PluralRules,Intl.Collator,Intl.ListFormatandIntl.DisplayNameswithout a locale, or withundefinedIntl.NumberFormat().resolvedOptions().locale, which returns the default itself
For dates, set the timezone too. An explicit locale alone does not prevent a timezone mismatch.
How to fix it
Pass the locale from the request
Read the locale from the URL, a cookie, or the request. Pass it to the component that formats the value:
import { Price } from "./price";
export default async function Page({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return <Price amount={1234.5} locale={locale} />;
}"use client";
export function Price({ amount, locale }: { amount: number; locale: string }) {
// Before: amount.toLocaleString() used each runtime's default
const text = amount.toLocaleString(locale, {
style: "currency",
currency: "EUR",
});
return <p>Total: {text}</p>;
}Pin one locale where it does not matter
If the product uses one locale, set it explicitly everywhere:
const percent = new Intl.NumberFormat("en-US", { style: "percent" });
export function Percent({ value }: { value: number }) {
return <span>{percent.format(value)}</span>;
}Also pass a locale to localeCompare. Otherwise a sorted list can appear in
a different order. See require-deterministic-list-order.
Fix an i18n hydration error in next-intl
A next-intl hydration error can come from different locale, timezone, or "now" values. Set them together in the request config:
import { getRequestConfig } from "next-intl/server";
export default getRequestConfig(async () => ({
locale: "en",
// One zone for both sides. Read it from the user's profile or a cookie if it varies.
timeZone: "Europe/Berlin",
// One "now" for both sides, used by relative time formatting.
now: new Date(),
}));When a Server Component renders NextIntlClientProvider, it passes the
request configuration to the client. If you render the provider in a Client
Component, pass the values it needs yourself. See next-intl's
date-formatting guide.
Format after mount when only the browser knows
If the server cannot know the browser's locale, render a stable value first and format after hydration. See two-pass rendering.
Catch it with ESLint
no-locale-without-explicit-locale
flags formatting calls without a locale. It accepts a variable such as
toLocaleString(locale). Choose the right locale for your users rather than
relying on a runtime default.
npm install -D eslint-plugin-hydration-proofCatch it in CI
Add the locales your visitors use. A local test with only your machine's locale may miss the problem:
import { defineConfig } from "hydration-proof";
export default defineConfig({
matrix: {
locale: ["en-US", "de-DE", "ar-EG"],
},
});The scenario also sets Accept-Language. Use --probe to change only the
browser locale and see whether the finding follows it. See
probes.
npx hydration-proof test --probe