Most sites that need a map end up with one of three options. The first is a commercial map API that bills per view. The second is the OpenStreetMap tile server, but its Tile Usage Policy rules out commercial and heavy-traffic use. The third is a tile server you run yourself, which you then have to keep running.

There is a fourth option, because almost nobody needs the whole planet. You need the area your site covers, and you can host that part yourself.

MMJ is built on that idea. It is an open-source set of Web Components plus a tool that cuts out map tiles. We borrow the map data and own how it looks.

A typhoon approaching Japan, drawn over a rain-radar animation

The whole live demo is static files on GitHub Pages. Nothing in it needs a backend, including the animation above.

What it does

  1. Cut your area into one file. One command takes a bounding box from the Protomaps daily build of the whole planet and writes it to a single PMTiles file. A city comes to 10–60 MB and takes under a minute.
  2. Serve it as a file. All you deploy is that file on static hosting. The browser asks for the parts it needs with HTTP Range requests, so traffic costs only what your host charges to serve a file.
  3. Write HTML. Eight custom elements render the map with MapLibre: mmj-map, mmj-marker, mmj-poi, mmj-cluster, mmj-route, mmj-circle, mmj-raster and mmj-fill. They are plain ES modules, so there is no build step.
npx @mmj-map/tiles extract osaka --bbox=135.4,34.6,135.6,34.8 
<mmj-map tiles="./tiles/osaka.20260924.pmtiles" style-url="./styles/modern-dark.json" center="135.5023,34.6937" zoom="12"> <mmj-marker lnglat="135.4959,34.7024" popup="Umeda"></mmj-marker> </mmj-map> 

center takes longitude first, then latitude, in the same order as GeoJSON.

Why we own the look but not the data

OpenStreetMap data is free. Its default style, though, has to serve every use on earth with one design, so it fits no particular site well.

The line we draw is this: fixes to the data go to OSM; how the map looks belongs to each site. Your brand colour, labels in your readers' language, your own points, a photo at the corner where a route turns: none of that is map data, and none of it belongs upstream. MMJ comes with six hand-written styles. Give it your brand colour and it puts that colour on the motorways and station rings, not across the whole map.

Your own points stay yours, too. The components read the GeoJSON from your own hosting, and MMJ has no server of its own to send it to.

What you skip

Starting from zero, several problems stand between you and a working map. You usually find each one only after it has bitten you:

Problem What happens
A host answers Range requests with 200 The map still draws, but every tile downloads the whole file. npx @mmj-map/tiles check-range <url> tests your host for this
Old daily builds are removed upstream The build we pinned was gone nine days later. The extractor pins a build and falls back if it disappears
name:zh does not exist The real keys are zh-Hant / zh-Hans. MMJ switches label language with one lang attribute
Outside your file's area, the map is empty That is correct, but it looks exactly like a broken map. MMJ shades the tiles that came back empty

Because the map is just a file, it can keep working with the network off. One demo page needs 1.9 MB of tiles, 562 KB of glyphs and 1.15 MB of renderer. That is enough for a venue with no signal. GPS still works without a network; only the map was missing.

What it is not

MMJ is not a tile server, a rendering engine, a geocoder or a router. It is also not a way around a provider's terms. If your coordinates come from a provider that requires you to show them on its own map, check those terms before you swap the base map.

Try it

  1. Cut your town: npx @mmj-map/tiles extract <name> --bbox=<w,s,e,n>
  2. Follow the install guide. It is also in Japanese, Traditional Chinese, Simplified Chinese and Vietnamese
  3. Keep © OpenStreetMap contributors visible on the map. The map data is under the ODbL; the code, styles and components are MIT

It is early but usable. @mmj-map/elements and @mmj-map/tiles are on npm. There are no hosted tiles: you cut your own, which is the point. The styles are most complete for Japan so far. Bug reports and requests are welcome on Issues, in English or Japanese.