Timer hitung mundur sederhana dan ringan, dibangun dengan Next.js 16 (App Router) + Tailwind CSS v4. Tanpa dependensi tambahan di luar bawaan Next — tidak ada state library, tidak ada komponen UI pihak ketiga.
- Preset 1 / 3 / 5 / 10 / 25 menit
- Start / Pause / Resume / Restart dan Reset
- Cincin progres SVG yang menyusut mengikuti sisa waktu
- Alarm 3× beep 880 Hz saat waktu habis, plus angka berkedip
- Judul tab menampilkan sisa waktu selama timer berjalan
- Ikut
prefers-color-scheme— terang/gelap otomatis - Bisa dipasang sebagai PWA (home screen) dan tetap berjalan offline
npm run dev # http://localhost:3000Untuk membuka dari perangkat lain di jaringan yang sama (HP, laptop lain):
npm run dev:lanScript ini mendeteksi alamat LAN mesin dan menampilkannya sebelum dev server menyala:
Lokal http://localhost:3000
Perangkat lain http://192.168.137.121:3000 (dari ip route get)
| Tombol | Aksi |
|---|---|
Space |
Mulai / jeda — selalu, termasuk saat fokus sedang di sebuah tombol |
R |
Reset ke durasi penuh |
app/
layout.tsx metadata + viewport + font
page.tsx merender <Timer />
timer.tsx seluruh logika dan tampilan timer (client component)
pwa-register.tsx registrasi service worker (produksi saja)
manifest.ts web app manifest (/manifest.webmanifest)
icon.svg favicon vektor
favicon.ico favicon fallback 16–256 px
apple-icon.png ikon home screen iOS
globals.css token warna, tema terang/gelap, animasi
public/
sw.js service worker: app shell + aset ber-hash
icons/ ikon 192/512 dan versi maskable untuk manifest
scripts/
dev-lan.mjs deteksi alamat LAN lalu jalankan next dev
next.config.ts allowedDevOrigins + header /sw.js
Dua hal yang sengaja ditangani supaya timer-nya benar-benar akurat:
Tanpa drift. Hitung mundur dihitung dari target timestamp (endAt - Date.now()), bukan dengan mengurangi counter setiap tick. Interval yang tersendat — misalnya karena tab tidak aktif — tidak menumpuk error, karena setiap tick membaca ulang jam.
Alarm tetap bunyi saat tab di background. Tab yang tersembunyi men-throttle setInterval sampai kira-kira sekali per menit, jadi beep tidak bisa diandalkan kalau dijadwalkan lewat JS timer. Bunyinya dijadwalkan langsung ke jam AudioContext saat Start ditekan, dan dibatalkan saat Pause/Reset. Yang di-throttle hanya pembaruan tampilan — itu pun langsung terkoreksi begitu tab dibuka lagi, karena state-nya juga dihitung dari timestamp.
Aplikasi bisa dipasang ke home screen dan dijalankan tanpa jaringan.
app/manifest.tsmendefinisikan nama, warna, dan ikon yang dipakai saat dipasang.app/icon.svgdanapp/favicon.icoadalah favicon;app/apple-icon.pngdipakai iOS.public/icons/berisi ikon 192/512 untuk manifest, termasuk versi maskable agar tidak terpotong di Android.public/sw.jsmenyimpan app shell dan seluruh chunk ber-hash saat instalasi, menyajikan HTML secara network-first, dan aset/_next/static/*secara cache-first. Reload saat offline tetap membuka timer yang sudah ter-hidrasi.
Service worker hanya didaftarkan pada build produksi. Di next dev, app/pwa-register.tsx justru mencabut service worker lama: next dev dan next start memakai origin yang sama, jadi service worker sisa build produksi bisa menyajikan chunk dev yang basi.
Service worker memerlukan secure context. http://localhost memenuhi syarat, http://192.168.x.x tidak — jadi memasang PWA dari HP lewat alamat LAN tidak akan bekerja. Gunakan deployment HTTPS (mis. Vercel) atau tunnel HTTPS.
Kalau logika cache di sw.js berubah, naikkan VERSION di baris paling atas; cache lama dihapus saat service worker baru aktif. Uji lokal dengan:
npm run build
npm start # lalu buka http://localhost:3000Sejak Next 16, permintaan ke resource dev (/_next/*, HMR) dari origin lain diblokir secara default. Gejalanya menyesatkan: halaman tampil normal karena HTML-nya dirender server, tetapi chunk JS-nya ditolak 403 — React tidak pernah hidrasi dan semua tombol diam.
next.config.ts mendaftarkan alamat LAN mesin dengan membacanya dari networkInterfaces() saat config dimuat:
for (const [name, addrs] of Object.entries(networkInterfaces())) {
if (VIRTUAL_IFACE.test(name)) continue; // buang docker0, vpn, dll
for (const addr of addrs ?? []) {
if (addr.family === "IPv4" && !addr.internal) origins.add(addr.address);
}
}Karena dibaca saat start, IP dari DHCP boleh berubah tanpa menyunting apa pun — termasuk kalau dijalankan dengan npm run dev biasa.
Kalau perlu menambahkan hostname mDNS atau IP statis:
ALLOWED_DEV_ORIGINS=mesinku.local,10.0.0.5 npm run dev:lanNext membaca config sekali saat start, jadi kalau jaringan berpindah (mis. Wi-Fi → hotspot) saat server sedang jalan, jalankan ulang perintahnya.
Jaringan di mesin ini lambat dan tidak stabil. Koneksi ke GitHub tetap jalan — push pertama butuh sekitar 30 detik — tetapi registry.npmjs.org konsisten timeout: curl gagal pada detik ke-20, dan npm install menggantung lebih dari 10 menit lalu berakhir dengan network error, setelah lebih dulu menghapus node_modules. Jadi jangan menjalankan npm install di sini.
node_modules diisi dari project lokal lain yang sudah punya Next 16, jadi versinya next@16.3.5 (bukan 16.3.8 yang dipilih create-next-app). Versi di package.json sengaja dipatok agar cocok dengan tree tersebut. Konsekuensinya node_modules masih berisi beberapa paket sisa project sumber (supabase, jose, server-only) yang tidak dipakai — tidak berpengaruh ke build, dan bisa dibersihkan dengan npm install begitu jaringan tersedia.
Verifikasi tanpa install:
npm run buildBisa dideploy ke Vercel. Halaman / sepenuhnya statis, jadi tidak ada konfigurasi khusus — allowedDevOrigins hanya berlaku di mode development dan diabaikan saat produksi.