Skip to content
abinnovisionPublic

About

💫 Server-side static map rendering for Typescript, with a React-shaped API

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

stillmap: server-side map rendering for TypeScript, with a React-shaped API.

Describe a map as JSX, get back an SVG string or a PNG buffer. No browser, no canvas, no native map library, and no API key.

If people pan and zoom it, use MapLibre GL JS. If the map is a picture that happens to be a map, use stillmap: store locators, order confirmations, report headers, social cards, invoices, printed pages.

Open it in CodeSandbox to run the Next.js store finder against live tiles, without a checkout.

Getting started

yarn add @stillmap/react @stillmap/sources react react-dom

PNG output goes through resvg, which is an optional peer. Skip it if SVG is all you need.

yarn add @resvg/resvg-js
import {
  Attribution,
  Font,
  Map,
  Pin,
  Road,
  Water,
  renderMap,
} from "@stillmap/react";
import { openFreeMap } from "@stillmap/sources";
import { writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";

const HAMBURG = [9.9937, 53.5511] as const;
const INTER = fileURLToPath(new URL("./Inter.ttf", import.meta.url));

const { png } = await renderMap(
  <Map
    source={openFreeMap()}
    center={HAMBURG}
    zoom={13}
    width={1200}
    height={300}
    background="#F5F5F3"
  >
    <Font family="Inter" file={INTER} />
    <Water fill="#E1E4E7" />
    <Road classes={["secondary", "tertiary"]} stroke="#FFFFFF" width={2} />
    <Road classes={["motorway", "trunk"]} stroke="#FCFBF9" width={3.2} />
    <Pin position={HAMBURG} fill="#9DB59D" />
    <Attribution />
  </Map>,
  { format: "png", scale: 2 },
);

await writeFile("map.png", png);

A font is not optional for PNG. Every render carries attribution, and the rasteriser loads no system fonts, so renderMap refuses rather than hand back an image with the attribution missing. Fonts are passed as file paths; see fonts.

Nothing is styled by default. A layer you do not declare is not drawn, and a layer you declare without a colour paints black, so the palette above is doing real work rather than decorating. @stillmap/styles ships three styles you can drop in and recolour later, if you would rather not pick every colour yourself; see styles.

Why this exists

Putting a static map on a page usually means pointing an <img> at a provider's CDN. That is one line of HTML, and it quietly buys four things you may not want.

A third party in front of your page. Every visitor makes a request to that provider, carrying their IP address, before they have agreed to anything. In some countries that turns a map in a footer into a consent question, and a piece of decoration into a compliance artifact with an owner and a review cycle.

A bill that tracks your traffic. Static image APIs charge per request, so a store finder with 200 locations is 200 pictures you pay for once per visitor, forever. How many maps you have is a number you control. How many people look at them is not.

An API key. A signup, a secret per environment, a rate limit to design around, and terms someone else can change under a page you already shipped.

A rasteriser. You get a PNG. A map inside a PDF at print resolution, a map in a transactional email, a map that recolours with the page's dark mode instead of being a second render: none of those start from a PNG.

The data stopped being the obstacle a long time ago. OpenStreetMap is free to use commercially, and several providers serve vector tiles built from it. What was left was the rendering, and doing that yourself meant driving a headless browser or binding a native map library. Both are a lot of machinery for a map in a footer, and neither one installs on Vercel, Cloudflare Workers, or a plain Lambda.

stillmap renders in plain Node, wherever your code already runs. Call it per request behind your own domain, or call it once in a build step and keep the image for reuse. Either way the browser only ever talks to you. examples/nextjs is the per-request shape, with the caching that makes it affordable.

Where it does not belong

  • Anything interactive. No panning, no zooming, no click targets. Reach for MapLibre GL JS, which is excellent.
  • Terrain, 3D, hillshade, sprite-heavy cartography. Out of scope.
  • Maps as the product. If the map is what people came for rather than a component of the page, you want the full engine.

Packages

Package Description
@stillmap/core Rendering engine. No React, no native dependency.
@stillmap/sources Tile sources and schema adapters.
@stillmap/react JSX declaration API and renderMap().
@stillmap/serve Serve rendered maps over HTTP, with caching.
@stillmap/styles Ready-made styles and their palettes.
stillmap CLI. stillmap dev previews templates in a browser.

Documentation

Status

Pre-release. The API is not stable.

License

Apache-2.0

About

💫 Server-side static map rendering for Typescript, with a React-shaped API

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages