01
The Challenge
Coffee discovery apps are either soulless directories or closed review silos. I wanted to build a real social product — follows, feeds, shared lists, top-3 rankings — around café culture in Paris, and ship it solo: design, mobile development, backend, push notifications, store submissions and analytics. The hard part was the full product surface: auth (Apple/Google), interactive map with clustering, real-time feed, moderation tools, deep links, and a guest mode that converts.
02
The Solution
Built with React Native + Expo (file-based routing via Expo Router) on a Supabase backend: Postgres with row-level security, storage, RPCs and auth triggers. React Query handles caching and optimistic updates across the feed, reviews, lists and follows. The map clusters hundreds of cafés with supercluster, profiles feature top-3 podiums and shareable lists, and a perks system rewards early members with swipe-to-claim offers. Shipped to both stores with EAS, instrumented with PostHog and Sentry, localized FR/EN, with universal links and a public web page per café.
// Map clustering — rebuild index only when cafés change,
// re-query clusters when the visible region moves
export function useClusters({ cafes, region, radius = 60 }: Options) {
const index = useMemo(() => {
if (!cafes?.length) return null;
const sc = new Supercluster<PointProps, ClusterProps>({
radius,
maxZoom: 16,
minPoints: 3,
});
sc.load(
cafes.map((cafe) => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [cafe.longitude, cafe.latitude],
},
properties: { cafe },
}))
);
return sc;
}, [cafes, radius]);
const clusters = useMemo(() => {
if (!index || !region) return [];
const zoom = Math.round(Math.log2(360 / region.latitudeDelta));
return index.getClusters(regionToBbox(region), zoom);
}, [index, region]);
return { clusters, index };
}↳ Supercluster-based map clustering hook — the index is only rebuilt when the café list changes, while region moves trigger a cheap bbox re-query





