Membangun Fitness Tracker Pribadi — Next.js, Supabase, dan Vercel
Beberapa bulan lalu aku memutuskan untuk lebih serius soal fitness. Bukan jadi atlet atau binaragawan — sekedar rutin olahraga biar badan nggak cepat remek saking lama duduk di depan laptop. Masalahnya, aplikasi fitness tracker yang ada di pasaran rasanya overkill. Ada yang bulanan, ada yang penuh iklan, ada yang fiturnya terlalu kompleks buat sekadar nyatet "hari ini push up 3 set 12 rep".
Akhirnya muncul ide: kenapa nggak bikin sendiri? Sebagai developer, bikin tools sendiri itu terasa lebih... meaningful. Plus, bisa belajar teknologi baru sambil jalan. Maka lahirlah proyek ini — fitness tracker pribadi yang dibangun dengan Next.js, Supabase, dan di-deploy ke Vercel.
Repo-nya open-source di github.com/adammuizweb/fitness. Aku juga deploy di fitness.jyavani.com untuk dipakai sendiri.
Fitur-Fitur Utama
Aplikasi ini dirancang simpel tapi fungsional. Fokusnya bukan jadi pengganti Strava atau MyFitnessPal — lebih ke daily log yang ringan, cepat, dan nggak perlu mikir banyak.
Daily Workout Log
Core dari aplikasi ini. Setiap hari aku bisa mencatat workout apa aja yang sudah dilakukan — tinggal centang exercise dari daftar, isi sets dan reps, selesai. Tidak perlu input kalori, durasi, atau data lain yang nggak relevan.
Streak Tracker
Salah satu motivator terkuat buat tetap rutin. Aplikasi ini menampilkan kalender kontribusi ala GitHub — hijau-hijau sesuai intensity — plus statistik streak saat ini dan terpanjang. Kalau ada hari terlewat, streak di-reset. Lumayan bikin malu kalau sampai putus.
Custom Activity
Nggak semua exercise punya template di database. Kadang tiba-tiba pengen stretching tertentu atau gerakan baru. Fitur custom activity memungkinkan input bebas — cukup ketik nama aktivitas, submit, langsung tercatat tanpa perlu bikin exercise template dulu. Otomatis ikut menjaga streak juga.
Photo Upload + Lightbox
Adakalanya foto lebih bermakna daripada angka — misalnya progress foto atau dokumentasi form. Aplikasi ini support upload foto langsung dari hape (dengan kompresi WebP otomatis di client side), disimpan ke CDN sendiri di cdn.jyavani.com. Ada lightbox modal untuk lihat foto dalam ukuran penuh.
Community Share
Workout bisa di-share ke komunitas dalam bentuk post, lengkap dengan kartu-kartu workout berwarna yang menampilkan exercise, sets, dan reps. Cocok buat yang pengen pamer progress atau sekadar berbagi rutinitas.
Technical Stack
Kenapa pilih stack ini? Next.js memberikan fleksibilitas penuh — dari landing page yang static sampai dashboard yang fully interactive. Supabase menghilangkan kebutuhan backend server sendiri: PostgreSQL sebagai database, auth built-in, dan RLS untuk keamanan. Vercel tinggal connect GitHub, deploy otomatis tiap push ke main.
Cerita di Balik Layar
Di permukaan, aplikasi ini kelihatan sederhana. Tapi ada beberapa tantangan teknis yang cukup seru selama pengembangan.
1. Menyimpan Banyak Log ID di Satu Post
Fitur share post awalnya hanya support satu workout log per post — menggunakan foreign key workout_log_id di tabel posts. Seiring waktu, aku ingin bisa share semua aktivitas hari ini, termasuk custom activities.
Solusi ideal: tabel pivot post_logs atau kolom array dengan foreign key. Tapi ada kendala — koneksi PostgreSQL langsung ke Supabase nggak bisa diakses (IPv6 only, nggak punya password). Hanya REST API via service role key yang tersedia.
Solusi: Meta Markers di Array Photos
Karena nggak bisa DDL (Data Definition Language — CREATE TABLE, ALTER TABLE, dll.), aku memanfaatkan kolom photos TEXT[] yang sudah ada. Setiap log ID disimpan sebagai string marker: meta:log:uuid-log-id. Saat post ditampilkan, marker dipisah dari URL foto asli pakai helper getPostPhotos(), lalu log di-fetch via .in('id', logIds).
Namanya "photos as a message bus" — bukan praktik terbaik, tapi bekerja dengan sempurna dalam keterbatasan infrastruktur. Nanti kalau akses database langsung sudah bisa, baru refactor ke tabel relasional.
2. Custom Activity Sebagai Hidden Workout
Custom activities perlu masuk ke tabel workout_logs supaya ikut trigger streak. Tapi workout_logs punya constraint UNIQUE(user_id, workout_id, logged_date) — artinya cuma bisa satu log per workout per hari.
Solusinya: setiap custom activity dibuatkan workout tersembunyi (is_active=false) dengan nama yang sama dengan aktivitas. Jadi kalau user input "Stretching Pagi", sistem otomatis bikin workout "Stretching Pagi" yang nggak muncul di daftar exercise biasa. Fungsi getOrCreateWorkout() mencari workout dengan nama tersebut — kalau belum ada, bikin baru.
3. Streak Trigger Function
Streak dikelola oleh trigger PostgreSQL yang di-fire tiap kali ada row baru di workout_logs. Trigger function update_streak() mengecek last_activity_date — kalau kemarin, streak +1; kalau lebih dari sehari, reset ke 1.
Ada satu corner case: frontend perlu menampilkan 0 streak kalau user melewatkan hari, tapi trigger function hanya jalan saat INSERT. Jadi frontend punya logika tambahan — kalau last_activity_date bukan hari ini atau kemarin, streak dianggap 0. Akurat tanpa perlu cron job atau scheduler.
4. CDN Upload — Dari Hape ke Server Rumah
Fitur foto upload membutuhkan kompresi client-side (biar bandwidth hemat di Vercel), endpoint API Next.js yang forwarding ke home server, dan handler PHP di home server yang validasi + simpan file. Alurnya:
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[]
Tampilan Aplikasi
Berikut beberapa screenshot dari aplikasi yang sedang berjalan:
Apa yang Dipelajari
Proyek ini mengajarkan banyak hal:
- Next.js App Router — server components, client components, route groups, API routes, middleware untuk auth protection
- Supabase SSR — setup cookies untuk server-side session, Row Level Security, service role untuk admin operations
- PostgreSQL Triggers — fungsi plpgsql untuk otomatisasi streak, auto-create profile saat registrasi
- Creative problem solving — menyiasati keterbatasan akses database dengan meta markers di array yang sudah ada
- CDN pipeline — dari kompresi gambar di browser, upload via API, sampai serve dari home server via Cloudflare Tunnel
- PWA — service worker dengan @serwist/next, manifest.json, offline support
Penutup
Membangun fitness tracker sendiri adalah salah satu proyek yang paling rewarding secara personal. Nggak cuma dapet aplikasi yang sesuai kebutuhan, tapi juga belajar banyak teknologi baru dalam prosesnya. Kadang solusi terbaik bukan dari membeli produk mahal, tapi dari membuat sendiri — sembari belajar hal baru.
Kode sumber terbuka di GitHub. Kalau ada masukan, ide fitur, atau nemu bug, silakan buka issue atau pull request. Aplikasi ini juga bisa diakses di fitness.jyavani.com — tapi perlu diingat ini proyek personal, jadi registrasi mungkin dibatasi.
Selamat berolahraga! 💪