Inspiration

I'm a computer science student, and I only started running a few months ago. Like a lot of beginners, I started alone: alone on the road, alone deciding whether 5K at 6:30/km was "good", alone trying to convince myself to go out when it was cold and dark outside.

I kept opening apps that told me exactly how slow I was, but none of them helped me with the actual problem, which was that running alone gets lonely fast, and motivation on your own has a shelf life of about two weeks.

I also noticed something specific: as a young woman, "just go join a running club" is not always a simple answer. You want to know who you're meeting, where, and whether the pace will leave you humiliated in the first 400 meters. So I built the app I wanted to have on day one: a way to find people who run like me, near me, without the awkwardness and without the safety anxiety.

What it does

Runly connects you with runners who match your pace, your level and your goals, so you never have to run alone unless you want to.

  • Smart matching: find runners near you who actually run at your pace, not 20 seconds per kilometer faster

  • Sessions: create a run in a few seconds or join one happening nearby, from a chill 5K to a marathon prep long run

  • Running clubs: browse and join real clubs, discover their events

  • Live map: see sessions and partners around you during a run, with proximity alerts when your partner is arriving

  • Progress tracking: stats, records, personal goals and Apple Health integration

  • Safety first: verified profiles, deliberately fuzzy location zones, no real time GPS broadcasting, safety tips and community guidelines built into the product

And then there is Runly Pro, powered by RevenueCat: reserved seats in full sessions and events, early access before everyone else, the full live map of runners around you, advanced stats and detailed records, and a training plan built around your own goals. The core of the app (matching, sessions, planning) stays free, because the whole point was to get people running together, not to gate the basics.

How I built it

Solo, mostly at night, between classes and assignments.

  • Mobile: React Native + Expo (Expo Router, file based routing), TypeScript everywhere Backend: Supabase (Postgres, auth, storage, row level security, edge functions), with Apple, Google and magic link sign in

  • Monetization: RevenueCat (react-native-purchases and react-native-purchases-ui), with a custom native paywall, the Customer Center for subscription management, and a webhook that mirrors the subscription status into Supabase

  • Maps and live features: react-native-maps, expo-location and background tasks for live sessions State: React Query for server state, Zustand for local state

  • Quality: Sentry for crash reporting, Vitest for the business logic that must not break (pro access rules, capacity reservation, session mapping), and a full QA checklist before each build

Everything is bilingual (French and English) from the start, with a lint rule that catches hardcoded strings Website: Next.js App Router with a small Supabase powered back office for the blog and the club directory

Challenges I ran into

Subscription state is much harder than "isPro: true". My first version treated a failed RevenueCat call as "not subscribed". Result: a subscriber going through a tunnel with no signal suddenly got the paywall shoved in their face. I ended up writing a proper resolver with three states (pro, free, unknown) that arbitrates between RevenueCat, the Supabase mirror and the last known status stored locally. The key insight: "unknown" is not "free", and you should only ever show the paywall when the status is actually confirmed. That file is one of the most heavily unit tested in the codebase.

Testing purchases without shipping to production. RevenueCat needs a custom dev build, so I had to build a demo mode for Expo Go and web, with clearly fake prices, so I could keep iterating on the paywall UI without a full build every time. Related trap I fell into: product.introPrice describes the offer configured on the product, not whether this specific user is still eligible for the free trial. Announcing "3 days free" to someone who already used it is a great way to get a one star review.

Reserved capacity for Pro members. Holding the last spots in a session for Pro users sounds trivial until two people tap "join" at the same time on the last seat. That needed real reservation logic on the database side, not optimistic UI.

Safety and privacy as a product constraint. Meeting strangers to go running is the whole premise, so privacy could not be an afterthought. No live GPS broadcast, intentionally imprecise zones, verified profiles, controlled sharing. Every feature had to pass the "would I feel safe using this alone at 7am?" test.

Costs, as a student. Storage egress and image delivery add up quickly when everyone uploads a profile picture and session photos. Optimizing bandwidth ended up being a design constraint from the start rather than a cleanup task at the end.

Accomplishments that I am proud of

I shipped a real, complete, bilingual app to the stores by myself, as a student, while learning to run at the same time.

The RevenueCat integration is not a bolted on paywall: native paywall, Customer Center, restore purchases, webhook synced Supabase mirror, offline resilient status resolution. It behaves correctly in the ugly cases (no network, restored purchase, account switch), and those cases are covered by tests.

The premium model feels fair. The things that get people running together are free, and Pro adds depth for people who are really into it.

Safety is designed in, not written in a FAQ afterwards. That matters a lot to me personally.

I built the app I actually needed. I now run with people I met through Runly, which is easily the best test result I could have hoped for.

What I learned

Payments are a state machine, not a boolean. Most of the real work is in the edge cases: no network, expired subscription, restored purchase, refund, user logged in on a second device. RevenueCat handles the hard part with the stores, but you still have to decide what your app does when a source is unreachable.

Ship the boring safety net first. Sentry, tests on business rules and a QA checklist saved me way more time than they cost.

Talking to users beats guessing. Several features (fuzzy zones, pace based matching rather than distance based) came directly from what beginner runners told me they were afraid of.

Being a beginner is a feature. I built for the person I was three months ago, and that gave me a much clearer product than if I had been an experienced runner designing for other experienced runners.

Done and shipped beats perfect and unfinished. The version in the stores is not the version I dreamed about, and that's exactly why it exists.

What's next for Runly

Growing the community city by city, because a matching app is only as good as the number of runners near you. Clubs and local ambassadors are the priority.

Smarter training plans in Pro, adapting to what you actually ran rather than what you said you'd run. Better group features: recurring sessions, club leaderboards, challenges between friends and post run recaps worth sharing.

More safety tools: live sharing with a trusted contact, check in after a run, better reporting flows. Deeper stats and Health integration, plus wearables support.

Monetization experiments with RevenueCat: testing pricing, offers and paywall variants properly instead of guessing, and looking at club or partner offers so Runly can keep growing without making the core experience pay to play.

Built With

Share this project:

Updates