How to fix common hydration issues in Next.js and React 18

Common Next.js hydration issues and how to fix them

Since updating React 18, Next.js has been stricter when it comes to hydrating the client-side after the page was server-rendered initially on the server.

In fact, you likely keep seeing errors such as "Text content does not match server-rendered HTML" when the page loads. It took a while for me to understand it, hoping it was a bug in React; but as it turned out, it was an error on my end.

While upgrading Makerkit to React 18, I found two common issues:

  1. The HTML was invalid - e.g. malformed
  2. Conditional Rendering based on browser vs server conditions

Malformed HTML

Malformed HTML doesn't automatically mean it will render badly as browsers are capable of handling these situations fairly well. As such, it's rather hard being able to locate places where we used a tag in the wrong place, such as in the code below:

<p>
{ */ Wrong! */ }
<div>
{children}
</div>
<p>

In the example above, p shouldn't be a parent of div. Browsers may be forgiving, but other DOM implementations may not, stripping the internal invalid tags. This is why the server and the browser end up rendering different HTML strings, even when there is no apparent difference between the two.

The fix here is very simple: make sure you write syntactically valid HTML code.

Conditional Rendering

Very often, we only want to display a value on the client instead than on the server. In Makerkit's SaaS boilerplate for Next.js, this issue happens a lot, because we cannot run the Web Firebase SDK on the server.

Therefore, to avoid rendering on the server, we used to do the following checks:

{ isBrowser() ? <MyComponent /> : null }

And the above still looks fine, but React 18 didn't quite like it. To solve this issue, we used next/dynamic to import the components dynamically and by setting the option ssr to false:

import dynamic from 'next/dynamic';
const MyComponent = dynamic(() => import(`./MyComponent.tsx`), {
ssr: false
});
// somewhere in your code simply render the component
return <MyComponent />

And this solved the second issue. To summarize, whenever you're using conditions to avoid rendering components on the server with Next.js and React 18, simply use a dynamic import instead.

I hope these two solutions will help you fix your hydration issues. If you have any questions, do contact me!