DEV Community

Cover image for How to Fix React Hydration Mismatches with a Simple Inline Script Hack (Zero Flicker SSR)
Sidharth Mohanty
Sidharth Mohanty

Posted on

How to Fix React Hydration Mismatches with a Simple Inline Script Hack (Zero Flicker SSR)

React's hydration process is a crucial part of server-side rendering (SSR) where React attaches event handlers to the server-rendered HTML. However, sometimes we encounter situations where the server and client need to render different content, which can lead to hydration mismatches. In this blog post, we'll explore a neat trick to handle this scenario without triggering React's hydration errors.

The Problem

When using React with SSR, you might encounter situations where you need to render different content on the server versus the client. A common example is when we need to render something that depends upon browser cookies. React expects the server-rendered HTML to match exactly what would be rendered on the client during hydration. If there's a mismatch, React throws a hydration error:

Warning: Text content did not match. Server: "Bye World" Client: "Hello World"
Enter fullscreen mode Exit fullscreen mode

Example Approach (That Causes Problems)

Here's a typical example that would cause hydration issues:

export default function Home() {
  const isBrowser = typeof window !== "undefined";

  return (
    <div>
      <h1>{isBrowser ? "Hello World" : "Bye World"}</h1>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

This code will cause a hydration mismatch because the server renders "Bye World" (since window is undefined), but the client renders "Hello World".

You could use useEffect here and render but that would cause a flicker.

The Solution: The Inline Script Trick

Here's where our trick comes in. We can use an inline script that executes immediately after the HTML is rendered but before React hydration begins. Here's how it works:

'use client'

const isBrowser = () => typeof window !== "undefined";

export default function Home() {
  const inlineScriptFn = () => {
    const helloElement = document.getElementById('hello');
    if (helloElement) {
      helloElement.textContent = 'Hello World';
    }
  }

  return (
    <div>
      {isBrowser() ? <h1 id="hello">Hello World</h1> : <h1 id="hello">Bye World</h1>}
      <script dangerouslySetInnerHTML={{ __html: `(${inlineScriptFn.toString()})()` }} />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Let's break down how this trick works:

  1. On the server, React renders <h1 id="hello">Bye World</h1> because isBrowser() returns false.
  2. Immediately after the HTML is sent to the browser, but before React hydration begins, our inline script executes:
    • It finds the element with id "hello"
    • Changes its content to "Hello World"
  3. When React begins hydration, it sees "Hello World" in both:
    • The actual DOM (thanks to our script)
    • The virtual DOM it wants to render (because isBrowser() is now true)
  4. No hydration mismatch occurs because the content matches!

Why This Works

This approach works because:

  1. The inline script executes synchronously before React's hydration process begins
  2. By the time React performs hydration, the DOM already contains the client-side content
  3. React sees matching content in both the real DOM and its virtual DOM

Conclusion

This hydration trick provides a clean solution to handle server/client content differences without triggering React's hydration warnings.

At Builder.io, we rely on this very inline-script hydration trick to select the winning variant in our SDKs. You can see the production implementation here: inlined-fns.ts.

I first discovered the technique while building "Variant Containers" in our SDKs (block-level personalization container), essentially A/B testing at the block level, and it felt downright magical once it clicked. If you’re curious, you can peek at its implementation here: inlined-fns.ts.

Have you run into hydration mismatches or found other clever solutions? I’d love to hear about your experience. Feel free to reach out, and if you’d like to see what else I’m up to, visit my portfolio at sidharthmohanty.com.

Top comments (0)