My Website Wasn’t Broken—Just Hydrated Wrong
How a Chrome extension broke my React app’s hydration—and how I tracked it down in Remix
My Website Wasn’t Broken—Just Hydrated Wrong
Debugging a Sneaky Hydration Error in React + Remix
Recently, my site natalieoldroyd.com started throwing a React hydration error on every page refresh. I hadn’t pushed any risky code, and everything looked fine in the terminal and the Vercel deploy logs. But the browser console told a different story:
In development:
Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
In production:
Error: Minified React error #418
React suppresses detailed errors in production to save bundle size, so you get these vague “minified error codes” instead. For hydration-related bugs, you might also see:
Error: Minified React error #425
There are more, but I won’t attempt a comprehensive list. If your React site goes blank and logs show a mysterious minified error code, hydration is a good place to start looking.
What Even Is a Hydration Error?
Hydration is the process where React “attaches” to static HTML that the server rendered. If the DOM on the client doesn’t exactly match the server output, React throws an error and may skip hydration entirely — leaving parts of your UI broken or unresponsive.
Common causes include:
Using
new Date()
,Math.random()
, orwindow
during renderDifferences in rendered content between server and client
Injected DOM elements from outside your app (spoiler: this was my case)
The Bug That Wasn’t Mine
After a full audit of my root.tsx
, I confirmed everything was rendering as expected.
Yet, the error persisted.
Then I tried my site on another laptop — and the error disappeared. Same deploy. Same browser. But no issue.
This led me to suspect the environment. I opened the site in an incognito window on the laptop where the error appeared — and the error vanished. I then disabled Chrome extensions one by one until I was able to reproduce that result outside of incognito mode.
💥 In my case, the culprit was Alpine.js devtools.
The extension was injecting scripts or modifying the DOM before React could hydrate — breaking the fragile match between the server-rendered HTML and what React expected on the client.
Extensions like Alpine.js Devtools, Grammarly, ad blockers, password managers, and privacy tools can all interfere with hydration by touching the DOM early.
🔧 A Snippet to Disable Hydration for Debugging
One trick I used during this process was to disable hydration and compare the HTML the server sent vs. what the client expected (before realizing the issue was just a Chrome extension).
Here’s snippet I added inside the App
component:
export default function App() {
const isDebugHydration =
typeof document !== "undefined" &&
document.location.search.includes("nohydrate");
return (
<>
....
</>
);
}
Then inside the <body>
, I conditionally rendered <Scripts />
like this:
{!isDebugHydration && <Scripts />}
💪 To disable hydration, visit your site like this:
https://your-site.com?nohydrate
Now when you view source, you’ll see the raw server-rendered HTML — perfect for comparing against the client’s version using a diff tool.
💡 Hydration-safe tip:
Usedocument
instead ofwindow
for client-only logic inroot.tsx
.
During the first paint,document
is often available slightly earlier and more reliably thanwindow
.
Final Thoughts
This bug reminded me that hydration errors can be caused by perfectly valid code — and that tools outside your app, like browser extensions, can make you doubt yourself.
If your React + Remix site is showing hydration errors only in certain environments:
Check if the issue disappears in Incognito Mode
Try the site on a different device or browser profile
Disable browser extensions one by one
Use the
?nohydrate
trick to inspect what the server rendered
Your code might be just fine — but hydration is more fragile than it looks.