Wilson Staley

Using React.lazy and Suspense to Load Client-Side Only Packages

February 5th, 2021

I recently went to use a react npm package on my Gatsby site and it seemed to be working just fine... until I tried to build the site! I got this nasty error WebpackError: ReferenceError: self is not defined.

After doing some research, I learned that running gatsby build will mimick a server side rendering of the page. This means that packages which reference browser methods or variables (like window, or navigator), will cause the build to fail!

To fix this problem, I had to ensure that the component which used this package was rendered by the client. There are several suggested workarounds on Gatsby's official site, but I only found one method that worked for me.

Here's what I did...

Step 1 - Dynamically Import the React Component

A typical component import would look like this:

import MyComponent from './MyComponent'

However, you can dynamically import a component with React like this:

const MyComponent = React.lazy(() =>
import('./MyComponent'))

What is a dynamic import you ask? Well it has to do with code splitting. Code splitting is when you break up your app's bundle into multiple files. In doing so, you can send a smaller file to the client, load only the most important parts of the app, and asynchronously wait for the other files. This will speed up your app if its bundle is becoming too large! When you add a dynamic import, webpack will automatically start code-splitting your app.

It is important to know that dynamic imports return promises. You can call .then() on a dynamic import and then make use of whatever was imported. However, React provides a nifty function, lazy(), which lets you render a dynamic import as a regular component. So I can use <MyComponent /> in the jsx without having to await the import or call .then().

Step 2 - Adding a Server Side Rendering Check

Before we render our component on the client, we still want to add a check to make sure we don't use the client-side only package on the server. You can add a simple check like so...

import React from "react"

const MyComponent = React.lazy(() => import('../components/MyComponent'))

const MyPage = () => {
  const isSSR = typeof window === 'undefined'

  return (
    {!isSSR && (
      <MyComponent />
    )}
  )
}

If the typeof window is undefined, then we know this is being rendered on the server! So to summarize, we are lazy loading our component, which means the client should have to await it. We are also adding an isSSR check to make sure this component is never rendered outside of the client.

Step 3 - Wrapping the Component with React.Suspense

You can use React's <Suspense> component to wait for some code to load before rendering a component. It also provides a fallback prop which lets you specify a loading state. Here's what the final setup looks like:

import React from "react"

const MyComponent = React.lazy(() => import('../components/MyComponent'))

const MyPage = () => {
  const isSSR = typeof window === 'undefined'

  return (
    {!isSSR && (
      <React.Suspense fallback={<div />}>
        <MyComponent />
      </React.Suspense>
    )}
  )
}

This adds an additional safety measure by allowing you to specify a fallback component to render while waiting on the code needed for MyComponent.

And there you have it! That's how I made use of a client-side only package on my Gatsby site.

Resources:

Created by Wilson Staley, © 2021