Blog Building a Personal Fitness Tracker — Next.js, Supabase, and Vercel Written by Adam Muiz 06 Jul 2026 Updated: 06 Aug 2026 7 min read Building a Personal Fitness Tracker — Next.js, Supabase, and Vercel A few months ago I decided to be more serious about fitness. Not being an athlete or bodybuilder — just exercising regularly so that your body doesn't get tired quickly from sitting in front of a laptop for a long time. The problem is, the fitness tracker applications on the market seem to be overkill. Some are monthly, some are full of advertisements, some have too complex features to just say "today push ups 3 sets of 12 reps". Finally the idea emerged: why not make your own? As a developer, making your own tools feels more... meaningful. Plus, you can learn new technology as you go. So this project was born — a personal fitness tracker built with Next.js, Supabase, and deployed to Vercel. The repo is open-source at github.com/adammuizweb/fitness. I also deployed it on fitness.adammuiz.com for my own use. Main Features This application is designed to be simple but functional. The focus is not on being a replacement for Strava or MyFitnessPal — more on a daily log that is light, fast, and doesn't require much thinking. Daily Workout Log The core of this application. Every day I can record what workouts I have done — just check the exercise from the list, fill in the sets and reps, done. No need to input calories, duration, or other irrelevant data. Streak Tracker One of the strongest motivators to stay routine. The app displays a GitHub-style contribution calendar — green-green according to intensity — plus current and longest streak statistics. If a day is missed, the streak is reset. It's quite embarrassing if you break up. Custom Activity Not all exercises have templates in the database. Sometimes you suddenly want certain stretches or new movements. The custom activity feature allows free input — just type the name of the activity, submit, it will be recorded immediately without the need to create an exercise template first. Automatically helps maintain the streak too. Photo Upload + Lightbox Sometimes photos are more meaningful than numbers — for example progress photos or form documentation. This application supports uploading photos directly from the cellphone (with automatic WebP compression on the client side), saved to its own CDN at cdn.jyavani.com. There is a modal lightbox to view photos in full size. Community Share Workouts can be shared with the community in post form, complete with colored workout cards displaying exercises, sets and reps. Suitable for those who want to show off their progress or just share their routine. Technical Stack Next.js 16 App Router, Server Components, API routes, Middleware — the latest Next.js features. TypeScript Strict mode, type safety from frontend to database. Supabase Auth, PostgreSQL, Row Level Security — one-stop backend solution. Tailwind CSS v4 Utility-first, fast prototyping, consistent styling. shadcn/ui Radix UI components + easy-to-customize Tailwind styling. TanStack Query Caching server state, optimistic updates, auto-refetch. Vercel Automated deployment from GitHub, free, global performance. CDN (cdn.jyavani.com) Upload PHP handler on home server, Cloudflare Tunnel, serve static files. Why choose this stack? Next.js provides complete flexibility — from static landing pages to fully interactive dashboards. Supabase eliminates the need for your own backend server: PostgreSQL as the database, built-in auth, and RLS for security. Vercel just connects to GitHub, deploys automatically every time you push to main. The Story Behind the Scenes On the surface, this app seems simple. But there were some pretty exciting technical challenges during development. 1. Storing Multiple ID Logs in One Post The share post feature initially only supported one workout log per post — using the workout_log_id foreign key in the posts table. Over time, I want to be able to share all today's activities, including custom activities. Ideal solution: post_logs pivot table or array column with foreign keys. But there was a problem — the direct PostgreSQL connection to Supabase couldn't be accessed (IPv6 only, no password). Only REST API via service role key is available. Solution: Meta Markers in Photos Array Because I can't do DDL (Data Definition Language — CREATE TABLE, ALTER TABLE, etc.), I used the existing photos TEXT[] column. Each log ID is stored as a marker string: meta:log:uuid-log-id. When the post is displayed, the marker is separated from the original photo URL using the getPostPhotos() helper, then the log is retrieved via .in('id', logIds). It's called "photos as a message bus" — not a best practice, but it works perfectly within the infrastructure limitations. Later, if you can access the database directly, then refactor to the relational table. 2. Custom Activity As Hidden Workout Custom activities need to be entered into the workout_logs table to be included in the trigger streak. But workout_logs has the constraint UNIQUE(user_id, workout_id, logged_date) — meaning it can only log one workout per day. The solution: each custom activity creates a hidden workout (is_active=false) with the same name as the activity. So if the user inputs "Morning Stretching", the system automatically creates a "Morning Stretching" workout which doesn't appear in the regular exercise list. The getOrCreateWorkout() function looks for a workout with that name — if it doesn't exist, create a new one. 3. Streak Trigger Function Streaks are managed by PostgreSQL triggers that fire every time there is a new row in workout_logs. Trigger function update_streak() checks last_activity_date — if yesterday, streak +1; if more than a day, reset to 1. There is one corner case: the frontend needs to display 0 streaks if the user misses a day, but the trigger function only runs on INSERT. So the frontend has additional logic — if last_activity_date is neither today nor yesterday, the streak is considered 0. Accurate without needing a cron job or scheduler. 4. CDN Upload — From Cell Phone to Home Server The photo upload feature requires client-side compression (to save bandwidth on Vercel), a Next.js API endpoint that forwards to the home server, and a PHP handler on the home server that validates + saves the file. The flow: Hape → Kompresi WebP (max 1200px, target ≤300KB) → Next.js API Route (/api/upload) → Forward ke cdn.jyavani.com/upload.php → Simpan ke uploads/media/img/fitness/ → Balikin URL → Catat ke tabel upload_logs (rate limit 2MB/hari) → URL masuk ke photos[] Application View Here are some screenshots of the running application: Main dashboard — displays streak, today's activity, and sidebar navigation. Workout log page — tick exercises, fill in sets/reps, or input custom activity. Community posts — each workout is displayed as a colored card with a chapter number. 12 month streak calendar — GitHub contribution graph style. What to Learn This project taught me a lot: Next.js App Router — server components, client components, route groups, API routes, middleware for auth protectionSupabase SSR — setup cookies for server-side sessions, Row Level Security, service role for admin operationsPostgreSQL Triggers — plpgsql function for streak automation, auto-create profile during registrationCreative problem solving — getting around database access limitations with meta markers in existing arraysCDN pipeline — from image compression in the browser, upload via API, to serving from the home server via Cloudflare TunnelPWA — service worker with @serwist/next, manifest.json, offline support Closing Building your own fitness tracker is one of the most personally rewarding projects. Not only do you get the application that suits your needs, but you also learn a lot of new technology in the process. Sometimes the best solution doesn't come from buying expensive products, but from making your own — while learning something new. Open source code on GitHub. If you have input, feature ideas, or find bugs, please open an issue or pull request. The app can also be accessed at fitness.adammuiz.com — but keep in mind this is a personal project, so registration may be limited. Have fun exercising! 💪