Project ·
JigSpot
Offline-first React Native app + a 100% serverless Cloudflare API for Filipino anglers.
JigSpot turns a smartphone into a marine fish-finder companion: it shows seafloor depth contours and structure, records fishing trips by GPS, logs catches, and lets friends share productive spots — all working offline at sea, with no cell signal.
At a glance
- Role: Product builder and engineer across mobile app, API, offline data, maps, and sync architecture.
- Scope: Offline maps, GPS trip recording, catch logs, spot sharing, subscription gating, serverless API, and map data pipeline.
- Stack: Expo, React Native, MapLibre, SQLite, MMKV, TanStack Query, Cloudflare Workers, D1, R2, TypeScript, Python.
- Status: Native iOS and Android apps in Google Play internal testing.
- Proof: Network-optional core loop, around 30 JSON API routes, shared Zod contracts, and custom bathymetry bundles served from R2.
The problem
Filipino anglers who jig and bottom-fish offshore hit a stack of problems that off-the-shelf apps don’t solve:
- No signal where the fish are. Productive grounds are kilometres offshore, outside cellular coverage — so anything that needs the network is useless exactly when it matters.
- Generic maps don’t show the seafloor. Anglers need bathymetry — depth contours, drop-offs, and structure — because fish hold on specific depth bands and features, not on the surface.
- Chartplotters are expensive and stuck on the boat, and local knowledge of “good spots” stays trapped in people’s heads with no record of what worked.
The goal: an affordable, offline-first “smart fishing log + seafloor map” in every angler’s pocket, with hard-won local knowledge made shareable.
The solution
An offline-first mobile app backed by a lightweight serverless API. The phone is the source of truth during a trip; the cloud is for sync, sharing, and distributing map data.
- Offline bathymetric maps — vector depth contours and structure edges rendered with MapLibre. Region packs download over Wi-Fi and run fully offline, with target-depth highlighting and an “examine area” tool that reports depth, slope, and a computed fishability score.
- GPS trip tracking — one-tap recording that logs the route in the background with the screen off, plus in-trip catch logging and auto-generated shareable trip cards.
- Spots & sharing — save spots with photos, revisit them offline, and share them into a trusted circle.
- Friends & live location — opt-in live broadcast with battery-aware polling.
- Catch log & conditions — filterable history with per-trip weather/marine conditions so results can be correlated with tide and weather over time.
Premium features (broadcast, offline_download, premium_map, spot_limit)
are gated server-side as the source of truth, with the UI mirroring it.
Technical overview
The whole backend runs on Cloudflare’s edge — no servers to manage, global low latency, and a cost model that scales to zero.
Mobile — Expo SDK 56 / React Native 0.85 / React 19 in TypeScript, with
Expo Router, @maplibre/maplibre-react-native for native vector maps,
expo-sqlite as the local database, MMKV for settings, and Zustand + TanStack
Query for state. Background GPS uses an expo-location + expo-task-manager
foreground service for screen-off tracking.
Offline-first data layer — local SQLite is the source of truth during a trip. Typed repositories cover tracks, spots, trips, photos, region packs, and a sync queue; writes happen locally and instantly, then reconcile with the Worker when connectivity returns.
API — a single Cloudflare Worker (~30 JSON routes) with Better Auth, Cloudflare D1 (edge SQLite, strictly parameterized queries), and R2 for versioned map bundles (PMTiles + GeoJSON) proxied for efficient offline caching.
Shared monorepo — @jigging-map/core holds Zod schemas and the fishing math
(target depth, fishability scoring, geo, route simplification), and
@jigging-map/api is a typed client used by both the app and a companion PWA —
so the contract can’t drift between clients. Every Worker response is parsed
through a core Zod schema before it’s trusted.
Map data pipeline — bathymetry is generated from raw GEBCO seafloor rasters: Python derives contours and structure into compact PMTiles + GeoJSON, a Node script publishes versioned bundles to R2 with a manifest, and the Worker serves them — with reduced-detail free variants for the free tier.
Key decisions
- Truly offline, not “offline-tolerant” — the network is optional for the entire core loop; the device is the source of truth and syncs later.
- Imperative map control — camera, sources, and layer styling are kept out of React/Zustand state and driven through a dedicated controller, avoiding a whole class of re-render bugs and keeping panning smooth on mid-range Android.
- One set of rules, three surfaces — depth bands, fishability, and validation live once in shared code and mean the same thing in the app, the PWA, and the API.
Status
Native iOS and Android apps on a shared, type-safe monorepo with a companion PWA, a fully serverless Cloudflare backend, and a custom offline map pipeline from raw bathymetry. Currently in Google Play internal testing, ahead of a public launch.
Have a similar system to build?
Send the product goal, timeline, and current blockers. I’ll help you turn it into a practical build plan.
Start a conversation