TL;DR: Build a habit tracker with AI in under an hour — track daily habits, see your current streak, and get a weekly completion summary. Start with JavaScript and local storage (zero setup), then optionally upgrade to SQLite for persistence across devices. Five copy-paste prompts. One working app. Real skills you'll reuse in every project after this.
What You're Building
Imagine opening a clean dashboard every morning. Your habits are listed — exercise, read for 20 minutes, drink 8 glasses of water, no phone before 9 AM. A single tap marks each one done. A flame icon shows your current streak. A calendar grid shows which days you were consistent this month.
That's what you're building. Here's the full feature list:
- Habit list with daily check-ins — tap to mark a habit done for today, tap again to unmark it
- Streak counter — shows your current consecutive-day streak and your all-time best
- Habit creation and management — add new habits, set a goal frequency (daily, weekdays only, custom days), archive habits you've dropped
- Monthly calendar grid — a GitHub-style contribution graph showing which days each habit was completed
- Weekly summary dashboard — completion rate per habit for the past 7 days, best streak, and a motivational message based on your performance
- Data persistence — your check-in history survives page refreshes (local storage) or device switches (SQLite/database)
This project is a step up in complexity from a basic to-do app — you'll deal with dates, time-based logic, and visualizing historical data. But it's still well within what AI can generate on the first prompt. And unlike a to-do app, you'll actually use this one every day.
The Tech Stack (and Why AI Chose It)
When you ask AI to build a habit tracker, it usually reaches for one of two stacks depending on how you frame the request. Here's what each one is and when to pick it:
| Stack | Best For | Setup Time | Data Persists? |
|---|---|---|---|
| Next.js + Local Storage | Personal use, quick prototype | 5 minutes | Same browser only |
| Next.js + SQLite | Long-term use, data export | 15 minutes | Yes, on your machine |
| Next.js + Supabase | Multi-device, sharing with others | 30 minutes | Yes, any device |
This guide uses Next.js with local storage first, then shows you how to upgrade to SQLite in Step 4. The reason to start with local storage: you get a working app in minutes, with no accounts or config needed. The reason to upgrade later: local storage disappears when you clear your browser cache, and you can't access your data on your phone.
A habit tracker is mostly a frontend app — it doesn't need a server. You could build it in plain HTML and JavaScript. But Next.js gives you React components (reusable UI pieces like your habit card and streak counter), a dev server that auto-refreshes, and an easy path to deployment. AI generates cleaner code with a real framework than with raw HTML. And if you later want to add a backend — authentication, cloud sync, push notifications — Next.js already supports it. Use Cursor if you want AI woven into your editor as you build.
Step 1: Setting Up the Project
Start with this prompt. It creates the entire project scaffold — folder structure, dependencies, a working Next.js app, and a placeholder for your habit tracker:
Create a new Next.js 14 project for a habit tracker app with these specifications:
Tech stack: Next.js 14 with App Router, TypeScript, Tailwind CSS for styling.
Project structure:
app/page.tsx— the main dashboard showing today's habitsapp/habits/page.tsx— manage habits (add, edit, archive)app/stats/page.tsx— weekly and monthly statisticscomponents/HabitCard.tsx— a single habit with check-in button and streakcomponents/CalendarGrid.tsx— monthly contribution graph for a habitlib/habits.ts— all data operations (will use local storage for now)lib/types.ts— TypeScript types for Habit and CheckIn
Design: Dark theme, clean and minimal. Think Notion meets Streaks app. Card-based layout. Accent color: indigo (#6366f1). Show today's date prominently at the top of the dashboard.
Don't add any habits yet — just the scaffold with navigation between the three pages.
Run the setup commands AI gives you (npx create-next-app@latest, npm install, npm run dev) and you should see a blank dark-themed app at localhost:3000. If you hit errors, paste the exact error message back to AI and ask it to fix the setup.
It's tempting to write one giant prompt and get everything at once. But scaffolding separately means you can verify the project runs before adding any logic. If your project doesn't even start up, you want to know now — not after AI has generated 500 lines of habit-tracking code on top of a broken foundation. Build in layers. Test each layer. This is how the pros do it too.
Step 2: Building the Core Feature — Habit Tracking with Streaks
This is the heart of the app. Paste this into AI after your scaffold is working:
Implement the core habit tracking features in the Next.js project:
Data types (lib/types.ts):
- Habit: id (string), name (string), emoji (string), frequency ("daily" | "weekdays" | number[]), createdAt (string ISO date), archivedAt (string | null)
- CheckIn: habitId (string), date (string YYYY-MM-DD), completedAt (string ISO timestamp)
Local storage operations (lib/habits.ts):
getHabits()— return all non-archived habitsaddHabit(name, emoji, frequency)— create and save a new habittoggleCheckIn(habitId, date)— mark a habit done or undo itgetCheckIns(habitId)— return all check-ins for a habit, sorted by dategetCurrentStreak(habitId)— return the current consecutive-day streak. Rules: a streak is consecutive days where the habit was completed. Today counts toward the streak if it has been checked in. If today has not been checked in yet, the streak is based on yesterday and earlier. A streak breaks if any required day was missed.getBestStreak(habitId)— return the highest streak ever recorded for this habit
HabitCard component: Shows habit emoji and name, a large check button (filled green when done today, outlined when not), current streak with a flame icon, and best streak. Clicking the check button calls toggleCheckIn and updates the UI immediately.
Main dashboard (app/page.tsx): Show today's date at the top. List all habits using HabitCard. Show a progress bar: "X of Y habits done today."
After this step, you'll have a working habit tracker. Add a few test habits, check them in, and verify the streak counts look right. Here's what AI generated for the streak logic — it's worth understanding because it's where bugs usually hide:
// lib/habits.ts — streak calculation
export function getCurrentStreak(habitId: string): number {
const checkIns = getCheckIns(habitId);
if (checkIns.length === 0) return 0;
const today = formatDate(new Date()); // 'YYYY-MM-DD'
const yesterday = formatDate(subDays(new Date(), 1));
// If checked in today, start counting from today
// If not checked in today, start counting from yesterday
const startDate = checkIns.some(c => c.date === today)
? today
: yesterday;
let streak = 0;
let current = startDate;
while (true) {
const hasCheckIn = checkIns.some(c => c.date === current);
if (!hasCheckIn) break;
streak++;
current = formatDate(subDays(parseDate(current), 1));
}
return streak;
}
What this does: Walks backward from today (or yesterday, if today hasn't been checked in yet) counting consecutive days with a check-in. The moment it finds a gap — a day with no check-in — the streak ends. The formatDate and subDays helpers handle the date math. JavaScript date arithmetic is notoriously tricky, so AI wraps it in helpers to avoid timezone bugs.
Step 3: Adding the Dashboard — Weekly and Monthly Views
A habit tracker without history isn't much better than a sticky note. This step adds the views that make long-term tracking valuable:
Build the stats page (app/stats/page.tsx) and CalendarGrid component:
Weekly summary section:
- Show the past 7 days as column headers (Mon, Tue, ... with today highlighted)
- For each habit, show a row with: habit name/emoji, a colored dot for each day (green = completed, gray = missed, empty = future), and completion percentage for the week
- Below the table: overall weekly completion rate across all habits, best performing habit, most improved habit (compared to previous week)
CalendarGrid component:
- Show a full month as a grid of squares (like GitHub's contribution graph)
- Each square is a day: dark green if completed, lighter green if partially done (for habits with frequency other than daily), gray if missed, empty/outline if future date
- Show month/year label above the grid, prev/next month navigation arrows
- On hover, show a tooltip: "March 15 — Completed" or "March 12 — Missed"
Stats page layout: Weekly summary at top, then a CalendarGrid for each habit below. Include a "streak hall of fame" card showing all-time best streaks across all habits.
The calendar grid is the piece that makes this app feel polished and real. Here's the core logic AI generates for it:
// components/CalendarGrid.tsx — building the grid data
function buildMonthGrid(year: number, month: number, checkIns: CheckIn[]) {
const daysInMonth = getDaysInMonth(new Date(year, month));
const firstDayOfWeek = getDay(new Date(year, month, 1)); // 0 = Sunday
const today = formatDate(new Date());
// Pad the start with empty cells so the grid aligns to the correct weekday
const cells = Array(firstDayOfWeek).fill(null);
for (let day = 1; day <= daysInMonth; day++) {
const date = formatDate(new Date(year, month, day));
const isCompleted = checkIns.some(c => c.date === date);
const isFuture = date > today;
const isToday = date === today;
cells.push({ date, day, isCompleted, isFuture, isToday });
}
return cells;
}
What this does: Builds an array of "cell" objects for every day in the month. The Array(firstDayOfWeek).fill(null) part adds empty placeholder cells at the start so day 1 falls on the correct column (if March 1st is a Wednesday, the grid needs two empty cells before it). Each cell knows whether it was completed, is in the future, or is today — the component uses this to decide which color to render.
This pattern — building a data structure first, then rendering it — is used everywhere in dashboards. Once you understand it here, you'll recognize it in every calendar, heatmap, and schedule UI you see. If you go on to build a full analytics dashboard with AI, you'll use the exact same approach.
Step 4: Local Storage or Database — Persisting Your Data
Your habit tracker now works. But if you clear your browser cache, everything disappears. This step is about making your data permanent.
Option A: Keep Local Storage (Recommended to Start)
Local storage is already in place from Step 2. It's fine for personal use on a single device. The data survives page refreshes and even closing the browser — it only disappears if you explicitly clear your browser data. For most people building their first habit tracker, this is good enough. Skip to Step 5 if this is you.
Option B: Upgrade to SQLite (Recommended for Long-Term Use)
If you want your data to survive browser cache clears and be exportable, upgrade to SQLite. It stores data in a file on your machine — lightweight, no account needed, and your data is genuinely permanent.
Migrate the habit tracker from local storage to SQLite using better-sqlite3:
Install: npm install better-sqlite3 @types/better-sqlite3
Database file: lib/db.ts — create a SQLite database at ./habits.db
Schema:
- habits table: id TEXT PRIMARY KEY, name TEXT NOT NULL, emoji TEXT, frequency TEXT NOT NULL, created_at TEXT NOT NULL, archived_at TEXT
- check_ins table: id TEXT PRIMARY KEY, habit_id TEXT NOT NULL REFERENCES habits(id), date TEXT NOT NULL, completed_at TEXT NOT NULL, UNIQUE(habit_id, date)
API routes: Create Next.js API routes in app/api/ to replace the local storage functions:
- GET /api/habits — list all non-archived habits
- POST /api/habits — create a new habit
- POST /api/check-ins — toggle a check-in (create or delete)
- GET /api/check-ins?habitId=X — get all check-ins for a habit
- GET /api/stats?habitId=X — return current streak, best streak, and weekly data
Move streak logic to the server side (in the API route) so it's calculated from the database, not the browser.
Here's what the database layer looks like — notice how much simpler the queries are compared to manually searching arrays in local storage:
// lib/db.ts — SQLite operations
import Database from 'better-sqlite3';
const db = new Database('./habits.db');
// Initialize tables
db.exec(`
CREATE TABLE IF NOT EXISTS habits (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
emoji TEXT DEFAULT '✅',
frequency TEXT NOT NULL DEFAULT 'daily',
created_at TEXT NOT NULL,
archived_at TEXT
);
CREATE TABLE IF NOT EXISTS check_ins (
id TEXT PRIMARY KEY,
habit_id TEXT NOT NULL REFERENCES habits(id),
date TEXT NOT NULL,
completed_at TEXT NOT NULL,
UNIQUE(habit_id, date)
);
`);
export function toggleCheckIn(habitId: string, date: string) {
const existing = db.prepare(
'SELECT id FROM check_ins WHERE habit_id = ? AND date = ?'
).get(habitId, date);
if (existing) {
db.prepare('DELETE FROM check_ins WHERE habit_id = ? AND date = ?')
.run(habitId, date);
return { checked: false };
} else {
db.prepare(
'INSERT INTO check_ins (id, habit_id, date, completed_at) VALUES (?, ?, ?, ?)'
).run(crypto.randomUUID(), habitId, date, new Date().toISOString());
return { checked: true };
}
}
What this does: Checks if a check-in exists for this habit and date. If it does, delete it (un-check). If it doesn't, create it (check in). The UNIQUE(habit_id, date) constraint in the schema means the database itself prevents duplicate check-ins — even if two requests arrive simultaneously, only one will succeed.
This is also a great moment to understand why databases beat local storage for anything real. The UNIQUE constraint is a guarantee the database enforces. With local storage, you'd have to write that check yourself in JavaScript — and if your code has a bug, you get duplicates. Databases handle integrity for you.
Step 5: Polish and Deploy
You have a working habit tracker. Now make it feel like something you actually want to open every morning.
Polish the habit tracker UI and prepare it for deployment:
UI improvements:
- Add smooth CSS transitions when checking in a habit (the check button animates, the streak counter ticks up)
- Add a celebratory micro-animation when the user completes all habits for the day (confetti burst or a "🎉 All done!" banner)
- Add an empty state on the main dashboard if no habits exist: "Add your first habit →" button
- Make the app fully mobile responsive — the calendar grid should scroll horizontally on small screens
- Add a habit management page where users can: add a new habit (name, emoji picker, frequency), reorder habits by dragging, archive habits they no longer track
Performance: The streak calculations run on every render. Memoize them with React's useMemo so they only recalculate when check-in data changes.
Deployment prep: Add a README with setup instructions. Create a .env.example file. Make sure npm run build succeeds with no TypeScript errors.
Once npm run build passes cleanly, you're ready to deploy. The easiest path is Vercel — connect your GitHub repo, and Vercel deploys automatically on every push. See our step-by-step guide on deploying to Vercel with AI if you haven't done this before.
Once deployed to Vercel, you can add it to your phone's home screen as a web app — it looks and feels like a native app. On iPhone: open the Vercel URL in Safari, tap the share icon, and choose "Add to Home Screen." On Android: tap the three-dot menu and choose "Add to Home Screen." For a truly installable experience with offline support, prompt AI to add a service worker and manifest.json to turn it into a Progressive Web App (PWA).
What AI Gets Wrong About Habit Trackers
AI builds habit trackers well, but there are four specific places where it consistently fumbles. Know these before you test:
The basic streak counter works. The edge cases don't. Watch out for: (1) AI sometimes counts a streak as broken if the user hasn't checked in yet today — the streak should still show if all previous days were completed. (2) Midnight boundary: if a user is in UTC-8 and it's 11 PM on March 15th, that's March 16th in UTC. AI using UTC for "today" will mark March 15th as a missed day. Fix: always calculate "today" using the user's local timezone, not UTC. Prompt: "Fix the streak calculation to use the user's local timezone for determining today's date."
Different months start on different days of the week. The grid needs empty placeholder cells at the start so day 1 lines up under the correct column header. AI gets this right about 70% of the time. If your calendar has March 1st showing up on the wrong column, add: "Fix the calendar grid — the first day of the month should align to its actual day of the week, with empty cells for earlier columns."
In Safari's private browsing mode, local storage throws a security error. In some corporate browser setups, it's disabled entirely. AI rarely handles this. Add a try/catch around all local storage reads and writes, and fall back to in-memory storage if local storage throws. This prevents your app from crashing for users in private mode.
If you ask AI to support "weekdays only" habits, it often builds the UI but forgets to update the streak calculation. A "weekdays only" habit shouldn't break its streak on Saturday — Saturday is not a required day. If your habit has frequency logic, test it explicitly: check in Monday through Friday, skip Saturday and Sunday, and verify the streak didn't reset on Monday. If it did, prompt: "Fix getCurrentStreak to respect the habit's frequency setting — skipped days should not break the streak for habits that don't require those days."
What to Build Next
Your habit tracker is live. Here's how to keep leveling it up — each is a single prompt:
🔔 Daily Reminder Notifications
A push notification at 9 PM: "You haven't checked in yet today. 3 habits remaining." Prompt: "Add browser push notifications. At 9 PM each day, if any habits haven't been completed, send a notification listing them. Ask the user for notification permission when they first open the app."
📊 Habit Insights with AI
Use Claude to analyze your check-in history and surface patterns. Prompt: "Add an AI insights feature. Send the last 30 days of check-in data to the Claude API and ask it to identify patterns: which habits I stick to best, which day of the week I'm most consistent, and one specific suggestion for improvement."
👥 Accountability Partner
Share a read-only view of your habits with a friend. Prompt: "Add a 'share my habits' feature. Generate a unique shareable link that shows a read-only view of my habit streaks for the past 30 days — no editing or check-in ability, just a public progress page."
🎯 Habit Templates
Pre-built habit packs for common goals (morning routine, fitness, learning). Prompt: "Add a habit template system. Create 5 templates: Morning Routine, Fitness, Learning, Mindfulness, and Productivity. Each template has 4-6 pre-configured habits. Show a template picker when the user has zero habits."
📤 Export Your Data
Export your full check-in history to CSV for analysis in Excel or Google Sheets. This is the same pattern used in the expense tracker project — once you learn it once, you can add it to any app. Prompt: "Add a CSV export button on the stats page. Export all check-ins for all habits: columns are date, habit name, completed (true/false). Include only the past 90 days by default, with an option to export all time."
Frequently Asked Questions
About 45-60 minutes for a working version with daily check-ins, streak counting, and local storage. Add another hour for the weekly dashboard, multiple habit management, and a polished UI. The streak logic and calendar grid are the parts where AI saves the most time — these would take hours to write from scratch but AI generates them in seconds.
No. You need to understand what you want the app to do and be able to read error messages when things break. AI generates all the JavaScript. This guide explains what each piece does so you can debug and extend the app — not write it from scratch.
Start with local storage — it requires zero setup, works offline, and is more than enough for a personal habit tracker. Upgrade to a database like SQLite when you want to sync across devices, share data with others, or build analytics on top of months of history. This guide covers both paths so you can choose based on your needs.
Streak logic. The definition of a "current streak" sounds simple but has edge cases: did the user check in today? What if they check in at 11:59 PM? Does a missed day yesterday break the streak or only count once the current day passes? AI usually gets the basic case right but misses edge cases. This guide covers exactly what to watch for and how to prompt around it.
Yes — Vercel makes deploying a Next.js app completely free. If you used local storage, the app works immediately with no backend needed. If you used SQLite or a hosted database, you'll need to configure environment variables. Our guide to deploying to Vercel with AI walks through both scenarios step by step.