惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
Stack Overflow Blog
Stack Overflow Blog
美团技术团队
博客园_首页
T
Tailwind CSS Blog
博客园 - 三生石上(FineUI控件)
B
Blog
D
DataBreaches.Net
腾讯CDC
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
U
Unit 42
月光博客
月光博客
V
V2EX
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
The Cloudflare Blog
博客园 - 叶小钗
Y
Y Combinator Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Building a Ride Analysis Web App with Antigravity and the...
Akira Kikusa · 2026-05-16 · via DEV Community

Introduction

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.

While poking around, I noticed Strava offers a public Strava API. That was enough motivation to build my own analytics on top of it.

Concretely, I wanted these features:

  • Ride and training management
    • Review past rides … ①
    • Visualize power and heart-rate distributions by intensity zone … ②
    • View power and heart-rate traces color-coded by intensity zone … ③
    • Show recent training load and fatigue to get a sense of current condition … ④
  • Ride and training planning
    • Build training plans by intensity and goal for upcoming events … ⑤
    • Recommend the next workout based on recent condition (training load and fatigue) … ⑥

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.

Overview of the App: "PerfRide"

The result is a web app called PerfRide that uses the Strava API to manage ride records and training plans for road cycling.

PerfRide 🚴

A performance management toolkit for road cyclists, powered by the Strava API.

Simulate climbs, optimize race pacing, plan periodized training, and track your fitness — all in one app.

Features

📊 Dashboard

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.

🏔️ Climb Simulator

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.

🎯 Pace Optimizer

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

📅 Training Planner

Generate periodized training plans for your target race…

The main features (including some experimental ones) are:

Feature Description Maps to Needs Strava Experimental
Dashboard Activity list, Fitness/Fatigue/Form charts ①, ②, ③, ④ Yes
Weekly Plan This week's workout plan ⑤, ⑥ Partial
Climb Simulator Predicts climb time from power and weight using physics-based math Partial
Pace Optimizer Computes optimal pacing based on a course profile Partial
Training Planner Generates a phased training plan working backward from a goal race No
Settings User parameters: FTP, weight, max HR, etc. No

PerfRide landing page
PerfRide landing page

Dashboard

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:

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

Clicking on a ride drills into details like heart-rate zone distribution, power profile, and elevation chart.

Dashboard page
Dashboard page

Weekly Plan

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.

Weekly plan page
Weekly plan page

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.

About the experimental features

The three features below — Climb Simulator, Pace Optimizer, Training Planner — are currently marked as experimental. 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.

Climb Simulator 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.

Climb Simulator page
Climb Simulator page

Pace Optimizer computes optimal power distribution based on a course profile. It's based on a 2025 paper, "A numerical design methodology for optimal pacing strategy in the individual time trial discipline of cycling". 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.

Pace Optimizer page
Pace Optimizer page

Training Planner generates a phased training plan working backward from a goal race date. The phases are split roughly like this:

Phase Share of time Purpose
Base 35% Build aerobic foundation
Build 1 25% Tempo and threshold work
Build 2 20% VO2max and high-intensity
Peak 10% Race-specific simulation
Taper Remainder Recovery and tuning

Training Planner page
Training Planner page

Architecture

System overview

The app is structured as frontend + BFF + external APIs, with the next-workout-suggestion agent split off as a separate service.

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.

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.

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.

┌─────────────────────────────────────────────────────┐
│                    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                            │
└─────────────────────────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

The main tech stack:

Category Stack
Frontend Next.js 16 (App Router) / TypeScript / React 19
Visualization Recharts (charts) / Leaflet + React-Leaflet (maps)
Auth NextAuth.js (Strava OAuth 2.0)
Agent Python / FastAPI / Google ADK + Gemini (covered in a future post)
Storage Cloud Storage (JSON objects only; no relational DB)
Deployment Cloud Run (Web and Agent deployed as separate services)

Handling Strava API rate limits

According to Strava's official docs, as of May 2026 the default rate limits are as follows, applied per application. Going over returns 429 Too Many Requests.

Category 15 min 1 day
Overall 200 req 2,000 req
Non-Upload (read-heavy) 100 req 1,000 req

The request count limits weren't really a problem in practice. The bigger constraint is Connected Athletes = 1. 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.

Notes on development

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):

  1. You can grok the logic by talking to the LLM. 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.
  2. Tweaking physics constants and formulas is mostly language-agnostic. Editing constants like const GRAVITY = 9.81 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.

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.

Source code

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 (deploy.sh.example) as a starting point and plug in your own project ID.

PerfRide 🚴

A performance management toolkit for road cyclists, powered by the Strava API.

Simulate climbs, optimize race pacing, plan periodized training, and track your fitness — all in one app.

Features

📊 Dashboard

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.

🏔️ Climb Simulator

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.

🎯 Pace Optimizer

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

📅 Training Planner

Generate periodized training plans for your target race…




Wrap-up

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.

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


This article was originally published in Japanese on Zenn.