'Next.js dynamic import with server-side-rendering turned off not working on production build

I'm currently building a site using the leaflet package. This package however needs the window object. That's why I'm importing a component made with leaflet as a dynamic component with ssr turned off. Just like this:

import dynamic from "next/dynamic";
   const MapWithNoSSR = dynamic(() => import("../../map"), {
             ssr: false
 });
 
export default function faqOnly(props){
 ...
 return (<> <MapWithNoSSR /></>)
 }

Map component looks like this:

    import React, { useEffect, useState, useRef } from "react";
import { OpenStreetMapProvider } from 'leaflet-geosearch';
import "leaflet/dist/leaflet.css";
import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'
import "leaflet-defaulticon-compatibility";
import dynamic from "next/dynamic";

const L = dynamic(() => import("leaflet"), {
      ssr: false,
      suspense: true,
      loading: () => <p>...</p>
    });


function Map(props) {


  useEffect(async () => {
    if(window === undefined) return
    const provider = new OpenStreetMapProvider();
     const results = await provider.search({ query: props.adress });
     if(results.length > 0 == true){
      var map = L.map('map', {
          center: [results[0].y, results[0].x],
          zoom: 18,
          layers: [
              L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png', {
                  attribution: ''
              }),
          ]
      })
      L.marker([results[0].y, results[0].x]).addTo(map)
  }else{
    document.getElementById("map").style.display = "none"
  }
  }, [])


    return <div id="map" style={{ height: "30vh"}}></div>

}

export default Map;

And I get this error when I run npm run build:

ReferenceError: window is not defined
    at E:\Github\Planer\rl-planer\node_modules\leaflet\dist\leaflet-src.js:230:19
    at E:\Github\Planer\rl-planer\node_modules\leaflet\dist\leaflet-src.js:7:66
    at Object.<anonymous> (E:\Github\Planer\rl-planer\node_modules\leaflet\dist\leaflet-src.js:10:3)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (E:\Github\Planer\rl-planer\node_modules\leaflet-geosearch\dist\geosearch.js:1:7) {
  type: 'ReferenceError'
}

This works fine in development, but when I try to build the project (next build), it throws a "window is not defined" error inside the leaflet package, which it hasn't been doing before when I was working on it in development mode.

I looked through other questions here, but it seems like moving the dynamic import outside the component fixed it for everyone but me. Am I just stupid or what's the problem here?



Solution 1:[1]

Maybe you don't need next/dynamic at all and can just use simple js dynamic import inside the useEffect function body.

(useEffect will only run on the client)

Nextjs has an example that looks pretty similar: https://nextjs.org/docs/advanced-features/dynamic-import. (the first on the page with fuse.js)

import React, { useEffect, useState, useRef } from "react";
import { OpenStreetMapProvider } from 'leaflet-geosearch';
import "leaflet/dist/leaflet.css";
import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'
import "leaflet-defaulticon-compatibility";

function Map(props) {
  useEffect(async () => {
    const L = await import("leaflet")
    const provider = new OpenStreetMapProvider();
    const results = await provider.search({ query: props.adress });

    if(results.length > 0 == true) {
      var map = L.map('map', {
        center: [results[0].y, results[0].x],
        zoom: 18,
        layers: 
          [
            L.tileLayer(
              'https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png',
              { attribution: ''}
            ),
          ]
      })
      L.marker([results[0].y, results[0].x]).addTo(map)
    } else {
      document.getElementById("map").style.display = "none"
    }
  }, [])


  return <div id="map" style={{ height: "30vh"}}></div>
}

export default Map;

Updated

Okay so maybe trying to move all js that requires window in a useEffect hook is a bit messy. Seams easier to just import the whole component client only. To me your example code looks good – apart from the fact that you import leaflet dynamically again in the map file:

page.jsx

import dynamic from "next/dynamic";
const MapWithNoSSR = dynamic(() =>
  import("../../map"), { ssr: false });
 
export default function faqOnly(props){
  ...
  return <MapWithNoSSR />
}

map.jsx

import React, { useEffect, useState, useRef } from "react";
import { OpenStreetMapProvider } from 'leaflet-geosearch';
import L from 'leaflet'
import "leaflet/dist/leaflet.css";
import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'
import "leaflet-defaulticon-compatibility";

function Map(props) {
  useEffect(async () => {
    const provider = new OpenStreetMapProvider();
    const results = await provider.search({ query: props.adress });

    if(results.length > 0 == true) {
      var map = L.map('map', {
        center: [results[0].y, results[0].x],
        zoom: 18,
        layers: 
          [
            L.tileLayer(
              'https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png',
              { attribution: ''}
            ),
          ]
      })
      L.marker([results[0].y, results[0].x]).addTo(map)
    } else {
      document.getElementById("map").style.display = "none"
    }
  }, [])


  return <div id="map" style={{ height: "30vh"}}></div>
}

export default Map;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1