This is a personal blog for race journals and ride analysis. It’s a modern Gatsby 5 + React 18 site with MDX content, custom data ingestion (GPX and stats JSON), and visualization components for routes, elevation, power curves, and race overviews.
- Gatsby 5, React 18, TypeScript, Theme UI
- MDX posts under
content/posts/** - Stats JSON linked to posts via frontmatter and Gatsby schema
@link - GPX/JSON parsing into custom node fields
- Visualization wrappers for easy MDX usage:
RaceOverviewWrapperVisualOverviewWrapperNew(map + elevation slice/graph)PowerCurveGraphStatsWrapper(power curve from stats JSON)
- Framework: Gatsby 5
- Language: TypeScript + React 18
- Styling: Theme UI
- Charts/Maps: Recharts, Mapbox GL
- Content: MDX
- Build/runtime: Node 20 LTS (see
.nvmrc,.tool-versions)
Posts live in content/posts/<slug>/index.mdx with images placed alongside. Example frontmatter:
title: Gorge Gravel Grinder
date: 2023-04-23
publishedDate: 2023-07-29
headerImage: './my-header.jpg'
headerImageCaption: 'Photo credit…'
tags: [gravel, race journal]
type: Race Journal
subType: Gravel Race
teaser: Short summary teaser
currentFtp: 273
statsFile: 'stats_gorge_gravel' # links to a File node in content/stats
location: Dufur, Oregon
stravaUrl: https://www.strava.com/activities/...- A filesystem source ingests stats files (e.g., under
content/stats). - A custom parser attaches parsed JSON at
File.fields.data. - Schema customization links each MDX node to a
statsData: Fileviafrontmatter.statsFile. - In GraphQL you can query:
mdx {
statsData {
fields { data }
}
}Wrappers hide data shaping so MDX stays clean.
-
RaceOverviewWrapper- Props:
data={props.data.mdx.statsData.fields.data} - Optional:
selectedFields=[...]
- Props:
-
VisualOverviewWrapperNew- Props:
data,elevationToAdd, optionalyMin,downsampleRate,axisLeftTickValues,axisXTickValues - Shapes
SimplifiedElevations,SimplifiedDistances, andMergedDatainto the map + elevation graph.
- Props:
-
PowerCurveGraphStatsWrapper- Props:
data,ftp, optionalxTicks,xScale,yDomain - Converts
PowerAnalysis(object map) to{x,y}array and renders a Recharts power curve with an FTP reference line.
- Props:
- Requirements: Node 20 LTS (see
.nvmrc,.tool-versions) - Environment: Mapbox token required for maps:
GATSBY_MAPBOX_TOKEN - Useful npm scripts (see
package.json):start→ Gatsby developdevelop→vercel dev(optional local dev via Vercel)build→ Gatsby buildserve→ Gatsby serveclean→ Gatsby clean
Notes:
- Images are constrained in GraphQL queries to reduce Sharp work (faster builds).
- Some components expect metric/imperial conversions and may reference units via context providers.
- Platform: Vercel
- Build command:
npm run build(runsgatsby build) - Output directory:
public - Node: 20 LTS (see
.nvmrc/.tool-versions)
Local dev with Vercel:
vercel devStats are generated via https://github.com/saegey/fit-analysis and stored in content/stats/.
Example:
./processFitFile --ftp 295 --fit ~/Downloads/Gran_Fondo_Leavenworth.fit > stats_gran_fondo_leavenworth.jsonFrontmatter must reference the file name without the .json extension via statsFile. For the example above:
statsFile: 'stats_gran_fondo_leavenworth'content/
posts/ # MDX posts
<slug>/
index.mdx
*.jpg
src/
components/
posts/
RaceStats/
RaceOverviewWrapper.tsx
PowerCurveGraph/
PowerCurveGraph.tsx
PowerCurveGraphStatsWrapper.tsx
VisualOverviewWrapperNew.tsx
templates/
post.tsx # MDX template & shortcodes
pages/
home.tsx # Home listing
src/app/gatsby/
createSchemaCustomization.ts
parsers/ # JSON/GPX parsers
To finalize this doc, a few quick confirmations would help:
- Contact/feedback mechanism (serverless function or form) to mention?
GATSBY_MAPBOX_TOKEN(required for maps)
- License: MIT
- Forks welcome if you want to build something similar.