DEV Community

Cover image for How to use client-side only packages with SSR in Gatsby and Next.js
Elisabeth Leonhardt
Elisabeth Leonhardt

Posted on

How to use client-side only packages with SSR in Gatsby and Next.js

So you started a project in Gatsby or Next.js to take advantage of static site generation or server-side rendering and its performance benefits because that's what Youtube/StackOverflow/some course told you to do. You are coding along happily until this error appears:

ERROR #95312 
"window" is not available during server side rendering.
Enter fullscreen mode Exit fullscreen mode

Maybe, you are a heroic senior dev who just understands this error, fixes it in 2 minutes, and goes on with his work. (Why are you even reading this?) If this is not the case, here is a down-to-earth explanation and a few ideas on how to solve it.

If you just want the fix: Here is the repository with the solution for Gatsby and here is what you need for Next.js.

Basics first: Understanding SSR versus CSR

I had lots of trouble understanding what exactly the difference between the two is, so I hope to enlighten you with an analogy:
Remember the last time you went to IKEA: you chose a beautiful bookshelf for your home, fought with your partner over it, got a heavy box from IKEAs warehouse-like part, made up with your partner over a hotdog, and then went home and assembled your purchase (without insulting the instructions, of course).

This is how client-side rendering works: Your browser requests a page, gets a nice block of code, pulls out his tools (Javascript, in this case), and builds the page together. Ikea in this analogy is the server, and your home is the client. Since you assemble your bookshelf client-side, it takes you a little longer and maybe a support call until you can enjoy your new furniture, but for Ikea, it was convenient to provide you with one of its products.


How our browser should not assemble the webpage

 
Assuming, you are not a handyman and you decide to pay IKEA to deliver your bookshelf already assembled to your home. You won't be able to enjoy the hotdog, but it's worth the evaded fight with your partner. You just request what you need and once you get it delivered, it is ready to go.

This is how server-side rendering works: the server we requested the page from executes all the code in a node server: your browser just has to paint it, no javascript needed. This is why you can disable javascript in your browser's dev tools, and server-side rendered pages will still appear flawlessly. It's a more expensive option for IKEA (and the owner of the webpage) since they need resources to assemble and deliver your request, but the user experience is better unless you really like these hotdogs.

Perfect User Experience

 

Can you mix server side and client side rendering?

Maybe IKEA delivers your bookshelf assembled but you realize the distance between shelves isn't just right. You will have to take out a screwdriver and make some adjustments, no big deal. If something similar happens on a webpage, like a browser conditionally rendering a modal based on preferences set in local storage: is it still server-side rendered? Or does it now count as client-side rendered?

Kind of both, right?

You can add client-side parts to server-side rendered pages. Maybe you clicked "Don't show me this dialog every time" and the page is adjusted on the client-side based on data stored in the browser. Or the page realizes an API call for targeted products based on your locally stored preferences.

Some tiny adjustments

 

Understanding the error

The error we see here is caused by the following: Some of our code is thought to be client-side rendered and assuming certain globals like window or document are available because the code is meant to be executed in the browser. This can be caused by our code or third-party libraries.

But if the code is server-side rendered, it is not executed by the browser, it is executed by a node server and globals like window and document are not available: hence the error we see.

The solution

1. Use the useEffect hook or just ask for window

That's the easiest solution: if you just need to access window to scroll somewhere or render some special component only on the client-side, you can do it like this:

import React, { useEffect } from "react";

export default function TestComponent(props) {
  console.log("I get executed in the browser and the client");

  const isSSR = typeof window === "undefined";
  console.log(isSSR);

  useEffect(() => {
    console.log("I am only being executed in the browser");
  }, []);

  return (
    <div>
      I am a test component!
      {!isSSR && <p>This is only rendered on the client side!</p>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

2. @loadable/component in Gatsby

The library @loadable/component allows you to dynamically import components to your project, so they don't get rendered on the server. The following component uses leaflet, a library similar to google maps that only supports client-side rendering:

import React from 'react';
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';

export default function Map(props) {
    const defaultPosition = [-31.41528, -64.18156];
    const myIcon = new L.Icon({
      iconUrl: '/marker-icon.png',
    });
    return (
      <div className="map__container">
        <MapContainer
          center={defaultPosition}
          zoom={16}
          style={{ height: 400, width: '100%' }}
        >
          <TileLayer
            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          <Marker position={defaultPosition} icon={myIcon}>
            <Popup>
              Entrá en la galería! <br /> Estamos en frente de Nunatak. <br /> Te
              esperamos!
            </Popup>
          </Marker>
        </MapContainer>
      </div>
    );
  }
Enter fullscreen mode Exit fullscreen mode

To be able to use this component in Gatsby, I used @loadable/component like so:

import loadable from '@loadable/component';
import React from 'react';
// import Map from './Map'; // uncomment this line to see the Gatsby build error

export default function MapWrapper() {
    const Map = loadable(() => import("./Map")) // comment this line to see the Gatsby build error
    return <div>
        <Map />
    </div>
}
Enter fullscreen mode Exit fullscreen mode

Go ahead, clone the project and play around with the different types of imports. Then try running: npm run build so you can see the result in your terminal.

Gatsby also mentions some alternative solutions in the docs.

3. Dynamic loading in Next.js

Next.js has its own mechanism to dynamically import components on the client-side only: check out the docs. This is how I added the leaflet map in Next.js:

import React, { useState } from "react";
import dynamic from "next/dynamic";
import styles from "../styles/Contacto.module.css";

export function FindMe(props) {
  const Map = dynamic(
    () => import("../components/Map"), // replace '@components/map' with your component's location
    { ssr: false } // This line is important. It's what prevents server-side render
  );

  return (
        <div className={styles.map}>
          <Map />
        </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The <Map /> component is exactly the same as in the Gatsby project. To try this, just spin up a Next.js project and import the Map component together with its wrapper into a server-side generated page.

As always, I hope this helped! Let me know what you think in the comments and have a great day!

Top comments (4)

Collapse
 
octaviosanti351 profile image
octaviosanti351 • Edited

Nice post my dear friend!! I hope I can apply this soon in my work (well I'm a backend dev but... who knows... ;) )

Collapse
 
frontenddeveli profile image
Elisabeth Leonhardt

thank you!! Glad you liked it - you can switch sides whenever you like 😜

Collapse
 
armas0n profile image
Adam Okhotskiy

Hi. Thanks for the post!
I have a question btw:
Next docs explicitly say that it is forbidden to use next/dynamic imports inside of react components like it was used in your last example. They recommend using it at a top of a module along with other imports.
I noticed that it works both ways, so is there any real implications on using it inside react component?

Collapse
 
tchassi_jordan profile image
Tchassi Jordan

Had a similar issue, and the next docs unblocked it for me.