Photo from Miti on Unsplash

How to Get Rid of “Window is Not Defined” and Hydration Mismatch Errors in Next.js

Eric Burel

--

Client-Only Code in Next.js is a Difficult Problem

It’s ironic that JavaScript code that works only in the browser is actually a common source of confusion and bugs in React applications, even for experienced developers. Yet it makes sense when you add server-side rendering into the mix.

Despite the many articles written on this topic, I couldn’t find a truly exhaustive resource. So I’ve tried my best to craft one!

Let’s meet our archenemies

If you have clicked on this article, it’s probably because you’ve encountered one of the two most dreaded errors in modern JavaScript history:

  • “window is not defined”
  • hydration failed, hydration mismatch, text content does not match server-rendered HTML, or any similar iffy message about a lack of hydration or too much of it
Grrr I hate this “window is not defined” error! Let’s get rid of it for good!

Fear no more, I will show you how to get rid of them for good. At the end of this article, you will have 5 new techniques in your tool-belt to manage client-only code in Next.js.

First, let’s frame the problem more precisely.

Why it’s hard to solve these errors?

I think it’s the fact that they are generic errors but come up in multiple, resembling, yet slightly different situations.

Worse, the window object might be familiar for intermediate/advanced developers, but “hydration” is really a tricky React concept even for senior programmers.

Those two errors are symptoms of the same underlying issue: managing client-only code in a framework that enables server-side rendering, like Next.js, is hard.

Here are two code samples that would generate them in a React component rendered by Next.js:

// BOOM! 
// During the server render,
// this code is going to throw the nasty "window is not defined" error
return <p>The title of this page is: {window.title}</p>
// BOOM! 
// During the first client render,
// this code is going to throw a nasty "hydration mismatch" error
if (typeof window === "undefined") {
return <p>The title of this page is: {window.title}</p>
}

Before jumping on the solutions, let’s introduce a new concept that better explains what happens under the hood: React Browser Components.

Say Hello To React Browser Components

The root cause of an hydration mismatch or a client-only code leak during the server render is most often the presence of an unwanted “React Browser Component”. Learn to detect them and you will never see these errors again.

React and prerendering, a love story

In the latest versions of React and Next.js (with the App Router), we have 2 types of components:

  • React Server Components (RSCs): they are rendered on the server.
  • React Client Components: they are rendered client-side, using JavaScript, but also prerendered server-side.

This means that Client Components are actually rendered twice, once on the server, and once in the browser!

React expects both renders to output exactly the same HTML content, at least during the first client render. Otherwise, you’ll get an hydration error.

This trips up many beginners, and many non-beginners too.

Some might think that this is a Next.js quirk, but it’s actually how components are designed to work in React itself. Therefore you might hit this issue in other React “meta-frameworks” too like Remix and Astro.

Then, how should we handle components that cannot be rendered on the server at all, because they rely on JavaScript code that works only in the browser?

We can’t just dismiss them. There are many use cases for such components:

  • Using a client-only library like Leaflet for maps, D3 for data visualization or good old jQuery
  • Using the browser API to translate content or show dates
  • Storing information in the localStorage or sessionStorage
  • Using WebGL or any other browser-specific feature
  • And the list goes on

Browser Components, the missing concept

What I call a “Browser Component” is a React component that cannot be pre-rendered on the server.

Here is a summary of the 3 types of React components that exist in the world, if we add Browser Components:

Server and Client components are the only kind of valid components in React. If you meet a Browser Component, you should try to rewrite it, or wrap it so it becomes a Client Component. This article provides techniques to achieve that.

It’s important to understand that Client Component are rendered server-side and client-side, despite the “client” in their name.

So, I’ve invented the concept of “Browser Components” and added those to the list in order to have a true symmetry with React Server Components that are not rendered at all in the browser.

Keep in mind that there is a good reason why React defines Client and Server components, but not “Browser Components”.

That’s because Browser Components are not a good thing!

When you see one of them, you should do everything to make them compatible with server-side rendering.

Transform Browser Components into Client Components

If you hit a “window is not defined” or an “hydration mismatch” error, you can be sure that a nasty Browser Component is involved somehow.

Therefore, to make our errors disappear, the goal is to transform Browser Components into Client Components that are safe to prerender on the server.

Even if the Client Component renders nothing server-side, or a loading message, it’s ok.

You just want the component NOT to crash during the server render. Then it will display its actual content client-side.

To do so, you just need to apply one of the fixes I list below.

Five Ways To Get Rid of “Window Is Not Defined” and Hydration Mismatch Errors

I’ve ordered the solutions depending on the scenario they solve, to make troubleshooting easier.

0) My HTML is invalid

First, let’s get rid of one very common issue that generates hydration mismatch errors: invalid HTML!

Sometimes the hydration error is not due to your React code at all but a simple mistake in your HTML code.

For instance you may have two “<html>” tags, repeated into different layouts.

Next.js 15 catches these errors more clearly.

1) My component needs a browser-only library like Leaflet or jQuery.

These libraries crash when being imported server-side, because they expect the window object to be available globally.

// BOOM! 
// This will trigger the "window is not defined error" during server-render
import "react-leaflet"

A component that imports them is a Browser Component. It’s a dangerous one, so what you need here is to create a second component that makes it innocuous.

