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.
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.
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.
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.
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>
);
}
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='© <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>
);
}
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>
}
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>
);
}
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)
Nice post my dear friend!! I hope I can apply this soon in my work (well I'm a backend dev but... who knows... ;) )
thank you!! Glad you liked it - you can switch sides whenever you like 😜
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?
Had a similar issue, and the next docs unblocked it for me.