skies.dev

How to Ensure Code Only Runs in the Browser in Next.js

3 min read

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:

  1. in the browser, when window.navigator is null
  2. on the server, onClient returns undefined, so the expression is nullish coalesced to true

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.

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like