<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ugo</title>
    <description>The latest articles on DEV Community by Ugo (@racescore).</description>
    <link>https://dev.to/racescore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3319456%2F9fbbb213-1eea-4a57-a4ee-ccb6fcfcd1b1.png</url>
      <title>DEV Community: Ugo</title>
      <link>https://dev.to/racescore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/racescore"/>
    <language>en</language>
    <item>
      <title>How I built RaceScore: From a simple MVP to a 20,000-race endurance sports platform</title>
      <dc:creator>Ugo</dc:creator>
      <pubDate>Thu, 23 Jul 2026 08:30:09 +0000</pubDate>
      <link>https://dev.to/racescore/how-i-built-racescore-from-a-simple-mvp-to-a-20000-race-endurance-sports-platform-4a3g</link>
      <guid>https://dev.to/racescore/how-i-built-racescore-from-a-simple-mvp-to-a-20000-race-endurance-sports-platform-4a3g</guid>
      <description>&lt;p&gt;Hey Dev.to community! 👋&lt;/p&gt;

&lt;p&gt;A year ago, I posted here presenting the early MVP of RaceScore—a side project I started to solve a simple frustration: trail runners had universal indices like Betrail or ITRA to evaluate their level, but road runners and multi-sport athletes had nothing similar.&lt;/p&gt;

&lt;p&gt;Fast forward to today, and RaceScore has evolved from a small prototype into a full-fledged endurance sports platform with over 20,000+ races, a dynamic performance scoring algorithm, and B2B tools for race directors.&lt;/p&gt;

&lt;p&gt;Here is a look at what changed, the tech stack behind it, and what I learned along the way! 🏃‍♂️💨&lt;/p&gt;

&lt;p&gt;🚀 What &lt;a href="https://racescore.fr" rel="noopener noreferrer"&gt;RaceScore&lt;/a&gt; Does Today&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Universal Dynamic Performance Score: Instead of just looking at raw finish times or rankings, RaceScore calculates a relative score based on race difficulty, field depth, and conditions.&lt;/li&gt;
&lt;li&gt;Interactive Event Discovery: Powered by Mapbox GL JS, athletes can explore over 20,000+ running, trail, and triathlon events with elevation profiles and GPX tracks.&lt;/li&gt;
&lt;li&gt;Ecosystem Sync &amp;amp; Features: Strava OAuth integration for auto-syncing race activities, an HMAC-signed ICS calendar feed, and a peer-to-peer secondhand bib marketplace.&lt;/li&gt;
&lt;li&gt;B2B Organizer Suite: Turnkey race management including Stripe online registrations, waiting list management, and QR-code bib scanning on race day for volunteers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🛠️ The Tech Stack&lt;br&gt;
Here is what powers racescore.fr under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: React 19 + Vite + TypeScript&lt;/li&gt;
&lt;li&gt;UI &amp;amp; Styling: Tailwind CSS v4 + shadcn/ui (Radix UI)&lt;/li&gt;
&lt;li&gt;Routing &amp;amp; Data Fetching: React Router v7 + TanStack React Query&lt;/li&gt;
&lt;li&gt;Backend &amp;amp; Database: Supabase (PostgreSQL, Auth, Row Level Security)&lt;/li&gt;
&lt;li&gt;Serverless API: Vercel Serverless Functions (Node/TypeScript)&lt;/li&gt;
&lt;li&gt;Mapping &amp;amp; Data Vis: Mapbox GL JS + Recharts&lt;/li&gt;
&lt;li&gt;Third-Party Services: Stripe (Payments), Strava API (OAuth), Nodemailer/Brevo (Transactional Emails), hCaptcha&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔐 Key Technical Learnings &amp;amp; Security Highlights&lt;br&gt;
Building a platform with thousands of public race pages and personal athletic data brought some fun engineering challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Securing Calendar Subscriptions: To prevent IDOR vulnerabilities on user ICS calendar exports, feed URLs are signed using HMAC tokens.&lt;/li&gt;
&lt;li&gt;OAuth Cookie Security: Strava authentication tokens are sealed strictly inside httpOnly cookies, keeping them inaccessible to client-side JavaScript.&lt;/li&gt;
&lt;li&gt;Constant-Time Timing Protection: Internal webhooks and CRON triggers use constant-time comparison (crypto.timingSafeEqual) to mitigate timing attacks.&lt;/li&gt;
&lt;li&gt;Granular Database Access: Leveraging Supabase's Row Level Security (RLS) paired with strict role checks on administrative endpoints (/api/admin).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👀 Check it out!&lt;br&gt;
The app is live and fully accessible:&lt;/p&gt;

