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/418hydration-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:
| Feature | What it changes | Turn it off |
|---|---|---|
| Auto Minify (HTML) | Removes whitespace and comments | Cloudflare 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 Obfuscation | Replaces visible email addresses with a link and adds a decode script | Security, Settings, Client-side abuse, Email Address Obfuscation: Off. A configuration rule can turn it off for some paths only |
| Rocket Loader | Defers all JavaScript until after rendering, rewriting the page's scripts | Speed, Settings, Content Optimization, Rocket Loader: Off. data-cfasync="false" before src excludes one script |
| Other proxies and edge functions | Minify, inject banners or analytics, rewrite links | Check 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
- 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.
- 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.
- Turn off Rocket Loader for the app. React and your framework load their own scripts in a specific order.
- 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:
<meta name="format-detection" content="telephone=no" />In the Next.js App Router, set it through the metadata API:
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 /contactThe 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.