Build a streak-based habit tracker with V0 using Next.js, Supabase for habit data, and a custom heatmap calendar showing daily completions. You'll create a today checklist, streak counters, and completion statistics — all in about 30-60 minutes without touching a terminal.
What you're building
Habit tracking helps people build consistency. By checking off daily habits and seeing streaks grow over time, users stay motivated. A heatmap calendar makes months of progress visible at a glance.
V0 makes this fast by generating the checklist UI, streak logic, and heatmap calendar from prompts. Supabase stores habits, completions, and streak data. The beginner-friendly architecture keeps everything simple with just three pages.
The architecture uses Next.js App Router with a client component for the interactive today checklist, Server Components for the stats page, Server Actions for toggling completions and recalculating streaks, and a custom CSS grid for the heatmap calendar.
Final result
A habit tracking app with daily checklist, streak counters, habit management, completion heatmap calendar, and overall statistics.
Tech stack
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 daily habits and streaks
- No coding experience needed — perfect for beginners
Build steps
Set up the project and habit database schema
Open V0 and create a new project. Connect Supabase via the Connect panel. Create the schema for habits, completions, and streaks.
1// Paste this prompt into V0's AI chat:2// Build a habit tracking app. Create a Supabase schema with:3// 1. habits: id (uuid PK), user_id (uuid FK to auth.users), name (text), description (text), icon (text), color (text default '#22c55e'), frequency (text check in 'daily','weekdays','weekly' default 'daily'), target_per_day (int default 1), position (int), is_archived (boolean default false), created_at (timestamptz)4// 2. completions: id (uuid PK), habit_id (uuid FK to habits), date (date), count (int default 1), created_at (timestamptz), unique(habit_id, date)5// 3. streaks: id (uuid PK), habit_id (uuid FK to habits), current_streak (int default 0), longest_streak (int default 0), last_completed_date (date)6// Add RLS: users see only their own habits and completions.7// Create a trigger that auto-creates a streaks row when a new habit is inserted.Pro tip: Use Design Mode (Option+D) to customize habit card colors and the heatmap palette — perfect for beginners since visual tweaks cost no credits.
Expected result: Database schema created with habits, completions, and streaks tables plus RLS policies restricting access to the authenticated user.
Build the today page with habit checklist
Create the main page showing today's habits as a checklist. Each habit shows its name, streak count, and a toggle to mark it complete. Include a weekly overview showing the last 7 days.
1// Paste this prompt into V0's AI chat:2// Build a habit tracker homepage at app/page.tsx as a 'use client' component.3// Requirements:4// - Today's date prominently displayed at top5// - List of active habits as Card components, each with:6// - Checkbox for completion toggle (optimistic UI)7// - Habit name and icon8// - Badge showing current streak with fire emoji9// - Color indicator matching the habit's color10// - Weekly overview below: 7-day row per habit showing filled/empty circles11// - Overall completion rate for today (e.g., "4/6 habits done")12// - "Add Habit" Button linking to /habits13// - When Checkbox is toggled, call a Server Action that upserts into completions14// and recalculates the streak in the streaks table15// - Use Card for each habit and Badge for streak countExpected result: The homepage shows today's habits as cards with checkboxes, streak badges, and a weekly overview of the last 7 days.
Create the streak calculation Server Action
Build the Server Action that toggles a habit completion and recalculates the current streak. The streak logic counts consecutive days backwards from today.
1// Paste this prompt into V0's AI chat:2// Build a Server Action at app/actions/habits.ts for toggling habit completion.3// Requirements:4// - toggleCompletion(habitId: string, date: string) Server Action5// - If completion exists for that habit+date, delete it. Otherwise upsert with count=1.6// - After toggling, recalculate the streak:7// Query completions for this habit ordered by date DESC8// Count consecutive days backwards from today (break on first missing day)9// Update streaks table: current_streak = calculated value10// If current_streak > longest_streak, update longest_streak too11// - Use Zod to validate habitId (uuid) and date (string)12// - revalidatePath('/') after the update13// - Also create addHabit and updateHabit Server Actions with Zod validationPro tip: The streak recalculation runs in a single Server Action call, keeping the UI responsive with optimistic updates on the client side.
Expected result: Server Actions handle completion toggling with automatic streak recalculation, updating both the completions and streaks tables.
Build the completion heatmap calendar
Create the stats page with a GitHub-style heatmap calendar showing completion frequency over 365 days. Include per-habit streaks and overall statistics.
1// Paste this prompt into V0's AI chat:2// Build a stats page at app/stats/page.tsx.3// Requirements:4// - Completion heatmap: a 365-day CSS grid (like GitHub contribution graph)5// Each cell colored by completion count for that day:6// 0 = gray-100, 1 = green-200, 2 = green-400, 3+ = green-6007// Wrap the heatmap in a 'use client' component, keep page as Server Component8// - Month labels above the heatmap grid9// - Streak leaderboard: Card for each habit showing current and longest streak10// sorted by current streak DESC, with Badge for streak count11// - Overall stats Cards: total completions, current longest streak, best day, completion rate this month12// - Select to filter heatmap by specific habit or show all habits combined13// - Use Card for stat containers and Badge for streak indicatorsExpected result: The stats page shows a 365-day heatmap calendar with green shading, per-habit streak rankings, and overall completion statistics.
Add habit management and archiving
Build the habit management page where users create, edit, reorder, and archive habits. Archived habits disappear from the today checklist but keep their history.
1// Paste this prompt into V0's AI chat:2// Build a habits management page at app/habits/page.tsx.3// Requirements:4// - List all active habits in a reorderable list (drag handle icon)5// - Each habit row: icon, name, frequency Badge, color circle, edit Button, archive Switch6// - "Add Habit" Button opening Dialog with:7// Input for name, Input for description, Select for icon (emoji picker),8// color picker (6 preset color buttons), Select for frequency (daily/weekdays/weekly)9// - Edit: same Dialog pre-filled with existing values10// - Archive toggle: Switch that sets is_archived=true, habit disappears from today page11// - Archived section at bottom (collapsed by default) showing archived habits with restore Button12// - Server Actions for create, update, reorder (update position), and archive13// - Use Dialog for the form, Switch for archive toggle, Badge for frequencyExpected result: Users can create habits with custom names, icons, and colors, reorder them by dragging, and archive habits while preserving their completion history.
Complete code
1'use server'23import { createClient } from '@/lib/supabase/server'4import { revalidatePath } from 'next/cache'5import { z } from 'zod'67const toggleSchema = z.object({8 habitId: z.string().uuid(),9 date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),10})1112export async function toggleCompletion(input: z.infer<typeof toggleSchema>) {13 const supabase = await createClient()14 const { data: { user } } = await supabase.auth.getUser()15 if (!user) throw new Error('Unauthorized')1617 const { habitId, date } = toggleSchema.parse(input)1819 const { data: existing } = await supabase20 .from('completions')21 .select('id')22 .eq('habit_id', habitId)23 .eq('date', date)24 .single()2526 if (existing) {27 await supabase.from('completions').delete().eq('id', existing.id)28 } else {29 await supabase.from('completions').insert({ habit_id: habitId, date })30 }3132 const { data: completions } = await supabase33 .from('completions')34 .select('date')35 .eq('habit_id', habitId)36 .order('date', { ascending: false })3738 let streak = 039 const today = new Date()40 for (let i = 0; i < 365; i++) {41 const checkDate = new Date(today)42 checkDate.setDate(today.getDate() - i)43 const dateStr = checkDate.toISOString().split('T')[0]44 if (completions?.some((c) => c.date === dateStr)) {45 streak++46 } else break47 }4849 const { data: streakRow } = await supabase50 .from('streaks')51 .select('longest_streak')52 .eq('habit_id', habitId)53 .single()5455 await supabase56 .from('streaks')57 .upsert({58 habit_id: habitId,59 current_streak: streak,60 longest_streak: Math.max(streak, streakRow?.longest_streak ?? 0),61 last_completed_date: existing ? null : date,62 }, { onConflict: 'habit_id' })6364 revalidatePath('/')65 revalidatePath('/stats')66}Customization ideas
Add habit categories
Group habits by category (Health, Work, Personal) with colored section headers and per-category completion rates.
Add reminders
Let users set a daily reminder time per habit that triggers a browser notification using the Notification API.
Add social accountability
Let users share their streak milestones with friends via public profile pages showing heatmaps.
Add habit templates
Offer pre-built habit bundles (Morning Routine, Fitness, Mindfulness) that users can import with one click.
Common pitfalls
Pitfall: Not using optimistic UI for the completion toggle
How to avoid: Toggle the checkbox state immediately on click, then call the Server Action. Revert if the action fails.
Pitfall: Recalculating streaks by scanning all completions on every page load
How to avoid: Store the current streak in a dedicated streaks table and only recalculate when a completion is toggled.
Pitfall: Forgetting the unique constraint on completions(habit_id, date)
How to avoid: Add a unique constraint on (habit_id, date) and use upsert instead of insert for completion toggling.
Best practices
- Store streaks in a dedicated table updated on each toggle rather than computing from all completions on every page load.
- Use the unique constraint on completions(habit_id, date) to prevent duplicate entries from rapid toggling.
- Use Design Mode (Option+D) to customize habit card colors and heatmap shading for free — ideal for beginners.
- Implement optimistic UI for the completion checkbox so the toggle feels instant even with server-side validation.
- Use RLS policies with auth.uid() = user_id on all tables so each user sees only their own habits.
- Cache the heatmap data with ISR revalidation since historical completions don't change frequently.
AI prompts to try
Copy these prompts to build this project faster.
I'm building a habit tracker with Next.js and Supabase. I need to calculate streaks efficiently — counting consecutive days backwards from today where a completion exists. Show me a PostgreSQL query that does this without scanning all rows, how to store the result in a streaks table, and how to update it atomically when a completion is toggled.
Build the today's habits checklist component for a habit tracker. Create a 'use client' component that fetches active habits with their streak counts. Each habit renders as a Card with a Checkbox that toggles completion via a Server Action (optimistic UI). Show a Badge with the current streak and fire icon. Below the checklist, render a 7-day weekly overview per habit using filled/empty circles. Show overall completion rate at the top.
Frequently asked questions
Can I build this with the free V0 plan?
Yes. The free tier provides enough credits for the core checklist, heatmap, and habit management pages. Use Design Mode (Option+D) for visual tweaks at no cost.
How do streaks work?
A Server Action counts consecutive days backwards from today where a completion exists. The streak is stored in a dedicated streaks table and updated only when you toggle a habit, not on every page load.
Can I track habits that aren't daily?
Yes. The frequency column supports daily, weekdays, and weekly. The streak calculation adjusts to only count applicable days based on the frequency setting.
How does the heatmap calendar work?
A CSS grid renders 365 cells, one per day. Each cell queries the completions table for that date and is colored from gray (no completions) to dark green (3+ completions), similar to GitHub's contribution graph.
Can I customize habit colors?
Yes. Each habit has a color field. The Card component uses this color for the left border accent, and the heatmap can filter by habit to show that habit's specific color.
How do I deploy?
Publish via V0's Share menu. Set NEXT_PUBLIC_SUPABASE_ANON_KEY and NEXT_PUBLIC_SUPABASE_URL for client-side completion toggling, and SUPABASE_SERVICE_ROLE_KEY for Server Actions.
Can RapidDev help build a custom habit tracking app?
Yes. RapidDev has built 600+ apps including habit and wellness platforms with social features, push notifications, and Apple Health integration. Book a free consultation to discuss your vision.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation