Sync Strava activities, normalize and aggregate them, and generate GitHub-style calendar heatmaps (SVG) per workout type/year. A single preview is shown in this README, with the full interactive view on GitHub Pages.
- Live site: Interactive Heatmaps
- For forks, this link is auto-updated to your own GitHub Pages URL after your first successful sync run.
Preview:
No local clone is required for this setup. You can run everything from GitHub Actions. Clone locally only if you want to customize or run the scripts yourself.
-
Fork this repo to your account: Fork this repository
-
Create a Strava API application at Strava API Settings. Set Authorization Callback Domain to
localhost, then copy:STRAVA_CLIENT_IDSTRAVA_CLIENT_SECRET
-
Generate a refresh token via OAuth (the token shown on the Strava API page often does not work). Open this URL in your browser (replace
CLIENT_IDwith the Client ID value from your Strava API application page):https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://localhost/exchange_token&approval_prompt=force&scope=read,activity:read_allAfter approval you’ll be redirected to a
localhostURL that won’t load. That’s expected. Example redirect URL:http://localhost/exchange_token?state=&code=12345&scope=read,activity:read_allCopy the value of the
codequery parameter from the failed URL (in this example,12345) and exchange it. Run this command in a terminal app (macOS/Linux Terminal, or Windows PowerShell/Command Prompt). Use theCLIENT_IDandCLIENT_SECRETvalues from your Strava API application page in Step 2.curl -X POST https://www.strava.com/oauth/token \ -d client_id=CLIENT_ID \ -d client_secret=CLIENT_SECRET \ -d code=THE_CODE_FROM_THE_URL \ -d grant_type=authorization_code
Copy the
refresh_tokenfrom the response. -
Add GitHub secrets (repo → Settings → Secrets and variables → Actions):
STRAVA_CLIENT_IDSTRAVA_CLIENT_SECRETSTRAVA_REFRESH_TOKEN(from the OAuth exchange above)
-
Enable GitHub Pages (repo → Settings → Pages):
- Under Build and deployment, set Source to GitHub Actions.
-
Run Sync Strava Heatmaps:
- If GitHub shows an Enable workflows button in Actions, click it first.
- Go to Actions → Sync Strava Heatmaps → Run workflow.
- The same workflow is also scheduled in
.github/workflows/sync.yml(daily at06:00 UTC).
-
Open your live site at
https://<your-username>.github.io/<repo-name>/after deploy finishes. This workflow will:- sync raw activities into
activities/raw/(local-only; not committed) - normalize + merge into
data/activities_normalized.json(persisted history) - aggregate into
data/daily_aggregates.json - generate SVGs in
heatmaps/ - build
site/data.json
- sync raw activities into
By default, all Strava activity types are included automatically when you run the workflow.
To narrow the dashboard to specific activity types:
- Edit
config.yamlin your fork. - Set
activities.include_all_types: false. - Set
activities.typesto only the types you want. - Run Sync Strava Heatmaps again.
Example:
activities:
include_all_types: false
types:
- Run
- Ride
- WeightTrainingEverything in this section is optional. Defaults work without changes.
Base settings live in config.yaml.
Key options:
sync.start_date(optionalYYYY-MM-DDlower bound for history)sync.lookback_years(optional rolling lower bound; used only whensync.start_dateis unset)sync.recent_days(sync recent activities even while backfilling)sync.resume_backfill(persist cursor to continue older pages across days)activities.types(featured activity types shown first in UI)activities.include_all_types(include non-featured Strava types; defaulttrue)activities.group_other_types(auto-group non-featured types into smart categories)activities.other_bucket(fallback group name when no smart match is found)activities.group_aliases(optional explicit map of a raw/canonical type to a group)activities.type_aliases(map Strava types to your canonical types before grouping)units.distance(miorkm)units.elevation(ftorm)rate_limits.*(free Strava API throttling caps)
- Raw activities are stored locally for processing but are not committed (
activities/raw/is ignored). This prevents publishing detailed per‑activity payloads and gps location traces. - If neither
sync.start_datenorsync.lookback_yearsis set, sync backfills all available Strava history. - On first run for a new athlete, the workflow auto-resets persisted outputs (
data/*.json,heatmaps/,site/data.json) to avoid mixing data across forks. A fingerprint-only file is stored atdata/athletes.jsonand does not include athlete IDs or profile data. - The sync script rate-limits to free Strava API caps (200 overall / 15 min, 2,000 overall daily; 100 read / 15 min, 1,000 read daily). The cursor is stored in
data/backfill_state.jsonand resumes automatically. Once backfill is complete, only the recent sync runs. - The GitHub Pages site is optimized for responsive desktop/mobile viewing.