How to Ensure Code Only Runs in the Browser in Next.js
Have you ever hit
this error when trying to access the global window
object in Next.js?
ReferenceError: window is not defined
This is because Next.js is rendering your HTML on the server before your app gets hydrated on the client.
Hydration is to say after the page loads initially, your client-side React app kicks in to make your site interactive.
So why is this error happening? 🤔
window
is a global object available in the browser. Your server-side
code has no reference to any global window
object, hence getting an error.
What you need to do is check if window
is defined before executing code
that should only run in the browser.
if (typeof window !== 'undefined') {
// everything in this branch only runs in the browser
} else {
// and everything here only runs on the server
}
We can alternatively package this logic into a utility function that accepts a high-order function that will only run in the browser.
export function onClient<T>(fn: () => T): T | undefined {
if (typeof window !== 'undefined') {
return fn();
}
}
onClient
implicitly returns undefined
when running on the server. We can use nullish
coalescing to resolve a default value when onClient
is executed on the server.
For example, let's say we had a button component that copied text to the user's clipboard.
We only want to render this function on the client and if window.navigator
is available.
We can use our onClient
function as follows:
interface CopyButtonProps {
textToCopy: string;
}
function CopyButton({textToCopy}: CopyButtonProps) {
if (onClient<boolean>(() => window.navigator == null) ?? true) {
return null;
}
return (
<button onClick={() => window.navigator.clipboard.writeText(textToCopy)}>
Copy
</button>
);
}
There are two situations leading into the first if
branch:
- in the browser, when
window.navigator
isnull
- on the server,
onClient
returnsundefined
, so the expression is nullish coalesced totrue
We could've also written the React component in a way that utilizes the fact that undefined
is a falsy value.
interface CopyButtonProps {
textToCopy: string;
}
function CopyButton({textToCopy}: CopyButtonProps) {
return (
Boolean(onClient<boolean>(() => window.navigator != null)) && (
<button onClick={() => window.navigator.clipboard.writeText(textToCopy)}>
Copy
</button>
)
);
}
Whichever way you decide to go about it, you can be confident knowing which code is restricted to running only in the browser.