Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@page-speed/maps

Package Overview
Dependencies
Maintainers
2
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@page-speed/maps

Performance-optimized MapLibre React components and style utilities for DashTrack.

latest
Source
npmnpm
Version
0.2.4
Version published
Weekly downloads
23
-62.3%
Maintainers
2
Weekly downloads
 
Created
Source

@page-speed/maps

High-performance MapLibre primitives. An open source tool by OpenSite AI

PageSpeed Map React Component


npm version npm downloads License TypeScript Tree-Shakeable

Install

pnpm add @page-speed/maps maplibre-gl react-map-gl

Quick Start

import { MapLibre } from "@page-speed/maps";

export function Example() {
  return (
    <div style={{ width: "100%", height: 420 }}>
      <MapLibre
        stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
        mapStyle="osm-bright"
        viewState={{ latitude: 40.7128, longitude: -74.006, zoom: 12 }}
        markers={[
          {
            id: "nyc",
            latitude: 40.7128,
            longitude: -74.006,
            label: "New York"
          }
        ]}
      />
    </div>
  );
}

Why This Package

  • Explicit Stadia auth: no hard-coded keys
  • Auto-loads MapLibre CSS: no extra stylesheet import required
  • Keyless fallback map style: if no Stadia key is available, roads/landmarks still render via Carto Positron
  • Tree-shakable exports: import only what you need
  • Auto-centering hooks: compute optimal center and zoom for any set of coordinates
  • Drop-in API compatibility: works with the MapLibre component used in dt-cms

Table of Contents

Components

MapLibre / DTMapLibreMap

The main map component. Renders a MapLibre GL map with markers, controls, and full interactivity.

import { MapLibre } from "@page-speed/maps";

<MapLibre
  stadiaApiKey="your-api-key"
  mapStyle="osm-bright"
  viewState={{ latitude: 33.4484, longitude: -112.074, zoom: 10 }}
  markers={[
    { id: "phx", latitude: 33.4484, longitude: -112.074, label: "Phoenix" }
  ]}
  showNavigationControl
  showGeolocateControl
  onClick={(coord) => console.log("Clicked:", coord)}
  onMoveEnd={(center, zoom, bounds) => console.log("Moved:", center, zoom)}
/>

Props

PropTypeDefaultDescription
stadiaApiKeystringrequiredYour Stadia Maps API key
mapStylestring"osm-bright"Built-in style name or custom style URL
viewStatePartial<MapViewState>-Controlled view state (lat, lng, zoom)
onViewStateChange(state) => void-Callback when view state changes
markersArray<MapLibreMarker | BasicMarkerInput>[]Array of markers to display
center{ lat, lng }-Initial center (alternative to viewState)
zoomnumber14Initial zoom level
styleUrlstring-Custom style URL (overrides mapStyle)
onClick(coord) => void-Map click handler
onMoveEnd(center, zoom, bounds) => void-Called when map stops moving
onMarkerDrag(markerId, coord) => void-Called when draggable marker moves
showNavigationControlbooleantrueShow zoom/rotation controls
showGeolocateControlbooleanfalseShow user location button
navigationControlPositionMapControlPosition"bottom-right"Position of nav controls
geolocateControlPositionMapControlPosition"top-left"Position of geolocate button
flyToOptionsMapLibreFlyToOptions{}Animation options for flyTo
classNamestring-CSS class for wrapper div
styleCSSProperties-Inline styles for wrapper
childrenReactNode-Additional map layers/overlays
mapLibreCssHrefstringjsDelivr CDNCustom MapLibre CSS URL

Hooks

useGeoCenter

Computes the geographic center of an array of coordinates using the Cartesian 3D averaging method. Handles antimeridian crossing and polar coordinates correctly.

import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
// or
import { useGeoCenter } from "@page-speed/maps";

const markers = [
  { lat: 33.4585, lng: -112.0715 },  // Downtown Phoenix
  { lat: 33.6510, lng: -111.9244 },  // Scottsdale
  { lat: 33.3062, lng: -111.8413 },  // Mesa
];

const center = useGeoCenter(markers);
// Result: { lat: 33.4719, lng: -111.9457 }

API

function useGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;
function computeGeoCenter(coordinates: GeoCoordinate[]): GeoCenterResult | null;

interface GeoCoordinate {
  lat: number;
  lng: number;
}

interface GeoCenterResult {
  lat: number;
  lng: number;
}

Behavior

  • Empty array: Returns null
  • Single coordinate: Returns that coordinate
  • Multiple coordinates: Returns the geographic midpoint

useDefaultZoom

Computes the optimal zoom level to fit all coordinates within a given viewport, using Mercator projection math. Uses MapLibre's native 512px tile size.

import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";
// or
import { useDefaultZoom } from "@page-speed/maps";

const markers = [
  { lat: 33.4585, lng: -112.0715 },
  { lat: 33.6510, lng: -111.9244 },
];

const zoom = useDefaultZoom({
  coordinates: markers,
  mapWidth: 600,
  mapHeight: 400,
  padding: 50,
  maxZoom: 16,
  minZoom: 1,
});
// Result: ~10.5 (fits both markers with padding)

API

function useDefaultZoom(options: DefaultZoomOptions): number | null;
function computeDefaultZoom(options: DefaultZoomOptions): number | null;

interface DefaultZoomOptions {
  coordinates: GeoCoordinate[];
  mapWidth: number;
  mapHeight: number;
  padding?: number;   // default: 50
  maxZoom?: number;   // default: 18
  minZoom?: number;   // default: 1
}

Behavior

  • Empty array: Returns null
  • Single coordinate: Returns maxZoom
  • Multiple coordinates: Returns the highest zoom that fits all markers with padding
  • Invalid dimensions: Returns null or minZoom

Combined Usage: Auto-Centering Map

import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";

function AutoCenteringMap({ locations }) {
  const coordinates = locations.map(loc => ({
    lat: loc.latitude,
    lng: loc.longitude,
  }));

  const center = useGeoCenter(coordinates);
  const zoom = useDefaultZoom({
    coordinates,
    mapWidth: 800,
    mapHeight: 600,
    padding: 60,
  });

  if (!center) return <div>No locations to display</div>;

  return (
    <div style={{ width: 800, height: 600 }}>
      <MapLibre
        stadiaApiKey={process.env.NEXT_PUBLIC_STADIA_API ?? ""}
        viewState={{
          latitude: center.lat,
          longitude: center.lng,
          zoom: zoom ?? 10,
        }}
        markers={locations.map((loc, i) => ({
          id: loc.id ?? i,
          latitude: loc.latitude,
          longitude: loc.longitude,
          label: loc.name,
        }))}
      />
    </div>
  );
}

Utilities

getMapLibreStyleUrl(style, stadiaApiKey)

Resolves a style name or URL to a fully-qualified MapLibre style URL with authentication.

import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";

const url = getMapLibreStyleUrl("osm-bright", "your-api-key");
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"

const fallback = getMapLibreStyleUrl("osm-bright", "");
// "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" (keyless fallback)

appendStadiaApiKey(styleUrl, stadiaApiKey)

Appends the Stadia API key to a style URL if it's a Stadia Maps URL.

import { appendStadiaApiKey } from "@page-speed/maps/utils/style-url";

const url = appendStadiaApiKey(
  "https://tiles.stadiamaps.com/styles/osm_bright.json",
  "your-api-key"
);
// "https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=your-api-key"

generateGoogleMapLink(latitude, longitude, zoom?)

Generates a Google Maps URL for a location.

import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";

const url = generateGoogleMapLink(33.4484, -112.074, 15);
// "https://www.google.com/maps/@33.4484,-112.074,15z"

generateGoogleDirectionsLink(latitude, longitude)

Generates a Google Maps directions URL to a destination.

import { generateGoogleDirectionsLink } from "@page-speed/maps/utils/google-links";

const url = generateGoogleDirectionsLink(33.4484, -112.074);
// "https://www.google.com/maps/dir/?api=1&destination=33.4484,-112.074"

Types

All types are exported from @page-speed/maps/types or the main entry point:

import type {
  BasicMarkerInput,
  MapControlPosition,
  MapCoordinate,
  MapLibreFlyToOptions,
  MapLibreMarker,
  MapLibreProps,
  MapViewState,
  GeoCoordinate,
  GeoCenterResult,
  DefaultZoomOptions,
  MapLibreBuiltInStyle,
} from "@page-speed/maps";

