Imagery
Mapbox tiles, Unsplash photos
Overview
The admin Mapbox map uses a public token. Every other image — routes, stops, drivers, offers — runs through a small Unsplash client that downloads to disk on first request and serves from there on every subsequent request. We never re-hit Unsplash for the same query during a server's lifetime, which keeps us comfortably within the 50-requests-per-hour free tier.
How it works
Mapbox: NEXT_PUBLIC_MAPBOX_TOKEN is a public pk token, safe to expose to browsers. A separate MAPBOX_SECRET_KEY lives only on the API for tile-usage analytics and is never bundled.
Unsplash client: `apps/api/src/lib/unsplash.ts` wraps `searchPhotos(query, count, orientation)` and `pickPhoto(query, index, orientation)`. The first call for a given query hits Unsplash; subsequent calls read from .unsplash-cache/<hash>.json.
Image binary cache: each picked photo is downloaded to `apps/api/public/assets/<photoId>.regular.jpg` and served by Next.js static middleware. The API returns a server-relative URL (`/assets/<id>.regular.jpg`) in the response payload.
Client resolution: a resolveImageUrl(path) helper on mobile and web prefixes the API base URL to relative paths, so the same data row works across dev, staging, and prod.
Attribution: the Unsplash response includes the photographer name and profile URL. We store both on the consuming row (route.coverAttribName, route.coverAttribUrl, etc.) and render them where the image is shown.
Map style: we picked Mapbox light-v11 specifically — the muted gray-green palette frames our sage route lines without competing. Other styles (streets-v12, outdoors-v12) felt too busy.
Key decisions
Download once, serve forever
Hitting Unsplash on every page load would burn the free tier in an hour and add latency to every request. Downloading on first request and serving the binary from disk gets us infinite-rate-limit imagery and a snappy cold cache.
Server-relative URLs in the data
Storing the full URL in the database would couple data to environment. Storing /assets/foo.jpg and resolving on the client means the same Postgres row works in dev, staging, and prod with zero rewrite. The trade-off is a tiny client helper; the win is environment portability.
Mapbox on web, Apple Maps on mobile
We picked the best tool for each surface. Mapbox light-v11 gives us a designer-grade map on a 27-inch monitor. Apple Maps is free, ships with iOS, and is more than enough for the rider's phone. Consistency would have cost money or quality.