<?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: Akira Kikusato</title>
    <description>The latest articles on DEV Community by Akira Kikusato (@akira_kikusato_d36776f7e4).</description>
    <link>https://dev.to/akira_kikusato_d36776f7e4</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3921606%2F4739d12f-0654-48b2-b96f-6bbed3a8737f.png</url>
      <title>DEV Community: Akira Kikusato</title>
      <link>https://dev.to/akira_kikusato_d36776f7e4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akira_kikusato_d36776f7e4"/>
    <language>en</language>
    <item>
      <title>Building a Ride Analysis Web App with Antigravity and the Strava API</title>
      <dc:creator>Akira Kikusato</dc:creator>
      <pubDate>Sat, 16 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/gde/building-a-ride-analysis-web-app-with-antigravity-and-the-strava-api-439g</link>
      <guid>https://dev.to/gde/building-a-ride-analysis-web-app-with-antigravity-and-the-strava-api-439g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I ride bikes a lot these days, and like many cyclists I use Strava to log my rides. Even on the free tier it automatically syncs with smartwatches, cycling computers, and smart trainers, which is plenty for just keeping a record. But the more I learned about training, the more I wanted richer analytics and planning — and most of that lives behind Strava's paid subscription.&lt;/p&gt;

&lt;p&gt;While poking around, I noticed Strava offers a public &lt;a href="https://developers.strava.com/" rel="noopener noreferrer"&gt;Strava API&lt;/a&gt;. That was enough motivation to build my own analytics on top of it.&lt;/p&gt;

&lt;p&gt;Concretely, I wanted these features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ride and training management&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Review past rides … ①&lt;/li&gt;
&lt;li&gt;Visualize power and heart-rate distributions by intensity zone … ②&lt;/li&gt;
&lt;li&gt;View power and heart-rate traces color-coded by intensity zone … ③&lt;/li&gt;
&lt;li&gt;Show recent training load and fatigue to get a sense of current condition … ④&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Ride and training planning&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Build training plans by intensity and goal for upcoming events … ⑤&lt;/li&gt;
&lt;li&gt;Recommend the next workout based on recent condition (training load and fatigue) … ⑥&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I built the app using Antigravity. There are two parts: the analytics app itself, and an agent that suggests the next workout based on recent condition. This post covers the analytics app. The agent will be a separate post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of the App: "PerfRide"
&lt;/h2&gt;

&lt;p&gt;The result is a web app called &lt;strong&gt;PerfRide&lt;/strong&gt; that uses the Strava API to manage ride records and training plans for road cycling.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kikuriyou" rel="noopener noreferrer"&gt;
        kikuriyou
      &lt;/a&gt; / &lt;a href="https://github.com/kikuriyou/PerfRide" rel="noopener noreferrer"&gt;
        PerfRide
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A performance management toolkit for road cyclists
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;PerfRide 🚴&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A performance management toolkit for road cyclists, powered by the &lt;a href="https://developers.strava.com/" rel="nofollow noopener noreferrer"&gt;Strava API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Simulate climbs, optimize race pacing, plan periodized training, and track your fitness — all in one app.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📊 Dashboard&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;Connect with Strava to view your recent rides, weekly training summary, and fitness progress chart (CTL / ATL / TSB). Includes per-ride detail with heart rate zones, power profile, and elevation overlay.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🏔️ Climb Simulator&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Predict climbing times based on power, weight, and real segment data. Uses physics-based simulation (air resistance, rolling resistance, drivetrain loss). Search segments by map or use your Strava starred segments.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🎯 Pace Optimizer&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Calculate optimal pacing strategy for time trials based on course elevation profile. Based on the research paper &lt;em&gt;"A numerical design methodology for optimal pacing strategy in the individual time trial discipline of cycling"&lt;/em&gt; (Sports Engineering, 2025).&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📅 Training Planner&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Generate periodized training plans for your target race…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kikuriyou/PerfRide" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The main features (including some experimental ones) are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Maps to&lt;/th&gt;
&lt;th&gt;Needs Strava&lt;/th&gt;
&lt;th&gt;Experimental&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;Activity list, Fitness/Fatigue/Form charts&lt;/td&gt;
&lt;td&gt;①, ②, ③, ④&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly Plan&lt;/td&gt;
&lt;td&gt;This week's workout plan&lt;/td&gt;
&lt;td&gt;⑤, ⑥&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Climb Simulator&lt;/td&gt;
&lt;td&gt;Predicts climb time from power and weight using physics-based math&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pace Optimizer&lt;/td&gt;
&lt;td&gt;Computes optimal pacing based on a course profile&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training Planner&lt;/td&gt;
&lt;td&gt;Generates a phased training plan working backward from a goal race&lt;/td&gt;
&lt;td&gt;⑤&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;User parameters: FTP, weight, max HR, etc.&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F2mjhum8d6z8c7xuty7yb.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%2F2mjhum8d6z8c7xuty7yb.png" alt="PerfRide landing page" width="800" height="567"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;PerfRide landing page&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Dashboard
&lt;/h3&gt;

