Portfolio ini sendiri: situs Next.js dwibahasa dengan admin dashboard sendiri, dibangun untuk mengelola proyek, sertifikat, dan pesan tanpa pernah menyentuh kode atau deploy ulang.

Timeline: 2026, live sejak 25 Agustus 2026
Portfolio ini bukan CV statis yang dipoles. Ini situs pribadi dwibahasa (EN/ID) yang ditopang admin dashboard sendiri: setiap proyek, sertifikat, riwayat karier, pendidikan, dan skill yang tampil di sini hidup di Postgres dan diedit lewat dashboard itu, tidak pernah hardcode di kode sumber.
Sudah live di production di fatihaljabar.com. Contact form, upload gambar, autentikasi admin berbasis sesi, dan rate limiting sudah rilis dan menangani trafik nyata, bukan dipersiapkan untuk demo.
CV statis jadi basi begitu resume di baliknya berubah. Mengeditnya berarti menyentuh kode dan deploy ulang, yang pada praktiknya berarti tidak pernah diedit. Situs ini dibangun untuk tetap up-to-date tanpa satu baris kode pun disentuh setelah rilis, dan membuktikan lewat keberadaannya sendiri kemampuan full-stack yang sama yang cuma bisa diklaim CV statis: auth, CRUD, file storage, validasi, internasionalisasi.
Semua yang tampil di situs publik (Projects, Achievements, riwayat karier, pendidikan, skill) punya CRUD lengkap di balik /admin, plus inbox Messages untuk contact form. Menambah proyek cukup lewat submit form, bukan deploy.
Setiap operasi tulis dijaga dua kali, bukan sekali. src/proxy.ts mengarahkan pengunjung tanpa sesi menjauh dari /admin/*, dan itu menangani pengalaman pengguna, bukan keamanan: Server Action bisa dipanggil langsung, melewati navigasi halaman sepenuhnya. Jadi setiap Server Action admin memeriksa ulang sesi Supabase Auth-nya sendiri, di baris pertama, sebelum menyentuh apa pun.
Upload gambar berjalan lewat Server Action yang memakai client Supabase Storage service role, bukan anon key yang terekspos ke browser, sehingga kredensial yang bisa menulis ke storage tidak pernah sampai ke client. File dibatasi JPEG atau PNG, dibatasi ukurannya, dan diberi nama ulang secara otomatis sehingga tidak ada yang bertabrakan atau membawa nama berbahaya. Editor yang sama juga bisa menyisipkan gambar langsung di dalam body markdown proyek, itulah cara screenshot di dalam dokumentasi proyek-proyek lain sampai ke sana.

Setiap rute berprefiks locale (/en/..., /id/...) lewat next-intl, dengan paritas key terjemahan penuh antara Inggris dan Indonesia yang dijaga lewat konvensi, bukan tooling. Ganti bahasa adalah transisi client-side, bukan reload, dan sengaja mempertahankan posisi scroll: bug yang diperbaiki setelah menelusuri kenapa halaman terasa lompat saat ganti locale. Next.js me-reset scroll ke atas di setiap navigasi secara default; perbaikannya cuma satu opsi, scroll: false.
Tema, gelap sebagai default dengan terang tersedia, ditangani next-themes tanpa fallback preferensi sistem. Artinya tidak ada flash tema salah saat render pertama dan tidak bergantung pada setelan OS pengunjung.
Setiap rute dan setiap gambar punya loading state skeleton: loading.tsx per rute, plus wrapper ImageWithSkeleton di sekitar next/image, menggantikan spinner yang dulu dipakai. Wrapper itu ada karena kasus nyata: gambar dari cache bisa selesai load sebelum listener onLoad React sempat terpasang, jadi komponennya juga memeriksa .complete langsung saat mount sebagai fallback.
SEO ditangani per halaman: metadata dan OG image unik per rute per locale, sitemap.xml, robots.txt, dan tag hreflang yang menautkan versi EN dan ID setiap halaman. Ini sudah diukur, bukan diasumsikan. Audit Lighthouse mobile mencatat skor 99 untuk Performance dan 100 masing-masing untuk Accessibility, Best Practices, dan SEO.
Build production berjalan lewat Webpack, bukan Turbopack default Next.js 16. Audit menemukan output production Turbopack memuat sekitar 18 KB polyfill browser lama ke setiap pengunjung lewat tag <script> biasa. Webpack memuat fallback yang sama di balik <script nomodule>, sehingga browser modern, lebih dari 99% trafik, melewatinya sepenuhnya.
Contact form dan tombol Love keduanya dibatasi laju per IP dengan sliding window in-memory, sehingga keduanya tidak bisa dibanjiri untuk spam ke database. Setiap input divalidasi dengan Zod di client, untuk feedback cepat, dan di server, batas yang benar-benar penting, karena pemeriksaan di client saja selalu bisa dilewati. Pesan dari contact form dirender sebagai teks biasa, tidak pernah lewat dangerouslySetInnerHTML, sehingga pesan tidak bisa menyisipkan markup ke inbox admin.
Pengerjaan situs ini dilacak di Notion, tugas demi tugas, masing-masing membawa checklist "selesai bila" yang eksplisit sebelum ditandai tuntas. Disiplin itu ada karena satu alasan: proyek ini pernah mangkrak sekali, dikerjakan tanpa batas scope sama sekali.
Satu-satunya kali data harus dipindahkan di luar aplikasi yang berjalan itu sendiri, migrasi data proyek dan sertifikat asli dari project Supabase development ke production, dijalankan sepenuhnya lewat command line: pg_dump dialirkan ke psql untuk baris database, rclone untuk file storage, keduanya diinstal lokal lewat Homebrew. Migrasi itu sama sekali tidak menyentuh aplikasi yang di-deploy; itu operasi sekali jalan, bukan dependency situs ini.

| Lapisan | Pilihan |
|---|---|
| Frontend | Next.js 16 (App Router), React 19, TypeScript (strict) |
| Styling | Tailwind CSS, shadcn/ui (gaya new-york), Framer Motion |
| Backend | Next.js Server Actions, tanpa REST API layer terpisah |
| Database | PostgreSQL lewat Supabase, Prisma ORM |
| Auth | Supabase Auth, sesi diperiksa di dalam setiap Server Action admin |
| Storage | Supabase Storage, upload via service role, JPEG/PNG saja |
| Validasi | Zod, diberlakukan di client maupun server |
| CI | GitHub Actions, typecheck + lint + test di setiap pull request |
| i18n & tema | next-intl (EN/ID), next-themes (default gelap) |
| Lint & format | Biome |
| Hosting | Hostinger, auto-deploy dari main |
revalidatePath() untuk rute yang terdampak, sehingga perubahan yang dibuat di dashboard langsung terlihat di situs publik pada request berikutnya, bukan menunggu deploy berikutnya.