Skip to main content
RapidDev - Software Development Agency
how-to-build-v030-60 minutes

How to Build Fitness tracking with V0

Build a fitness tracking app with V0 using Next.js, Supabase for workout data, and Recharts for progress charts. You'll create a workout logger with exercise search, set tracking, personal records, and a progress heatmap calendar — all in about 30-60 minutes without touching a terminal.

What you'll build

  • Interactive workout builder with exercise search and set logging using Accordion and Input
  • Today's workout checklist with streak counter and weekly overview on the homepage
  • Progress charts showing weight progression per exercise using Recharts LineChart
  • Personal records (PRs) tracking via a Supabase database view with DISTINCT ON
  • Workout history heatmap calendar showing activity frequency
  • Exercise library with 50+ seeded exercises and custom exercise creation
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read30-60 minutesV0 FreeApril 2026RapidDev Engineering Team
TL;DR

Build a fitness tracking app with V0 using Next.js, Supabase for workout data, and Recharts for progress charts. You'll create a workout logger with exercise search, set tracking, personal records, and a progress heatmap calendar — all in about 30-60 minutes without touching a terminal.

What you're building

Fitness tracking helps people stay consistent with their exercise routines. By logging workouts, tracking sets and weights, and visualizing progress over time, users can see their improvement and stay motivated.

V0 makes this fast by generating the workout builder, exercise library, and progress charts from prompts. Supabase stores workout data and a database view computes personal records efficiently. The beginner-friendly architecture keeps everything simple.

The architecture uses Next.js App Router with a client component for the interactive workout builder, Server Components for the workout history and progress pages, Server Actions for workout logging with Supabase transaction RPC, and Recharts for weight progression charts.

Final result

A fitness tracking app with workout logging, exercise library, set tracking, personal record detection, progress charts, and a workout history heatmap.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
RechartsCharts

Prerequisites

  • A V0 account (free tier works for this project)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Basic understanding of workout tracking (exercises, sets, reps, weight)
  • No coding experience needed — perfect for beginners

Build steps

1

Set up the project and fitness database schema

Open V0 and create a new project. Connect Supabase via the Connect panel. Create the schema for exercises, workouts, sets, and a personal records view.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a fitness tracking app. Create a Supabase schema with:
3// 1. exercises: id (uuid PK), name (text), muscle_group (text), type (text check in 'strength','cardio','flexibility'), is_custom (boolean default false), created_by (uuid FK to auth.users nullable)
4// 2. workouts: id (uuid PK), user_id (uuid FK to auth.users), name (text), date (date), duration_minutes (int), notes (text), created_at (timestamptz)
5// 3. workout_exercises: id (uuid PK), workout_id (uuid FK to workouts), exercise_id (uuid FK to exercises), position (int)
6// 4. sets: id (uuid PK), workout_exercise_id (uuid FK to workout_exercises), set_number (int), reps (int), weight_kg (decimal), duration_seconds (int), completed (boolean default true)
7// Create a database view personal_records: SELECT DISTINCT ON (exercise_id, user_id) exercise_id, user_id, weight_kg, reps, sets.created_at FROM sets JOIN workout_exercises ... ORDER BY exercise_id, weight_kg DESC
8// Seed the exercises table with 50 common exercises (bench press, squat, deadlift, etc.)
9// Add RLS: users see their own workouts, exercises are readable by all.

Pro tip: Use Design Mode (Option+D) to customize the workout card layout and chart styling — perfect for beginners since visual tweaks cost no credits.

Expected result: Database schema created with seeded exercises and a personal_records view for efficient PR lookups.

2

Build the workout homepage with today's overview

Create the main page showing today's workout summary, current streak, and a weekly overview. The page motivates users to log their workouts consistently.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a fitness tracker homepage at app/page.tsx.
3// Requirements:
4// - Today's date prominently displayed
5// - "Start Workout" Button linking to /workouts/new
6// - If a workout exists for today, show it as a Card with: workout name, duration, exercise count
7// - Weekly overview: 7-day row showing which days had workouts (filled circles) and which didn't (empty circles)
8// - Streak counter Card: current consecutive days with workouts, with a fire emoji
9// - Recent workouts list (last 5) as Card components with: date, workout name, exercise count, total volume (weight x reps)
10// - Quick stats Cards: total workouts this month, total volume this week, most trained muscle group
11// - "View Progress" Button linking to /progress
12// - Use Badge for muscle group tags and Card for all stat containers

Expected result: The homepage shows today's workout status, current streak, weekly activity dots, and recent workout summaries.