&lt;p&gt;Once connected to Strava, the dashboard shows your recent rides and a weekly summary (ride count, distance, elevation gain, moving time). It also derives fitness metrics from roughly the last 13 weeks of activity data and visualizes the following three indicators along with trends in distance and elevation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fitness (CTL: Chronic Training Load)&lt;/strong&gt;: Long-term accumulated training load. Higher = better aerobic base.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fatigue (ATL: Acute Training Load)&lt;/strong&gt;: Fatigue from recent training. Higher = more tired.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form (TSB: Training Stress Balance)&lt;/strong&gt;: Fitness − Fatigue. Indicates how race-ready you are (+10 to +25 is generally considered optimal for racing).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clicking on a ride drills into details like heart-rate zone distribution, power profile, and elevation chart.&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%2F2m5kbfl7795dmx9kmg63.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%2F2m5kbfl7795dmx9kmg63.png" alt="Dashboard page" width="800" height="863"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Dashboard page&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Weekly Plan
&lt;/h3&gt;

&lt;p&gt;The Weekly Plan shows "what training you should do this week" laid out day by day. The Dashboard surfaces a card for just today's session, and the Weekly Plan page shows the full week (session type, target TSS, status) along with past plan history.&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%2F0ej7mu3wpvw9i790dvw9.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%2F0ej7mu3wpvw9i790dvw9.png" alt="Weekly plan page" width="800" height="636"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Weekly plan page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The plan itself is generated by a separate agent that runs early every Monday morning and builds out the following week automatically. It looks at recent Fitness/Fatigue/Form and user settings, then proposes sessions aligned with a periodization cycle (Base → Build → Peak → Taper). I'll cover the agent in a separate post.&lt;/p&gt;
&lt;h3&gt;
  
  
  About the experimental features
&lt;/h3&gt;

&lt;p&gt;The three features below — Climb Simulator, Pace Optimizer, Training Planner — are currently marked as &lt;strong&gt;experimental&lt;/strong&gt;. They work, but the UX flow and parameters are still rough. I built them quickly with help from Antigravity and LLMs, referenced some papers, but haven't fully polished them yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Climb Simulator&lt;/strong&gt; estimates climb time. Enter power, weight, distance, and elevation gain, and it computes a predicted time using physics-based math. If you're connected to Strava, you can also estimate times for your starred segments.&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%2Fa5eqcj4o4p2cyub4caxq.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%2Fa5eqcj4o4p2cyub4caxq.png" alt="Climb Simulator page" width="800" height="437"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Climb Simulator page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pace Optimizer&lt;/strong&gt; computes optimal power distribution based on a course profile. It's based on a 2025 paper, &lt;a href="https://link.springer.com/article/10.1007/s12283-025-00493-9" rel="noopener noreferrer"&gt;"A numerical design methodology for optimal pacing strategy in the individual time trial discipline of cycling"&lt;/a&gt;. The core idea: push higher power on climbs and lower power on descents to shorten total time while keeping Normalized Power (NP) roughly constant. Intuitively, at lower speeds on climbs, aerodynamic drag is smaller, so extra watts translate more directly into time saved. There's still room to improve, but it's a feature grounded in recent research.&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%2Fs81hbds6uinbpo65sych.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%2Fs81hbds6uinbpo65sych.png" alt="Pace Optimizer page" width="800" height="801"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pace Optimizer page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training Planner&lt;/strong&gt; generates a phased training plan working backward from a goal race date. The phases are split roughly like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Share of time&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;td&gt;Build aerobic foundation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build 1&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;Tempo and threshold work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build 2&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;VO2max and high-intensity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;Race-specific simulation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taper&lt;/td&gt;
&lt;td&gt;Remainder&lt;/td&gt;
&lt;td&gt;Recovery and tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fpuw55yhhj19vsf0gfcso.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%2Fpuw55yhhj19vsf0gfcso.png" alt="Training Planner page" width="800" height="751"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Training Planner page&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;
&lt;h3&gt;
  
  
  System overview
&lt;/h3&gt;

&lt;p&gt;The app is structured as &lt;strong&gt;frontend + BFF + external APIs&lt;/strong&gt;, with the next-workout-suggestion agent split off as a separate service.&lt;/p&gt;

&lt;p&gt;The frontend doesn't call the Strava API directly. Instead it goes through Next.js API Routes (BFF pattern), which lets me keep access tokens, refresh logic, and a cache layer on the server. The browser never sees secrets, and rate-limit-friendly caching only needs to live in one place.&lt;/p&gt;

&lt;p&gt;There's no database for ride history — Strava is the source of truth. Activity lists, ride details, and segment info are fetched from the Strava API on demand and cached for a short window inside the API Routes (BFF layer). That removes the need for a sync job and doubles as rate-limit protection. The only data I persist app-side is per-user settings, tokens, and weekly plans, all stored in Cloud Storage. No schema migrations, no per-environment ops; for personal-scale use this setup is plenty.&lt;/p&gt;

&lt;p&gt;The agent that suggests and adjusts the next workout combines "LLM + domain logic," and Python has a stronger library ecosystem for that (and I'm more comfortable in Python for ML work), so I split it out as its own FastAPI service on Cloud Run. Details in a future post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│                    Frontend                          │
│  Next.js 16 (App Router) + TypeScript + React 19    │
│  - Auth: NextAuth.js                                 │
│  - Charts: Recharts                                  │
│  - Maps: Leaflet + React-Leaflet                     │
└─────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────┐
│               API Routes (BFF)                       │
│  Next.js API Routes (server-side)                    │
│  - Strava access token management / refresh          │
│  - Cloud Storage read/write (settings/cache/plans)   │
└─────────────────────────────────────────────────────┘
            │                          │
            ▼                          ▼
┌─────────────────────────────┐  ┌─────────────────────────┐
│     External Services        │  │        Storage          │
│  - Strava API (OAuth 2.0)    │  │  - Cloud Storage        │
│  - Nominatim (geocoding)     │  │   (JSON objects only)   │
└─────────────────────────────┘  └─────────────────────────┘
            │
            ▼
┌─────────────────────────────────────────────────────┐
│   Agent Service (covered in a future post)           │
│  Python / FastAPI / Google ADK + Gemini             │
│  - Daily Recommendation                              │
│  - Weekly Plan Generation                            │
└─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The main tech stack:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 16 (App Router) / TypeScript / React 19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visualization&lt;/td&gt;
&lt;td&gt;Recharts (charts) / Leaflet + React-Leaflet (maps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;NextAuth.js (Strava OAuth 2.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;Python / FastAPI / Google ADK + Gemini (covered in a future post)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Cloud Storage (JSON objects only; no relational DB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Cloud Run (Web and Agent deployed as separate services)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Handling Strava API rate limits
&lt;/h3&gt;

&lt;p&gt;According to Strava's &lt;a href="https://developers.strava.com/docs/rate-limits/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;, as of May 2026 the default rate limits are as follows, applied per application. Going over returns &lt;code&gt;429 Too Many Requests&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;15 min&lt;/th&gt;
&lt;th&gt;1 day&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Overall&lt;/td&gt;
&lt;td&gt;200 req&lt;/td&gt;
&lt;td&gt;2,000 req&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-Upload (read-heavy)&lt;/td&gt;
&lt;td&gt;100 req&lt;/td&gt;
&lt;td&gt;1,000 req&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The request count limits weren't really a problem in practice. The bigger constraint is &lt;strong&gt;Connected Athletes = 1&lt;/strong&gt;. With that default, only one user (me) can authorize the app, so I can't easily share it with friends. To raise it, you submit a form via Strava's Developer Program — they approved my request and bumped the limit within a day or two.&lt;/p&gt;
&lt;h2&gt;
  
  
  Notes on development
&lt;/h2&gt;

&lt;p&gt;In the end the split landed at: frontend and BFF (Next.js API Routes) in TypeScript, and the agent (LLM + domain logic) in Python (FastAPI). I write Python day-to-day at work and don't have deep TypeScript experience, but writing the web side in TypeScript wasn't really a problem this time. Two main reasons (with the caveat that this app is small):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You can grok the logic by talking to the LLM.&lt;/strong&gt; Asking "explain the logic of this chart" or "show me where this is implemented" gets me the shape of the logic and the right code to read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tweaking physics constants and formulas is mostly language-agnostic.&lt;/strong&gt; Editing constants like &lt;code&gt;const GRAVITY = 9.81&lt;/code&gt; or basic arithmetic and trig formulas doesn't really depend on the language — as long as you can find the right spot. Iterative optimization code is a different story; for that you do need to understand the implementation itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the other hand, the agent (next-workout suggestion and adjustment, covered separately) leans on the Python LLM ecosystem and my own familiarity, so I kept it separate. I did briefly experiment with using Python for the web backend, but the integration with the frontend created more debugging overhead, so I settled on "TypeScript for the UI-adjacent layer, Python for the LLM/inference layer." Carefully written interface definitions might mitigate that friction. Either way, the right split depends on the app's size and the kind of logic involved, and I'll keep refining my heuristic here.&lt;/p&gt;
&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The source code is available here. The README walks through how to set it up yourself. It runs locally, and I've also made it deployable to Cloud Run so I can access it on the go from a phone. Use the deploy script (&lt;code&gt;deploy.sh.example&lt;/code&gt;) as a starting point and plug in your own project ID.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kikuriyou" rel="noopener noreferrer"&gt;
        kikuriyou
      &lt;/a&gt; / &lt;a href="https://github.com/kikuriyou/PerfRide" rel="noopener noreferrer"&gt;
        PerfRide
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A performance management toolkit for road cyclists
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;PerfRide 🚴&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A performance management toolkit for road cyclists, powered by the &lt;a href="https://developers.strava.com/" rel="nofollow noopener noreferrer"&gt;Strava API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Simulate climbs, optimize race pacing, plan periodized training, and track your fitness — all in one app.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📊 Dashboard&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Connect with Strava to view your recent rides, weekly training summary, and fitness progress chart (CTL / ATL / TSB). Includes per-ride detail with heart rate zones, power profile, and elevation overlay.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🏔️ Climb Simulator&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Predict climbing times based on power, weight, and real segment data. Uses physics-based simulation (air resistance, rolling resistance, drivetrain loss). Search segments by map or use your Strava starred segments.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🎯 Pace Optimizer&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Calculate optimal pacing strategy for time trials based on course elevation profile. Based on the research paper &lt;em&gt;"A numerical design methodology for optimal pacing strategy in the individual time trial discipline of cycling"&lt;/em&gt; (Sports Engineering, 2025).&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;📅 Training Planner&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Generate periodized training plans for your target race…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kikuriyou/PerfRide" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;This post introduced PerfRide, a minimal app built on top of the Strava API. With agentic coding tools like Antigravity, even outside your home language and library ecosystem, you can put together analytics that fit your own needs without much friction. As a bonus, it can save on subscription costs for paid features. For personal projects, I think it's a strong approach — give it a try.&lt;/p&gt;

&lt;p&gt;I'll write up the agent features ("next recommended workout" and "automated weekly plan generation") in a separate post.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published in Japanese on &lt;a href="https://zenn.dev/kikuriyou/articles/2605122159_ride-analysis-app" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cycling</category>
      <category>nextjs</category>
      <category>googlecloud</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
