Project ·

JigSpot

Offline-first React Native app + a 100% serverless Cloudflare API for Filipino anglers.

  • React Native
  • Expo
  • Cloudflare Workers
  • D1
  • MapLibre
  • TypeScript

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

All works