Personal trainer — site + agenda + FitLab
Bookings that respect the trainer's own calendar, plus a client area and a cockpit for his business.
- Astro
- React
- React Three Fiber
- Cloudflare Workers
- Cloudflare D1
- Google Calendar API
- Resend
- Workers AI
- ≈45API endpoints for booking, admin, client area and cockpit
- 24SQL migrations on Cloudflare D1
- €0monthly hosting: Cloudflare and Resend free tiers
Bookings that bend around his own calendar
One booking engine shared by the site and a cron worker, his Google Calendar as the source of busy time, one D1 database, a client area and a cockpit for the business.
Tap a block to see what it does.
- Input
- People
- Logic
- Data
- Output
- Infrastructure
Input
One-page site
Astro SSR on Workers with React islands. The booking wizard asks the server for free slots; the 3D barbell loads only on desktop without reduced motion.
- Astro
- React
- React Three Fiber
Hands to Booking engine

Input
Trainer's calendar
His own Google Calendar is the source of busy time, read through a service account or its private feed. No OAuth, and no new app for him to learn.
- Google Calendar API
- iCal
Hands to Booking engine
People
Trainer cockpit
Phone app for clients, packages and payments. A voice note goes to Whisper, Claude turns it into draft entries, and nothing is saved until he confirms.
- Workers AI
- Claude API
Hands to Cloudflare D1
Logic
Booking engine
Shared package: slots from working hours minus busy time, UTC everywhere with one conversion module, signed cancel links. Unit-tested on both clock changes.
- TypeScript
- Vitest
Gets from One-page site, Trainer's calendarHands to Cloudflare D1, Branded emails
Data
Cloudflare D1
24 migrations: bookings, clients, programmes, workouts, ledger. A single NOT EXISTS insert prevents double booking.
- Cloudflare D1
- SQL
Gets from Trainer cockpit, Booking engineHands to Client area, Cron worker
Output
Client area
Installable app: login by email link and 6-digit code, intake form, measurements with charts, programmes, workouts with set logging, PDF export.
- React
- PWA
Gets from Cloudflare D1
Infrastructure
Cron worker
Separate worker every 15 minutes: reminders, and deletion of personal data after 24 months.
- Cron Worker
Gets from Cloudflare D1Hands to Branded emails
Output
Branded emails
Confirmation with an .ics file, reminder and cancellation, sent through Resend from the client's own domain.
- Resend
- .ics
Gets from Booking engine, Cron worker
The problem
A personal trainer booked his clients by hand, over messages and phone calls. He wanted a booking system of his own, with no subscription tool and nothing new to learn: when he blocks time in his Google Calendar, that slot disappears from the site.
What I built
- I split the code into a pnpm monorepo: the site, a cron worker and a shared booking engine used by both.
- I computed slots on the server from his working hours minus his calendar's busy times, stored every time in UTC and converted it in one tested module.
- I added a client area behind an email link and a 6-digit code: intake form, measurements, training programmes, workouts with set logging, programme as PDF.
- I built a cockpit for clients, packages and payments, where a voice note becomes draft entries (Whisper, then Claude) that he confirms before anything is saved.
- I put a procedural 3D barbell in the hero, downloaded only on desktop when the browser is idle; phones get a CSS poster.
The hard part
Preventing double bookings without transactions. Each booking is one insert guarded by NOT EXISTS, which D1 runs one at a time, and the calendar is checked again just before writing. Clock changes are covered by tests on both daylight-saving switches.
Result
In production on the client's own domain, on free Cloudflare and Resend tiers. The same engine became the base of two later client sites.
My role
Solo client project. I gathered the requirements, chose the architecture, reviewed and tested every flow against a go-live checklist and set up domain, DNS and secrets; Claude Code wrote 219 of the 220 commits.