Skip to main content

Using with MapLibre

Import pathdeck.gl overlayOverlaidInterleavedReact Map GL
@deck.gl-community/react-fiber/maplibreMapLibreOverlay✓✓✓

MapLibre GL JS is an open-source library that renders interactive maps from vector tiles. The /maplibre provider root creates a MapLibreOverlay from @deck.gl/maplibre and renders its JSX <layer> children into it. The application owns the MapLibre map and attaches the overlay to it. Using with maps describes the shared provider-root behavior and lifecycle.

deck.gl's Using with MapLibre guide covers the same integration without React Fiber.

Integration modes​

  • Interleaved: pass interleaved to render deck.gl layers into the MapLibre WebGL2 context. See deck.gl's interleaved mode description.
  • Overlaid: omit interleaved, or pass false, to render deck.gl into its own canvas inside the MapLibre controls container. See deck.gl's overlaid mode description.
  • Reverse controlled: provider roots do not support this mode. See Using with maps.

MapLibre interleaved rendering requires WebGL2 and supports one interleaved overlay per map. Use beforeId to position deck layers. In shared-context mode, configure MSAA and pixel ratio on the host map with canvasContextAttributes.antialias and pixelRatio; deck's useDevicePixels and device do not control that context.

The MapLibre overlay excludes width, height, and gl in addition to the map-owned props listed in Provider prop boundary.

Install​

Install the provider integration and the host SDK:

yarn add @deck.gl/maplibre maplibre-gl

Keep map creation in client-only code: in a Next.js or TanStack Start application, use a client boundary or a module that runs only in the browser.

Configure the MapLibre worker​

MapLibre GL JS 6 ships as ES modules and runs its tile parsing in a separate worker module. A bundled application tells MapLibre where the bundler emitted that worker: call setWorkerUrl(url) once, in browser code, before it creates the first map. This applies to MapLibre only; Mapbox needs no worker setup.

With Vite, including React Router and TanStack Start, import the worker with Vite's ?worker&url query:

import {setWorkerUrl} from 'maplibre-gl';
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
import 'maplibre-gl/dist/maplibre-gl.css';

setWorkerUrl(workerUrl);

The ?worker&url query is Vite-only. Next.js (Turbopack or webpack) and other bundlers use new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url) instead, which requires maplibre-gl 6.13 or later. MapLibre's installation guide lists the setup for each bundler.

The React Fiber examples configure the worker in their MapLibre modules:

MapLibre loads a same-origin worker URL directly, so a Content Security Policy needs only worker-src 'self'. A cross-origin worker URL, such as one served from a CDN, is wrapped in a blob: URL and needs blob: in worker-src. See MapLibre's CSP directives for the full list.

Examples​

React Map GL​

The React Map GL MapLibre component owns all MapLibre options. It can contain provider DeckGL because that component does not render a host child.

import {useEffect, useRef, useState} from 'react';
import {Map, type MapRef} from 'react-map-gl/maplibre';
import type {MapLibreOverlay} from '@deck.gl/maplibre';
import {DeckGL} from '@deck.gl-community/react-fiber/maplibre';

export function MapLibreMap() {
const mapRef = useRef<MapRef>(null);
const [loaded, setLoaded] = useState(false);
const [overlay, setOverlay] = useState<MapLibreOverlay | null>(null);

useEffect(() => {
const map = mapRef.current?.getMap();
if (!map || !loaded || !overlay) return;

let attached = false;
map.addControl(overlay);
attached = true;

return () => {
if (attached) map.removeControl(overlay);
};
}, [loaded, overlay]);

return (
<Map
ref={mapRef}
mapStyle="https://demotiles.maplibre.org/style.json"
initialViewState={{longitude: -122.4, latitude: 37.8, zoom: 12}}
canvasContextAttributes={{antialias: true}}
onLoad={() => setLoaded(true)}
>
<DeckGL interleaved onDeckChange={setOverlay}>
{/* React Fiber <layer> elements */}
</DeckGL>
</Map>
);
}

Manual map​

A manual MapLibre container must have no children before new Map({container}). The detached provider-root registry key preserves that requirement.

import {useEffect, useRef, useState} from 'react';
import {Map as MapLibreMap} from 'maplibre-gl';
import type {MapLibreOverlay} from '@deck.gl/maplibre';
import {DeckGL} from '@deck.gl-community/react-fiber/maplibre';

export function ManualMapLibreMap() {
const containerRef = useRef<HTMLDivElement>(null);
const [overlay, setOverlay] = useState<MapLibreOverlay | null>(null);

useEffect(() => {
if (!containerRef.current || !overlay) return;

const map = new MapLibreMap({
canvasContextAttributes: {antialias: true},
container: containerRef.current,
center: [-122.4, 37.8],
pixelRatio: window.devicePixelRatio,
style: 'https://demotiles.maplibre.org/style.json',
zoom: 12
});
let disposed = false;
let attached = false;

const attachOverlay = () => {
if (!disposed) {
map.addControl(overlay);
attached = true;
}
};

if (map.loaded()) {
attachOverlay();
} else {
map.once('load', attachOverlay);
}

return () => {
disposed = true;
map.off('load', attachOverlay);
if (attached) map.removeControl(overlay);
map.remove();
};
}, [overlay]);

return (
<div ref={containerRef} style={{height: 500}}>
<DeckGL interleaved onDeckChange={setOverlay}>
{/* React Fiber <layer> elements */}
</DeckGL>
</div>
);
}

Additional information​

React Map GL​

deck.gl's guide attaches MapLibreOverlay with React Map GL's useControl hook. Do not use useControl with a provider root: its factory runs once when the hook mounts, but the provider root creates the overlay itself and delivers it later through onDeckChange. Attach the overlay in an effect, as in the example above.

Choosing a map tile service​

MapLibre GL JS renders tiles from any compatible service or from tiles you host. deck.gl's guide lists tile services and self-hosting resources.