Blog Membangun Fitness Tracker Pribadi — Next.js, Supabase, dan Vercel Ditulis oleh Adam Muiz 06 Jul 2026 Diperbarui: 06 Aug 2026 6 menit baca 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.adammuiz.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 Next.js 16 App Router, Server Components, API routes, Middleware — fitur Next.js terkini. TypeScript Strict mode, type safety dari frontend sampai database. Supabase Auth, PostgreSQL, Row Level Security — one-stop backend solution. Tailwind CSS v4 Utility-first, fast prototyping, styling konsisten. shadcn/ui Komponen Radix UI + styling Tailwind yang mudah di-customize. TanStack Query Caching server state, optimistic updates, auto-refetch. Vercel Deployment otomatis dari GitHub, gratis, performa global. CDN (cdn.jyavani.com) Upload handler PHP di home server, Cloudflare Tunnel, serve file statis. 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: Dashboard utama — menampilkan streak, aktivitas hari ini, dan navigasi sidebar. Halaman log workout — centang exercise, isi sets/reps, atau input custom activity. Postingan komunitas — setiap workout ditampilkan sebagai kartu berwarna dengan nomor chapter. Kalender streak selama 12 bulan — gaya GitHub contribution graph. 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.adammuiz.com — tapi perlu diingat ini proyek personal, jadi registrasi mungkin dibatasi. Selamat berolahraga! 💪