Setiap kali memulai project baru, saya biasanya melakukan ritual yang sebenarnya tidak terlalu berguna. Saya membuka banyak tab. Next.js atau Nuxt? Node.js atau Go? PostgreSQL atau MongoDB? Prisma atau Drizzle? Monolith atau microservices? Deploy ke Vercel, VPS, container, serverless, atau menunggu sampai industri menemukan istilah baru yang membuat semuanya terdengar lebih rumit?
Setelah beberapa jam, jumlah tab bertambah, project belum dibuat, dan saya mulai merasa sedang melakukan riset arsitektur untuk sistem pertahanan negara. Padahal aplikasinya baru butuh login, dashboard, dan tombol tambah data. Saya pernah cukup sering terjebak dalam situasi ini. Semakin banyak teknologi yang saya pelajari, semakin banyak pula kemungkinan yang harus dibandingkan. Pengetahuan yang seharusnya membantu mengambil keputusan justru berubah menjadi mesin penghasil keraguan. Akhirnya saya membuat satu keputusan sederhana:
Untuk mayoritas aplikasi web, saya akan menggunakan satu tech stack default sampai ada kebutuhan nyata yang membuktikan bahwa stack tersebut tidak cocok.
Dan inilah stack tersebut.
Tech Stack yang Saya Gunakan
Untuk membangun SaaS, CMS, dashboard, marketplace, internal tools, portal pelanggan, dan mayoritas aplikasi berbasis web, default saya adalah:
TypeScript.
Node.js versi LTS.
Next.js dengan App Router.
Tailwind CSS dan shadcn/ui.
PostgreSQL.
Drizzle ORM.
Better Auth.
Zod.
Vitest dan Playwright.
Docker Compose untuk deployment.
Object storage kompatibel S3 bila aplikasi menyimpan file.
Apakah stack ini paling cepat di dunia? Tidak. Apakah stack ini cocok untuk semua jenis software?
Juga tidak. Apakah saya akan menggunakannya untuk membuat game engine, firmware mesin cuci, sistem operasi, atau melatih model AI berskala besar? Tentu tidak. Saya masih memiliki sedikit rasa takut terhadap konsekuensi. Namun untuk sebagian besar produk web, stack ini memberikan keseimbangan yang sangat baik antara kecepatan development, kemampuan scaling, kemudahan maintenance, ketersediaan dokumentasi, dan jumlah masalah yang harus dipikirkan sejak awal. Itulah arti “100% Guaranteed” dalam judul artikel ini.
Bukan jaminan bahwa aplikasi tidak akan memiliki bug. Bug adalah salah satu bentuk kontribusi manusia terhadap dunia digital. Ini adalah jaminan bahwa kita bisa berhenti membandingkan puluhan teknologi dan mulai membuat produk.
1. TypeScript sebagai Bahasa Utama
Saya memilih TypeScript bukan karena JavaScript tidak mampu membangun aplikasi besar.
JavaScript jelas mampu. Masalahnya, JavaScript juga cukup sopan untuk membiarkan kita melakukan banyak kesalahan tanpa mengganggu. Kesalahannya baru diperkenalkan ketika aplikasi sudah berjalan, pengguna sedang mencoba membayar, dan kita sedang tidak berada di depan komputer. TypeScript memberikan pemeriksaan tipe sebelum kode dijalankan. Kesalahan seperti nama properti yang salah, nilai yang mungkin kosong, atau data dengan bentuk yang tidak sesuai dapat ditemukan lebih awal. Untuk project baru, saya menggunakan strict mode.
TypeScript bahkan menjadi bahasa dengan jumlah kontributor bulanan terbanyak di GitHub pada Agustus 2025. GitHub menghubungkan pertumbuhannya dengan meningkatnya kebutuhan terhadap guardrail dalam pengembangan yang dibantu AI. Hal ini semakin relevan ketika kita menggunakan Codex, Claude Code, Copilot, atau coding agent lain. AI dapat menulis kode dengan sangat cepat. Sayangnya, salah dengan cepat tetap berarti salah. TypeScript tidak menjamin kode yang dihasilkan AI benar, tetapi type checker memberikan lapisan pemeriksaan otomatis sebelum hasil tersebut masuk lebih jauh ke aplikasi.
Aturan saya sederhana:
TypeScript memeriksa asumsi kita ketika development. Untuk data yang datang dari API, form, webhook, atau environment variable, kita tetap membutuhkan runtime validation. Di situlah Zod digunakan.
2. Node.js LTS, Bukan Versi yang Paling Baru
Pada Juli 2026, Node.js 26 merupakan versi Current, sedangkan Node.js 24 berada pada jalur LTS. Dokumentasi Node.js menyarankan aplikasi produksi menggunakan versi Active LTS atau Maintenance LTS. Karena itu, saya memilih Node.js 24 LTS untuk produksi. Saya tidak membutuhkan fitur terbaru hanya agar nomor versi di terminal terlihat lebih besar.
Saya membutuhkan:
kompatibilitas package yang baik,
pembaruan keamanan,
perilaku yang stabil,
dan runtime yang tidak membuat deployment hari Jumat berubah menjadi kegiatan akhir pekan.
Versi Current tetap berguna untuk eksperimen dan pengujian kompatibilitas. Namun untuk aplikasi yang benar-benar dipakai pengguna, versi LTS merupakan default yang lebih waras.
3. Next.js sebagai Full-Stack Framework
Bagian ini kemungkinan akan mengundang perdebatan karena developer dapat berdebat mengenai framework bahkan ketika tidak ada satu pun pengguna yang meminta fitur baru.
Saya memilih Next.js karena satu alasan utama:
Saya dapat membangun frontend dan kebutuhan backend dasar dalam satu codebase.
Dengan App Router, halaman dan layout menggunakan Server Components secara default. Data dapat diambil langsung di server, sedangkan Client Components digunakan hanya pada bagian yang memang membutuhkan interaksi, state, atau API browser.
Next.js juga menyediakan Route Handlers untuk membuat endpoint GET, POST, PUT, PATCH, DELETE, dan metode HTTP lainnya. Artinya, untuk banyak aplikasi, saya tidak perlu langsung membuat:
Saya memulai sebagai modular monolith. Semua berada dalam satu codebase, tetapi setiap domain tetap dipisahkan dengan jelas.
Contohnya:
src/
├── app/
├── components/
├── features/
│ ├── auth/
│ ├── billing/
│ ├── customers/
│ └── reports/
├── server/
├── db/
├── lib/
└── tests/
Satu repository tidak berarti seluruh kode dicampur seperti laci kabel lama.
Struktur domain tetap harus jelas. Bedanya, kita tidak membayar kompleksitas distributed system sebelum produk memiliki alasan untuk membutuhkannya.
4. PostgreSQL sebagai Database Default
Saya memilih PostgreSQL untuk hampir semua data utama. Bukan MongoDB. Bukan karena MongoDB buruk, tetapi karena sebagian besar aplikasi bisnis ternyata memiliki hubungan data. Pengguna memiliki organisasi. Organisasi memiliki cabang. Cabang memiliki transaksi. Transaksi memiliki item. Produk memiliki kategori. Langganan memiliki invoice. Lalu suatu hari seseorang meminta laporan berdasarkan pengguna, cabang, produk, rentang waktu, dan status pembayaran. Pada titik itu, hubungan yang sebelumnya dianggap “tidak terlalu penting” mulai meminta perhatian. PostgreSQL memberikan:
PostgreSQL dinilai sebagai database paling desired dan admired dalam Stack Overflow Developer Survey 2025. Pada Juli 2026, PostgreSQL 18 merupakan jalur stabil, sedangkan PostgreSQL 19 masih beta. Untuk project baru, saya menggunakan versi stabil yang masih didukung. Saya tidak menggunakan beta untuk production hanya karena changelog-nya terlihat menggoda.
MongoDB tetap masuk akal ketika bentuk dokumen sangat fleksibel dan pola akses memang berorientasi dokumen. Namun database default saya tetap PostgreSQL.
Pengecualian harus memiliki alasan. Bukan sebaliknya.
5. Drizzle ORM, tetapi Gunakan Versi Stabil
Di antara aplikasi dan PostgreSQL, saya menggunakan Drizzle ORM. Drizzle memberikan API TypeScript yang dekat dengan SQL. Saya masih dapat melihat hubungan antara kode yang saya tulis dan query yang dijalankan ke database. Ini penting karena saya tidak ingin ORM menjadi bahasa pemrograman kedua yang menyembunyikan database sampai terjadi masalah performa.
Dokumentasi Drizzle menggambarkannya sebagai lapisan TypeScript tipis di atas SQL, dengan dukungan query SQL-like, relational query, schema, dan migration.
Satu catatan penting pada Juli 2026:
Drizzle 1.0 masih berada pada jalur release candidate. Untuk produksi, saya tetap menggunakan versi stabil 0.45.2 sampai versi final 1.0 benar-benar dirilis dan kompatibilitasnya sudah diverifikasi.
Ini prinsip yang akan muncul berulang kali:
Jangan memilih teknologi berdasarkan nomor versi yang terlihat paling baru. Pilih versi yang paling aman untuk dipertanggungjawabkan.
Prisma tetap menjadi pilihan valid, terutama jika tim sudah menguasainya. Saya tidak akan memigrasikan aplikasi sehat dari Prisma ke Drizzle hanya karena internet sedang memiliki mainan baru. Drizzle adalah default untuk project baru, bukan agama baru.
6. Tailwind CSS dan shadcn/ui
Untuk styling, saya menggunakan Tailwind CSS. Tailwind CSS 4 memperkenalkan konfigurasi berbasis CSS, automatic content detection, theme variables, dan proses instalasi yang lebih sederhana. Di atas Tailwind, saya menggunakan shadcn/ui sebagai fondasi komponen.
shadcn/ui berbeda dari component library tradisional karena source code komponen dimasukkan langsung ke project. Kita memiliki dan dapat mengubah kodenya sendiri. Dokumentasinya juga secara eksplisit merancang struktur komponen agar mudah dibaca dan dimodifikasi oleh manusia maupun coding agent. Kombinasi ini membantu saya:
Namun shadcn/ui bukan pengganti desain. Menginstal komponen bagus tidak otomatis membuat UX bagus, sama seperti membeli palu mahal tidak otomatis menjadikan seseorang arsitek.
7. Better Auth untuk Authentication
Authentication terlihat sederhana sampai kita mulai menambahkan:
Saya tidak ingin membangun semua itu sendiri kecuali produk memang memiliki kebutuhan keamanan yang sangat khusus. Better Auth menyediakan authentication dan authorization untuk TypeScript, dengan dukungan email/password, social provider, organization, access control, 2FA, passkey, dan plugin tambahan. Better Auth juga menyediakan adapter resmi untuk Drizzle dan handler untuk Next.js App Router. Untuk aplikasi normal, ini memberikan fondasi yang cukup kuat tanpa menyerahkan seluruh sistem pengguna kepada layanan eksternal. Tetap lakukan konfigurasi security dengan benar. Library authentication bukan jimat. Salah mengatur cookie, secret, origin, atau permission masih dapat menghasilkan bencana dengan dokumentasi yang sangat rapi.
8. Jangan Langsung Menginstal Semua Hal
Inilah bagian terpenting dari stack ini.
Saya tidak langsung menambahkan:
Teknologi tersebut memiliki kegunaan nyata. Namun kegunaan nyata tidak berarti semua project membutuhkannya pada hari pertama. Redis ditambahkan ketika caching atau distributed coordination memang diperlukan. Queue ditambahkan ketika ada pekerjaan yang tidak seharusnya menahan HTTP request. Search engine ditambahkan ketika pencarian PostgreSQL tidak lagi memenuhi kebutuhan. WebSocket ditambahkan ketika produk benar-benar membutuhkan komunikasi dua arah secara real-time. Microservices dipertimbangkan ketika domain, tim, deployment, dan scaling sudah memiliki alasan kuat untuk dipisahkan.
Kubernetes digunakan ketika masalah orchestration memang lebih besar daripada biaya menjalankan Kubernetes itu sendiri.
Aturannya:
Jangan memasang solusi sebelum kita memiliki masalah yang dapat dijelaskan dengan jelas.
“Siapa tahu nanti butuh” bukan kebutuhan teknis.
Itu kecemasan yang memakai hoodie perusahaan teknologi.
9. Server State dan Client State
Next.js sudah dapat mengambil data dan menjalankan logic di server. Karena itu, saya tidak otomatis memasang TanStack Query pada seluruh aplikasi. TanStack Query digunakan ketika aplikasi memiliki server state yang sering berubah di sisi client, misalnya:
TanStack Query memang dirancang untuk fetching, caching, synchronizing, dan memperbarui server state. Untuk state lokal yang benar-benar harus dibagikan antarkomponen, saya menggunakan Zustand.
Contohnya:
status sidebar,
cart sementara,
wizard multi-step,
player state,
atau state editor.
Namun saya tidak memindahkan seluruh data aplikasi ke Zustand. Database bukan state manager.
State manager juga bukan database. Keduanya bisa dibuat bingung, tetapi sebaiknya tidak oleh kita.
10. Testing yang Tidak Berlebihan
Saya menggunakan dua lapisan utama:
Vitest menyediakan test runner dengan watch mode dan integrasi yang dekat dengan toolchain modern. Playwright dapat menjalankan pengujian pada beberapa browser serta menyediakan mode debug, trace, dan HTML report. Saya tidak mengejar coverage 100 persen hanya agar dashboard berubah menjadi hijau. Saya memprioritaskan pengujian pada:
Coverage tinggi tidak selalu berarti aplikasi aman. Terkadang itu hanya berarti kita sangat rajin menguji fungsi yang tidak terlalu penting.
11. Deployment dengan Docker Compose
Untuk deployment yang saya kelola sendiri, saya menggunakan Docker dan Docker Compose.
Compose memungkinkan service, network, volume, dan dependency aplikasi didefinisikan dalam konfigurasi yang dapat digunakan untuk development, testing, staging, dan production. Docker juga mendokumentasikan deployment Compose pada satu server sebagai pola yang didukung.
Default deployment sederhana saya:
Cloudflare
↓
Reverse Proxy
↓
Next.js Container
↓
PostgreSQL
↓
Object Storage / External Services
Untuk project kecil, PostgreSQL dapat berjalan pada server yang sama apabila backup, resource limit, dan monitoring dikelola dengan benar. Untuk project yang mulai berkembang, database dapat dipindahkan ke server atau layanan terpisah. Saya tidak langsung menggunakan Kubernetes.
Satu VPS yang dikelola dengan baik dapat menjalankan cukup banyak aplikasi sebelum kita membutuhkan ruang kontrol yang terlihat seperti kokpit pesawat.
Kapan Stack Ini Tidak Cocok?
Jangan menggunakan stack ini secara membabi buta. Gunakan teknologi lain ketika kebutuhan memang berbeda. Contohnya:
Machine learning dan data science
Python memiliki ekosistem yang lebih sesuai.
Sistem dengan kebutuhan latency ekstrem
Go, Rust, Java, atau teknologi lain mungkin lebih tepat untuk komponen tertentu.
Mobile application yang benar-benar native
Gunakan Swift untuk iOS atau Kotlin untuk Android bila kemampuan native penuh merupakan kebutuhan utama.
Game
Gunakan engine dan teknologi yang memang dirancang untuk game.
Backend yang sangat kompleks
Pisahkan backend ketika:
API digunakan banyak aplikasi berbeda,
backend harus dikembangkan dan di-deploy secara independen,
terdapat banyak worker dan workflow panjang,
kebutuhan scaling backend berbeda jauh dari frontend,
atau batas domain sudah cukup matang.
Dalam kondisi tersebut, NestJS, Encore.ts, Go, atau framework backend lain dapat digunakan.
Namun pemisahan itu dilakukan karena kebutuhan. Bukan karena diagram arsitekturnya akan terlihat lebih mengesankan di LinkedIn.
Cara Memulai
Untuk membuat fondasi project:
pnpm create next-app@latest my-app --yes; Set-Location my-app; pnpm add drizzle-orm pg zod better-auth; pnpm add -D drizzle-kit @types/pg vitest @playwright/test
Perintah create-next-app dengan default yang direkomendasikan sudah mengaktifkan TypeScript, Tailwind CSS, ESLint, App Router, Turbopack, dan alias import. Setelah itu:
Aktifkan dan periksa TypeScript strict mode.
Buat struktur berdasarkan fitur atau domain.
Hubungkan PostgreSQL.
Tambahkan schema dan migration Drizzle.
Konfigurasikan Better Auth.
Buat satu vertical slice sampai selesai.
Tambahkan test untuk alur tersebut.
Deploy lebih awal.
Tambahkan infrastruktur hanya berdasarkan masalah yang ditemukan.
Jangan membangun seluruh sistem selama tiga bulan sebelum deployment pertama.
Deployment adalah bagian dari development. Production memiliki bakat menemukan asumsi salah yang tidak pernah muncul di localhost.
Kesimpulan
Tech stack terbaik bukan kumpulan teknologi yang memenangkan benchmark terbanyak. Tech stack terbaik adalah stack yang:
Untuk mayoritas aplikasi web, default saya adalah:
TypeScript + Node.js LTS + Next.js + PostgreSQL + Drizzle + Tailwind + shadcn/ui + Better Auth + Docker.
Mulai dengan modular monolith. Gunakan versi stabil. Jangan menambahkan service hanya untuk berjaga-jaga. Biarkan kebutuhan produk yang menarik arsitektur menjadi lebih kompleks.
Bukan ego teknis kita. Jadi, saat saya memulai project web baru dan belum memiliki alasan kuat untuk memilih teknologi lain, saya tidak membuka tiga puluh tab lagi. Saya menggunakan stack ini. Lalu mulai membuat produknya. Karena pengguna tidak pernah bertanya ORM apa yang kita gunakan. Mereka hanya ingin tombolnya berfungsi.