The solution is to use lazy loading with next/dynamic and ssr: false to import your Browser Component.

"use client";
import dynamic from "next/dynamic";

// This component is now safe to use in Next
// It wraps "BrowserComponent" to turn it into a Client Component
export const ClientComponent = dynamic(
() => import("./BrowserComponent")
// this part is needed if your use a named export
// you can replace by ".default" when using a default export
.then((mod) => mod.BrowserComponent),
{
// This prevents server-side rendering of BrowserComponent
ssr: false,
}
);

Now you can use ClientComponent safely, it wraps your Browser Component with a dynamic loader to make it server-friendly.

2) My component uses the window object or any other browser-only code to render something. I can modify its code.

You can turn your Browser Component into an inoffensive Client Component.

You can use the useMounted hook I provide below to rewrite your component and render conditionally.

The useMounted hook:

export const useMounted = () => {
const [mounted, setMounted] = useState<boolean>()
// effects run only client-side
// so we can detect when the component is hydrated/mounted
// @see https://react.dev/reference/react/useEffect
useEffect(() => {
setMounted(true)
}, [])
return mounted
}

Tip: you can find a similar piece of code in many places under different names like “useHydrated”, “useClient”… Next doc calls this hook “useClient” but I find “useMounted” more explicit.

When “mounted” is true, this means you can use the “window” object to render something.

// This is a Client Component:
// it can be rendered safely on the server
// (it will display nothing there but that's ok)
// Then it will render the window title client-side
export function ClientComponent({ children }) {
const mounted = useMounted();
if (!mounted) return null;
return <p>{window.title}</p>;
}

This will effectively turn the offending “Browser Component” into a Client Component.

3) My component uses the window object or any other browser-only code to render something. Sadly, I cannot modify its code.

It’s a Browser Component. Ideally, it should be rewritten to be a Client Component (see paragraph above), but since you cannot control the code you may not be able to do that.

The first solution is to use useMounted in the parent that renders the Browser Component.

const mounted = useMounted()
if (mounted) return <BrowserComponent />

This is very similar to the solution just above, where we have used the useMounted hook to check if it was safe to use the window object during render.

It means you have to create a new parent component, and that this component has to be a Client Component to be able to use a hook. It can be a bit annoying.

The second solution is to craft a NoSsr component. You just need to wrap the offending browser component into a NoSsr, and it won't be prerendered on the server.

The NoSsr component, built upon the useMounted hook:

"use client";
import React from "react";
import { useMounted } from "../hooks/useMounted";

export function NoSsr({ children }) {
const mounted = useMounted();
if (!mounted) return null;
return <>{children}</>;
}

Usage:

return <NoSsr><BrowserComponent /></NoSsr>

4) My component needs a library that can be imported server-side but not used there, like D3 for dataviz.

More broadly, this is the case where you want to do imperative modifications of the DOM.

You can make it a Client Component thanks to the useEffect hook. Indeed, effects run only when the component is mounted, and components are mounted only in the browser.

So this means you can use the window object safely in effect callbacks!

Notice that we have used this nice property of the useEffect hook to craft our useMounted utility.

useEffect(() => {
if (divRef.current) {
divRef.innerText = "Loaded!"
}
}, [])
return <div ref={divRef}>Loading...</div>

It will render a loader during server-side rendering, but that’s totally fine.

5) My component code is exactly the same, but behaves slightly differently in the browser and on the server.

This happens when using dates or the localization features of JavaScript, you have the same code client-side and server-side but it generates a different result.

This one is very tricky so I am going to share the solution directly. You need a component that wraps the code into a Suspense and re-renders after being mounted:

"use client";

import React, { Suspense } from "react";
import { useMounted } from "../hooks/useMounted";

export function SafeHydrate({ children }: { children: React.ReactNode }) {
const isMounted = useMounted();
return (
<Suspense key={isMounted ? "client" : "server"}>{children}</Suspense>
);
}

I owe this trick to an article from François Best, you’ll find the link in the Resources section below.

There are other solutions to this issue but I didn’t find them satisfying:

  • React “supressHydrationWarning” prop may remove the hydration error but last time I’ve checked, it will keep displaying the server-rendered value and won’t update to the client value. Not sure if it’s a bug or intended. If you happen to be a Next.js core contributor, please comment :)
  • I’ve just discovered the experimental React “unstable_postpone” API (thanks to Andrew Ingram), which on the contrary seems to totally disable the server-render.

SafeHydrate on the other hand will properly display the server-rendered value, and then the client-rendered one.

Here You Are!

Much thanks for reading this article!

With these 5 solutions, you are solidly equipped to get rid of the dreaded “window is not defined” and “hydration mismatch” errors!

If you happen to hit a specific issue with client-only code that you still can’t solve, drop a comment or reach me out on Twitter (@ericbureltech) : I’ll try my best to find a solution to your problem.

Want To Jump in the Rabbit Hole?

This article is a summary of my recently released video course: “Manage client-only code in Next.js with React Browser Components”.

If you have liked this article and want to get better at managing client-side JavaScript code in Next.js, click here to discover the course.

See you soon!

Resources

--

--

Eric Burel

Next.js teacher and course writer. Co-maintainer of the State of JavaScript survey. Follow me to learn new Next.js tricks ✨ - https://nextjspatterns.com/