Leaflet with next.js?

Answer for 2020

I also had this problem and solved it in my own project, so I thought I would share what I did.

NextJS can dynamically load libraries and restrict that event so it doesn’t happen during the server side render. See the documentation for more details.

In my examples below I will use and modify example code from the documentation websites of both NextJS 10.0 and React-Leaflet 3.0.

Side note: if you use TypeScript, make sure you install @types/leaflet because otherwise you’ll get compile errors on the center and attribution attributes.

To start, I split my react-leaflet code out into a separate component file like this:

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

const Map = () => {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false} 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={[51.505, -0.09]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  )
}

export default Map

I called that file map.tsx and placed it in a folder called components but you might call it map.jsx if you don’t use TypeScript.

Note: It is important that this code is in a separate file from where it is embedded into your page because otherwise you’ll still get window undefined errors.

Also Note: don’t forget to specify the style of the MapContainer component so it doesn’t render as zero pixels in height/width. In the above example, I added the attribute style={{height: 400, width: "100%"}} for this purpose.

Now to use that component, take advantage of NextJS’s dynamic loading like this:

import dynamic from 'next/dynamic'

function HomePage() {
  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 <Map />
}

export default HomePage

If you want the map to be replaced with something else while it’s loading (probably a good practice) you should use the loading property of the dynamic function like this:

import dynamic from 'next/dynamic'

function HomePage() {
  const Map = dynamic(
    () => import('@components/map'), // replace '@components/map' with your component's location
    { 
      loading: () => <p>A map is loading</p>,
      ssr: false // This line is important. It's what prevents server-side render
    }
  )
  return <Map />
}

export default HomePage

Adrian Ciura commented on the flickering which may occur as your components re-render even when nothing about the map should change. They suggest using the new React.useMemo hook to solve that problem. If you do, your code might look something like this:

import React from 'react'
import dynamic from 'next/dynamic'

function HomePage() {
  const Map = React.useMemo(() => dynamic(
    () => import('@components/map'), // replace '@components/map' with your component's location
    { 
      loading: () => <p>A map is loading</p>,
      ssr: false // This line is important. It's what prevents server-side render
    }
  ), [/* list variables which should trigger a re-render here */])
  return <Map />
}

export default HomePage

I hope this helps. It would be easier if react-leaflet had a test for the existence of window so it could fail gracefully, but this workaround should work until then.

Leave a Comment

File not found.