███████╗████████╗██████╗ ██╗██████╗ ███████╗
██╔════╝╚══██╔══╝██╔══██╗██║██╔══██╗██╔════╝
███████╗ ██║ ██████╔╝██║██║ ██║█████╗
╚════██║ ██║ ██╔══██╗██║██║ ██║██╔══╝
███████║ ██║ ██║ ██║██║██████╔╝███████╗
╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚══════╝
STRIDE is a GPS territory conquest game built on real-world running. You go outside, track a route, and when you close a loop back on itself you capture the geographic area inside it as your territory on a live city map. Run through someone else's ground and you clip it off them. The map is a shared, contested battlefield that decays if you stop defending it, so standing still loses you ground. It is Strava meets Splix.io, with the competition layer being the entire point rather than a side feature.
Live: https://stride-6rm.pages.dev
| Website | PWA-Supported APP |
|---|---|
![]() |
![]() |
- GPS territory capture. Close a running loop and the enclosed area becomes a PostGIS polygon you own. Area is measured in real square meters.
- Territory theft. A new loop that overlaps a rival's ground clips their polygon with ST_Difference and hands you the slice. Pure Splix.io mechanic, played on real geography.
- Rival system. Every new runner is auto-assigned a rival from their own city with the closest total distance, highlighted in danger red on the map.
- Real-time leaderboards. City, global, and friends views ranked by territory owned, with weekly distance as a secondary sort. Your own rank stays pinned even when you fall outside the top ten.
- Streaks and decay. A daily run keeps your streak alive. Any territory left undefended for 48 hours shrinks 10 percent on a cron job, so the map punishes inactivity.
- Push notifications. Web Push over a Supabase Edge Function alerts you when territory is stolen, a rival runs, or a streak is at risk.
- Installable PWA. Offline shell, home-screen install, and a dark athletic UI that holds 60fps on mobile.
- Run replay and heatmap. Replay any past run drawing itself on the map, and see every route you have ever run stacked into a personal heatmap.
| Library | Purpose | Version |
|---|---|---|
| Next.js (App Router) | Frontend framework and routing | 14.2 |
| TypeScript | Strict typing across the whole codebase | 5.x |
| Tailwind CSS | Utility-first styling with named design tokens | 3.4 |
| Supabase JS / SSR | Auth, database access, session handling | 2.x / 0.10 |
| PostGIS | Polygon storage and spatial operations | via Postgres |
| Google Maps JS API Loader | Map rendering and polygon overlays | 2.x |
| Rive | Stride loader and capture celebration animations | 4.x |
| Lottie | Streak fire and micro-interactions | 3.x |
| next-pwa | Service worker, offline support, Web Push handler | 5.6 |
| SWR | Client data fetching and cache | 2.x |
| date-fns | Date and duration formatting | 4.x |
The frontend is a Next.js App Router application rendered mostly as static pages, with the dynamic profile route running on the Cloudflare Edge runtime. All data access lives in lib/supabase/queries, reusable logic in hooks, and types in types, so components stay presentational.
Supabase is the entire backend. Postgres with PostGIS stores territories as GEOGRAPHY polygons and runs the spatial math directly in SQL through RPC functions. capture_territory validates an incoming loop, clips any overlapping enemy polygons, and inserts the new one in a single transaction. Two Edge Functions run the background game loop: territory-decay shrinks undefended ground and resets stale streaks on a cron schedule, and push-notifications signs and delivers Web Push messages.
Google Maps renders the dark themed battlefield, drawing each territory as a colored polygon overlay and the live GPS trail as a polyline. Realtime subscriptions push territory and rival changes to the map without a refresh. The whole thing ships to Cloudflare Pages through the next-on-pages adapter.
Browser (PWA)
└─ Next.js App Router ──> Google Maps overlays (polygons, GPS trail)
│
├─ SWR + hooks ──> lib/supabase/queries
│ │
│ ▼
│ Supabase Postgres + PostGIS
│ ├─ capture_territory() (ST_Intersects / ST_Difference / ST_Area)
│ ├─ leaderboard RPCs
│ └─ Realtime channels ──> back to the map
│
└─ Edge Functions
├─ territory-decay (cron: shrink + streak reset)
└─ push-notifications (Web Push / VAPID)
Requirements: Node 18 or newer, a Supabase project with PostGIS enabled, and a Google Maps API key.
git clone https://github.com/ShiBui2003/stride.git
cd stride
npm installCreate .env.local in the project root (see the table below), then apply the database migrations in order through the Supabase SQL editor:
supabase/migrations/001_initial_schema.sql
supabase/migrations/002_leaderboard_weekly_km.sql
supabase/migrations/003_streak_system.sql
supabase/migrations/004_push_subscriptions.sql
Run the dev server:
npm run devOpen http://localhost:3000. The PWA service worker is disabled in development, so test offline and push behaviour against a production build:
npm run build
npm run start.env.local holds the public client keys. The Supabase service role key and the VAPID private key are never placed here; they live as Supabase Edge Function secrets.
| Variable | Description |
|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Supabase project URL |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase anon public key |
| NEXT_PUBLIC_GOOGLE_MAPS_API_KEY | Google Maps JS API key |
| NEXT_PUBLIC_APP_URL | Deployed app URL, used for auth redirects |
| NEXT_PUBLIC_VAPID_PUBLIC_KEY | Web Push VAPID public key |
Edge Function secrets, set with supabase secrets set:
| Secret | Description |
|---|---|
| VAPID_PUBLIC_KEY | Web Push VAPID public key |
| VAPID_PRIVATE_KEY | Web Push VAPID private key |
| VAPID_SUBJECT | Contact URL or mailto for push |
STRIDE deploys to Cloudflare Pages. It is a full App Router app with middleware and a dynamic route, so it builds through the next-on-pages adapter rather than a static export.
| Setting | Value |
|---|---|
| Build command | npx @cloudflare/next-on-pages |
| Build output directory | .vercel/output/static |
| Compatibility flag | nodejs_compat |
The five NEXT_PUBLIC variables above are set as build-time environment variables in the Pages dashboard. Pushing to the main branch triggers an automatic build and deploy. Edge Functions are deployed separately with supabase functions deploy.
Six tables, all with row level security enabled. Reads for the map and leaderboard are public; writes are restricted to the owning user.
| Table | What it holds |
|---|---|
| users | Profile, city, territory color, total km, streak, assigned rival |
| runs | GPS route as a GeoJSON LineString plus distance, pace, duration, calories |
| territories | PostGIS GEOGRAPHY polygon, area in m2, capture and last-defended timestamps |
| follows | Follower and following pairs for the social graph |
| activities | Run-to-feed link with like counts |
| push_subscriptions | Web Push endpoint and keys per device |
Built and live:
- Auth, onboarding, and protected routing
- Full-screen map with polygon overlays and realtime updates
- GPS run tracking, loop detection, and territory capture
- Territory theft through PostGIS intersection and clipping
- Activity feed, follow system, and leaderboards
- Streak system, territory decay, and rival assignment
- Run replay, personal heatmap, push notifications
- Rive and Lottie animations, PWA, Cloudflare deployment
Next:
- Google OAuth provider on top of the existing email auth
- Wiring the push Edge Function to fire automatically on game events
- Named segments with per-segment fastest-time leaderboards
- Frontend visual redesign
Rahul Jha BTech Computer Science, Bennett University
- GitHub: https://github.com/ShiBui2003
- LinkedIn: https://linkedin.com/in/rahuljha
MIT

