hero

Render client-side only component in Next.js

If you are familiar with Next.js then you will know it is the React SSR (server-side rendering) framework created by Vercel. There are a lot of headaches in trying to build SSR sites in React, Next.js makes it drastically simpler by doing many of the not-so-nice parts and hiding it from developers. The problem with frameworks, in general, is that it does what it is designed to do very well while sacrifices control and customisation to achieve it. Next.js is very good at handling components rendered on server-side and then sent to the client. But how would you render a client-side only component in Next.js? That is an issue I encountered and what I'll be discussing in this article.

How to make code run only on client-side with Next.js?

To my surprise, this is surprisingly easy. Next.js inherited the process.browser check from Webpack. So the following check is all we need to run client-only code:

if (process.browser) {
    // on browser
}

What about client-side components?

Since it is just React, surely we can do the same check before rendering the client-side only component right?

<div>
    { process.browser && <MyAwesomeComponent /> }
    <div>some other component</div>
</div>

Well, yes and no. If I try to run the code above, the app will function normally, but React will throw this error in the browser console Warning: Expected server HTML to contain a matching <div> in <div>.

Here's the complete warning:

React hydration error

Looking at the error itself, it is not too obvious what is going on at first. Don't worry, it is actually quite simple.

In the example above, I've told React not to render anything on the server-side, so it didn't. When the code got handed over to the browser, React suddenly recognised the fact something needs to be rendered, and it does just that on the client-side. Since React expects the rendered content to be identical on both the server-side and the client-side, I have now confused it by breaking that expectation. So it then returns the favour and displays a confusing error that had me scratching my head for hours.

The solutions

I managed to narrow down the problem to "something is causing React to go ape s**t during render and hydration". But I haven't gotten any further than that. Later, I spoke to a colleague and he pointed out how simple the solution was.

Solution 1 - suppressHydrationWarning

I felt pretty embarrassed, because all I had to do was add suppressHydrationWarning={true} attribute to the component with the issue.

<div suppressHydrationWarning={true}>
    { process.browser && <MyAwesomeComponent /> }
    <div>some other component</div>
</div>

It is worth noting that, this is merely hiding the error and the issue never went away. Make sure you are aware of the following if you decide to go with this approach:

  • It only works one level deep (not a problem in this case)
  • This should be avoided if at all possible, only to be used as last resort - me paraphrasing the React team
  • All the patching could impact site performance in some scenarios

Solution 2 - two-pass rendering

This is the recommended approach by the React team. As long as the DOM elements stay the same during the first pass render, it doesn't bug us with the error we saw above. To trigger a second pass, we will need to implement useEffect() React Hook and achieve a similar result as componentDidMount().

useEffect(() => {
    // update some client side state to say it is now safe to render the client-side only component
    this.state.renderClientSideComponent = true;
});

Decision

In my team, we ended up going for the first solution since it is more straightforward, the client-side only component was a single level deep, and there wasn't any crazy logic which may cause performance issues.

Additional Reading

Here is the original React documentation if you want to read more about it.