Frontend GIS · WebGL · Next.js

MapLibre GL JS: High-Performance Interactive Maps

The open-source successor to Mapbox GL JS — WebGL rendering directly on the GPU, zero per-tile API fees, PMTiles support, and a clean Next.js integration path.

Talha WaheedCo-Founder & Head of Engineering15 min read
MapLibre GL JS WebGL map tile grid with vector layers composited on the GPU
← Back to Blog

What Is MapLibre GL JS?

MapLibre GL JS is an open-source TypeScript library that renders interactive vector maps directly in the browser using WebGL. Its official documentation covers the renderer, sources, controls, events, and required CSS stylesheets.

MapLibre treats the map as a layered scene rendered with WebGL. Performance depends on the style, source data, viewport, browser, and device, so production tuning should be based on measurements from the actual application.

Why Teams Choose MapLibre

MapLibre is fully open source under the BSD license, GPU-accelerated, supports 3D terrain and vector styling, and pairs effortlessly with self-hosted PMTiles and Martin vector tile services.

The WebGL Rendering Pipeline

Understanding MapLibre's internal architecture unlocks the ability to write truly performant maps. Every tile load travels through four distinct stages:

01. Fetch & Decode

Vector tiles (.pbf / MVT) are fetched over HTTP and decoded on a Web Worker thread, keeping the main UI thread buttery smooth.

02. Bucket Compilation

Features are sorted into render buckets per layer type, pre-computing vertex buffers and collision spatial indexes.

03. GPU Upload

Compiled geometry buffers are transferred to the GPU as WebGL vertex buffer objects (VBOs) with sprite atlases.

04. Per-Frame Render

The renderer iterates styled layers and issues efficient GPU draw calls at up to 60–120 fps.

Four-stage MapLibre GL JS WebGL rendering pipeline

Next.js Setup

MapLibre GL JS requires browser APIs and cannot run during server-side rendering. In Next.js, initialize the map inside a useEffect hook after the component mounts.

'use client';
import { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { Protocol } from 'pmtiles';

export default function MapLibreMap({ center = [77.209, 28.6139], zoom = 11 }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<maplibregl.Map | null>(null);

  useEffect(() => {
    const protocol = new Protocol();
    maplibregl.addProtocol('pmtiles', protocol.tile);

    if (containerRef.current) {
      mapRef.current = new maplibregl.Map({
        container: containerRef.current,
        style: 'https://demotiles.maplibre.org/style.json',
        center,
        zoom,
      });
    }

    return () => {
      mapRef.current?.remove();
      maplibregl.removeProtocol('pmtiles');
    };
  }, []);

  return <div ref={containerRef} className="h-[500px] w-full rounded-xl" />;
}

PMTiles Integration

PMTiles is a single-file tile archive that can be read directly from object storage via HTTP Range Requests. Protomaps documents MapLibre integration with the custom pmtiles:// protocol scheme.

// Load custom PMTiles directly from Cloudflare R2
map.addSource('parcels-source', {
  type: 'vector',
  url: 'pmtiles://https://data.infrynetechworks.com/parcels.pmtiles',
});

map.addLayer({
  id: 'parcels-layer',
  type: 'fill',
  source: 'parcels-source',
  'source-layer': 'parcels',
  paint: {
    'fill-color': '#06b6d4',
    'fill-opacity': 0.6,
  },
});

Layer System & Style Specification

The MapLibre Style Specification defines the JSON schema for sources, layers, sprites, and fonts drawn onto the WebGL canvas.

MapLibre GL JS style layer stack

Custom WebGL Layers

For custom GLSL shaders, particle simulations, or radar sweeps, MapLibre exposes a custom style-layer interface.

const customLayer = {
  id: 'custom-gl-glow',
  type: 'custom',
  renderingMode: '2d',
  onAdd(map, gl) {
    // Compile shaders and initialize GL buffers
  },
  render(gl, matrix) {
    // Execute custom WebGL draw calls
  }
};
map.addLayer(customLayer);

Feature State for High-Performance Hover & Selection

MapLibre's setFeatureState() updates feature properties in O(1) time without rebuilding vertex buffers or re-fetching tiles.

map.on('mousemove', 'parcels-layer', (e) => {
  if (e.features?.length) {
    map.setFeatureState(
      { source: 'parcels-source', sourceLayer: 'parcels', id: e.features[0].id },
      { hover: true }
    );
  }
});

Performance Optimization

TechniqueImpactConfiguration
Set maxzoom on sourcesPrevents unnecessary higher-zoom tile requestsmaxzoom: 14 on source; MapLibre overzooms
Use promoteIdEnables O(1) feature-state stylingpromoteId: { layer: "id" }
Cluster point datasetsReduces low-zoom draw callscluster: true on GeoJSON source
Minimize layer countReduces GPU state switches per frameCombine line layers with filter expressions

Connecting to Martin Tile Server

For dynamic PostGIS data, point MapLibre at Martin endpoints. Martin auto-discovers table geometry columns and returns standard TileJSON.

map.addSource('live-sensors', {
  type: 'vector',
  url: 'https://tiles.infrynetechworks.com/sensors', // Martin TileJSON
});

Building a Mapping Application?

High-Performance WebGL Map Engineering

Infryne TechWorks builds production MapLibre GL JS frontends backed by Martin and PMTiles — from interactive geospatial dashboards to real-time asset tracking.

Primary Sources

Related Infryne Articles