Routing that runs in the browser.

Turn-by-turn directions, isochrones and time-distance matrices computed on the device. No routing server to run, no per-request bill, and — in offline mode — no coordinates leaving the browser at all.

Try the live demo npm install routewasm

MIT licensed and free to self-host — build tiles from OpenStreetMap yourself and owe us nothing. Development access opens the real hosted datasets for three sessions a day; a subscription buys production capacity and tiles you don't have to build, host or keep current. Built on Valhalla.

Private by construction

Where your users are going is computed where they are. Once an area is cached there is no request to intercept, log or subpoena.

Actually offline

Routing and map rendering keep working with the network off, across reloads. Tiles persist to the browser's origin-private filesystem.

Independent

Hosted tiles are served from EU infrastructure. Or point the SDK at your own storage and have no dependency on us at all — the engine is MIT.

Three lines to a route

import { RouteWasmClient } from 'routewasm';

const client = new RouteWasmClient();
await client.init();                  // free Frankfurt tiles, no account

const json = await client.call('route', JSON.stringify({
  locations: [{ lat: 50.1069, lon: 8.6638 }, { lat: 50.1214, lon: 8.6567 }],
  costing: 'auto',
}));

Requests and responses are Valhalla's JSON API verbatim — route, isochrone, sources_to_targets, trace_route, optimized_route and the rest, across eight costing profiles. A repeat route in a cached area costs zero bytes and about 33 ms.

With a subscription

Your API key stays on your server. It mints a token that expires in an hour, and the browser only ever holds that — the SDK refreshes it on its own.

// your backend, once per session
app.post('/tiles/auth', async (req, res) => res.json(
  await fetch('https://app.routewasm.dev/v1/tile-auth', {
    method: 'POST',
    headers: { authorization: `Bearer ${process.env.ROUTEWASM_KEY}` },
  }).then(r => r.json())
));

// the browser
const mint = () => fetch('/tiles/auth', { method: 'POST' }).then(r => r.json());
await client.init({ tileAuth: await mint(), refreshTileAuth: mint });

Development keys use this exact path too. The only difference is the three-mint daily limit, so an evaluation exercises the same data, authentication and caching as production.

The things you're about to ask

How big is the SDK?
The engine is a 3.2 MB WebAssembly binary, under 1 MB over the wire gzipped. It runs in a Web Worker, so it never blocks your UI.
Which browsers?
Chrome, Edge and Firefox 111+ fully. Safari 17+ works, but its storage quota is smaller and iOS can evict cached tiles after a week of not opening your site — they re-download on next use. Routing itself works everywhere WebAssembly does.
What happens on a phone?
The same thing, minus the patience. A first route in a new city is tens of megabytes, which is a real cost on cellular — trigger the download deliberately (on Wi-Fi, or behind a "make this area available offline" button) rather than on page load.
How long until the first route?
A few seconds cold: the engine loads while tiles download in parallel. After that, routes in a cached area return in about 33 ms with no network at all.
Do you see my users' locations?
We see which tiles get fetched, which is roughly "somebody looked at this part of the map". We never see origins, destinations or computed routes — those exist only in the browser. Once an area is cached there are no requests at all.
What if I stop paying?
Tiles already on your users' devices keep working. New downloads stop. The SDK is MIT, so you can point it at your own tiles and owe us nothing.
How is a "new user" counted?
One per browser that downloads tiles for the first time in a month. Someone who returns and routes from cache costs nothing and is not counted. Your dashboard shows the running total.
What does the free limit mean?
Your backend can mint three one-hour browser credentials per UTC day. That is enough for one developer to test separate sessions against the full dataset, but not enough to run a public app. Self-hosted tiles have no limit.
Can somebody copy the browser token?
If you register a browser origin, copying the token into another website fails. You can leave it empty for local or non-browser testing, but then anybody who obtains the key can consume its tile bandwidth. Do not leave an unrestricted key in a public app. A determined scraper can also impersonate an allowed origin, so expiry, mint limits and monitoring still matter.
Isn't this just Valhalla compiled to WebAssembly?
The routing engine is Valhalla, unchanged and unforked where it matters — that's the point, it's a decade-old engine that national mapping agencies run in production. The work is everything around it: making a 500 MB-at-a-time tile format stream into a browser on demand, caching it so the second route is free, the build pipeline that turns OpenStreetMap into those tiles every quarter, and the SDK so you don't touch any of it.
You're selling OpenStreetMap data?
No — OSM data is free and you can build these tiles yourself; the pipeline is open. You're paying for the build, the hosting and keeping it current. Attribution stays intact, and the tiles are an ODbL Produced Work, so your app needs the © OpenStreetMap contributors credit and nothing more.

Pricing

The SDK is free forever. You pay for map data you don't have to build, host or keep current.

Development

€0

  • The full SDK, MIT licensed
  • Choose the real Europe or worldwide dataset
  • 3 credential mints per UTC day
  • Optional browser-origin binding
  • Same integration and cache behaviour as paid
  • Or build your own tiles and host them anywhere

Leaving this empty is useful for testing, but anybody with the key can use its tile bandwidth. Add your exact origin before putting the key in a public app.

Development keys open the Europe dataset — current, with elevation. Worldwide evaluation is available on request.

Europe

€29/month

  • Europe, built
  • Elevation included (grades on every edge)
  • Usage is not metered during preview
  • EU-hosted, API key included

Growth

€299/month

  • Europe and worldwide, both datasets
  • Usage is not metered during preview
  • Priority on refresh requests
  • EU-hosted, API key included

A "new user" is someone opening your app for the first time. Once the map data is on their device it stays there, so every later route, matrix and isochrone is free and never reaches us. For comparison, metered APIs charge roughly $2–$5 per thousand requests (Mapbox $2, Google $5, HERE $5 for matrix and isochrone calls).

Exactly what you get

DatasetSource dataBuiltElevationSize
Europe OpenStreetMap, Geofabrik Europe extract Yes 14 GB, 25,073 tiles
Worldwide OpenStreetMap planet No Planet coverage

Every dataset is served from a dated path, so a refresh never changes what your users are already routing on — you move when you choose to. Cadence is quarterly; say so below if you need it faster.

Being straight about the worldwide set: it was built in February and roads change. Europe is current and has elevation. If your coverage is European, take that one — the worldwide set is for apps that need to route anywhere and can live with data a couple of quarters behind.

Not included yet

Something else?

A region we don't list, transit agencies ingested, a faster refresh, or tiles delivered to your own storage — tell us what you need and what it's worth to you.

What do you need?

One download per area, then nothing

Tiles are fetched on demand and cached. What you pay for is area, not calls: unlocking a city costs tens of megabytes once, and everything after that is free. Measured against the Frankfurt dataset, one user, cold cache:

What the user doesDownloadsThen
20 routes around one metro74 MB0 bytes, ~33 ms
…plus a 10×10 travel-time matrix+0 MB
…plus 10/20/30-minute isochrones+115 MB

Isochrones are the expensive one — they fan out in every direction and pull tiles a route never touches. Everything above is a single allowance; we don't meter calls differently.

Long routes are the exception: past roughly 400 km the cost climbs steeply, so wider requests are rejected up front rather than quietly pulling hundreds of megabytes. Raise or disable that limit when you serve a smaller region. Continent-crossing routes are a job for a server.

Try it

The demo routes in your browser, then keeps routing after you turn the network off.