Key Types

type MapViewState = {
  latitude: number;
  longitude: number;
  zoom: number;
};

type MapCoordinate = {
  latitude: number;
  longitude: number;
};

type GeoCoordinate = {
  lat: number;
  lng: number;
};

type BasicMarkerInput = {
  id?: string | number;
  latitude: number;
  longitude: number;
  color?: string;
  draggable?: boolean;
  label?: string;
  element?: (() => React.ReactNode) | React.ReactNode;
  onClick?: () => void;
};

type MapControlPosition =
  | "top-left"
  | "top-right"
  | "bottom-left"
  | "bottom-right";

Tree Shaking

This package supports granular tree-shaking. Import only what you need:

// Full bundle (all exports)
import { MapLibre, useGeoCenter, useDefaultZoom } from "@page-speed/maps";

// Just the component
import { MapLibre } from "@page-speed/maps/core";

// Just hooks (tree-shakable)
import { useGeoCenter, useDefaultZoom } from "@page-speed/maps/hooks";

// Individual hooks (maximum tree-shaking)
import { useGeoCenter } from "@page-speed/maps/hooks/useGeoCenter";
import { useDefaultZoom } from "@page-speed/maps/hooks/useDefaultZoom";

// Just utilities
import { getMapLibreStyleUrl } from "@page-speed/maps/utils/style-url";
import { generateGoogleMapLink } from "@page-speed/maps/utils/google-links";

// Just types
import type { MapLibreProps } from "@page-speed/maps/types";

Map Styles

Built-in style presets (requires Stadia API key):

Style NameDescription
osm-brightClean, bright OpenStreetMap style (default)
alidade-smoothModern, smooth cartography
alidade-smooth-darkDark theme variant
stadia-outdoorsOutdoor/terrain focused
stamen-tonerHigh-contrast black & white
stamen-terrainTerrain with hillshading
stamen-watercolorArtistic watercolor style
maplibre-defaultCarto Positron (no API key required)
<MapLibre mapStyle="stamen-terrain" stadiaApiKey="..." />

Or use a custom style URL:

<MapLibre styleUrl="https://your-tiles.com/style.json" stadiaApiKey="..." />

Advanced Usage

Custom Markers

Pass a custom React element for full control over marker rendering:

<MapLibre
  stadiaApiKey="..."
  markers={[
    {
      id: "custom",
      latitude: 33.4484,
      longitude: -112.074,
      element: (
        <div className="custom-marker">
          <img src="/pin.svg" alt="Location" />
          <span>Phoenix HQ</span>
        </div>
      ),
    },
  ]}
/>

Draggable Markers

<MapLibre
  stadiaApiKey="..."
  markers={[
    {
      id: "draggable",
      latitude: 33.4484,
      longitude: -112.074,
      draggable: true,
      label: "Drag me!",
    },
  ]}
  onMarkerDrag={(markerId, coord) => {
    console.log(`Marker ${markerId} moved to:`, coord);
  }}
/>

Controlled View State

function ControlledMap() {
  const [viewState, setViewState] = useState({
    latitude: 33.4484,
    longitude: -112.074,
    zoom: 12,
  });

  return (
    <>
      <MapLibre
        stadiaApiKey="..."
        viewState={viewState}
        onViewStateChange={setViewState}
      />
      <button onClick={() => setViewState(prev => ({ ...prev, zoom: prev.zoom + 1 }))}>
        Zoom In
      </button>
    </>
  );
}

Fly To Animation

<MapLibre
  stadiaApiKey="..."
  viewState={viewState}
  flyToOptions={{
    speed: 1.2,
    curve: 1.5,
    easing: (t) => t,
  }}
/>

Composing with UI Libraries

This package provides the core map primitives. For feature-rich components with clustering, info windows, and styled markers, see @opensite/ui which builds on top of @page-speed/maps:

// In @opensite/ui (consumer library)
import { useGeoCenter, useDefaultZoom, type GeoCoordinate } from "@page-speed/maps/hooks";

