Skip to content
Frameworks

Frameworks

A map library and a component framework disagree about who owns the DOM. The map wants a stable element it can keep for its lifetime; the framework reserves the right to unmount, re-render and re-run effects whenever it likes. Almost every bug in this section comes from that disagreement.

These pages cover the framework-specific part: where to create the map, where to destroy it, what to store it in, and what breaks during a build. The map code itself is the same as everywhere else, so use Maps JS or MapLibre GL JS for the API, and these pages for the wiring.


React

Using Maptoolkit maps in React covers the useRef and useEffect pattern, cleaning up on unmount, and driving the map imperatively from effects instead of recreating it. With a wrapper library, use React Leaflet for raster tiles or react-map-gl for MapLibre GL JS.

The one that catches everybody: in development, React 18 and later mounts, unmounts and remounts every component once. Without a guard the effect runs twice and you get two maps stacked in one container, with doubled controls and events firing twice. It does not happen in production, so it never shows up where you would notice it.


Next.js

Using Maptoolkit maps in Next.js covers next/dynamic with ssr: false, the App Router client boundary, where the stylesheet goes, and static export. Leaflet in Next.js covers the same for React Leaflet, plus the marker icon fix for webpack and Turbopack.

The one that catches everybody: "use client" is not enough. A client component is still server rendered for the initial HTML, so the module is still evaluated on the server, where the library reaches for window and throws ReferenceError: window is not defined from inside the library rather than from your code.


Vue

Using Maptoolkit maps in Vue covers the Composition API lifecycle hooks, driving the map from watchers, and the Nuxt server-rendering case. With a wrapper library, use Vue Leaflet for raster tiles or vue-maplibre-gl for MapLibre GL JS.

The one that catches everybody: the map has to go in a shallowRef, not a ref. A plain ref makes Vue deep-proxy the whole map instance, which ranges from a badly degraded frame rate to the library’s own identity checks failing because it is comparing an object against a Proxy of itself. The symptoms look like library bugs and are not.


Angular

Using Maptoolkit maps in Angular covers a standalone map component, the stylesheet in angular.json, and @defer. With a wrapper library, use ngx-leaflet for raster tiles or ngx-maplibre-gl for MapLibre GL JS.

The one that catches everybody: the production build fails its size budget. A WebGL map library is larger than Angular’s default 1 MB limit for the initial bundle, so importing it directly breaks ng build. A @defer block around the map moves it into a lazy chunk.


The three rules that hold everywhere

  1. Create the map once, against a real element. Not on every render, and not before the element exists. Give that element an explicit height, or it is zero pixels tall and the map initializes into nothing with no error.
  2. Destroy it on unmount. Call map.remove(). A map that is not removed keeps its WebGL context, its workers and its event listeners, and browsers cap live contexts at around 8 to 16. Past the cap, existing maps go blank rather than throwing.
  3. Do not put the map instance in reactive state. Frameworks deep-proxy or diff whatever you hand them. A map instance is a large object graph with circular references. Keep it in a plain ref.