From idea to shipped mobile app with Expo, maps, and serverless
Listen while you read
Mobile product work gets real when the phone leaves perfect conditions. For JigSpot, that meant boats, weak signal, battery constraints, GPS tracking, and maps that still had to work offshore.
The result is an offline-first fishing app built with Expo, React Native, MapLibre, local SQLite, and a Cloudflare backend.
Start with the riskiest loop
The riskiest loop was not account creation. It was this:
- Download a map pack on Wi-Fi.
- Go offshore with no signal.
- Read bathymetric structure.
- Track a trip by GPS.
- Log catches and spots.
- Sync later.
That loop shaped the architecture.
Offline-first means the device is trusted first
The local SQLite database is the source of truth during a trip. Writes happen locally. Sync is a later concern. That keeps the core product useful when the network disappears.
This changes how you design errors. A failed network request should not block a catch log or trip note when the user is at sea.
Maps need imperative control
Map rendering is one place where pushing everything through React state can hurt performance. Camera movement, layer styling, and source updates need careful boundaries so the map stays smooth on real devices.
For JigSpot, MapLibre does the native map work while app state handles product state around it.
Serverless is enough for many mobile backends
Cloudflare Workers, D1, and R2 cover a surprising amount of the backend:
- JSON API routes
- auth and entitlement checks
- map bundle manifests
- R2-hosted PMTiles and GeoJSON
- sync endpoints
The product does not need a server sitting idle while users are offline.
The lesson
The right architecture followed the field conditions. When the user is offshore, the network is optional. When the user gets back online, the cloud reconciles.
If you are building a mobile product with maps, sync, or a lean backend, get in touch.
Need help with something like this?
Send the product goal, timeline, and current blockers. I’ll help you find the smallest useful next step.
Start a conversation