function GeoMap({ markers, clusters, defaultViewState }) {
  // Collect all coordinates
  const allCoordinates: GeoCoordinate[] = [
    ...markers.map(m => ({ lat: m.latitude, lng: m.longitude })),
    ...clusters.map(c => ({ lat: c.latitude, lng: c.longitude })),
  ];

  // Auto-compute center and zoom
  const geoCenter = useGeoCenter(allCoordinates);
  const defaultZoom = useDefaultZoom({
    coordinates: allCoordinates,
    mapWidth: 600,
    mapHeight: 520,
    padding: 60,
  });

  return (
    <MapLibre
      viewState={{
        latitude: defaultViewState?.latitude ?? geoCenter?.lat ?? 0,
        longitude: defaultViewState?.longitude ?? geoCenter?.lng ?? 0,
        zoom: defaultViewState?.zoom ?? defaultZoom ?? 10,
      }}
      // ... clustering, custom markers, info windows, etc.
    />
  );
}

API Reference

Exports Summary

ExportPathDescription
MapLibre@page-speed/mapsMain map component
DTMapLibreMap@page-speed/mapsAlias for MapLibre
useGeoCenter@page-speed/maps/hooks/useGeoCenterGeographic center hook
computeGeoCenter@page-speed/maps/hooks/useGeoCenterPure function version
useDefaultZoom@page-speed/maps/hooks/useDefaultZoomAuto-zoom hook
computeDefaultZoom@page-speed/maps/hooks/useDefaultZoomPure function version
getMapLibreStyleUrl@page-speed/maps/utils/style-urlStyle URL resolver
appendStadiaApiKey@page-speed/maps/utils/style-urlAPI key appender
generateGoogleMapLink@page-speed/maps/utils/google-linksGoogle Maps link
generateGoogleDirectionsLink@page-speed/maps/utils/google-linksGoogle Directions link

License

BSD-3-Clause. See LICENSE for details.

New: Feature-Rich Components

GeoMap

Full-featured map component with markers, clusters, rich media panels, and automatic view calculation.

import { GeoMap, createMapMarkerElement } from "@page-speed/maps";
import type { GeoMapMarker } from "@page-speed/maps";

const markers: GeoMapMarker[] = [
  {
    id: 'office',
    latitude: 40.7128,
    longitude: -74.0060,
    title: 'New York Office',
    summary: 'Our headquarters in downtown Manhattan',
    locationLine: '123 Broadway, New York, NY 10001',
    hoursLine: 'Mon-Fri: 9:00 AM - 6:00 PM',
    mediaItems: [
      { id: '1', src: '/office.jpg', alt: 'Office' },
    ],
    markerElement: createMapMarkerElement({ size: 'lg' }),
    actions: [
      {
        label: 'Get Directions',
        href: 'https://maps.app.goo.gl/example',
      },
    ],
  },
];

<GeoMap
  markers={markers}
  stadiaApiKey="your-key"
  panelPosition="bottom-left"
  showNavigationControl
/>

Key Features:

  • ✅ Auto-calculated center and zoom from markers
  • ✅ Rich media carousels (images/videos)
  • ✅ Interactive marker panels
  • ✅ Clustering support
  • ✅ Custom marker elements
  • ✅ Action buttons and links

→ Full GeoMap Documentation

MapMarker

Beautiful concentric circle markers with hover and selection states.

import { MapMarker, NeutralMapMarker, createMapMarkerElement } from "@page-speed/maps";

// Direct usage
<MapMarker
  size="lg"
  isSelected
  dotColor="#1E40AF"
  innerRingColor="#3B82F6"
  middleRingColor="#93C5FD"
  outerRingColor="#DBEAFE"
/>

// With GeoMap
const markers = [{
  id: 'loc-1',
  latitude: 40.7128,
  longitude: -74.0060,
  markerElement: createMapMarkerElement({ size: 'lg' }),
}];

Sizes: sm | md | lg
Pre-configured: NeutralMapMarker for neutral gray design

→ MapMarker Examples

Migration from @opensite/ui

If you're migrating map components from @opensite/ui to @page-speed/maps, see our comprehensive migration guide:

→ Migration Guide

Key Changes:

  • ✅ Fixed zoom/centering bugs
  • ✅ New MapMarker components
  • ✅ Better tree-shakability
  • ✅ Optional peer dependencies for icons/images

Documentation

Made with ❤️ for the DashTrack Platform

Keywords

react

FAQs

Package last updated on 10 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts