npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fyremaps/web-sdk

v0.4.3

Published

React / Next.js SDK for FyreMaps — MapLibre GL JS map rendering, geocoding search, and turn-by-turn navigation.

Readme

@fyremaps/web-sdk

npm version license: MIT

@fyremaps/web-sdk is a toolkit for adding maps to a React or Next.js app. It gives you four building blocks, and you can use as many or as few as your app needs:

  1. A map you can show on a page — pan, zoom, add pins, draw routes.
  2. Search — turn text like "1600 Amphitheatre Pkwy" into map coordinates (and the other way around).
  3. Directions — get a route between two places, or through several stops in a row.
  4. Turn-by-turn navigation — guide someone along a route in real time, using their device's GPS, with automatic rerouting if they go off track.

Everything is built on top of MapLibre GL JS (a free, open-source map renderer) and is fully typed in TypeScript, so your editor can tell you what every function expects and returns.

You don't need to understand MapLibre to use this SDK — the <FyreMap> component and the hooks below handle it for you.

Full docs (including the underlying HTTP API, not just this SDK) live at fyremaps.com/docs.

Table of contents

Install

npm install @fyremaps/web-sdk maplibre-gl react react-dom

maplibre-gl, react, and react-dom are peer dependencies — install whichever versions your app already uses. pmtiles (used to load map tiles) is installed automatically as part of this package.

One more step: MapLibre needs its own stylesheet. Import it once, somewhere global (your root layout or global CSS file) — this package doesn't do it for you:

import "maplibre-gl/dist/maplibre-gl.css";

Get an API key

Every request to FyreMaps needs an API key. Here's how to get one:

  1. Go to fyremaps.com and register an account.
  2. Log in to your dashboard.
  3. Select a plan.
  4. Copy your API key from the dashboard.

There are two ways to give the SDK your key, and it matters which one you pick:

| Option | Good for | Why | |---|---|---| | pkKey prop | Local development only | Anyone can open dev tools and read a key that's shipped to the browser. | | resolveAuth | Production | Your real key stays on your server. The browser gets a short-lived, disposable credential instead. |

Quick and dirty (dev only):

<FyreMapsProvider pkKey="pk_test_...">

The real setup (recommended for anything users will actually see):

Step 1 — add a small server route that holds your real secret key and hands out short-lived credentials:

// app/api/fyremaps/auth/route.ts — Next.js App Router.
// No server of your own (e.g. plain Vite/CRA)? Deploy this same handler as
// a standalone Vercel/Cloudflare/Netlify function instead, and pass
// `cors: { origin }` since it'll then live on a different origin.
import { createNextRouteHandler } from "@fyremaps/web-sdk/server";

export const { GET } = createNextRouteHandler({
  mint: async () => ({
    headers: { "x-api-key": process.env.FYREMAPS_SECRET_KEY! },
    expiresInMs: 5 * 60 * 1000, // how long the credential you hand out stays valid
  }),
});

Step 2 — point the SDK at that route:

import { FyreMapsProvider, createFetchAuthResolver } from "@fyremaps/web-sdk";

const resolveAuth = createFetchAuthResolver({ url: "/api/fyremaps/auth" });

<FyreMapsProvider resolveAuth={resolveAuth}>

That's it — createFetchAuthResolver automatically caches the credential and quietly refreshes it before it expires, so this doesn't mean a network call on every single request. If you want full control you can write your own resolveAuth function instead, but then caching is on you (wrap it in createCachingAuthResolver if you want it).

You don't need a proxy to make this work — api.fyremaps.com and tiles.fyremaps.com are already set up to accept requests straight from the browser.

Quick start (Next.js App Router) - Initializations

Step 1 — wrap your app in the provider. This is what shares one config/connection with every map and hook in your app.

// app/providers.tsx
"use client";
import { FyreMapsProvider, createFetchAuthResolver } from "@fyremaps/web-sdk";

const resolveAuth = createFetchAuthResolver({ url: "/api/fyremaps/auth" });

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <FyreMapsProvider resolveAuth={resolveAuth}>
      {children}
    </FyreMapsProvider>
  );
}

(Pair this with the server route from Get an API key above.)

Step 2 — render a map.

// app/map/page.tsx
"use client";
import "maplibre-gl/dist/maplibre-gl.css";
import { FyreMap, PMStyle } from "@fyremaps/web-sdk";

export default function MapPage() {
  return (
    <div style={{ width: "100vw", height: "100vh" }}>
      <FyreMap
        style={PMStyle.DEFAULT}
        center={[-97, 38]}
        zoom={4}
        onReady={(map) => {
          map.addMarker("home", { lat: 38.9, lon: -77.0 });
        }}
      />
    </div>
  );
}

That's a working map with one pin on it. Everything past this point is about the pieces you can add on top: search boxes, directions, live navigation, and more control over what's drawn on the map.

A couple of rules worth knowing up front:

  • Every hook in this package (useSearch, useRouting, useNavigation, …) only works inside <FyreMapsProvider>, and only in a Client Component ("use client") — the map and GPS APIs need a real browser.

  • <FyreMap> is the one exception: if all you need is a bare map with no search/directions/navigation, you can skip the provider entirely and pass pkKey/resolveAuth straight to <FyreMap> itself:

    <FyreMap pkKey="pk_test_..." style={PMStyle.DEFAULT} center={[-97, 38]} zoom={4} />

    The moment you need one of the hooks, switch to wrapping things in <FyreMapsProvider> instead — that's what lets <FyreMap> and the hooks share one connection. Don't configure auth in both places at once — if <FyreMap> is inside a provider, the provider always wins, and any auth props passed directly to <FyreMap> are silently ignored (with a console warning in dev, so you notice).

Showing a map

<FyreMap> is a normal React component. style picks one of five built-in looks:

import { FyreMap, PMStyle } from "@fyremaps/web-sdk";

<FyreMap
  style={PMStyle.DEFAULT}   // also: DARK, SATELLITE, REAL_SATELLITE, TERRAIN
  center={[-97, 38]}         // [longitude, latitude]
  zoom={4}
  pitch={0}
  bearing={0}
  onReady={(map) => {
    // `map` is your remote control for everything else — see below.
  }}
/>

A couple of notes on the styles:

  • REAL_SATELLITE uses a third-party imagery provider (Esri) — check their terms of service and attribution requirements before shipping it.
  • TERRAIN isn't built yet. Picking it quietly falls back to DEFAULT and logs a console warning, so your app doesn't break — it just won't look like terrain until this ships.

Controlling the map (camera, markers, pins)

Once the map has loaded, onReady gives you a controller — think of it as the remote control for everything on the map that isn't set by a prop. Every example below assumes you're inside that onReady callback (or have stashed the controller somewhere you can use it later).

<FyreMap
  onReady={(map) => {
    // Move the camera
    map.moveCamera(38.9, -77.0, 14); // fly to lat, lng, zoom
    map.fitPoints([{ lat: 38.9, lon: -77.0 }, { lat: 40.7, lon: -73.9 }]); // fit both points on screen
    map.toggle3DMode(); // tilt the camera for a 3D-ish view, and back

    // Add a pin, and react to taps on it
    map.addMarker("home", { lat: 38.9, lon: -77.0 }, { color: "#1a73e8" });
    map.setOnMarkerClickListener((id) => console.log("tapped:", id));

    // Let users long-press (or right-click) the map to drop a pin there
    map.setOnMapLongClickListener((location) => {
      void map.showDroppedPin(location); // shows the pin + looks up its address
    });
    // Then read it back with:
    //   map.getDroppedPinLocation()
    //   map.getDroppedPinShareText()        — e.g. "123 Main St (38.9, -77.0)"
    //   map.getDroppedPinDirectionsUri()     — a ready-to-open directions link

    // Draw a route on the map (see "Getting directions" for where `route` comes from)
    map.displayRoute(route.shape);
    map.displayAlternateRoute("alt-1", altRoute.shape); // drawn in a muted color
    map.removeRoute();

    // Switch styles later, without re-creating the map
    map.applyStyle(PMStyle.DARK);
  }}
/>

If something goes wrong loading a style (a bad tile URL, for example), it won't throw — instead map.styleError is a small live value you can watch if you want to show an error in your UI.

Searching for a place

The simplest way to add a search box is the two hooks below: one that searches as the user types, one you trigger yourself (e.g. on Enter).

"use client";
import { useAutocomplete, useSearch } from "@fyremaps/web-sdk";

function SearchBox() {
  const [query, setQuery] = useState("");

  // Fires automatically 300ms after typing stops, once there are 3+ characters
  const { results, loading } = useAutocomplete(query);

  // Fires only when you call `search(...)` yourself
  const { search, results: searchResults } = useSearch();

  return (
    <input value={query} onChange={(e) => setQuery(e.target.value)} />
    // render `results?.features` as a dropdown; call `search(query)` on submit
  );
}

useSearch actually covers five different kinds of lookup, all with the same loading/error handling:

| Method | What it does | |---|---| | search(text) | Free-text search, like a search engine | | autocomplete(text) | Same, but tuned for "as you type" suggestions | | reverse(location) | Coordinates → nearest address | | structuredSearch({ ... }) | Search by individual fields (street, city, postal code, country, ...) instead of one text string | | place(ids) | Look up specific places you already have the ID for |

Every one of these accepts a second, optional argument to narrow the results — e.g. "only within 5km of here," "only in this country," "only venues, not addresses." The full list: focusLat/focusLon (bias results toward a point), boundaryRect/boundaryCircle/boundaryCountry (only search inside this area), layers (limit to venues, streets, postal codes, ...), sources, size (how many results, 1–40), lang.

Need to search from outside a React hook — say, inside a button's onClick handler? Reach for GeocodingClient directly:

const { geocoding } = useFyreMaps();
const results = await geocoding.searchNear("coffee", 38.9, -77.0, 2 /* km */);
const location = geocoding.getFirstCoordinates(results); // { lat, lon } | null

Remembering what someone searched for last: useSearchHistory saves picks to the browser's local storage (IndexedDB) and gives you a live-updating list:

const { entries, addToHistory, clear } = useSearchHistory();
await addToHistory("1600 Amphitheatre Pkwy", 37.422, -122.084);

Getting directions

useRouting turns a list of places into a drivable (or walkable, or cyclable) route.

A → B

const { getRoute } = useRouting();
const route = await getRoute([origin, destination]);
// route.shape          — the line to draw on the map
// route.steps          — turn-by-turn instructions
// route.totalDistanceMeters / route.totalDuration (seconds)

A → B → C → D (multiple stops, in order)

This is the same function — just hand it more than two places. The route visits them in the order you listed, so put your stops in the order you want them visited:

const stops = [origin, warehouseA, warehouseB, destination];
const route = await getRoute(stops, RoutingProfile.AUTO, { avoidTolls: true });

"I have 4 stops, but I don't care what order" (route optimization)

If you don't know the best order to visit your stops in, let the routing engine figure it out for you — it picks the order that minimizes total travel:

const { getOptimizedRoute } = useRouting();
const result = await getOptimizedRoute([depot, stopA, stopB, stopC]);
// result.route — a ready-to-use route, same shape as getRoute() returns
// result.order — the visiting order it picked (treat as provisional —
//                 not yet verified against a live backend)

Other things useRouting and RoutingClient can do

  • Different travel profiles: pass RoutingProfile.AUTO (default), TRUCK, BICYCLE, PEDESTRIAN, or MOTORCYCLE as the second argument.

  • Avoid tolls/highways/ferries: pass { avoidTolls: true } etc. as route options.

  • Alternate routes: getRoutes(locations, profile, alternates) returns up to N routes instead of one — draw the non-primary ones with map.displayAlternateRoute(...).

  • Truck-specific routing (height/width/length/weight/hazmat limits), isochrones (draw "everywhere reachable in 10 minutes" as a shape on the map), and distance/time matrices (travel time between many points at once) aren't wrapped as hooks yet — call them straight off RoutingClient:

    const { routing } = useFyreMaps();
    
    await routing.getTruckRoute(stops, { heightMeters: 4.1, weightKg: 26000 });
    await routing.getTimeIsochrone(origin, RoutingProfile.PEDESTRIAN, [5, 10, 15]);
    await routing.getMatrix(sources, targets);