&lt;p&gt;🔗 Website: &lt;a href="https://racescore.fr" rel="noopener noreferrer"&gt;racescore.fr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts, feedback on the performance scoring concept, or any technical critiques regarding the stack.&lt;/p&gt;

&lt;p&gt;Happy running &amp;amp; coding! 🏃‍♂️⚡&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
      <category>supabase</category>
    </item>
    <item>
      <title>RaceScore – Suivez vos performances sportives et comparez-vous avec vos amis !</title>
      <dc:creator>Ugo</dc:creator>
      <pubDate>Thu, 03 Jul 2025 12:02:50 +0000</pubDate>
      <link>https://dev.to/racescore/racescore-suivez-vos-performances-sportives-et-comparez-vous-avec-vos-amis--18eg</link>
      <guid>https://dev.to/racescore/racescore-suivez-vos-performances-sportives-et-comparez-vous-avec-vos-amis--18eg</guid>
      <description>&lt;p&gt;Salut la communauté Dev.to ! 👋&lt;/p&gt;

&lt;p&gt;Je suis ravi de vous présenter &lt;a href="https://racescore.fr/" rel="noopener noreferrer"&gt;&lt;strong&gt;RaceScore&lt;/strong&gt;&lt;/a&gt;, un projet personnel que je développe sur mon temps libre. RaceScore est une application web dédiée aux sportifs qui participent à des courses.&lt;/p&gt;

&lt;p&gt;L'idée derrière &lt;a href="https://racescore.fr/" rel="noopener noreferrer"&gt;RaceScore&lt;/a&gt; :&lt;br&gt;
Quand on participe à plusieurs courses, il n'est pas toujours évident de suivre ses progrès, de comparer ses performances ou de les partager simplement avec ses amis. L'objectif de RaceScore est simple :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centraliser vos résultats de course.&lt;/li&gt;
&lt;li&gt;Attribuer un score à chaque performance.&lt;/li&gt;
&lt;li&gt;Comparer vos scores avec ceux de vos amis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📈 Le score est basé sur vos temps et la difficulté de la course, pour offrir une évaluation plus représentative que la simple position ou chrono.&lt;/p&gt;

&lt;p&gt;🔧 Stack technique&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend : React + Tailwind CSS&lt;/li&gt;
&lt;li&gt;Backend / BDD : Supabase – Authentification, base PostgreSQL, API auto-générées&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚙️ Fonctionnalités principales&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Création de compte et authentification&lt;/li&gt;
&lt;li&gt;Ajout de résultats sur des courses existantes (issues de la base de données)&lt;/li&gt;
&lt;li&gt;Calcul automatique du score&lt;/li&gt;
&lt;li&gt;Profil avec historique de vos performances&lt;/li&gt;
&lt;li&gt;Classement entre amis (fonctionnalité à venir)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et c’est juste le début !&lt;/p&gt;

&lt;p&gt;👀 Envie de tester ?&lt;br&gt;
🖱️ 👉 Lien vers l’app : &lt;a href="//racescore.fr"&gt;racescore.fr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pas besoin d’intégration à Strava, Garmin ou autre pour le moment. L’idée est d’avoir un outil simple, épuré, utile… et qui va évoluer 💡&lt;/p&gt;

&lt;p&gt;Merci d’avoir lu, et n’hésitez pas à me faire des retours ou à tester l’app ! 🙏&lt;br&gt;
Je suis preneur de toute critique constructive pour améliorer l’expérience.&lt;/p&gt;

&lt;p&gt;🏃‍♂️ À très vite sur RaceScore !&lt;br&gt;
🔗 &lt;a href="https://racescore.fr/" rel="noopener noreferrer"&gt;racescore.fr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94ooiu85xj7gpao13qhf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94ooiu85xj7gpao13qhf.jpg" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyyqdh8olhheruyfyple.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyyqdh8olhheruyfyple.png" alt=" " width="722" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdp5zx6a2rnblfpgfq87i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdp5zx6a2rnblfpgfq87i.png" alt=" " width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