3

Build the interactive workout logger

Create the workout builder page where users select exercises, add sets with weight and reps, and save the complete workout. The form is interactive and builds up the workout incrementally.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a workout logging page at app/workouts/new/page.tsx as a 'use client' component.
3// Requirements:
4// - Input for workout name (default: "Workout" + date)
5// - Exercise search: Input with Command/Popover that searches the exercises table by name
6// - Filter exercises by muscle_group using Select (Chest, Back, Legs, Shoulders, Arms, Core, Cardio)
7// - Selected exercises appear as Accordion items, each containing:
8// - Exercise name and muscle_group Badge
9// - Set rows: set number, Input for weight (kg), Input for reps, Checkbox for completed
10// - "+ Add Set" Button below the last set
11// - Remove exercise Button (trash icon)
12// - "+ Add Exercise" Button at the bottom to search and add more exercises
13// - Timer display showing workout duration since page opened
14// - "Finish Workout" Button that calls a Server Action to insert:
15// - workout row, workout_exercises rows, and sets rows in a single Supabase RPC transaction
16// - After saving, redirect to /workouts/[id] showing the completed workout summary
17// - Use Card to wrap each exercise section

Pro tip: Seed the exercises table with common exercises via a SQL migration so users have a library to choose from immediately.

Expected result: Users can search exercises, add multiple sets per exercise, track weight and reps, and save the complete workout in one action.

4

Create the progress charts page

Build the progress page showing weight progression per exercise, personal records, and workout frequency as a heatmap calendar.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a progress page at app/progress/page.tsx.
3// Requirements:
4// - Select dropdown to choose an exercise from the user's history
5// - Recharts LineChart showing weight_kg over time for the selected exercise (each data point = heaviest set per workout)
6// - Personal Records section: Card for each exercise showing the heaviest weight ever lifted with date and reps, using the personal_records database view
7// - PR Badge indicator on exercises where the latest workout matches the PR
8// - Workout frequency heatmap: a 365-day grid (like GitHub contribution graph) where each cell is colored by workout count for that day
9// - Use green shades: lightest for 1 workout, darkest for 3+ workouts
10// - Stats Cards: total workouts, total volume (kg x reps), average workout duration, most improved exercise
11// - Tabs to switch between Strength Progress, Cardio Progress, and Records views
12// - Wrap charts in 'use client' components, keep page as Server Component
13// - Use Card for each section and Badge for PR indicators

Expected result: The progress page shows weight progression charts, personal records with badges, and a GitHub-style heatmap of workout frequency.

5

Add workout history and exercise library

Build the workout history list and exercise library pages. Users can view past workouts and manage their custom exercises.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build two pages:
3// 1. app/workouts/page.tsx — workout history:
4// - Server Component fetching all user's workouts ordered by date DESC
5// - Each workout as a Card with: date, name, duration, exercise names as Badge list, total volume
6// - Click to view full workout detail at /workouts/[id]
7// - Filter by month using Select
8// 2. app/exercises/page.tsx — exercise library:
9// - Server Component showing all exercises in a searchable grid
10// - Filter by muscle_group using Tabs (All, Chest, Back, Legs, etc.)
11// - Search Input for exercise name
12// - Card for each exercise with name, muscle_group Badge, type Badge
13// - "Add Custom Exercise" Button opening Dialog with name Input, muscle_group Select, type Select
14// - Custom exercises marked with "Custom" Badge
15// - Server Action for creating custom exercises with Zod validation

Expected result: Users can browse workout history, view details, and manage a searchable exercise library with custom exercise creation.

Complete code

