How to get started with Geoman and Next.js

Next.js is a popular React framework for the web and is a great companion for Leaflet and Geoman. We have put together a short guide on how to get started by setting up the scaffolding. If you would rather get straight to coding, you can find an example app on our GitHub repo.

Use create-next-app to set up the project

Next.js ships natively with the JavaScript package managers like NPM, Yarn, Bun, and PNPM. Using NPX, you can set up a Next.js project by opening your terminal and running:

npx create-next-app@latest

This will start a CLI to guide you through selecting a project name, whether you will use TypeScript and other app configurations. Next.js CLI For our config we pick TypeScript and set up a src/ directory.

Install Leaflet and Leaflet-Geoman

Now that we have set up our Next.js project, we need to install both Leaflet and Geoman. At the project root open a terminal and run:

npm i @geoman-io/leaflet-geoman-free leaflet react-leaflet  
npm install -D @types/leaflet

After installing these, we are ready to implement the business logic for our app. We will be creating three react components, one for handling our leaflet map, one for listening to events and one for creating the Geoman controls.

React Components for our Leaflet Map, Events Listeners and Geoman Controls

We start with the React component for the Leaflet map by creating a file src/components/Map.tsx. This component displays our Leaflet map.

src/components/Map.tsx

import React from "react";
import { MapContainer, TileLayer } from "react-leaflet";
import { GeomanControl } from "./GeomanControl";
import Events from "./Events";

const Map = () => {
  return (
    <>
      <MapContainer
        center={[51.505, -0.09]}
        zoom={13}
        scrollWheelZoom={true}
        style={{ width: "100vw", height: "100vh" }}
      >
        <TileLayer
          attribution='<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> <a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>'
          url="https://api.maptiler.com/maps/streets-v2/256/{z}/{x}/{y}.png?key=FTejVijNkqKWPbQui8i9"
        />

        <GeomanControl position="topleft" oneBlock />
        <Events />
      </MapContainer>
    </>
  );
};

export default Map;

Next, we create an event listener component in the file src/components/Events.tsx. This component integrates with our Leaflet map using React Leaflet and adds event listeners for different interactions like of editing map layers.

src/components/Events.tsx

import { useEffect } from "react";
import { useMap } from "react-leaflet";

const Events = () => {
  const map = useMap();

  useEffect(() => {
    if (map) {
      map.on("pm:create", (e) => {
        console.log("Layer created:", e);

        e.layer.on("click", () => {
          console.log("Layer clicked", e);
        });

        e.layer.on("pm:edit", () => {
          console.log("Layer edited", e);
        });

        e.layer.on("pm:update", () => {
          console.log("Layer updated", e);
        });

        e.layer.on("pm:remove", (e) => {
          console.log("Layer removed:", e);
        });

        e.layer.on("pm:dragstart", (e) => {
          console.log("Layer dragstart:", e);
        });

        e.layer.on("pm:dragend", (e) => {
          console.log("Layer dragend:", e);
        });
      });

      map.on("pm:drawstart", (e) => {
        console.log("Layer drawstart:", e);
      });

      map.on("pm:drawend", (e) => {
        console.log("Layer drawend:", e);
      });

      map.on("pm:globaldrawmodetoggled", (e) => {
        console.log("Layer globaldrawmodetoggled:", e);
      });

      map.on("pm:globaldragmodetoggled", (e) => {
        console.log("Layer globaldragmodetoggled:", e);
      });

      map.on("pm:globalremovalmodetoggled", (e) => {
        console.log("Layer globalremovalmodetoggled:", e);
      });

      map.on("pm:globalcutmodetoggled", (e) => {
        console.log("Layer globalcutmodetoggled:", e);
      });

      map.on("pm:globalrotatemodetoggled", (e) => {
        console.log("Layer globalrotatemodetoggled:", e);
      });
    }
  }, [map]);

  return null;
};

export default Events;

Finally, we create the React component for the Geoman Control bar which adds the Geoman functionality to our Leaflet map.

src/components/GeomanControl.tsx

import { createControlComponent } from "@react-leaflet/core";
import * as L from "leaflet";
import "@geoman-io/leaflet-geoman-free";
import "@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css";

interface Props extends L.ControlOptions {
  position: L.ControlPosition;
  drawCircle?: boolean;
  oneBlock?: boolean;
}

const Geoman = L.Control.extend({
  options: {},
  initialize(options: Props) {
    L.setOptions(this, options);
  },

  addTo(map: L.Map) {
    if (!map.pm) return;

    map.pm.addControls({
      ...this.options,
    });
  },
});

const createGeomanInstance = (props: Props) => {
  return new Geoman(props);
};

export const GeomanControl = createControlComponent(createGeomanInstance);

We can now integrate these three components in our Next.js app.

Integrate the React Components

We need to update the page.tsx logic to include our components and to display a Leaflet map.

src/app/page.tsx

"use client";
import dynamic from "next/dynamic";

const Map = dynamic(() => import("../components/Map"), {
  ssr: false,
});

export default function Home() {
  return (
    <>
      <Map />
    </>
  );
}

We need also need to update the layout.tsx to include the Leaflet and Geoman scripts and CSS in our HTML head tag.

src/app/layout.tsx

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <link
          rel="stylesheet"
          href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
          integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
          crossOrigin=""
        />
        <link
          rel="stylesheet"
          href="https://unpkg.com/@geoman-io/leaflet-geoman-free@latest/dist/leaflet-geoman.css"
        />

        <script
          src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
          integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
          crossOrigin=""
          defer
        >
        </script>
        <script
          src="https://unpkg.com/@geoman-io/leaflet-geoman-free@latest/dist/leaflet-geoman.js"
          defer
        >
        </script>
      </head>
      <body className={inter.className}>{children}</body>
    </html>
  );
}

With these edits in place, we now have the scaffolding for a Next.js app that displays a Leaflet map with the Geoman controls in the browser. Runnning npm run dev:

Geoman in a Next.js Project

GitHub repo with full code and more examples

Get the full code by visiting our GitHub repo and find other starter kits for React Apps with Geoman.

If you have an interesting example of how you have used Geoman and Next.js, please you share it on our GitHub Repo.

Ready to dive in?
Start with Leaflet-Geoman Today.