Categorized Exercise Library
158 markdown-authored exercises grouped into 16 categories, rendered as a browsable grid with sticky category headers and per-category counts.
A searchable library of 158 kettlebell exercises across 16 categories, with structured instructions, safety cues, and common-mistake callouts for each movement.
Kettlebell Pro is an instructional platform for kettlebell training, built with Nuxt 4 and Nuxt Content. It catalogs 158 exercises across 16 categories — pressing, swings, deadlifts, mobility, carries, cleans, and more — with each movement rendered as a structured page covering difficulty, target muscles, step-by-step instructions, tips, cautions, and common mistakes. Supabase handles account auth, Resend powers transactional email, and the entire exercise library is authored as validated markdown so content and code stay decoupled.
Scaling structured exercise data without a database. Pushing every field — metadata, instructions, tips, cautions, common mistakes — into markdown frontmatter plus a Zod schema keeps authoring friction low and lets the full library render statically, but required custom MDC block components (:::exercise-metadata, :::instructions, :::exercise-tips) so the markdown body can render typed data as Vue components rather than generic prose.
158 markdown-authored exercises grouped into 16 categories, rendered as a browsable grid with sticky category headers and per-category counts.
Each exercise page renders difficulty, exercise type, body position, primary and secondary muscle groups, equipment, step-by-step instructions, tips, cautions, and common mistakes from validated frontmatter.
A schema in content.config.ts enforces difficulty and exercise-type enums, required metadata, and weight validation, keeping all 158 exercise files consistent as content grows.
A server route wired to Resend sends transactional welcome emails to new subscribers, with input validation handled server-side.
Kettlebell Pro is built on Nuxt 4 with Vue 3 and Nuxt Content 3. The exercise catalog lives in content/exercises/<category>/<slug>.md, and the dynamic route at app/pages/exercises/[...slug].vue resolves each file, hydrates its frontmatter into typed fields, and renders the body with custom MDC block components for instructions, tips, and metadata. The category index page groups entries by folder and surfaces counts, difficulty, and muscle-group tags in a responsive grid.
Content integrity is enforced at build time through a Zod schema in content.config.ts. Every exercise declares a fixed set of fields — difficulty and exercise type are locked to enums, muscle groups are typed arrays, and weight values are validated within a 0.5-lb tolerance when present. This moves validation out of the application layer and into content compilation, so a broken exercise file fails the build rather than reaching production.
Supabase handles authentication for account routes via serverSupabaseUser, redirecting unauthenticated users at the server boundary. Transactional email is delivered through /server/api/email/send.post.ts using Resend, and @nuxt/image optimizes exercise imagery. Together the stack keeps the site static-first where possible, with server routes reserved for the interactions that genuinely need them.
I'd love to discuss the technical details or answer any questions you may have.
Get in Touch