app/actions/workouts.ts
1'use server'
2
3import { createClient } from '@/lib/supabase/server'
4import { revalidatePath } from 'next/cache'
5import { redirect } from 'next/navigation'
6import { z } from 'zod'
7
8const setSchema = z.object({
9 set_number: z.number().int().positive(),
10 reps: z.number().int().positive(),
11 weight_kg: z.number().nonnegative(),
12 completed: z.boolean().default(true),
13})
14
15const exerciseSchema = z.object({
16 exercise_id: z.string().uuid(),
17 position: z.number().int(),
18 sets: z.array(setSchema).min(1),
19})
20
21const workoutSchema = z.object({
22 name: z.string().min(1).max(100),
23 date: z.string(),
24 duration_minutes: z.number().int().nonnegative(),
25 notes: z.string().max(500).optional(),
26 exercises: z.array(exerciseSchema).min(1),
27})
28
29export async function saveWorkout(data: z.infer<typeof workoutSchema>) {
30 const supabase = await createClient()
31 const { data: { user } } = await supabase.auth.getUser()
32 if (!user) throw new Error('Unauthorized')
33
34 const parsed = workoutSchema.parse(data)
35
36 const { data: workout, error } = await supabase
37 .from('workouts')
38 .insert({
39 user_id: user.id,
40 name: parsed.name,
41 date: parsed.date,
42 duration_minutes: parsed.duration_minutes,
43 notes: parsed.notes,
44 })
45 .select()
46 .single()
47
48 if (error) throw new Error(error.message)
49
50 for (const ex of parsed.exercises) {
51 const { data: we } = await supabase
52 .from('workout_exercises')
53 .insert({
54 workout_id: workout.id,
55 exercise_id: ex.exercise_id,
56 position: ex.position,
57 })
58 .select()
59 .single()
60
61 await supabase.from('sets').insert(
62 ex.sets.map((s) => ({
63 workout_exercise_id: we!.id,
64 ...s,
65 }))
66 )
67 }
68
69 revalidatePath('/')
70 redirect(`/workouts/${workout.id}`)
71}

Customization ideas

Add workout templates

Let users save favorite workout routines as templates they can quickly load, pre-filling exercises and target sets.

Add body measurements tracking

Track weight, body fat percentage, and measurements over time with a separate Recharts dashboard.

Add social features

Let users share completed workouts and personal records with friends via public profile pages.

Add rest timer

Build an in-app rest timer that starts automatically between sets with configurable duration and audio notification.

Common pitfalls

Pitfall: Not seeding the exercises table with common exercises

How to avoid: Seed the exercises table with 50+ common exercises via a SQL migration: bench press, squat, deadlift, pull-ups, etc.

Pitfall: Computing personal records by scanning all sets

How to avoid: Create a Supabase database view using DISTINCT ON (exercise_id) ORDER BY weight_kg DESC for efficient PR lookups.

Pitfall: Storing weight in both kg and lbs columns

How to avoid: Store weight in a single unit (kg) and convert for display client-side based on user preference.

Best practices

  • Seed the exercises table with 50+ common exercises so the library is useful from day one.
  • Use a Supabase database view for personal records with DISTINCT ON for efficient lookups.
  • Use Design Mode (Option+D) to customize workout cards and chart colors for free — ideal for beginners.
  • Store weight in a single unit (kg) and convert to lbs client-side based on user preference settings.
  • Use NEXT_PUBLIC_SUPABASE_ANON_KEY for client-side workout logging and SUPABASE_SERVICE_ROLE_KEY for Server Actions.
  • Validate workout data with Zod in Server Actions — enforce positive weights, at least one set per exercise.

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a fitness tracking app with Next.js and Supabase. I need to create a database view that efficiently computes personal records (heaviest weight per exercise per user) using DISTINCT ON. Show me the view SQL, how to query it from a Server Component, and how to display PR badges when the latest workout matches the record.

Build Prompt

Build the workout builder component for a fitness tracker. Create a 'use client' component with exercise search using Command/Popover that queries the exercises table. Selected exercises appear as Accordion items with set rows (set number, weight Input, reps Input, completed Checkbox). Add Set and Remove Exercise buttons. Include a running timer display. Save all data via a Server Action that inserts workout, workout_exercises, and sets.

Frequently asked questions

Can I build this with the free V0 plan?

Yes. The free tier provides enough credits for the core workout logger and exercise library. Use Design Mode (Option+D) for visual tweaks at no cost.

How do personal records work?

A Supabase database view uses DISTINCT ON to efficiently find the heaviest weight lifted per exercise per user. The progress page displays PR badges when the latest workout matches or exceeds the record.

Can I track cardio workouts too?

Yes. The exercises table has a type column (strength/cardio/flexibility). Cardio exercises use duration_seconds in the sets table instead of weight_kg and reps.

How do I deploy?

Publish via V0's Share menu. Set NEXT_PUBLIC_SUPABASE_ANON_KEY and NEXT_PUBLIC_SUPABASE_URL for client-side workout logging, and SUPABASE_SERVICE_ROLE_KEY for the Server Actions.

Can I add a rest timer between sets?

Yes. Build a 'use client' timer component that starts automatically when a set is completed. Use setInterval for the countdown and play an audio notification when time is up.

Can RapidDev help build a custom fitness app?

Yes. RapidDev has built 600+ apps including fitness platforms with social features, workout plan generation, and wearable device integration. Book a free consultation to discuss your vision.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.