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.
yarn add @stillmap/react @stillmap/sources react react-domPNG output goes through resvg, which is an optional peer. Skip it if SVG is all you need.
yarn add @resvg/resvg-jsimport {
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.
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.
- 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.
| 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. |
Pre-release. The API is not stable.
Apache-2.0
