Technical Documentation: yihong0618/running_page
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
Note
clone or Fork before vercel 404 need to pull the latest code
python in README means python3 python
use v2.0 need change vercel setting from gatsby to vite
2023.09.26 garmin need secret_string(and in Actions) get
bashpython run_page/get_garmin_secret.py ${email} ${password} # if cn python run_page/get_garmin_secret.py ${email} ${password} --is-cn2024.09.29: Added
Elevation Gainfield, If you forked the project before this update, please run the following command:- To resolve errors:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: activities.elevation_gain - If you don't have a local environment, set
RUN_TYPEtodb_updaterin the.github/workflows/run_data_sync.ymlfile once then change back.
bashpython run_page/db_updater.py- For old data: To include
Elevation Gainfor past activities, perform a full reimport. - To show the 'Elevation Gain' column, modify
SHOW_ELEVATION_GAINinsrc/utils/const.ts - note:
Elevation Gainmay be inaccurate. You can use Strava's "Correct Elevation" or Garmin's "Elev Corrections" feature for more precise data.
- To resolve errors:
This project now uses MapCN (free) by default. If you choose to use Mapbox, please get your own token. Do not use the project maintainer's token - check this issue and issue #1055
[ Create a personal running home page ](https://yihong.run)
[Runner's Page Show](https://github.com/yihong0618/running_page/issues/12)
Running page runners
How it works
Features
- GitHub Actions automatically synchronizes running data and generates page displays
- Support for Vercel (recommended) and GitHub Pages automated deployment
- React Hooks
- Mapbox for map display
- Supports most sports apps such as nike strava...
- Support for metric and imperial units
- Terminal UI (TUI) for browsing activities locally
automatically backup gpx data for easy backup and uploading to other software.
Note: If you don't want to make the data public, you can choose strava's fuzzy processing, or private repositories.
Support
- Garmin
- Garmin-CN
- New Way To Sync Nike Run Club
- Nike Run Club
- Strava
- GPX
- TCX
- FIT
- Garmin-CN_to_Garmin(Sync Garmin-CN activities to Garmin Global)
- Nike_to_Strava(Using NRC Run, Strava backup data)
- Tcx_to_Strava(upload all tcx data to strava)
- Tcx_to_Garmin(upload all tcx data to Garmin)
- Gpx_to_Strava(upload all gpx data to strava)
- Garmin_to_Strava(Using Garmin Run, Strava backup data)
- Strava_to_Garmin(Using Strava Run, Garmin backup data)
- COROS
- iGPSPORT
- Joyrun
- Komoot
- Onelap
- Intervals.icu
Download
Clone or fork the repo.
git clone https://github.com/yihong0618/running_page.git --depth=1Installation and testing (node >= 20 python >= 3.12)
pip3 install -r requirements.txt
npm install corepack && corepack enable && pnpm install
pnpm developOpen your browser and visit http://localhost:5173/
Strava Web Sync
For when your Strava API application is
inactive(all OAuth2 requests return 403), you can sync activities through Strava's web endpoints instead.
# Sync locally (last 7 days by default)
python run_page/strava_web_sync.py <JWT> --days 7
# Runs only
python run_page/strava_web_sync.py <JWT> --days 7 --only-runGetting the JWT:
- Log in to strava.com in your browser
- DevTools (F12) → Application → Cookies →
https://www.strava.com - Copy the value of
strava_remember_token(a longeyJ...JWT)
CI setup:
- Set
RUN_TYPEtostrava_webin the workflow - Add GitHub Secret
STRAVA_JWT(the JWT value) - Optional Variable
STRAVA_WEB_DAYS(default: 7)
⚠️ The JWT expires in ~30 days. Refresh
STRAVA_JWTby re-copying it from the browser when it does.
TUI (Terminal UI)
You can browse your activities in the terminal using the built-in Textual TUI.
# Using make
make tui
# Or run directly with uv
uv run run_page
# Or specify a custom activities.json path
uv run run_page /path/to/your/activities.jsonKeyboard shortcuts inside TUI:
1/2– Switch between List and Stats views←/→– Change year filter↑/↓– Navigate activitiesy– Cycle through yearst– Cycle through activity typesq– Quit
Docker
# NRC
docker build -t running_page:latest . --build-arg app=NRC --build-arg nike_refresh_token=""
# Garmin
docker build -t running_page:latest . --build-arg app=Garmin --build-arg secret_string=""
# Garmin-CN
docker build -t running_page:latest . --build-arg app=Garmin-CN --build-arg secret_string=""
# Strava
docker build -t running_page:latest . --build-arg app=Strava --build-arg client_id="" --build-arg client_secret="" --build-arg refresh_token=""
# Nike_to_Strava
docker build -t running_page:latest . --build-arg app=Nike_to_Strava --build-arg nike_refresh_token="" --build-arg client_id="" --build-arg client_secret="" --build-arg refresh_token=""
# Keep
docker build -t running_page:latest . --build-arg app=Keep --build-arg keep_phone_number="" --build-arg keep_password=""
# run
docker run -itd -p 80:80 running_page:latest
# visit
Open your browser and visit localhost:80Imperial Units
- add
--build-arg VITE_USE_IMPERIAL=truetodocker build ... - add
--units imperialflag to eachpython3 run_page/gen_svg.py ...command in the Dockerfile
Local sync data
Modifying Mapbox token
Security Notice: The Mapbox token has been migrated from
src/themes/classic/utils/const.tstoconfig.ymlfor better security management.For GitHub Actions / Automated Deployment:
- Go to your repository's Settings → Secrets and variables → Actions
- Create a new secret named
MAPBOX_TOKENwith your Mapbox token value- The build process automatically injects this token during GitHub Actions workflow execution
- You should NOT commit your token to the repository
Priority Order:
- GitHub Actions Secret (
MAPBOX_TOKENenv var) takes priority- Falls back to
config.ymlmapbox_token if secret is not set- Defaults to empty string if neither is available
Set your Mapbox token in one of these ways:
Option 1: GitHub Actions Secret (Recommended for GitHub Pages)
# Add MAPBOX_TOKEN to your repository secrets
# No changes needed to config.yml - it will use the secret automaticallyOption 2: Local Development with config.yml
# config.yml
mapbox_token: 'pk.eyJ1...your-token-here'Option 3: Environment Variable (Local Development)
export VITE_MAPBOX_TOKEN='pk.eyJ1...your-token-here'
pnpm developImportant: Do not use the project maintainer's token - check this issue and issue #1055 for security and rate limit concerns.
Change Default Map Tile Style (Classic Theme)
If using the classic theme, you can customize the map tile style in the classic theme's configuration. The dashboard theme uses Mapbox by default (configured via
config.yml).
const MAP_TILE_VENDOR = 'mapcn'; // Default (free!)
const MAP_TILE_STYLE = 'osm-bright';
const MAP_TILE_ACCESS_TOKEN = ''; // Not needed for MapCNCurrently supported MAP_TILE_VENDOR options include:
- "mapcn" - MapCN map services (FREE, no token required) ⭐ DEFAULT & RECOMMENDED
- "mapbox" - Mapbox map services (requires token, has costs)
- "maptiler" - MapTiler map services (free tier available)
- "stadiamaps" - Stadia Maps services (free tier available)
Using MapCN (Default)
MapCN is a free map tile provider and is now the default. No configuration needed!
Available MapCN styles:
- osm-bright - Light OpenStreetMap style (default)
- osm-liberty - Alternative light style
- dark-matter - Dark theme style
**No access token required! ** 🎉
Attribution
When using MapCN (Carto Basemaps), please ensure you comply with their attribution requirements:
- Map tiles: © CARTO
- Map data: © OpenStreetMap contributors
The project template already includes appropriate attribution in the map display.
Using Other Providers
If you prefer Mapbox, MapTiler, or Stadia Maps, you can change the vendor:
const MAP_TILE_VENDOR = 'mapbox'; // or 'maptiler' or 'stadiamaps'
const MAP_TILE_STYLE = 'dark-v10'; // style for chosen vendor
const MAP_TILE_ACCESS_TOKEN = 'your_access_token_here';Each MAP_TILE_VENDOR provides multiple MAP_TILE_STYLE options. Ensure the style matches your selected vendor. For available MAP_TILE_STYLE names, refer to the classic theme's map configuration.
When using "mapbox", "maptiler" or "stadiamaps", you must configure an ACCESS_TOKEN. The default token may cause quota limit issues if not replaced.
- Mapbox: Register at https://www.mapbox.com/ (Has usage costs)
- MapTiler: Register at https://cloud.maptiler.com/auth/widget (Free tier available)
- Stadia Maps: Sign up at https://client.stadiamaps.com/signup/ (Free tier available)
Theme System (3.0)
Running Page 3.0 introduces a pluggable theme architecture. Built-in themes include Dashboard (modern single-page layout) and Classic (original multi-page layout).
Switching Themes
Edit config.yml and rebuild:
# dashboard | classic | custom
theme_preset: classicFor detailed architecture, theme descriptions, custom theme creation, and the shared core layer API, see docs/theme-system.md.
Custom your page (3.0)
All personalization is done through config.yml at the project root. Edit this file directly — no code changes needed.
# config.yml
mapbox_token: 'your-token-here' # https://account.mapbox.com
avatar: 'https://...' # Profile avatar URL
locale: zh # zh | en
theme: dark # system | light | dark
theme_preset: dashboard # dashboard | classic | custom
goals:
Run:
yearly: 2000 # Annual distance target (km)
monthly: 150 # Monthly distance target (km)
weekly: 35 # Weekly distance target (km)
unit: distance # distance (km) | time (minutes)- To use Google Analytics, you need to modify the configuration in the
src/utils/analytics.tsfile (if present).
const USE_GOOGLE_ANALYTICS = false;
const GOOGLE_ANALYTICS_TRACKING_ID = '';privacy protection,setting flowing env:
# ignore distance for each polyline start and end.
IGNORE_START_END_RANGE = 200
# ignore meters for each point in below polyline.
IGNORE_RANGE = 200
# a polyline include point you want to ignore.
IGNORE_POLYLINE = ktjrFoemeU~IorGq}DeB
# Do filter before saving to database, you will lose some data, but you can protect your privacy, when you using public repo. enable for set 1, disable via unset.
IGNORE_BEFORE_SAVING =You can using Google map Interactive Polyline Encoder Utility, to making your IGNORE_POLYLINE.
Indoor routes
Activities without GPS keep their original route, subtype and location data.
When selected on the dashboard or classic map, they display the most recent
earlier activity with a usable GPS route and a notice identifying that route.
If no earlier mapped activity exists, no substitute route is shown. This fallback
is only for display; it does not change activity distances, exports or the database.
Configured privacy filters still apply to exported routes.
For SVG posters generated from the database, the grid layout omits activities
whose stored route is empty or NULL. Other layouts retain their dates and distances
in activity totals; they do not borrow the frontend's substitute route.
Previously generated routes already stored in run_page/data.db cannot reliably be
distinguished from source routes. Reimport the original activity data to replace them.
Download your running data
Download your running data and do not forget to generate svg in
totalpage
GPX
Make your GPX data
Copy all your gpx files to GPX_OUT or new gpx files
python run_page/gpx_sync.pyTCX
Make your TCX data
Copy all your tcx files to TCX_OUT or new tcx files
python run_page/tcx_sync.pyFIT
Make your FIT data
Copy all your tcx files to FIT_OUT or new fit files
python run_page/fit_sync.pyGarmin
Get your Garmin data
- If you only want to sync
type runningadd args --only-run - If you only want
tcxfiles add args --tcx - If you only want
fitfiles add args --fit - If you are using Garmin as a data source, it is recommended that you pull the code to your local environment to run and obtain the Garmin secret.
The Python version must be >=3.12
Get Garmin Secret
Enter the following command in the terminal
# to get secret_string
python run_page/get_garmin_secret.py ${your email} ${your password}Execute Garmin Sync Script
Copy the Secret output in the terminal,If you are using GitHub, please configure GARMIN_SECRET_STRING in GitHub Action.
# use this secret_string
python run_page/garmin_sync.py ${secret_string}example:
python run_page/get_garmin_secret.py xxxxxxxxxxxonly-run:
python run_page/garmin_sync.py xxxxxxxxxxxxxx(secret_string) --only-runGarmin-CN(China)
Get your Garmin-CN data
- If you only want to sync
type runningadd args --only-run - If you only want
tcxfiles add args --tcx - If you only want
fitfiles add args --fit - If you are using Garmin as a data source, it is recommended that you pull the code to your local environment to run and obtain the Garmin secret.
The Python version must be >=3.12
Get Garmin CN Secret
Enter the following command in the terminal
# to get secret_string
python run_page/get_garmin_secret.py ${your email} ${your password} --is-cnExecute Garmin CN Sync Script
Copy the Secret output in the terminal,If you are using GitHub, please configure GARMIN_SECRET_STRING_CN in GitHub Action.
example:
python run_page/garmin_sync.py xxxxxxxxx(secret_string) --is-cnonly-run:
python run_page/garmin_sync.py xxxxxxxxxxxxxx(secret_string) --is-cn --only-runGarmin-CN to Garmin
Sync your Garmin-CN data to Garmin
- If you only want to sync
type runningadd args --only-run
The Python version must be >=3.12
Get Garmin CN Secret
Enter the following command in the terminal
# to get secret_string
python run_page/get_garmin_secret.py ${your email} ${your password} --is-cnGet Garmin Secret
Enter the following command in the terminal
# to get secret_string
python run_page/get_garmin_secret.py ${your email} ${your password}Sync Garmin CN to Garmin
Enter the following command in the terminal
# to sync garmin-cn to garmin-global
python run_page/garmin_sync_cn_global.py ${garmin_cn_secret_string} ${garmin_secret_string}Nike Run Club New
Get your Nike Run Club data
Please note:Due to the discontinuation of Nike Run Club in mainland China, you can only log in through a VPN. Before starting, please ensure that you are using a global non-mainland China proxy, allowing you to access
nike.cominstead ofnike.com.cn, as shown in the following image.
Sign in/Sign up NikeRunClub account
after successful login,openF12->Application->localstorage-> copy the content of "access_token" from the value of key
https://www.nike.com.Execute in the root directory , you should be able to see the image below, and then you can log into your account on the mobile as usual:
bashpython run_page/nike_sync.py ${access_token}if you want to automate the submission of NRC data, you can refer to issue692.
If you've previously synced activities and want to continue syncing new ones, with
--continue-syncargsbashpython run_page/nike_sync.py ${access_token} --continue-sync
Nike Run Club
Get your Nike Run Club data
Please note: When you choose to deploy running_page on your own server, due to Nike has blocked some IDC's IP band, maybe your server cannot sync Nike Run Club's data correctly and display
403 error, then you have to change another way to host it.
Get Nike's refresh_token
ALL need to do outside GFW.
Login from this website, open F12 -> XHR -> get the
refresh_tokenfrom login api.copy this
refresh_tokenand use it in GitHub Secrets or in command lineExecute in the root directory:
python run_page/nike_sync.py ${nike refresh_token}example:
python run_page/nike_sync.py eyJhbGciThiMTItNGIw******Strava
Get your Strava data
[!NOTE]
Strava updated its Developer Program in June 2026. If you use Strava as the data source, or upload activities to Strava before syncing, check your app tier in the Strava API settings dashboard. Standard Tier developers need a Strava subscription to access the API; existing Standard Tier developers are affected from June 30, 2026. See Strava's announcement for details.
Sign in/Sign up Strava account
Open after successful Signin Strava Developers -> Create & Manage Your App
Create
My API Application: Enter the following informationCreated successfully:
Use the link below to request all permissions: Replace
${your_id}in the link withMy API ApplicationClient IDplaintexthttps://www.strava.com/oauth/authorize?client_id=${your_id}&response_type=code&redirect_uri=http://localhost/exchange_token&approval_prompt=force&scope=read_all,profile:read_all,activity:read_all,profile:write,activity:writeExample:
plaintexthttps://www.strava.com/oauth/authorize?client_id=115321&response_type=code&redirect_uri=http://localhost/exchange_token&approval_prompt=force&scope=read_all,profile:read_all,activity:read_all,profile:write,activity:writeGet the
codevalue in the linkexample:
bashhttp://localhost/exchange_token?state=&code=1dab37edd9970971fb502c9efdd087f4f3471e6e&scope=read,activity:write,activity:read_all,profile:write,profile:read_all,read_allcodevalue:bash1dab37edd9970971fb502c9efdd087f4f3471e6Use
Client_id、Client_secret、Codegetrefresh_token: Execute inTerminal/iTermbashcurl -X POST https://www.strava.com/oauth/token \ -F client_id=${Your Client ID} \ -F client_secret=${Your Client Secret} \ -F code=${Your Code} \ -F grant_type=authorization_codeexample:
bashcurl -X POST https://www.strava.com/oauth/token \ -F client_id=12345 \ -F client_secret=b21******d0bfb377998ed1ac3b0 \ -F code=d09******b58abface48003 \ -F grant_type=authorization_codeSync
StravadataThe first time you synchronize Strava data you need to change line 12 of the code False to True in strava_sync.py, and then change it to False after it finishes running.
If you only want to synctype runningadd args --only-runbashpython run_page/strava_sync.py ${client_id} ${client_secret} ${refresh_token}References:
TCX_to_Strava
upload all tcx files to strava
follow the strava steps
copy all your tcx files to TCX_OUT
Execute in the root directory:
bashpython run_page/tcx_to_strava_sync.py ${client_id} ${client_secret} ${strava_refresh_token}example:
bashpython run_page/tcx_to_strava_sync.py xxx xxx xxx or python run_page/tcx_to_strava_sync.py xxx xxx xxx --allif you want to all files add args
--all
TCX_to_Garmin
upload all tcx files to garmin
follow the garmin steps
copy all your tcx files to TCX_OUT
Execute in the root directory:
bashpython3 run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --is-cnexample:
bashpython run_page/tcx_to_garmin_sync.py xxx --is-cn or Garmin Global python run_page/tcx_to_garmin_sync.py xxxif you want to all files add args
--all
GPX_to_Strava
upload all gpx files to strava
follow the strava steps
copy all your gpx files to GPX_OUT
Execute in the root directory:
bashpython run_page/gpx_to_strava_sync.py ${client_id} ${client_secret} ${strava_refresh_token}example:
bashpython run_page/gpx_to_strava_sync.py xxx xxx xxx or python run_page/tcx_to_strava_sync.py xxx xxx xxx --allif you want to all files add args
--all
Nike_to_Strava
Get your Nike Run Club data and upload to strava
- follow the nike and strava steps
- Execute in the root directory:
python run_page/nike_to_strava_sync.py ${nike_refresh_token} ${client_id} ${client_secret} ${strava_refresh_token}example:
python run_page/nike_to_strava_sync.py eyJhbGciThiMTItNGIw****** xxx xxx xxxGarmin_to_Strava
Get your Garmin data and upload to strava
finish garmin and strava setup
Execute in the root directory:
bashpython run_page/garmin_to_strava_sync.py ${client_id} ${client_secret} ${strava_refresh_token} ${garmin_secret_string} --is-cne.g.
bashpython run_page/garmin_to_strava_sync.py xxx xxx xxx xx
Strava_to_Garmin
Get your Strava data and upload to Garmin
finish garmin and strava setup, at the same time, you need to add additional strava config in GitHub Actions secret:
secrets.STRAVA_EMAIL,secrets.STRAVA_PASSWORD,secrets.STRAVA_JWT, Note:STRAVA_JWTis superior toSTRAVA_EMAILandSTRAVA_PASSWORD,STRAVA_JWTis thestrava_remember_tokenfield of the Strava web login Cookie.Execute in the root directory:
bashpython run_page/strava_to_garmin_sync.py ${{ secrets.STRAVA_CLIENT_ID }} ${{ secrets.STRAVA_CLIENT_SECRET }} ${{ secrets.STRAVA_CLIENT_REFRESH_TOKEN }} ${{ secrets.GARMIN_SECRET_STRING }} ${{ secrets.STRAVA_EMAIL }} ${{ secrets.STRAVA_PASSWORD }}if your garmin account region is China, you need to execute the command:
bashpython run_page/strava_to_garmin_sync.py ${{ secrets.STRAVA_CLIENT_ID }} ${{ secrets.STRAVA_CLIENT_SECRET }} ${{ secrets.STRAVA_CLIENT_REFRESH_TOKEN }} ${{ secrets.GARMIN_SECRET_STRING_CN }} ${{ secrets.STRAVA_EMAIL }} ${{ secrets.STRAVA_PASSWORD }} ${{ secrets.STRAVA_JWT }} --is-cnIf you want to add Garmin Device during sync, you should add
--use_fake_garmin_deviceargument, this will add a Garmin Device (Garmin Forerunner 245 by default, and you can change device ingarmin_device_adaptor.py) in synced Garmin workout record, this is essential when you want to sync the workout record to other APP like Keep, JoyRun etc.the final command will be:
bashpython run_page/strava_to_garmin_sync.py ${{ secrets.STRAVA_CLIENT_ID }} ${{ secrets.STRAVA_CLIENT_SECRET }} ${{ secrets.STRAVA_CLIENT_REFRESH_TOKEN }} ${{ secrets.GARMIN_SECRET_STRING_CN }} ${{ secrets.STRAVA_EMAIL }} ${{ secrets.STRAVA_PASSWORD }} ${{ secrets.STRAVA_JWT }}--use_fake_garmin_deviceps: when initializing for the first time, if you have a large amount of strava data, some data may fail to upload, just retry several times.
COROS
Get your COROS data
- If you only want to sync
type runningadd args --only-run
Enter the following command in the terminal
python run_page/coros_sync.py 'your coros account' 'your coros password'Modify `run_data_sync.yml` `env.RUN_TYPE: coros`
Set the Coros account information in github action
- configure the
COROS_ACCOUNT,COROS_PASSWORD
iGPSPORT
Get your iGPSPORT data
Enter the following command in the terminal
python3 run_page/igpsport_sync.py 'your igpsport phone' 'password' --with-gpxYou can replace with-gpx with with-fit to acquire data in fit format.
Joyrun
Get your Joyrun data
Enter the following command in the terminal
python3 run_page/joyrun_sync.py 'your joyrun phone' 'verication code' --with-gpxYou can replace with-gpx with with-tcx to acquire data in tcx format.
Komoot
Get your Komoot data
Enter the following command in the terminal
python3 run_page/komoot_sync.py 'your komoot email' 'password' --with-gpx| Parameter | Description |
|---|---|
mail |
Login using specified email address |
password |
Use provided password and skip interactive prompt |
-n, --anonymous |
Skip authentication, no interactive prompt (valid only with -d) |
--with-gpx |
Download all tours as GPX |
-r, --remove-deleted |
Remove GPX files (from --output dir) without corresponding tour in Komoot (deleted and previous versions) |
--start-date=YYYY-MM-DD |
Filter tours on or after specified date |
--end-date=YYYY-MM-DD |
Filter tours on or before specified date |
-e, --no-poi |
Do not include highlights as POIs |
Onelap
Get your Onelap data
Enter the following command in the terminal
python3 run_page/onelap_sync.py 'your onelap phone' 'password' --with-fitIntervals.icu
Get your Intervals.icu data
Sync running activities from Intervals.icu. Downloads original FIT/GPX files.
Log in to Intervals.icu, go to Settings → Developer Settings to find your Athlete ID and create an API Key.
Execute in the root directory:
python run_page/intervals_icu_sync.py ${athlete_id} ${api_key}If you want to sync all historical data (default is last 6 months):
python run_page/intervals_icu_sync.py ${athlete_id} ${api_key} --allTo specify a custom start date:
python run_page/intervals_icu_sync.py ${athlete_id} ${api_key} --start-date 2024-01-01If your data comes from a Huawei/China device using the GCJ-02 coordinate system, add --gcj02 to fix the coordinate offset (converts GCJ-02 to WGS-84 in downloaded FIT/GPX/TCX files):
python run_page/intervals_icu_sync.py ${athlete_id} ${api_key} --gcj02GitHub Actions
- Change
RUN_TYPEtointervals_icuin therun_data_sync.ymlfile - Add
INTERVALS_ICU_ATHLETE_IDandINTERVALS_ICU_API_KEYto your GitHub repository secrets
Total Data Analysis
Running data display
- Generate SVG data display
- Display of results:Click to view、Click to view
/* Detailed source-code truncated for AI context efficiency. */Generate github style svg show
python run_page/gen_svg.py --from-db --title "${{ env.TITLE }}" --type github --athlete "${{ env.ATHLETE }}" --special-distance 10 --special-distance2 20 --special-color yellow --special-color2 red --output assets/github.svg --use-localtime --min-distance 0.5If you want to change the display color of empty data(only github style), please use --empty-data-color:
python run_page/gen_svg.py --from-db --title "${{ env.TITLE }}" --type github --athlete "${{ env.ATHLETE }}" --special-distance 10 --special-distance2 20 --special-color yellow --special-color2 red --output assets/github.svg --use-localtime --min-distance 0.5 ----empty-data-color greyGenerate grid style svg show
python run_page/gen_svg.py --from-db --title "${{ env.TITLE_GRID }}" --type grid --athlete "${{ env.ATHLETE }}" --output assets/grid.svg --min-distance 10.0 --special-color yellow --special-color2 red --special-distance 20 --special-distance2 40 --use-localtimepython run_page/gen_svg.py --from-db --type circular --use-localtimeGenerate a "Runner Month of Life" visualization as if your entire life consisted of only 1000 months.
python3 run_page/gen_svg.py --from-db --type monthoflife --birth 1989-03 --special-distance 10 --special-distance2 20 --special-color '#f9d367' --special-color2 '#f0a1a8' --output assets/mol.svg --use-localtime --athlete yihong0618 --title 'Runner Month of Life'Generate your share png using GPT gpt-image-1(last one)
python run_page/auto_share_sync.py --api_key xxxxxxxxx --base_url xxxxxxxxIf you want to generate a share png for a date
python run_page/auto_share_sync.py --api_key xxxxxxxxx --base_url xxxxxxxx --date 2023-11-11If you want to auto gen in ci you can refer this link
For more display effects, see:
https://github.com/flopp/GpxTrackPoster
server(recommendation vercel)
Use Vercel to deploy
vercel connects to your GitHub repo.
import repo
Awaiting completion of deployment
Visits
Use Cloudflare to deploy
Login to Cloudflare dashboard.
Click
Workers & Pageson the left side.Click
Create applicationand selectPagestab, connect your GitHub account and selectrunning_pageRepo, then clickBegin setup.Scroll down to
Build settings, chooseCreate React AppfromFramework preset, and setBuild output directorytodist.Scroll down, click
Environment variables (advanced), then add a variable like the below:Variable name =
PYTHON_VERSION, Value =3.12Click
Save and Deploy
Deploy to GitHub Pages
Go to repository's
Settings -> GitHub Pages -> Source, chooseGitHub ActionsGo to the repository's
Actions -> Workflows -> All Workflows, chooseRun Data Syncfrom the left panel, and clickRun workflow.- The
Run Data Syncwill update data and then trigger thePublish GitHub Pagesworkflow - Make sure the workflow runs without errors.
- The
Open your website to check on the results
- note if the website doesn't reflect the latest data, please refresh it by
F5. - Some browsers (e.g. Chrome) won't refresh if there is a cache, you then need to use
Ctrl+F5(Windows) orShift+Cmd+r(Mac) to force clearing the cache and reload the page.
- note if the website doesn't reflect the latest data, please refresh it by
make sure you have write permissions in Workflow permissions settings.
If you want to deploy your running_page to xxx.github.io instead of xxx.github.io/running_page or redirect your GitHub Pages to a custom domain, you need to do three things:
- Rename your forked running_page repository to
xxx.github.io, where xxx is your GitHub username - Modify the Build module in gh-pages.yml, remove
${{ github.event.repository.name }}and change torun: PATH_PREFIX=/ pnpm build - In
config.yml, set your custom domain URL or leave empty
- Rename your forked running_page repository to
GitHub Actions
Modifying information in GitHub Actions
Actions source code
The following steps need to be taken
change to your app type and info
Add your secret in repo Settings > Secrets (add only the ones you need).
My secret is as follows
Go to repository's
Settings -> Code and automation -> Actions ->General, Scroll to the bottom, findWorkflow permissions, choose the first optionRead and write permissions, clickSave.
Shortcuts
Automate with iOS Shortcuts
Take the keep app as an example. Close the app after running, and then automatically trigger Actions to update the data.
Get actions id (need to apply token)
bashcurl https://api.github.com/repos/yihong0618/running_page/actions/workflows -H "Authorization: token d8xxxxxxxxxx" # change to your configBinding shortcut instruction
Get it via icloud running-page-shortcuts-template
Modify the dictionary parameters in the following figure
Automation
Storing Data Files in GitHub Cache
Storing Data Files in GitHub Cache
When SAVE_DATA_IN_GITHUB_CACHE is set to true in the run_data_sync.yml file, the script can store fetched and intermediate data files in the GitHub Action Cache. This helps keep your GitHub commit history and directory clean.
If you are deploying using GitHub Pages, it is recommended to set this value to true, and set BUILD_GH_PAGES to true.
Fit file
supported manufacturer:
- Garmin
- magene
TODO
- Complete this document.
- Support Garmin, Garmin China
- support for nike+strava
- Support English
- Refine the code
- add new features
- tests
- support the world map
- support multiple types, like hiking, biking~
- support for Zeep life
Contribution
- Any Issues welcome.
- PR need to disscuss first(For LLM year 2026)
- You can PR share your Running page in README I will merge it.
Before submitting PR:
- Format Python code with
black(black .)
Special thanks
- @flopp great repo GpxTrackPoster
- @danpalmer UI design
- @shaonianche icon design and doc
- @geekplux Friendly help and encouragement, refactored the whole front-end code, learned a lot
- @MFYDev Wiki
Recommended Forks
- @gongzili456 for motorcycle version
- @ben-29 for different types support
- @geekplux for different types support
Support
Just enjoy it~
Raycast Extension
FAQ
Strava API limit
https://www.strava.com/settings/api
https://developers.strava.com/docs/#rate-limitingplaintextStrava API Rate Limit Exceeded. Retry after 100 seconds Strava API Rate Limit Timeout. Retry in 799.491622 secondsvercel git ignore gh-pages:
you can change settings -> build -> Ignored Build Step -> Custom command
bashif [ "$VERCEL_GIT_COMMIT_REF" != "gh-pages" ]; then exit 1; else exit 0;
Theme System (`docs/theme-system.md`)
主题系统 (3.0)
Running Page 3.0 引入了可插拔的主题架构。你可以切换内置主题或创建自己的主题,无需修改源代码。
工作原理
主题位于 src/themes/<name>/ 目录下,每个主题导出一个 React 组件。应用入口 (src/App.tsx) 维护一个主题注册表,将主题名称映射到懒加载的组件:
// src/App.tsx
const themes: Record<string, React.LazyExoticComponent<React.ComponentType>> = {
dashboard: lazy(() => import('./themes/dashboard')),
classic: lazy(() => import('./themes/classic')),
// 在此添加自定义主题
}构建时,config.yml 中的 theme_preset 决定加载哪个主题。所有主题共享核心层 (src/core/)——类型、i18n 翻译、活动数据钩子和语言工具。
内置主题
Dashboard
Dashboard 主题是为跑者设计的现代化单页布局,提供丰富的小部件:
- 统计卡片 — 年度/月度/周度进度条,含连续运动追踪
- 热力图 — GitHub 风格的活动网格,每日详情,支持导出 PNG
- 活动日志 — 分页表格,支持距离筛选(10km+、20km+、40km+),运动类型图标
- 路线地图 — 基于 Mapbox 的路线可视化,点击活动高亮轨迹
- 轨迹墙 — 全页轨迹网格,智能聚类(按起终点),支持导出 PNG
- 中国地图 — 省份级活动热力图,点击筛选
- 个人最佳 — 自动检测 PR(5K、10K、半马、全马)
- 日历小部件 — 月度日历,气泡显示距离
- 深色/浅色模式 — 跟随系统或手动切换
主要特性:
- 所有数据在一个页面展示(无需路由跳转)
- 点击任意活动即可在地图和日志中查看
- 热力图和轨迹墙支持导出为 PNG 图片
- 响应式两栏网格布局
Classic
Classic 主题保留了原始的多页面布局,每个视图有独立路由。使用 react-router-dom 导航和 react-map-gl 地图。如果你更喜欢原始风格,或从 v2.x 升级,推荐选择此主题。
注意: 如果你从 v2.x 升级,可在
config.yml中设置theme_preset: classic。
创建自定义主题
- 在
src/themes/<your-theme>/下创建新目录,例如src/themes/minimal/ - 创建
index.tsx导出默认 React 组件 - 使用共享核心层获取数据和 i18n:
// src/themes/minimal/index.tsx
import { getActivityData } from '@/hooks/useActivities'
import { useTheme } from '@/hooks/useTheme'
import { useLocale } from '@/hooks/useLocale'
import type { Activity } from '@/types'
export default function Minimal() {
const activities = getActivityData() as Activity[]
const { t } = useLocale()
// ... 你的自定义布局
}- 在
src/App.tsx中注册你的主题:
const themes = {
dashboard: lazy(() => import('./themes/dashboard')),
classic: lazy(() => import('./themes/classic')),
minimal: lazy(() => import('./themes/minimal')), // 添加这行
}- 在
config.yml中设置theme_preset: minimal
共享核心层 API
所有主题可用的钩子和工具:
| 模块 | 导出 |
|---|---|
@/hooks/useActivities |
getActivityData()、useFilteredActivities()、getAvailableYears()、formatDistance()、formatPace()、formatDuration()、parseMovingTime()、extractProvince() |
@/hooks/useLocale |
useLocale() → { t, locale } 用于 i18n |
@/hooks/useTheme |
useTheme() → { dark, toggle } 用于深/浅色模式 |
@/types |
Activity、SportFilter 类型 |
@/config |
MAPBOX_TOKEN、AVATAR、GOALS、DEFAULT_LOCALE、THEME_PRESET |
Dashboard 主题的组件 (src/components/) 也可复用 —— 如需要可在自定义主题中导入。
--- METRICS ---
- Files Extracted: 2
- Estimated Token Budget: ~13031 tokens
- Canonical Reference: https://codewiki.google/github.com/yihong0618/running_page