Hydration Proof

Search documentation

Find a page or section

Fix the Cloudflare Auto Minify hydration error

Your server sent one page. The browser received another.

A Cloudflare Auto Minify hydration error happens when a CDN or proxy rewrites the server's HTML on its way to the browser. Minification drops the whitespace between elements, email obfuscation replaces addresses, and injected scripts change the page, so React hydrates HTML its server never produced. Turn HTML rewriting off for server-rendered pages.

Symptoms

The app works locally and on a preview deployment, and fails only behind the CDN:

Hydration failed because the server rendered text didn't match the client.
Text content does not match server-rendered HTML.
Minified React error #418; visit https://react.dev/errors/418

hydration-proof reports whitespace-only differences as HP1015 and HTML changed between the origin and the browser as HP4003, with the cause HTML rewritten by a CDN or proxy. When only whitespace differs and the response carries a CDN header (cf-ray, x-amz-cf-id, x-served-by, x-cache, via), it names the cause with 88% confidence.

Why a Cloudflare Auto Minify hydration error happens

React renders text and spaces as separate pieces: Hello{" "}<strong>world</strong> needs the space between the text and the element. An HTML minifier removes "unneeded" whitespace between tags, the browser parses the shorter HTML, and React's first render no longer matches it. Minifiers that strip HTML comments do more damage: React's server HTML uses comments to separate adjacent text nodes and to mark Suspense boundaries.

CDN features that rewrite HTML:

FeatureWhat it changesTurn it off
Auto Minify (HTML)Removes whitespace and commentsCloudflare deprecated Auto Minify on August 5, 2024. A zone that still has it on can turn it off through the API, as Cloudflare's Turn off Auto Minify page shows
Email Address ObfuscationReplaces visible email addresses with a link and adds a decode scriptSecurity, Settings, Client-side abuse, Email Address Obfuscation: Off. A configuration rule can turn it off for some paths only
Rocket LoaderDefers all JavaScript until after rendering, rewriting the page's scriptsSpeed, Settings, Content Optimization, Rocket Loader: Off. data-cfasync="false" before src excludes one script
Other proxies and edge functionsMinify, inject banners or analytics, rewrite linksCheck the proxy's HTML settings

Cloudflare documents the email feature in Email Address Obfuscation and the script feature in Rocket Loader.

How to fix it

  1. Turn off HTML minification at the CDN for server-rendered pages. Your framework's build already minifies JavaScript and CSS; the HTML does not need a second pass.
  2. Turn off Email Address Obfuscation, or exclude your app's paths with a configuration rule. If you want to hide addresses from scrapers, do it in your own components.
  3. Turn off Rocket Loader for the app. React and your framework load their own scripts in a specific order.
  4. Test through the CDN again. The finding disappears once the HTML arrives unchanged.

Purge the CDN cache after changing the settings, or cached pages keep the rewritten HTML.

Fix an iOS format-detection hydration mismatch

Safari on iOS does its own rewriting: by default it detects strings that look like phone numbers and turns them into links that call the number. When that happens to server HTML before React hydrates, the DOM holds an <a> React did not render. Turn the detection off with a meta tag:

index.html
<meta name="format-detection" content="telephone=no" />

In the Next.js App Router, set it through the metadata API:

app/layout.tsx
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  formatDetection: {
    telephone: false,
    email: false,
    address: false,
  },
};

Next.js renders that as <meta name="format-detection" content="telephone=no, address=no, email=no" />. Render phone numbers you want linked as real tel: links yourself. The desktop browsers hydration-proof drives may not do this detection, so check this one on a real iPhone.

Catch it with ESLint

A CDN setting is not code, so no lint rule can catch it.

Catch it in CI

Run hydration-proof against the origin and against the CDN. A finding that only appears through the CDN is the CDN:

npx hydration-proof test --url https://origin.example.com --route / --route /contact
npx hydration-proof test --url https://www.example.com --route / --route /contact

The CDN run reports HP1015 or HP4003, with the CDN header that pointed at the cause in the reason. See CI for running this against a staging deployment.