All distances on a route (totalDistanceMeters, each step's distance) are in meters, and durations are in seconds.

Turn-by-turn navigation

This is the "blue dot following you down the highway" feature: live guidance along a route, using the device's real GPS position.

It's built from three pieces working together:

  1. useRouting — gets you the route.
  2. useNavigation — tracks progress along that route and tells you what to say next ("turn left in 200m").
  3. useGeolocation — reads the device's actual GPS position and feeds it into navigation.
"use client";
import { useEffect } from "react";
import { useGeolocation, useNavigation, useRouting } from "@fyremaps/web-sdk";

function NavigationDemo(origin: Location, destination: Location) {
  const { getRoute } = useRouting();
  const { startNavigation, guidanceState } = useNavigation();

  // Watches the device's position and automatically feeds it into navigation
  useGeolocation({ feedNavigation: true });

  useEffect(() => {
    getRoute([origin, destination]).then((route) => route && startNavigation(route));
  }, []);

  return (
    <div>
      {guidanceState.currentRoadName} — {guidanceState.remainingDistanceM}m remaining
    </div>
  );
}

That's it for the basic case. A few things happen for you automatically:

  • Off-route detection. If the person strays more than 50 meters from the route, navigation notices.
  • Automatic rerouting. When that happens, it silently fetches a fresh route from the current position — no user action needed.
  • Live progress. guidanceState updates continuously with everything you'd want to show in a UI (see the table below).

Navigating through multiple stops

Pass your in-between stops as waypoints — the third argument to startNavigation (leave out the origin and final destination; the destination is already implied by the route):

const stops = [origin, stopA, stopB, destination];
const route = await getRoute(stops);
if (route) startNavigation(route, RoutingProfile.AUTO, [stopA, stopB]);

Navigation keeps track of which stops have already been reached (within about 50 meters). If it has to reroute mid-trip, it's smart about it — the new route only goes through whichever stops haven't been visited yet, so a reroute never sends someone back to a stop they already passed.

Everything in guidanceState

| Field | What it tells you | |---|---| | navigationPhase | Where things stand: STARTINGACTIVEARRIVING/ARRIVED, or OFF_ROUTE/REROUTING if things went sideways | | currentStepIndex / totalSteps | Which turn-by-turn instruction is current | | distanceToNextManeuverM | Meters until the next turn | | remainingDistanceM / remainingDurationS | Meters/seconds left in the whole trip | | etaEpochMillis | Estimated arrival time | | currentRoadName / nextRoadName | Street names, for display | | snappedLocation / snappedBearing | The device's position "snapped" onto the route line, and which way it's facing | | progressFraction | 0 to 1, how far through the trip | | speedMps | Current speed, if the device reports it |

You can adjust how sensitive things are with setOffRouteThresholdM (default 50m) and setArrivalThresholdM (default 20m), either before starting or mid-trip.

Good to know

A few things that are worth knowing about before you rely on them:

  • hasTolls / hasHighways / hasFerries on a route come straight from your routing backend — double-check they're actually populated for your deployment before building UI around them.
  • Remaining ETA/duration during navigation is a straight-line, distance-proportional estimate — not a model that accounts for traffic or per-step timing.
  • No offline maps, no background navigation, no picture-in-picture. Browser tabs get throttled once they're not visible, so useGeolocation only reliably works while the tab is in the foreground.
  • REAL_SATELLITE style uses Esri World Imagery — check their terms of service and attribution requirements for your use case.
  • TERRAIN style isn't implemented yet — it falls back to DEFAULT with a console warning rather than breaking.

Everything mentioned in this doc is exported from the package root — import { ... } from "@fyremaps/web-sdk" — with one exception: @fyremaps/web-sdk/server (createFetchHandler / createNextRouteHandler) is a separate entry point on purpose, so your real secret key and other server-only code never ends up inside a browser bundle.

Docs

Full documentation — including the underlying HTTP API this SDK talks to — lives at fyremaps.com/docs.

Changelog

See CHANGELOG.md.

License

MIT