Skip to content
ZJ

Case study /07 · In production

CLZTR

Founder Product · Mobile · Maps · Real-Time

A live city discovery platform connecting people, events and nearby activity through an interactive map.

CLZTR is the product I founded: a mobile app that turns a city into a live map of what is happening right now — events, venues, and the presence of people around them.

  • Expo React Native
  • TypeScript
  • MapLibre
  • PostGIS
  • H3
  • Go
  • Redis
  • NATS
  • PostgreSQL
  • RevenueCat
CLZTR end-to-end live map flow: a viewport change resolves to H3 cells, queries PostGIS per cell, merges Redis presence and NATS deltas, and reconciles once per frame into the rendered live map.

Context

Discovery apps mostly show static listings. CLZTR's bet is that a city feels different when you can see it move: events appearing as they are published, crowd presence shifting through the evening, and clusters forming around neighborhoods as activity concentrates.

Problem

A live map is brutally demanding. The client must render large volumes of geospatial data at interactive frame rates on mid-range phones; the backend must answer viewport queries in tens of milliseconds and push presence changes to every relevant client without flooding the network.

Constraints

  • Mid-range Android devices as the performance baseline, not flagships.
  • Real-time presence had to degrade gracefully on unstable mobile connections.
  • A solo-founder budget: infrastructure had to stay simple enough for one person to operate.
  • Event volumes spike sharply around weekends and large happenings — the quiet-day architecture had to survive the loud days.

Responsibilities

  • Product definition, interface design and the full design system.
  • Expo React Native app with MapLibre rendering and native map optimizations.
  • Go backend services for events, presence, membership and notifications.
  • Geospatial data model on PostgreSQL + PostGIS with H3 cell indexing.
  • Real-time layer on Redis and NATS; store, fan-out and reconciliation logic.
  • RevenueCat subscription integration and membership entitlement architecture.

Architecture

  • An Expo React Native client talks to a small set of Go services over HTTP and WebSockets — no gateway layer a solo operator would have to babysit.
  • PostgreSQL + PostGIS is the source of truth; every event and venue is indexed into H3 cells at multiple resolutions for fast viewport aggregation.
  • Redis holds hot presence state and per-cell aggregate counters with TTL-based expiry, so a crashed client disappears from the map on its own.
  • NATS distributes presence and event mutations between services and WebSocket edge processes, keeping fan-out off the database.
  • Membership entitlements resolve through RevenueCat webhooks into a local entitlement table, so feature gates never require a synchronous third-party call.

Key decisions

  • H3 over ad-hoc geohashing: hexagonal cells give uniform neighbor distances, which makes clustering visually honest and viewport queries a fixed set of cell lookups.
  • Server-side clustering at low zooms, client-side at high zooms: the phone only ever receives what it can afford to render.
  • Presence is ephemeral by design — Redis TTLs instead of tombstone writes — trading perfect history for operational simplicity.
  • Go over Node for services: predictable memory under sustained WebSocket load mattered more than sharing types with the client.

Implementation

  • Viewport queries resolve to H3 cell sets; responses are aggregate counts per cell at low zoom and full features only inside the visible cells at high zoom.
  • The map pipeline batches marker updates into a single reconciliation pass per frame instead of reacting to every socket message.
  • WebSocket sessions subscribe to the NATS subjects for their current cells only, and re-subscribe as the viewport moves.
  • Event writes publish deltas; clients patch local state and periodically reconcile against a checksum to catch drift.
  • Membership state is cached on-device with signed entitlements so gating works offline.

Performance

  • Marker rendering stays on the UI thread's budget by capping visible features and promoting the rest into cluster aggregates.
  • Per-cell Redis counters make the 'what is nearby' query O(cells in viewport), independent of total event volume.
  • Presence fan-out is coalesced: rapid join/leave flapping within a window collapses into one message.
  • Cold-start map paint was profiled and tuned on low-end Android hardware, not the simulator.

Outcomes

  • Runs in production with real users discovering live events on the map.
  • The geospatial architecture has absorbed large-event spikes without structural changes.
  • Membership infrastructure supports paid tiers without any synchronous third-party dependency at request time.
  • [PLACEHOLDER — add real usage and growth metrics here when publishable.]

Lessons

  • Real-time features earn their complexity only where users can see them — presence yes, silent metadata no.
  • Designing for the mid-range device first made every later optimization cheaper.
  • As a founder, the architecture you can operate alone at 2am is the correct one.

Software Architecture

Seven-stage flow from a viewport change to rendered live city activity. 1. App viewport: the user pans or zooms the map in the Expo React Native client, with zoom-aware requests over a MapLibre canvas. 2. H3 cell resolution: the viewport becomes a fixed set of hexagonal cells at multiple resolutions, bounding query size and giving uniform neighbour distances. 3. Geospatial query: PostgreSQL with PostGIS answers per cell rather than per pixel — aggregate counts at low zoom, full features when close. 4. Presence layer: Redis holds hot presence as per-cell counters with TTL expiry instead of tombstones, so dead clients vanish on their own. 5. Real-time fan-out: NATS pushes deltas on a subject per cell, clients re-subscribe as the viewport moves, and join/leave flapping is coalesced. 6. Frame reconciliation: one batched marker pass per frame rather than per socket message, capping visible features into cluster aggregates within a mid-range device budget. 7. Live map: clusters, events and presence in view, clustered server-side when far and client-side when close, with periodic checksum reconciliation. A viewport and delta loop returns to the start, and a membership band covers RevenueCat webhooks, a local entitlement table and signed on-device caching so gating works offline.
Architecture diagram in four columns. Mobile client: Expo React Native holding the map UI and design system, the MapLibre renderer for markers and clusters, local state that patches deltas and verifies a checksum, and signed entitlements for offline feature gating. Go services: an events service for events, venues and listings, a presence service handling join, leave and TTL refresh, a membership service consuming RevenueCat webhooks, and a WebSocket edge managing per-cell subscriptions. Geospatial store: PostgreSQL as the single source of truth, PostGIS geometry for event and venue geography, an H3 cell index for multi-resolution aggregation, and a local entitlement table so no third-party call happens at request time. Real-time backbone: Redis presence as hot state with TTL expiry, per-cell counters that make the nearby query proportional to cells in view, NATS subjects keeping fan-out off the database, and coalescing to suppress join/leave flapping. A closing band notes the system is operable by one person: no gateway tier, ephemeral presence over history, per-cell backpressure and spike tolerance by design.