That's the whole thing explained. Here's the same component as a single, self-contained file -- copy it into any React/Next.js project and it works as-is.
"use client";
/**
* Drop-in MapLibre map -- the minimal setup, no extra controls beyond
* MapLibre's own built-in drag/scroll/zoom-buttons.
*
* Setup: npm install maplibre-gl react react-dom
*
* The worker script below loads from a CDN so this file needs zero extra
* setup. Pin the version in the URL to whatever maplibre-gl version you
* installed. (Why a CDN worker at all? MapLibre resolves its worker URL from
* import.meta.url, which isn't a real http(s) URL under bundlers like
* Turbopack/Webpack -- without this, it silently fails and no tiles load.)
*/
import * as maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { useEffect, useRef } from "react";
export interface SimpleMapProps {
/** [lng, lat] to center the map on. */
origin?: [number, number];
/** Any MapLibre style URL. */
mapStyle?: string;
initialZoom?: number;
}
export default function SimpleMap({
origin = [10.1862, 36.80025],
mapStyle = "https://tiles.openfreemap.org/styles/liberty",
initialZoom = 14,
}: SimpleMapProps) {
const mapContainerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!mapContainerRef.current) return;
maplibregl.setWorkerUrl(
"https://unpkg.com/maplibre-gl@6.6.0/dist/maplibre-gl-worker.mjs",
);
const map = new maplibregl.Map({
container: mapContainerRef.current,
style: mapStyle,
center: origin,
zoom: initialZoom,
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
return () => {
map.remove();
};
}, [origin, mapStyle, initialZoom]);
return <div ref={mapContainerRef} style={{ position: "absolute", inset: 0 }} />;
}