Akses kelas selamanya

Ambil Promo
flash sale
hamburger-menu

Tips All

Meningkatkan skills menjadi 1% lebih baik

Reset
Kelas Memahami Konsep Asynchronous JavaScript: Callback, Promise, dan Async/Await di BuildWithAngga

Memahami Konsep Asynchronous JavaScript: Callback, Promise, dan Async/Await

Halo, teman-teman pembaca setia BuildWithAngga (atau yang baru mampir, salam kenal dan selamat datang di dunia ngoding bareng kita!)! Pernah nggak sih kamu pas lagi asyik nge-develop project pakai JavaScript, terus tiba-tiba kode kamu kayak "macet" alias hang? Misalnya nih, kamu lagi coba ambil data dari API yang jauh di sana, eh, browser kamu jadi freeze, tombol nggak bisa diklik, atau animasi jadi patah-patah? Nah, itu dia yang namanya JavaScript lagi "nunggu"! Bayangin gini deh: Kamu lagi di dapur mau masak besar buat client (read: temen-temen di rumah). Kamu butuh air mendidih buat masak nasi. Masa iya kamu cuma berdiri diem aja di depan panci sambil nungguin air itu mendidih? Kan bisa sambil potong bawang, nyiapin bumbu, atau bahkan balas chat penting dari si doi, kan? Begitulah kira-kiira analogi JavaScript! Dia juga nggak mau dong cuma diem doang nungguin satu tugas selesai. Untungnya, JavaScript ini cerdas! Dia punya jurus rahasia biar nggak "mandek" cuma gara-gara satu proses yang butuh waktu. Namanya Asynchronous! Konsep ini keren banget, karena dia bikin JavaScript bisa melakukan banyak hal sekaligus, tanpa harus nunggu satu tugas selesai total. Jadi, browser kamu tetep responsif, user experience tetap smooth, dan kamu bisa tetap jadi developer yang kece. Penasaran banget nggak sih gimana caranya JavaScript bisa punya kekuatan super ini? Dari mulai teknik "nitip pesan" pakai Callback, janji-janji manis ala Promise, sampai mantra paling ajaib yang bikin kode rapi jali ala Async/Await? Yuk, kita kupas tuntas satu per satu, biar kamu makin jago dan pede ngoding di BuildWithAngga! Siap? Mari kita mulai petualangan ini! Ketika Si Callback Datang Menolong: Pesan Berantai yang Bikin Kode Nggak Mandek Image by Freepik Dulu kala, sebelum ada janji-janji manis dari Promise atau mantra-mantra ajaib dari Async/Await, para developer JavaScript mengandalkan seorang "pahlawan" bernama Callback. Ini adalah cara paling awal dan fundamental JavaScript untuk menangani operasi asynchronous. Konsepnya? Sederhana banget, mirip kayak kamu lagi nitip pesan atau titip pekerjaan ke teman. Bayangin skenario ini: kamu lagi di kantor dan butuh data penting dari bagian keuangan. Proses pengambilan datanya butuh waktu, karena harus nunggu database merespon. Kamu nggak mungkin kan berdiri di depan meja bagian keuangan sambil bengong nungguin datanya keluar? Itu namanya membuang-buang waktu (dan bikin kamu terlihat aneh di kantor 😂). Yang kamu lakukan adalah: "Eh, tolong ambilkan laporan bulanan ya. Nanti kalau laporannya sudah ada, kasih tahu aku ya, biar aku bisa lanjut kerjain presentasi!" Nah, kalimat "kasih tahu aku kalau laporannya sudah ada" ini adalah callback-mu. Kamu ngasih "instruksi balasan" yang akan dieksekusi setelah tugas utama (mengambil laporan) selesai. Kamu nggak perlu nungguin dia di depan mejanya. Kamu bisa sambil nyiapin slide presentasi, balas chat penting dari si doi, atau ngopi-ngopi cantik dulu. JavaScript juga pakai logika yang mirip! Dia bisa "nitip" sebuah fungsi untuk dipanggil nanti, setelah tugas yang butuh waktu itu selesai, tanpa harus menghentikan semua aktivitasnya. Callback dalam Aksi: Nitip Pesan ke JavaScript dengan setTimeout() Oke, biar kebayang banget gimana si Callback ini beraksi, yuk kita lihat contoh kode paling sederhana. Ini salah satu yang paling sering kita temui di JavaScript: setTimeout(). console.log("1. Program dimulai: 'Mulai pesan...'"); // Ini dieksekusi duluan, cepat! // Ini adalah operasi asynchronous setTimeout(function() { console.log("3. Callback dieksekusi: 'Pesan ini muncul setelah 2 detik!'"); }, 2000); // 2000 milidetik = 2 detik console.log("2. Program lanjut: 'Selesai nitip pesan, lanjut kerja yang lain...'"); // Ini dieksekusi setelah setTimeout didaftarkan, bukan setelah selesai! Gimana Penjelasannya? Yuk, Bedah Baris per Baris! console.log("1. Program dimulai: 'Mulai pesan...'");Baris ini langsung dieksekusi oleh JavaScript dan akan muncul di konsol kamu pertama kali. Ini adalah kode synchronous yang jalan instan.setTimeout(function() { ... }, 2000);Nah, di sinilah keajaiban asynchronous mulai terjadi. setTimeout() adalah sebuah fungsi bawaan JavaScript yang gunanya untuk menjadwalkan sebuah fungsi (yaitu function() { console.log(...) } yang ada di dalamnya) agar dieksekusi setelah jeda waktu tertentu (di sini 2000 milidetik atau 2 detik).Fungsi yang kita berikan ke setTimeout() inilah yang dinamakan Callback Function. JavaScript tidak langsung menjalankan fungsi ini. Dia cuma "mendaftarkan" fungsi ini ke dalam sebuah antrean asynchronous dan memberinya timer 2 detik.Setelah didaftarkan, JavaScript TIDAK MENUNGGU 2 detik itu habis. Dia langsung jalan terus ke baris kode selanjutnya.console.log("2. Program lanjut: 'Selesai nitip pesan, lanjut kerja yang lain...'");Karena JavaScript tidak menunggu setTimeout selesai, baris ini akan langsung dieksekusi setelah baris setTimeout didaftarkan. Jadi, di konsol kamu, pesan ini akan muncul kedua setelah pesan "Mulai pesan...".Setelah 2 detik berlalu...Barulah timer yang didaftarkan oleh setTimeout berbunyi! JavaScript kemudian mengambil Callback Function yang sudah didaftarkan tadi dari antrean dan menjalankannya. Hasilnya, pesan "Pesan ini muncul setelah 2 detik!" akan muncul di konsol sebagai pesan ketiga. Jadi, bisa kita lihat alurnya: Mulai -> Selesai nitip -> (2 detik berlalu) -> Pesan muncul! Hebatnya, JavaScript nggak perlu "macet" nungguin! Dia tetap bisa menjalankan kode-kode lain di antara waktu "nitip" dan waktu "dieksekusi". Asyik, kan? Ini adalah esensi dari asynchronous dengan Callback. Ketika Pesan Berantai Jadi Ruwet: Fenomena "Callback Hell" Image by Freepik Tapi, namanya juga hidup, nggak semua hal itu mulus semulus jalan tol. Meskipun Callback sangat fundamental dan berguna, dia punya satu kelemahan yang cukup bikin developer pusing tujuh keliling, namanya Callback Hell atau sering juga disebut Pyramid of Doom. Callback Hell terjadi ketika kita punya banyak operasi asynchronous yang harus dijalankan secara berurutan, di mana hasil dari satu operasi sangat dibutuhkan oleh operasi berikutnya. Karena setiap Callback harus "diletakkan" di dalam Callback sebelumnya, kode kita jadi terlihat seperti menjorok ke dalam, membentuk "piramida" atau "terowongan" yang makin lama makin dalam. Contoh paling klasik adalah skenario di mana kamu harus mengambil data dari server secara berantai: Ambil data user (misal dari API /users/{id}).Dari data user itu, kita butuh user_id untuk mengambil daftar pesanan mereka (misal dari API /orders?userId={id}).Dari daftar pesanan itu, untuk setiap pesanan, kita perlu mengambil detail produk yang ada di dalamnya (misal dari API /products/{productId}). Kalau kita paksa pakai Callback murni, bisa jadi kayak gini nih: // --- Simulasi Fungsi Asynchronous dengan Callback --- // Anggap fungsi-fungsi ini memanggil API sungguhan function ambilDataUser(userId, callback) { setTimeout(() => { console.log(`> Sedang mengambil data user ${userId}...`); const user = { id: userId, name: "Angga", email: "[email protected]" }; // Simulasi error: kalau userId tidak ada if (!userId) { return callback(new Error("User ID tidak valid!"), null); } callback(null, user); // null untuk error, user untuk data sukses }, 1000); // Simulasi waktu fetching 1 detik } function ambilPesananUser(user, callback) { setTimeout(() => { console.log(` > Sedang mengambil pesanan untuk user: ${user.name}...`); const orders = ["product-A123", "product-B456", "product-C789"]; // Simulasi error: kalau user tidak ada pesanan if (orders.length === 0) { return callback(new Error("Tidak ada pesanan ditemukan!"), null); } callback(null, orders); }, 1500); // Simulasi waktu fetching 1.5 detik } function ambilDetailProduk(productId, callback) { setTimeout(() => { console.log(` > Sedang mengambil detail produk: ${productId}...`); // Simulasi data detail produk const detailProduk = { "product-A123": { name: "Laptop Gaming", price: 15000000 }, "product-B456": { name: "Mouse RGB", price: 350000 }, "product-C789": { name: "Keyboard Mechanical", price: 900000 } }; const detail = detailProduk[productId]; // Simulasi error: kalau produk tidak ditemukan if (!detail) { return callback(new Error(`Detail produk ${productId} tidak ditemukan!`), null); } callback(null, detail); }, 500); // Simulasi waktu fetching 0.5 detik } // --- INILAH DIMULAINYA CALLBACK HELL! --- console.log("Memulai proses pengambilan data berantai (Callback Hell)..."); ambilDataUser(123, function(errorUser, user) { if (errorUser) { console.error("Kesalahan ambil data user:", errorUser.message); return; // Berhenti jika ada error } console.log(`Berhasil mengambil User: ${user.name}`); ambilPesananUser(user, function(errorOrders, orders) { if (errorOrders) { console.error(" Kesalahan ambil pesanan:", errorOrders.message); return; } console.log(` Berhasil mengambil pesanan user ${user.name}:`, orders); // Sekarang, kita perlu ambil detail untuk setiap produk. Ini yang bikin dalam! ambilDetailProduk(orders[0], function(errorLaptop, laptopDetail) { if (errorLaptop) { console.error(" Kesalahan ambil detail Laptop:", errorLaptop.message); return; } console.log(" Detail Laptop:", laptopDetail); ambilDetailProduk(orders[1], function(errorMouse, mouseDetail) { if (errorMouse) { console.error(" Kesalahan ambil detail Mouse:", errorMouse.message); return; } console.log(" Detail Mouse:", mouseDetail); ambilDetailProduk(orders[2], function(errorKeyboard, keyboardDetail) { if (errorKeyboard) { console.error(" Kesalahan ambil detail Keyboard:", errorKeyboard.message); return; } console.log(" Detail Keyboard:", keyboardDetail); console.log("\\nSemua data berhasil diambil! FINISH CALLBACK HELL 🎉"); // Bayangin kalau ada lebih banyak lagi nested callback di sini... // Atau kalau ada logika if/else di setiap level, makin kacau! }); // End callback ambilDetailProduk (Keyboard) }); // End callback ambilDetailProduk (Mouse) }); // End callback ambilDetailProduk (Laptop) }); // End callback ambilPesananUser }); // End callback ambilDataUser console.log("\\nProgram utama jalan terus di background, nggak nungguin proses di atas..."); Pusing, kan? Lihat deh struktur kodenya! Itu baru untuk 3-4 level aja udah lumayaan bikin mata juling dan indentation (penjorokan) yang dalam.Bayangkan kalau kamu punya 5, 7, atau bahkan 10 operasi asynchronous yang saling bergantung dan butuh hasil dari yang sebelumnya. Kode kamu akan semakin menjorok ke dalam, mirip bentuk piramida terbalik! Kenapa Callback Hell Ini Jadi "Neraka" Bagi Developer? Susah Dibaca (Readability): Struktur kode yang terlalu menjorok ke dalam sangat sulit dibaca dan dipahami alurnya. Mata kita harus mengikuti banyak kurung kurawal pembuka dan penutup.Susah Dilacak Kesalahannya (Debugging): Ketika terjadi error di salah satu level Callback yang dalam, melacak sumber error (disebut stack trace) bisa jadi mimpi buruk. Sangat sulit untuk melihat dengan cepat di mana masalah sebenarnya.Susah Dipelihara (Maintainability): Mengubah atau menambahkan logika baru di tengah-tengah Callback Hell itu seperti mencoba memperbaiki kabel listrik di dalam tumpukan benang kusut. Sedikit saja salah, bisa merusak seluruh alur.Error Handling yang Berulang: Perhatikan bagaimana kita harus menulis blok if (error) { ... return; } berulang kali di setiap level Callback. Ini duplikasi kode yang tidak efisien dan rentan lupa.Inversi Kontrol: Ini konsep yang lebih dalam. Dengan Callback, kita memberikan kontrol "siapa yang akan memanggil fungsi berikutnya" kepada fungsi yang kita panggil. Artinya, kita bergantung sepenuhnya pada implementasi fungsi tersebut untuk memanggil callback kita, dan tidak ada jaminan callback itu dipanggil sekali saja atau tidak sama sekali. Ini bisa membuat kode sulit diprediksi. Tapi tenang, jangan panik dulu! Kekacauan ini nggak berlangsung lama. Karena setelah ini, muncullah seorang pahlawan baru yng akan membawa kita keluar dari labirin callback hell ini dan menata kode kita jadi lebih rapi dan bisa diprediksi. Siapa dia? Lanjut ke babak selanjutnya: Promise! Janji Manis dari Promise: Kode Rapi, Hati Senang! Image by Freepik Setelah kita disibukkan dengan labirin Callback Hell yang bikin kepala cenat-cenut, muncullah seorang pahlawan baru di dunia JavaScript yang siap menata ulang kekacauan itu: Dialah Promise! 🎉 Secara filosofi, Promise ini beneran mirip kayak "janji" yang kamu buat sama seseorang. Setia janji itu pasti punya 3 kemungkinan status, kan? Pending: Status awal. Ini pas janjinya baru diucapin, kayak: "Ehh… Nanti aku traktir bakso ya!" Kamu belum tahu apakah beneran ditraktir atau enggak, masih nunggu.Fulfilled (atau Resolved): Nah, kalau ini janjinya ditepati! Si teman beneran datang bawa seporsi bakso hangat. Asyik!Rejected: Kalau yang ini, janjinya gagal alias nggak jadi ditepati. Mungkin temanmu tiba-tiba ada urusan mendadak, jadi nggak bisa traktir bakso. Sedih, tapi yaa mau gimana lagi. Intinya, sebuah Promise itu adalah objek yng merepresentasikan hasil akhir dari sebuah operasi asynchronous yang belum selesai, tapi akan selesai (entah sukses atau gagal) di masa depan. Keren, kan? Cara Kerja Janji-Janji Manis Ini: .then(), .catch(), dan .finally() Nah, kalau ada janji, pasti ada "reaksi" dong dari kita. Misalnya, kalau janji ditraktir bakso itu terpenuhi, apa yang mau kamu lakukan? Kalau gagal, gimana reaksimu? Dan, ada juga hal-hal yang tetap kamu lakukan, entah janji itu ditepati atau tidak. Nah, Promise di JavaScript punya tiga "metode" andalan buat nampung semua reaksi ini: .then() (Kalau Janjinya Terpenuhi / Fulfilled)Ini ibaratnya, "Kalau janjinya terpenuhi, maka (then) lakukan ini!"Di sinilah kode kamu akan berjalan ketika operasi asynchronous yang diwakili oleh Promise berhasil diselesaikan. Data hasil operasinya akan dikirimkan ke dalam then ini..catch() (Kalau Janjinya Gagal / Rejected)Ini kebalikannya then(). "Kalau janjinya gagal, tangkap (catch) kesalahannya dan lakukan ini!"Semua error atau kegagalan dari Promise akan ditangkap di sini. Penting banget buat error handling biar aplikasi kamu nggak tiba-tiba crash di tengah jalan..finally() (Apapun yang Terjadi, Lakukan Ini!)Nah, yang satu ini spesial. "Mau janjinya terpenuhi atau nggak, akhirnya (finally) lakukan ini!"Kode di dalam .finally() akan selalu dieksekusi, entah Promise itu sukses atau gagal. Ini cocok banget buat operasi bersih-bersih, misalnya untuk menyembunyikan loading spinner atau menutup koneksi, karena kamu pasti mau itu terjadi, kan? Dari "Terowongan" Menuju "Rel Kereta": Kode Jadi Lebih Teratur! Masih ingat contoh Callback Hell yang bikin kita sakit mata tadi? Sekarang, yuk kita sulap pakai Promise! Lihat betapa jauh lebih rapi dan "datar"-nya kode kita sekarang. Kita akan pakai bantuan new Promise() untuk mensimulasikan operasi asynchronous kita. // Fungsi-fungsi kita sekarang mengembalikan Promise! function ambilDataUser(userId) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(`[Promise] Mengambil data user ${userId}...`); const user = { id: userId, name: "Angga" }; // Anggap sukses, kita panggil resolve if (user.id) { resolve(user); // Mengirim data user jika berhasil } else { reject("User tidak ditemukan!"); // Mengirim error jika gagal } }, 1000); }); } function ambilPesananUser(user) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(`[Promise] Mengambil pesanan untuk user ${user.name}...`); const orders = ["Laptop", "Mouse", "Keyboard"]; // Anggap sukses if (orders.length > 0) { resolve(orders); // Mengirim daftar pesanan } else { reject("Pesanan kosong!"); } }, 1500); }); } function ambilDetailProduk(productName) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(`[Promise] Mengambil detail produk: ${productName}...`); const detail = { name: productName, price: Math.random() * 1000000 }; // Anggap sukses if (detail.name) { resolve(detail); // Mengirim detail produk } else { reject(`Detail produk ${productName} tidak ditemukan!`); } }, 500); }); } // INILAH KEINDAHAN PROMISE CHAINING! console.log("Program utama jalan terus di background (dengan Promise)..."); ambilDataUser(123) .then(user => { console.log(`Data User berhasil: ${user.name}`); return ambilPesananUser(user); // Mengembalikan Promise lagi untuk chaining! }) .then(orders => { console.log(`Pesanan yang ditemukan:`, orders); // Kita bisa melakukan Promise.all jika mau ambil semua detail produk sekaligus const productPromises = orders.map(product => ambilDetailProduk(product)); return Promise.all(productPromises); // Menunggu semua Promise detail produk selesai }) .then(details => { console.log("Detail Semua Produk:", details); console.log("Semua data berhasil diambil dengan Promise!"); }) .catch(error => { // Kalau ada salah satu Promise di atas yang gagal, dia langsung loncat ke sini! console.error("Terjadi kesalahan:", error); }) .finally(() => { console.log("Proses pengambilan data (dengan Promise) selesai, entah berhasil atau gagal."); // Sembunyikan loading spinner, dll. }); Perbandingan Callback Hell dengan Promise Chaining Lihat kan perbedaannya? Jauh lebih enak dibaca daripada yang tadi kayak terowongan Callback Hell! Dengan Promise, kita bisa merangkai operasi asynchronous secara berurutan menggunakan .then() yang berantai (chaining). Setiap .then() akan dieksekusi setelah Promise sebelumnya resolved, dan data dari Promise sebelumnya akan dilempar ke then berikutnya. Ini bikin alur kode kita jadi lebih lurus dan gampang dipahami. Keunggulan Promise: Bikin Ngoding Lebih Happy! Keterbacaan Kode yang Jauh Lebih Baik: Ini jelas poin utamanya! Dari struktur "piramida" yang bikin pusing, kita beralih ke "rantai" yang lurus dan mudah diikuti. Flow data jadi jelas, dari satu .then() ke .then() berikutnya.Penanganan Error yang Terpusat: Nah, ini salah satu fitur killer dari Promise! Dengan satu blok .catch() di akhir rantai, kita bisa menangani error dari seluruh Promise yang ada di rantai tersebut. Bayangkan, nggak perlu lagi bikin blok if (error) di setiap nested callback! Kalau ada Promise yang rejected di tengah jalan, eksekusi akan langsung melompat ke blok .catch() terakhir. Ini bikin error handling jadi sangat efisien dan rapi.Mengatasi "Inversi Kontrol": Di era Callback, kita menyerahkan kontrol sepenuhnya kepada fungsi yang kita panggil untuk memanggil callback kita. Kita nggak punya jaminan kapan callback itu akan dipanggil, atau bahkan apakah akan dipanggil berkali-kali. Dengan Promise, kita mendapatkan kembali kontrol itu. Kita yang mengontrol kapan resolve atau reject dipanggil, dan kita tahu Promise hanya akan settled (resolved atau rejected) satu kali saja. Ini membuat kode lebih prediktif dan aman.Komposisi Asynchronous yang Kuat: Promise memungkinkan kita untuk menggabungkan beberapa operasi asynchronous dengan mudah. Contohnya tadi, pakai Promise.all() untuk menjalankan banyak Promise secara paralel dan menunggu semuanya selesai. Ada juga Promise.race() untuk menunggu Promise tercepat, dan banyak lagi! Sedikit "PR" Promise: Ketika Janji Berantai Terlalu Panjang... Meskipun Promise ini udah jadi penyelamat hidup banget buat para developer dan bikin kode jauh lebih rapi, ada kalanya kalau chaining .then() nya itu terlalu panjang dan kompleks, kodenya masih bisa terlihat sedikit "berlapis" juga sih. 1. Keterbacaan yang Masih Bisa Ditingkatkan (Terutama untuk Logic Kompleks): Bayangkan kalau di dalam satu .then() kamu perlu melakukan beberapa pengecekan kondisi (if/else) atau bahkan looping sebelum mengembalikan Promise selanjutnya. Rantai .then() bisa jadi sangat panjang dan tetap membutuhkan banyak indentation (penjorokan ke dalam), apalagi kalau logika bisnisnya rumit. Ini bisa mengurangi keterbacaan, meskipun tidak seburuk Callback Hell. 2. Debugging yang Terkadang Masih Sedikit Tricky: Kalau ada error di tengah rantai Promise yang panjang, stack trace (daftar panggilan fungsi yang mengarah ke error) terkadang tidak sejelas yang kita harapkan. Bisa jadi sulit untuk melihat dengan cepat di mana tepatnya error itu berasal dalam rantai yang panjang. 3. Kurangnya Keserupaan degan Kode Synchronous: Meskipun sudah jauh lebih baik dari Callback, alur penulisan kode dengan .then().then().catch() masih terasa berbeda dengan kode synchronous yang kita tulis sehari-hari. Kita masih harus membayangkan "alur asinkron" nya. Tapi jagan khawatir, para engineer JavaScript nggak berhenti berinovasi! Untuk menjawab tantangan itu, muncullah sebuah fitur yang bikin asynchronous rasa synchronous, seolah-olah kamu bisa menulis kode asinkron layaknya menulis cerita langkah demi langkah. Penasaran? Siap-siap untuk mantra paling ajaib di babak terakhir kita! Lanjut ke part berikutnya! Mantra Ajaib Async/Await: Bikin Kode Asynchronous Jadi Semudah Baca Cerita! Image by Freepik Baiklah, teman-teman BuildWithAngga! Setelah kita melewati masa-masa seru (dan sedikit pusing) dengan Callback dan akhirnya merasakan "janji manis" dari Promise, sekarang saatnya kita bertemu dengan sang pahlawan pamungkas yang paling modern dan digandrungi banyak developer: Async/Await! Percaya atau tidak, Async/Await ini sebenarnya bukan konsep yang benar-benar baru, lho. Dia itu kayak "pembungkus" atau "sintaks gula" di atas Promise. Jadi, kalau kamu sudah paham Promise, kamu sudah punya modal besar buat memahami keajaiban Async/Await ini. Bayangkan, kalau Promise itu kamu dikasih petunjuk arah dan kamu harus ngikutin satu per satu belokan dan persimpangan. Nah, Async/Await itu kayak kamu punya GPS super canggih yang langsung nunjukkin jalan paling gampang, lurus, dan mulus, tanpa kamu perlu mikir detail urutan satu per satu. Serius, ini kayak sulap! Cara Kerja Keajaiban Ini: Si async dan Si await Ada dua kata kunci (keyword) utama yang jadi kunci mantra Async/Await ini: async (Untuk Fungsi)Keyword async ini kamu taruh di depan sebuah deklarasi fungsi (async function namaFungsi() { ... }).Apa artinya? Artinya, fungsi tersebut akan selalu mengembalikan sebuah Promise. Walaupun kamu menulis fungsi itu seolah-olah dia synchronous, JavaScript secara otomatis akan membungkus nilai kembaliannya dalam sebuah Promise yang akan resolved dengan nilai tersebut.Fungsi async ini juga spesial, karena hanya di dalam fungsi async inilah kamu bisa menggunakan keyword await.await (Menunggu Promise Selesai)Nah, ini dia bintang utamanya! Keyword await hanya bisa digunakan di dalam sebuah fungsi yang ditandai dengan async.Ketika JavaScript ketemu await di depan sebuah Promise (misalnya await ambilData()), dia akan "berhenti sejenak" di baris itu. Tapi ingat, dia berhenti tanpa memblokir jalannya program utama lainnya! Ini penting banget. JavaScript akan "menunggu" sampai Promise yang di-await itu selesai (baik resolved atau rejected).Begitu Promise-nya selesai, hasil resolved dari Promise itu akan langsung diberikan ke variabel di sebelah kiri await. Kalau Promise-nya rejected, dia akan melempar error yang bisa kita tangkap. Intinya, dengan await, kita bisa menulis kode asynchronous yang urutannya terlihat sequential (berurutan dari atas ke bawah) seperti kode synchronous biasa. Otak kita nggak perlu lagi loncat-loncat membayangkan callback atau .then() yang berantai. Keren banget, kan? Dari "Rantai" Menuju "Cerita": Kode Asynchronous Rasa Synchronous! Masih ingat kan contoh chaining Promise kita yang sudah rapi tadi? Sekarang, yuk kita sulap lagi pakai Async/Await. Siap-siap terkesima dengan betapa miripnya kodenya dengan alur cerita biasa! // Fungsi-fungsi kita tetap mengembalikan Promise, seperti yang sudah kita buat sebelumnya // (ambilDataUser, ambilPesananUser, ambilDetailProduk) // ... kode fungsi Promise sebelumnya ... // INILAH KEAJAIBAN ASYNC/AWAIT! console.log("Program utama jalan terus di background (dengan Async/Await)..."); async function prosesOrderPengguna(userId) { try { // 1. Ambil data user const user = await ambilDataUser(userId); // Kode 'menunggu' di sini sampai user didapat console.log(`[Async/Await] Data User berhasil: ${user.name}`); // 2. Dari data user, ambil daftar pesanan mereka const orders = await ambilPesananUser(user); // Kode 'menunggu' lagi di sini console.log(`[Async/Await] Pesanan yang ditemukan:`, orders); // 3. Dari daftar pesanan, ambil detail produk (bisa pakai Promise.all jika paralel) const productPromises = orders.map(product => ambilDetailProduk(product)); const details = await Promise.all(productPromises); // Menunggu semua detail produk selesai console.log("[Async/Await] Detail Semua Produk:", details); console.log("[Async/Await] Semua data berhasil diambil dengan Async/Await!"); return details; // Mengembalikan hasil akhir } catch (error) { // Kalau ada salah satu 'await' di atas yang gagal, dia langsung loncat ke sini! console.error("[Async/Await] Terjadi kesalahan:", error); } finally { console.log("[Async/Await] Proses pengambilan data (Async/Await) selesai, entah berhasil atau gagal."); // Sembunyikan loading spinner, dll. } } // Panggil fungsi async kita prosesOrderPengguna(123); Coba bandingkan dengan kode yang pakai Callback atau Promise sebelumnya! Jauh lebih mirip kode synchronous biasa, kan? Rasanya seperti kamu lagi menulis langkah-langkah dalm resep masakan, dari atas ke bawah. Ini bikin kita mikir seolah-olah kode kita jalan berurutan, padahal di baliknya ada keajaiban asynchronous yang bekerja dan JavaScript tetap tidak terblokir! Penanganan Error dengan try...catch: Semudah Menghela Napas! Salah satu keuntungan terbesar dari Async/Await adalah bagaimana cara kita menangani error. Jika di Promise kita pakai .catch(), nah di Async/Await, kita bisa pakai blok try...catch yang sudah sangat familiar di kode synchronous biasa. Semua kode yang berpotensi melempar error (misalnya panggilan await ke Promise yang rejected) kita masukkan ke dalam blok try.Jika ada error yang terjadi di dalam blok try (misal, ambilDataUser gagal karena network error), eksekusi akan langsung melompat ke blok catch. Di sinilah kita bisa menangani error tersebut dengan nyaman. Ini membuat error handling di kode asynchronous jadi terasa sangat intuitif dan "alami", sama seperti yang sering kita lakukan di kode-kode synchronous sehari-hari. Keunggulan Async/Await: Bikin Hidup Developer Lebih Indah! Keterbacaan dan Kemudahan Penulisan Kode yang Luar Biasa: Ini adalah selling point utamanya! Kode asynchronous terlihat seperti synchronous. Alur logika jadi sangat jelas, mudah diikuti, dan tidak perlu lagi membayangkan rantai .then() yang kompleks. Ini mengurangi cognitive load (beban pikiran) saat membaca atau menulis kode.Debugging yang Lebih Mudah: Ketika terjadi error, stack trace dari Async/Await cenderung lebih jelas dan informatif. Ini karena JavaScript engine bisa "mengingat" di mana await terjadi, sehingga debugging terasa lebih langsung dan efisien dibandingkan dengan Promise chain yang panjang.Error Handling yang Familiar (try...catch): Seperti yang sudah dibahas, kemampuan menggunakan try...catch membuat penanganan error di Async/Await terasa sangat familiar dan mirip dengan kode synchronous. Ini sangat membantu dalam mengelola flow aplikasi saat terjadi kegagalan.Menyederhanakan Kondisi dan Looping Asynchronous: Kalau di Promise kita mungkin agak ribet bikin if/else atau loop di antara .then(), dengan Async/Await ini jadi jauh lebih mudah. Kamu bisa menulis if atau for loop seperti biasa, dan di dalamnya ada await, hasilnya seperti menulis kode synchronous yang kompleks. Kekurangan Async/Await: Tidak Ada yang Sempurna! Meskipun Async/Await sangat superior dalam banyak hal, ada beberapa hal yang perlu kamu perhatikan: Tetap Membutuhkan Promise di Baliknya: Ingat, Async/Await itu cuma "gula sintaks" di atas Promise. Artinya, kamu harus tetap memahami konsep Promise dan cara kerjanya. Fungsi atau operasi yang kamu await haruslah mengembalikan sebuah Promise. Kalau tidak, await tidak akan tahu apa yang harus ditunggu! Jadi, Promise tetap jadi fondasi utama.Hanya Bisa Digunakan di Fungsi async: Kamu tidak bisa begitu saja menggunakan await di sembarang tempat. Dia harus berada di dalam fungsi yang sudah ditandai async. Ini berarti ada sedikit "overhead" untuk membungkus kode await di dalam fungsi async, meskipun ini bukan masalah besar dalam praktik.Potensi Blocking (Jika Salah Paham Konsep): Meskipun await tidak memblokir main thread JavaScript secara keseluruhan, dia memblokir eksekusi fungsi async saat ini sampai Promise-nya selesai. Jika kamu punya banyak operasi await yang tidak saling bergantung dan bisa dijalankansecara paralel, meng-await mereka satu per satu bisa jadi tidak efisien.Contoh Kekurangan: async function ambilDuaDataSekaligus() { const data1 = await ambilDataA(); // Menunggu data A selesai (misal 2 detik) const data2 = await ambilDataB(); // Baru mulai ambil data B (misal 3 detik) // Total waktu: 2 + 3 = 5 detik console.log(data1, data2); } // Padahal, dataA dan dataB bisa diambil bersamaan!Solusi: Untuk kasus ini, kita tetap perlu kembali ke Promise.all() atau Promise.race() dan kemudian meng-await hasilnya: async function ambilDuaDataBersamaan() { const promiseData1 = ambilDataA(); // Mulai ambil data A (Promise return) const promiseData2 = ambilDataB(); // Mulai ambil data B (Promise return) // Mereka jalan paralel di background! const data1 = await promiseData1; // Menunggu data A selesai const data2 = await promiseData2; // Menunggu data B selesai // Total waktu: Max(2, 3) = 3 detik (karena paralel) console.log(data1, data2); } Ini menunjukkan bahwa meskipun Async/Await memudahkan, pemahaman tentang Promise dan kapan harus memaksimalkan konkurensi (melakukan beberapa hal bersamaan) tetap penting. Petualangan Selesai, Saatnya Jadi Master Asynchronous! Image by Freepik Wah, nggak terasa ya, teman-teman BuildWithAngga! Petualangan kita memahami seluk-beluk Asynchronous JavaScript akhirnya sampai di penghujung jalan. Dari mulai kita berkenalan dengan JavaScript yang suka "nunggu", lalu dijemput oleh si penolong pertama, Callback, yang bikin kita belajar cara "nitip pesan" di dunia kode. Sempat deg-degan juga ketemu Callback Hell yang bikin kode kayak terowongan ruwet! Untungnya, muncullah pahlawan kedua, Promise, yang membawa "janji manis" dan keteraturan, mengubah "terowongan" jadi "rantai" yang lebih enak dibaca dan bikin error handling jadi lebih ringkas. Dan puncaknya, kita bertemu dengan mantra paling ajaib, Async/Await, yang bikin kode asynchronous kita serasa nulis cerita langkah demi langkah, seolah-olah kode kita jalan synchronous padahal di baliknya ada keajaiban yang bekerja! Setiap metode ini punya ceritanya sendiri, kelebihan dan kekurangannya masing-masing. Mereka adalah evolusi dari bagaimana kita, para developer, mengatasi tantangan asynchronous di JavaScript. Menguasai ketiganya berarti kamu punya pemahaman yang utuh tentang bagaimana JavaScript menangani waktu dan tugas-tugas yang butuh jeda. Jadi, Kapan Pakai Yang Mana Nih, Bang Angga? Pertanyaan bgus! Setelah tahu semua ini, mungkin kamu bertanya-tanya, "Oke, aku harus pakai yang mana sekarang?" Tenang, begini panduan ringkasnya, ala BuildWithAngga: Callback:Kapan? Jujur, untuk kode asynchronous yang kamu tulis sendiri di aplikasi modern, penggunaannya sudah sangat jarang sebagai pola utama. Kamu mungkin akan sering melihatnya di library lama atau API bawaan browser seperti setTimeout, addEventListener, atau beberapa API Node.js.Intinya: Kalau ketemu Callback, pahami saja cara kerjanya sebagai "nitip fungsi untuk dipanggil nanti". Jangan lagi pusing-pusing bikin Callback Hell sendiri ya! Hindari jika ada alternatif Promise atau Async/Await.Promise:Kapan? Ini adalah fondasi utama di dunia asynchronous JavaScript modern. Hampir semua library dan framework JavaScript (seperti fetch untuk mengambil data dari API, Axios, atau bahkan database client) akan mengembalikan sebuah Promise.Intinya: Kamu wajib paham Promise karena Async/Await bekerja di atasnya. Gunakan Promise chaining (.then().then()) ketika kamu perlu flexibilitas lebih dalam mengatur alur atau ketka kamu berinteraksi lansung dengan fungsi yang memang mengembalikan Promise. Cocok juga untuk operasi paralel dengan Promise.all() atau Promise.race().Async/Await:Kapan? Ini adalah cara yang paling direkomendasikan dan paling modern untuk menulis kode asynchronous di JavaScript saat ini.Intinya: Gunakan Async/Await hampir di setiap kesempatan ketika kamu bekerja dengan Promise (dan sebagian besar operasi asynchronous di JavaScript modern adalah Promise). Dia akan membuat kode kamu jadi sangat bersih, mudah dibaca (seperti cerita!), dan debugging pun jadi jauh lebih gampang dengan try...catch. Kalau kamu baru belajar, langsung fokus ke sini, tapi jangan lupakan fondasi Promise-nya ya! Singkatnya, kuasai Promise, dan gunakan Async/Await sesering mungkin. Itulah resep jitu untuk menulis kode asynchronous yang elegan dan efisien di JavaScript! Petualangan Belum Berakhir, Saatnya Praktik! Belajar pemrograman itu mirip kayak belajar naik sepeda. Nggak cukup cuma baca teori atau lihat video tutorial aja. Kamu harus nyemplung langsung, pegang sepedanya, goes pedalnya, jatuh bangun (dikit!), sampai akhirnya lancar jaya! Begitu juga dengan Asynchronous JavaScript ini. Tantang dirimu sendiri: Coba buat fungsi yang mensimulasikan ambil data dengan setTimeout menggunakan Callback, lalu ubah ke Promise, dan terakhir ke Async/Await. Rasakan perbedaannya!Coba buat skenario error dan lihat bagaimana .catch() dan try...catch bekerja.Bereksperimenlah dengan Promise.all() untuk operasi paralel agar aplikasi kamu makin ngebut! Semakin sering kamu mencoba dan bereksperimen, semakin dalam pemahamanmu, dan kamu akan jadi developer JavaScript yang makin jago dan percaya diri. Ingat, practice makes perfect! Kami di BuildWithAngga selalu semangat untuk berbagi ilmu dan menemani perjalanan ngodingmu. Semoga artikel ini bisa jadi panduan awal yng menyenangkan dan inspiratif buat kamu menyelami dunia Asynchronous JavaScript yang sangat penting ini. Sampai jumpa di petualangan kode selanjutnya! Happy Ngoding, Teman-teman BuildWithAngga! 🎉

Kelas 10 Plugin Figma yang gak kalah keren dan powerful di BuildWithAngga

10 Plugin Figma yang gak kalah keren dan powerful

Desainer UI/UX, siap-siap kerja makin ngebut dan tetap tampil keren! Di artikel ini, ada 10 plugin Figma andalan yang keren, powerwul, dan pastinya bisa jadi senjata rahasia kamu buat bikin desain lebih rapi, lebih cepat, dan klien langsung jatuh hati. Yuk, langsung kita bahas bareng! 1. Rename It Cover Figma Rename It Kalau kamu sering dibuat pusing cari layer yang nggak punya nama atau berantakan pakai nama default ‘Frame 45’ atau ‘Group 3’, nah plugin Rename It bakal jadi penyelamat kamu biar gak pusing lagi. Cukup pilih banyak layer, atur pola nama sesuai kebutuhan terus klik, selesai deh. Semuanya langsung rapi, keren kan?. Plugin Rename it ini membantu kamu mengganti nama banyak layer sekaligus secara instan dan konsisten. Cukup pilih layer yang ingin diubah, buka plugin Rename It, lalu masukkan pola nama yang diinginkan bisa pakai prefix, suffix, nomor urut, bahkan replace nama tertentu. Gak perlu rename satu-satu atau hafal urutan layer, plugin ini langsung beresin semuanya dalam sekali klik. Kenapa Rename It berguna banget buat desainer? Ganti nama banyak layer sekaligus gak perlu rename satu per satu. Pilih banyak layer, pakai plugin, dan semuanya selesai dengan satu klik.Tambah prefix/suffix dengan mudah. Ingin grup layer diberi awalan “Button/” atau akhiran “/Desktop”? Tinggal tambahkan di field, lalu klik Rename.Bantu rapiin struktur file desain dalam hitungan detik. Layer yang tertata bikin proses desain lebih enak, handoff ke developer juga lebih mulus dan minim miskom. Cara Menggunakan Plugin Rename It Select object yang ingin di ganti namanya. Lalu, search plugin “Rename it”.Kemudian input nama yang di inginkan pada quick rename kemudian enter.Nama otomatis terganti dan menjadi lebih rapi. 2. ARC - Bend Your type! Cover Figma ARC - Bend your type! Pernah kepikiran bikin teks melengkung untuk logo, badge, atau elemen visual lain, tapi males ribet gambar jalur curve dan tracing huruf satu-satu? Tenang pakai aja plugin Arc – Bend Your Type! “Arc – Bend Your Type!” adalah plugin Figma yang memungkinkan kamu untuk membengkokkan teks jadi bentuk lengkung hanya dalam beberapa klik. Cocok banget buat kamu yang pengen bikin teks melingkar atau efek curve tanpa harus ribet tracing manual atau pakai software desain tambahan. Dengan plugin ini, kamu tinggal pilih teks, atur seberapa melengkung kamu lewat slider yang simpel, dan klik Apply. Teks kamu langsung berubah jadi curve, entah itu setengah lingkaran, melengkung ke atas atau ke bawah, bahkan bisa jadi bentuk lingkaran penuh. Kenapa Arc – Bend Your Type! berguna banget buat desainer? Buat teks melengkung dalam hitungan detik. Pilih layer teks, jalankan plugin, atur Bend Strength, lalu klik Apply. Gak perlu trace curve secara manual hasilnya langsung bentuk lengkungFleksibel arah curve nya mau ke atas, bawah, atau lingkaran penuh pokoknya bebas. Mau arc kecil di atas judul atau teks melingkar penuh? Semua bisa diatur lewat slider aja .Cocok untuk logo, badge, UI kreatif. Plugin ini sangat pas dipakai untuk desain logo, banner, credit circle, badge acara, atau semua yang butuh teks melengkung dengan cepat Cara Menggunakan Arc – Bend Your Type! Select layer text yang ingin diubah menjadi melengkung.Kemudian klik plugin nya dan atur arah lengkungan nya (Bend Strength) bisa diliat juga dalam previewnya jika kurang pas.Klik apply dan otomatis text akan melengkung sesuai dengan slider yang kita atur. Cara pakai plugin Figma ARC - Bend your type! 3. Pixels Cover Figma Pixels Pernah ingin bikin efek visual unik seperti game jadul yang 8 bit? Coba nih plugin Pixels yang hadir buat kamu yang pengen transformasi gambar biasa jadi pixel art keren langsung dari Figma! Pixels adalah plugin Figma yang bikin gambar kamu berubah jadi garis-garis kayak efek visual di game 8-bit. Gambar tetap kelihatan, tapi tampil beda dengan gaya retro topografi yang artistik dan unik. Cocok banget buat kamu yang suka eksperimen visual di desain. Kenapa Pixels berguna banget buat desainer? Visual art yang menarik dan beda. Hasilnya bukan sekadar filter tapi pola garis yang membentuk bayangan, cocok untuk hero-page, album cover, poster, atau stylized illustration.Proses mudah & instan. Tinggal pilih gambar, jalankan plugin, atur density atau parameter lain, dan efek nya lansung muncul tanpa nunggu lama.Eksplorasi kreatif di Figma. Tanpa perlu pindah ke tool khusus seperti Illustrator atau plugin eksternal. Semua tinggal pakai file figma kamu sendiri. Cara Menggunakan plugin Pixels Select image atau object yang ingin dijadikan pixels.Kemudian klik plugin nya dan atur angka pada slider untuk effect filter 8 bit nya.Klik apply dan otomatis gambar akan berubah sesuai dengan slider yang kita atur. Cara pakai plugin figma Pixels 4. Neumorphism Cover Figma Neumorphism Lagi cari cara biar desain kamu kelihatan lebih lembut, elegan, dan punya efek 3D halus tanpa harus ribet mainin bayangan manual satu-satu? Nah, plugin Neumorphism ini cocok banget buat kamu. Dengan satu klik aja, kamu bisa langsung ngasih efek soft shadow yang khas ke elemen UI mu di Figma. Plugin Neumorphism ini dirancang buat kamu yang suka gaya desain modern minimalis tapi tetap ingin sentuhan visual yang lebih ‘hidup’. Cocok untuk bikin tombol, card, input field, hingga dashboard yang punya nuansa soft-touch dan lebih enak dilihat. Kenapa plugin Neumorphism berguna dan keren banget? Efek keren tanpa pusing shadow manual. Cukup satu tombol, plugin langsung menambahkan dua bayangan (gelap dan terang) serta warna gradient fill untuk hasil neumorphic yang keren.Customizable sesuai selera. Kamu bisa pilih arah cahaya, tingkat elevation nya, maupun intensitas bayangan. Pas banget deh untuk berbagai tema desain.Cocok buat desain modern dan minimalis. Mau bikin tampilan dashboard, form, atau card yang keliatan clean tapi tetap berdimensi? Gaya soft UI ini bikin desain kamu keliatan lebih halus dan profesional. Cara Menggunakan plugin Neumorphism Select object yang ingin dipakaikan effect.Kemudian klik plugin nya dan atur slider untuk elevation, intesity cahaya nya, bentuk, dan light source nya sesuai selera.Klik apply dan otomatis objecct akan ter apply effect dari neumorphism. 6. Displace Cover Figma Displace Desain kamu terasa flat, kosong, dan kurang ? Lagi pengen bikin tampilan yang lebih keren, punya tekstur, atau efek visual yang bikin mata langsung ‘wow’? Tenang, kamu gak butuh software lain cukup pakai plugin Displace! Displace adalah plugin Figma yang bisa ngasih efek visual super kreatif cuma dalam beberapa klik. Mau bikin efek kaca buram yang lembut dan modern? Bisa. Mau kasih noise halus biar desainmu gak keliatan terlalu polos? Bisa banget dong. Atau malah pengen tampilan glitch yang unik, futuristik, dan beda dari yang lain? Jelas bisa juga lah! Plugin ini cocok banget buat kamu yang suka bereksperimen, bosan sama gaya desain yang gitu-gitu aja, atau lagi pengen bikin desain standout buat poster, hero banner, sampai UI aplikasi. Efeknya langsung real-time, gak ribet, dan hasilnya? Bikin desain kamu makin keren banget. Kenapa plugin Displace berguna dan keren banget? Hemat waktu & bebas repot. Semua efek bisa langsung dibuat dari Figma. Tinggal pilih layer, pilih efek, atur intensitas, dan lihat preview secara real-time tanpa keluar dari figma.Eksperimen kreatif tanpa batas. Cocok untuk desain modern, UI futuristik, banner, poster, atau layout kreatif. semua bisa diatur deh dengan sekali klikPola glitch & displacement yang eye-catching. Buat efek visual rusak atau bergelombang cocok dan keren untuk poster digital, hero image, atau tampilan eksperimen desain. Cara Menggunakan plugin Displace Select image yang ingin dipakaikan effect.Kemudian klik plugin nya dan pilih effect yang ingin diterapkan atau bisa juga atur sesuai selera kamu.Klik apply dan otomatis image akan menerapkan effect yang telah dipilih Cara pakai plugin Figma Displace 7. Generative Gradient Pengen bikin gradien yang nggak sekadar warna halus, tapi punya tekstur dan karakter visual yang unik? Nih kenalin plugin Generative Gradient yang bisa ngasih kamu gradien dengan grain, noise, dan bentuk unik yang langsung bisa kamu atur dari dalam Figma. Plugin Generative Gradient memungkinkan kamu bikin gradien dengan tekstur unik, efek grainy yang estetik, dan komposisi warna yang nggak monoton langsung dari dalam Figma. Gak perlu ribet buka Photoshop, gak perlu cari noise overlay dari Google, dan yang paling penting… gak butuh skill teknis yang rumit dan repot. Kenapa generative gradients ini patut kamu coba? Gampang dipakai & nggak perlu API setup. Install plugin, open, dan langsung bisa bikin gradien tanpa ribet konfigurasi API atau coding.Perpaduan noise + gradien = efek visual wow. generative gradient membuat gradien terlihat lebih tajam, natural, dan nggak monoton. Cocok buat hero image, background, atau ilustrasi digital.Customizable meskipun gratis. Kamu bisa pakai banyak pilihan preset gradien yang keren, dengan opsi satu klik buat generate. Cara Menggunakan plugin Generative Gradients Buka dan klik plugin generative gradient di dalam figmaKemudia atur ukuran canvas untuk gradient yang akan digenerate dan atur juga preset yang ingin dipakai.Scroll kebawah dan klik create image. Image gradient akan langsung muncul dalam file figma mu. Cara pakai plugin Figma Generative Gradients 8. Glassify Cover Figma Glassify Pengen bikin efek kaca kayak di UI iOS yang bening, elegan, dan berkelas? Tapi males ribet atur blur, opacity, dan shadow satu-satu? Tenang, kamu gak sendirian. Nih coba plugin Glassify buat bantu kamu! Dengan plugin ini, kamu bisa langsung kasih sentuhan glassmorphism ke elemen desainmu cuma dalam sekali klik. Gak perlu pusing layering manual atau utak-atik efek. Tinggal pilih objek, jalanin plugin, dan efek kaca khas iOS langsung muncul di canvas kamu. Hasilnya clean, modern, dan bikin desain kelihatan lebih premium. Kenapa glassify menarik buat kamu coba? Hemat banyak waktu. Setting layering manual bisa makan banyak waktu. Dengan plugin ini, dalam hitungan detik aja efek kaca udah rapi dan siap pakai.Cocok untuk berbagai jenis desain. Baik itu card app mobile, panel modal, tombol modern, atau hero section, plugin ini bisa di adaptasi untuk gaya desain apa sajaBisa Buat elemen mu terlihat stylish & modern. Dengan Glassify elemen dalam figma mu bisa langsung terlihat lebih stylis, modern dan juga pastinya keren banget ada efek kaca-kacanya. Cara Menggunakan plugin glassify Select object yang ingin diaplikasikan dengan efek glass.Kemudian klik plugin glassify dan atur intesity, color dan blur.Klik glassify dan secara otomatis objek akan berubah menjadi efek glass. 9. Neubrutalist Cover Figma Neubrutalist Kamu pernah lihat desain yang bold, anti-mainstream, penuh warna mencolok, tepi kasar, bayangan super tajam terasa seperti web atau UI orang yang punya kreativitas berlebih dan pengen coba?. Nah, itu gaya yang dinamakan Neubrutalist. Dan plugin Neubrutalist ini bikin gaya itu bisa langsung kamu pakai di Figma dalam sekali klik. Kenapa Neubrutalist menarik buat kamu coba? Plugin ini juga jadi alat eksperimen visual yang seru. Mungkin kamu nggak bakal pakai di semua project, tapi sekali coba kamu bakal dapet inspirasi baru soal layout, warna, dan style neubrutalist.Langsung dapat style konsisten hanya dengan beberapa klik. Gak cuma ganti warna dan shadow, plugin ini bantu kamu bikin sistem visual neubrutalist yang rapi tapi tetap raw dalam waktu super singkpowr-1at.Desain langsung standout dan anti-mainstream. Di tengah trend desain yang flat, minimal, dan clean. Gaya neubrutalist muncul dengan style yang sangat berbeda. dengan plugin ini kamu bisa mencoba gaya neubrutalist dengan sekali klik. Cara pakai plugin neubrutalist Select object yang ingin diaplikasikan dengan plugin neubrutalist.Kemudian klik plugin neubrutalist dan atur shape color, shadow color dan offsetnya.Klik neubrutalise dan object mu langsung menjadi style neubrutalist. Cara pakai plugin Figma Neubrutalist 10. Clay Mockups 3D Cover Figma Clay Mockups 3D Desain kamu keren, tapi masih kelihatan flat waktu dipresentasiin? Gak mau dong, effort desain segitu niatnya cuma tampil seadanya. Saatnya bikin tampilan desain kamu terlihat super profesional, modern, dan eye-catching langsung dalam bentuk mockup 3D dengan plugin Clay Mockups 3D yang elegan dan minimalis! Dengan Clay Mockups, kamu bisa memilih jenis device (iPhone, Pixel, MacBook, dll), atur warna bodi, pilih sudut pandang kamera, lalu desainmu otomatis masuk ke layar perangkat virtual 3D yang hasilnya clean dan minimalist. Kenapa Clay Mockups 3D jadi plugin yang wajib banget kamu coba? Mockup 3D langsung di Figma, tanpa ribet. Gak perlu Photoshop atau aplikasi lain. install plugin, select layer, pilih device, dan mockup-mu langsung siap.Kustomisasi penuh: model, warna & sudut pandang. Kamu bisa pilih model (HP, tablet, laptop), atur warna bodi misalnya charcoal, pastel dan putar tampilan sesuai mood mockup-muEditing ulang setiap saat. Mau ubah desain yang tampil, sudut pandang, atau warna device? Tinggal klik “Edit Mockup” semuanya bisa diubah tanpa perlu repot-repot remake. Cara pakai plugin neubrutalist Select layer yang ingin dipakai dalam mockup.Kemudian klik plugin Clay mockups 3D dan atur device model, angle preset dan rotation nya.Klik save as image, layer mu akan langsung otomatis masuk kedalam mockup. Cara pakai plugin Figma Clay mockup 3D Penutup Nah, itu dia 10 plugin Figma yang bisa bantu kamu bawa desain ke level selanjutnya—gak cuma dari segi efisiensi, tapi juga dari sisi estetika dan eksplorasi gaya visual. Mulai dari plugin seperti Rename It yang bikin penamaan layer jadi super rapi, ARC - Bend Your Type! buat kasih twist unik ke teks, sampai plugin eksperimental seperti Pixels dan Displace yang bikin desain kamu lebih artistik dan penuh karakter. Ada juga Neumorphism dan Glassify buat kamu yang suka efek soft UI dan transparansi ala iOS. Buat yang mau eksplor visual style yang nyentrik dan anti-mainstream, Neubrutalist pasti cocok banget. Dan kalau kamu pengen hasil presentasi desain yang kelihatan pro tanpa ribet, tinggal pakai Clay Mockups 3D langsung auto tampil kece. Plugin-plugin ini bukan cuma tools, tapi juga sumber inspirasi dan buka jalan buat kamu berani bereksperimen, mengekspresikan style desainmu, dan tentunya bikin proses kerja jadi lebih cepat dan seru. Kalau kamu pengen belajar lebih lanjut tentang cara maksimalin plugin-plugin ini dalam workflow desain yang nyata, kamu bisa langsung gabung di kelas gratis Figma Efficiency Secrets: 15 Plugins You Can't Miss dan kalau mau lebih mantap lagi bisa coba kelas premium Boost Your Design Work Using Top Figma Plugins dari BuildWithAngga. Di sana nanti kamu bisa belajar langsung sambil praktik bareng mentor yang udah berpengalaman di industri. Selamat eksplorasi, dan semoga desainmu makin powerfull & penuh gaya!

Kelas Stack Frontend 2025: Tools dan Framework yang Sedang Naik Daun di BuildWithAngga

Stack Frontend 2025: Tools dan Framework yang Sedang Naik Daun

🧭 Dunia Frontend Itu Cepat Banget Berubah Pernah nggak sih, kamu baru aja ngerasa pede banget karena akhirnya bisa nguasain satu framework—misalnya React, atau Svelte? Baru juga kelarin satu course, atau bangun satu project kecil... eh, tiba-tiba timeline udah rame ngomongin hal baru lagi. “Next.js 15 udah keluar, bro!” atau “Lu masih pake Redux? Sekarang udah pake Zustand atau Jotai, cuy!” Dan kamu cuma bisa bengong sambil nanya dalam hati, “Kapan belajarnya kelar, sih?” Yap. Dunia frontend emang gitu. Cepat. Lincah. Kadang melelahkan, kadang bikin semangat. Mirip kayak tren fashion: yang dulunya dianggap aneh atau ribet, sekarang malah jadi standar. Ingat waktu Tailwind CSS pertama muncul? Banyak yang mencibir: “Kok styling pakai class kayak gini?!” Tapi sekarang? Tailwind jadi andalan. Bahkan UI library seperti Shadcn UI pun dibangun dari situ. Frontend itu seperti jalanan yng nggak pernah kosong — selalu ada yang baru lewat. Kamu bisa pilih untuk tetap di pinggir jalan dan nonton aja, atau ikut lari bareng. Tapi yang pasti, tahu siapa yang lagi lari di depan bisa bantu kamu ambil keputusan: mau ngejar, mau nonton, atau mau cari jalan lain. Apakah kamu harus coba semua teknologi baru? Tentu nggak. Tapi tahu apa yang lagi rame, apa yang banyak dipakai, dan apa yang bikin kerjaan lebih gampang... itu penting banget. Bukan cuma biar nggak FOMO, tapi juga biar kamu bisa ambil keputusan teknis yang lebih tepat — buat kariermu, buat timmu, atau buat project pribadimu. Dan ngomong-ngomong soal tren, di 2025 ini ada beberapa tools dan framework yang lagi naik daun. Bukan sekadar “rame diomongin”, tapi beneran dipakai, dikembangkan, dan dibanggakan oleh banyak developer — dari indie dev, startup, sampai tim-tim besar. Nah, di artikel ini, gue bakal ajak kamu keliling sebentar. Kita bakal bahas stack-stack yang lagi naik daun di dunia frontend 2025 — tools yang bisa bikin coding lebih menyenangkan, lebih cepat, dan (kalau kamu cocok) bisa jadi senjata andalan buat next project kamu. Siapin kopi dulu kalau perlu. Let’s dive in 🚀 🔍 Kenapa Harus Peduli dengan Stack yang Lagi Naik Daun? “Apakah semua stack baru harus kamu pelajari?” Jawaban singkatnya: nggak. Tapi... apakah kamu perlu tahu apa yang lagi berkembang? Jawabannya: iya, banget. Dunia frontend tuh kayak kota yang nggak pernah tidur. Satu sisi seru banget — banyak hal baru, banyak peluang, komunitasnya hidup. Tapi di sisi lain, kalau kamu nggak ngikutin perkembangan, bisa-bisa kamu kayak orang yang masih pakai peta zaman dulu buat nyari jalan di kota modern. Gue sendiri pernah ngerasain.Dulu gue ngerasa udah nyaman banget pakai stack yang lama: React + Redux + Webpack. Udah nguasain alurnya, udah hafal cara config-nya. Tapi makin lama makin kerasa berat — setup lama, maintain susah, nambah fitur harus edit banyak file.Sampai akhirnya gue nyobain stack yang lebih modern kayak Next.js + Zustand + Vite.Hasilnya? Banyak waktu yang biasanya kebuang buat “nyiapin alat tempur”, sekarang bisa langsung dipakai buat ngoding fitur inti. Kenapa penting tahu stack yang lagi naik daun? 1. 🚂 Biar Nggak Ketinggalan Kereta Teknologi itu terus berubah. Tools yang hari ini populer, bisa jadi besok udah diganti sama yang lebih cepat, lebih ringan, dan lebih developer-friendly. Kamu nggak harus langsung pindah ke setiap hal baru. Tapi tahu apa yang lagi hype atau naik daun itu bisa bikin kamu ngerti: Kenapa semua orang pindah dari Redux ke Zustand atau Jotai?Apa sih istimewanya Next.js 15 dibanding versi sebelumnya?Kenapa banyak UI library baru muncul padahal Tailwind udah enak? Kalau kamu ngerti perubahan tren, kamu bisa milih degan bijak: mau ikut, mau coba, atau tetap di tempat karena kamu tahu kenapa. 2. 💼 Biar Relevan Saat Cari Kerja Ini realita yang nggak bisa dihindari. Banyak lowongan kerja sekarang udah nyantumin stack modern di requirement mereka: “Familiar dengan Next.js”“Pengalaman menggunakan Shadcn UI”“Mengerti cara kerja server components & server actions” Kalau kamu masih stuck di stack 2020-an, bisa aja kamu tetap jago. Tapi kalau skill-mu nggak sesuai dengan kebutuhan industri saat ini, ya... bakal susah bersaing. Dengan tahu stack yang lagi naik daun, kamu bisa siapin diri. Nggak harus langsung jago, tapi minimal tahu cara kerjanya, bisa pakai di project kecil, dan nggak gugup pas interview ditanya: “Kamu pernah pakai Next.js versi 13 ke atas?” 3. 🧪 Buat Eksperimen & Side Project Jadi Lebih Menyenangkan Side project itu kayak taman bermain developer. Di situ kamu bebas bereksperimen tanpa tekanan. Dan justru di situ kamu bisa belajar banyak hal baru — termasuk nyobain stack modern. Bayangin kamu bikin web app pribadi: Login pakai Better Auth (tanpa ribet setup auth dari nol)Styling pakai Shadcn UI (biar enak dipandang, cepat dibikin)Backend pakai Supabase (nggak usah ngatur server)ORM-nya pakai Drizzle (biar nulis SQL kayak nulis TypeScript) Dalam seminggu, kamu bisa jadi udah punya MVP jalan dan tinggal polish desainnya. Kalau kamu cuma pakai tools lama karena udah nyaman, kamu mungkin bakal stuck di fase setup yang makan waktu dan bikin malas ngelanjutin project. Jadi... Perlu Ngikutin Semua Stack Baru? Nggak juga. Tapi perlu tahu dan ngerti perkembangan terbaru — iya. Karena ini bukan soal jadi “kekinian”, tapi soal efisiensi, relevansi, dan fleksibilitas kamu sebagai developer. Karena kadang, bukan kamu yang nyari tools baru… Tapi tools baru itulah yang bisa bantu kamu keluar dari kebuntuan. 🚀 🚀 Tools & Framework yang Lagi Ramai di 2025 Setiap tahun, selalu ada aja tools dan framework baru yang muncul. Tapi nggak semuanya bertahan. Ada yang muncul sebentar, viral di Twitter/X, dibahas di podcast developer, terus... hilang tanpa jejak. Tapi yang akan kita bahas di sini bukan sekadar tren instan. Stack-stack ini terbukti mulai banyak dipakai secara serius — bukan cuma oleh early adopter atau content creator, tapi juga oleh freelancer, startup, bahkan tim engineer di perusahaan besar. Bisa dibilang, 2025 adalah tahun di mana developer mulai berpikir: “Gue pengen tool yang bikin hidup lebih mudah, bukan lebih ribet. Gue butuh stack yang cepat, tapi tetap fleksibel dan scalable. Yang cocok buat MVP, tapi tetap tahan lama buat aplikasi gede.” Dan... inilah mereka. 🧱 1. Next.js 15 – Bukan Sekadar React Framework Lagi Homepage Next.j Next.js adalah framework React siap produksi yang dibangun oleh tim Vercel. Tujuannya simpel: bikin developer bisa membangun aplikasi web yang cepat, scalable, SEO-friendly, dan enak dikembangkan — tanpa harus nyetting semuanya dari nol. Kalau React itu library UI, maka Next.js adalah toolkit lengkap-nya. Dia datang bawa fitur server-side rendering, optimasi gambar, sistem routing otomatis, dan banyak lagi. 🤔 Kenapa Next.js Dibuat? React itu hebat buat bikin antarmuka interaktif. Tapi, kalau ngomongin produksi (production), kita bakal ketemu masalah-masalah kayak: 🔄 Lambat di awal: Karena semua render di browser (client-side), butuh waktu buat nampilin konten.🔍 SEO payah: Konten baru bisa muncul kalau JavaScript-nya udah jalan. Mesin pencari nggak suka ini.🛠️ Setup ribet: Harus setting Webpack, Babel, routing, dll. Manual dan rentan error.🧱 Kurang struktur: Kalau proyek makin besar, tanpa konvensi yang jelas bisa jadi berantakan. Next.js hadir buat nutupin kekurangan itu, dan dia kasih semua fitur penting langsung bisa dipakai. 🚀 Fitur-Fitur Utama Next.js 🖥️ Server-Side Rendering (SSR) Halaman dirender di server saat diminta, lalu dikirim dalam bentuk HTML siap tampil.Cocok banget buat SEO, muat lebih cepat, dan hemat performa di device kentang. 📦 Static Site Generation (SSG) Halaman dirender jadi HTML statis saat build, terus disimpan di CDN.Super cepat, super aman, ideal buat blog, dokumentasi, atau landing page. 🧠 Incremental Static Regeneration (ISR) Gabungan SSR + SSG. Halaman bisa diperbarui di background tanpa redeploy seluruh situs.Konten bisa tetap up-to-date tanpa kehilangan kecepatan. 📁 File-based Routing Bikin file di folder pages (atau app) = otomatis jadi URL.Gampang bikin dynamic route kayak /posts/[id]. 🖼️ Optimasi Gambar (next/image) Gambar auto diresize, dikompres, pakai WebP, dan blur placeholder.Nambah skor Core Web Vitals kamu. 🔤 Optimasi Font (next/font) Font auto inline, bebas layout shift.Performanya mantap, konsisten tampilannya. 🔌 API Routes & Route Handlers Bikin endpoint backend langsung dari Next.js, tanpa bikin project backend terpisah.Cocok buat auth, fetch database, dsb. ⛔ Middleware Bisa intercept request sebelum dikirim ke route.Bisa buat redirect, auth, header injection, dll. ♻️ Fast Refresh Edit kode, langsung kelihatan di browser tanpa reload.State komponen tetap aman. Produktivitas naik. ⚙️ Zero Config Udah include Webpack, Babel, linting, dsb. Tinggal pakai, gak usah ribet setup. 🗺️ Pages Router vs App Router Next.js sekarang punya 2 mode utama buat routing: 📄 Pages Router (Tradisional) Semua route diambil dari folder pages/.Data fetching pakai getServerSideProps, getStaticProps, getStaticPaths. 🧩 App Router (Next.js 13+) Folder app/ jadi pusat semua.Gunakan React Server Components (RSC) buat rendering yang lebih efisien.Fitur-fitur keren kayak:Layout & nested routeLoading UI & Error boundaryServer actionCaching dan data fetching langsung dari komponen async 🔔 Pages Router masih didukung penuh kok. Tapi App Router adalah masa depan Next.js. 👀 Kapan Harus Pakai Next.js? Next.js cocok banget kalau kamu: Bikin situs yang butuh SEO kayak blog, e-commerce, landing pageBikin dashboard atau admin panel yang performa dan UX-nya pentingButuh fitur backend ringan (misalnya buat handle form atau fetch database)Mau build aplikasi skala besar dengan struktur rapiPingin developer experience yang menyenangkan & gak ribet setup ✅ Kesimpulan Next.js bukan cuma React + routing — ini adalah ekosistem siap pakai buat bangun web modern. Mulai dari rendering, image, API, sampai struktur proyek, semuanya udah disiapin. Buat developer modern yang pengen web cepat, SEO-friendly, dan scalable, Next.js adalah jawaban yang solid — apalagi kalau kamu udah nyaman di ekosistem React. Mau pake Pages Router atau App Router? Dua-duanya oke, tergantung kebutuhan dan preferensimu. 🎨 2. Shadcn UI — Bukan Sekadar UI Library, Tapi Filosofi Desain yang Kamu Pegang Sendiri Homepage Shadcn UI Pernah pakai UI library yang kelihatan keren di awal, tapi begitu kamu butuh modifikasi dikit… malah bikin frustasi? Warna susah diubah. Margin ngotot. Komponen nggak bisa di-custom. Akhirnya kamu bikin ulang dari awal. Capek, kan? Nah, Shadcn UI hadir bukan sebagai solusi instan, tapi sebagai pendekatan baru: "Daripada kamu pakai komponen orang lain, kenapa nggak bikin sistem desain kamu sendiri — dengan fondasi yang bagus, aksesibel, dan fleksibel 100%?" 🔍 Apa Itu Shadcn UI? Shadcn UI bukan UI library seperti Material UI, Ant Design, atau Chakra UI. Kamu nggak install paket shadcn-ui di package.json. Kamu nggak import dari node_modules. Sebaliknya, kamu copy-paste kode komponen langsung ke dalam project kamu sendiri — lewat CLI. Artinya: kamu punya kendali penuh. Komponen itu sekarang milik kamu, bisa kamu ubah, rombak, dan sesuaikan sesuka hati. 🔨 Cara Kerja Shadcn UI (Step-by-step) Inisialisasi Proyek Kamu mulai dari proyek React atau Next.js biasa.Setup CLI Jalankan perintah: npx shadcn@latest init Kamu akan diminta pilih lokasi folder komponen, config Tailwind, dan beberapa pengaturan dasar. Ini juga akan membuat file components.json. Tambah Komponen yang Kamu Butuhkan Misalnya: npx shadcn@latest add button card dialog CLI akan:Mengambil file source code dari GitHub Shadcn UINyalin ke folder src/components/uiOtomatis install dependensi seperti Radix UI jika dibutuhkan Gunakan Komponennya Karena udah ada di project kamu, kamu tinggal: import { Button } from "@/components/ui/button" Mau ubah style-nya? Ubah langsung di file tersebut. 🎯 Filosofi di Balik Shadcn UI Shadcn UI lahir dari kebutuhan akan kontrol, fleksibilitas, dan efisiensi. Beberapa hal yang jadi fokus utama: Kamu Punya Kode-nya Sendiri Gak ada yang dikunci. Komponen itu milik kamu, bukan milik library.Styling Pakai Tailwind CSS Semua styling pakai Tailwind. Artinya, kamu bisa ngubah warna, spacing, radius, dan layout langsung dari utility class.Headless by Default Shadcn UI dibangun di atas Radix UI — library headless yang fokus pada accessibility dan behavior. Jadi kamu dapat fungsionalitas tingkat tinggi tanpa styling default yang membatasi.Ukuran Bundle yang Efisien Karena kamu hanya mengambil komponen yang kamu butuhkan, bundle size jadi minimal.“Bring Your Own Component” Kamu bukan “pengguna” komponen. Kamu adalah “pemiliknya”. 🌟 Keunggulan Shadcn UI Berikut beberapa alasan kenapa Shadcn UI jadi favorit banyak developer di 2025: 🎨 Kustomisasi Tak Terbatas Mau ubah bentuk, ukuran, animasi? Gak masalah. Semua bisa kamu kontrol lewat kode langsung.📦 Bundle Size Super Efisien Gak ada code bloat. Kamu hanya include komponen yang kamu tambahkan lewat CLI.🚀 Performa Unggul Karena gak ada abstraksi berat atau dependensi besar.♿ Aksesibilitas Bawaan Dibangun di atas Radix UI, jadi semua komponen udah mempertimbangkan accessibility sejak awal.📘 Belajar Tailwind Lebih Dalam Karena styling semua komponen pakai Tailwind, kamu jadi lebih ngerti utility dan best practice-nya.🛠️ Kontrol Penuh atas Kode Mau refactor? Mau tambahin fitur custom? Semuanya bisa, karena kamu pegang source-nya langsung.👥 Komunitas Aktif & Berkembang Cepat Banyak resource, contoh, dan inspirasi dari komunitas developer yang udah make ini di berbagai proyek.✨ Desain Minimalis dan Modern Komponen default-nya clean, simple, dan terasa modern. Gak ketinggalan zaman. ⚠️ Hal yang Perlu Diperhatikan Meski Shadcn UI punya banyak keunggulan, ada beberapa hal yang perlu kamu siapin juga: ⛔ Tidak Ada Theme Provider Global Gak seperti Chakra UI atau MUI, Shadcn UI nggak punya sistem tema bawaan. Tapi kamu bisa setup tema sendiri lewat Tailwind config.📚 Perlu Ngerti Tailwind CSS Kalau kamu belum familiar, mungkin butuh waktu adaptasi. Tapi sekaligus jadi kesempatan bagus buat belajar.🔧 Bukan Plug-and-Play Instan Setup awal butuh sedikit usaha lebih. Tapi hasil akhirnya lebih sesuai kebutuhan kamu.📦 Update Manual Kalau ada perubahan di upstream (misalnya tombol punya fitur baru), kamu perlu jalankan npx shadcn-ui add button lagi dan merge manual. 💡 Cocok Buat Siapa? Shadcn UI cocok banget buat kamu yang: Lagi bangun UI kustom dan nggak pengen kejebak gaya visual library besarUdah familiar dengan Tailwind CSS, atau pengen belajar lebih dalamMau kontrol penuh atas UI aplikasiFokus pada performa, aksesibilitas, dan bundle size kecilPengembang frontend yang kerja bareng designer dan butuh fleksibilitas tinggi 🧾 Penutup Shadcn UI bukan sekadar UI component kit. Ini adalah cara baru membangun antarmuka di era modern. Kamu gak lagi cuma “pakai” komponen, tapi kamu benar-benar punya dan paham setiap baris kode yang membentuk tampilan aplikasi kamu. Di 2025, makin banyak dev yang pindah ke Shadcn UI bukan karena hype… tapi karena mereka butuh kontrol, efisiensi, dan UI yang bisa diatur sesuai keinginan mereka sendiri. Kalau kamu udah capek ngerasa “terkunci” sama UI library, mungkin udah saatnya nyoba Shadcn UI. 🔐 3. Better Auth — Autentikasi yang Serius, Tanpa Ketergantungan Pihak Ketiga Homepage Better Auth Kalau ada satu bagian dalam aplikasi yang sering bikin frustrasi, tapi tetap harus dikerjain dengan benar dan aman, jawabannya adalah: autentikasi. Mulai dari login, register, session, sampai forgot password dan social login — semuanya penting. Tapi seringkali, solusi yang ada: Terlalu simpel dan kurang fleksibelAtau terlalu kompleks, dan akhirnya tetap harus ngoding banyak hal manual Di sinilah Better Auth muncul sebagai solusi serius, khususnya buat kamu yang pakai TypeScript. 🎯 Apa Itu Better Auth? Better Auth adalah framework autentikasi dan otorisasi yang komprehensif, tapi tetap framework-agnostic — artinya, dia nggak terikat sama Next.js, React, atau framework tertentu lainnya. Dirancang khusus buat TypeScript, Better Auth hadir buat menyederhanakan alur autentikasi — dari login sederhana sampai sistem autentikasi multi-tenant kelas enterprise, semua bisa diatur langsung dari backend dan database milikmu sendiri. Nggak perlu layanan pihak ketiga. Nggak perlu bayar mahal buat fitur standar. Dan yang paling penting: kamu tetap punya kontrol penuh atas data user-mu. 🧩 Kenapa Better Auth Hadir? Dalam ekosistem TypeScript, autentikasi sering terasa “setengah jadi”. Banyak library open-source hanya menyelesaikan bagian dasar: login dan session. Tapi ketika kamu butuh fitur seperti: Manajemen userProteksi rate-limit2FAMulti-tenant accessAdmin dashboardNotifikasi via email & SMS … maka kamu harus nambahin banyak kode manual, integrasi eksternal, atau bahkan nulis ulang semuanya. Better Auth hadir untuk mengisi kekosongan itu. Bukan cuma library login — tapi solusi autentikasi menyeluruh yang siap pakai tapi tetap fleksibel. ⚙️ Fitur-Fitur Kuat Better Auth Berikut fitur yang bikin Better Auth makin banyak dipakai di tahun 2025: 🔄 Framework Agnostic Dukungan untuk berbagai framework frontend dan backend: React, Vue, Svelte, Solid, Astro, Next.js, Nuxt, TanStack Start, Hono, dan lainnya. 📧 Email & Password Autentikasi standar tapi tetap aman, dengan hashing, manajemen akun, dan sesi otomatis. 🌐 Social Login Built-in dukungan untuk OAuth: Google, GitHub, Discord, Apple, dan banyak lagi. 🔒 2FA & Rate Limiter Keamanan yang nggak main-main. Kamu bisa langsung aktifkan autentikasi dua langkah dan mencegah brute-force attack dengan rate limit bawaan. 🧑‍💼 Manajemen Akun & Sesi Helper untuk memanage akun user, logout, sesi aktif, serta proteksi route langsung dari server. 🧰 Multi-Tenant System Buat kamu yang bikin SaaS atau sistem organisasi, Better Auth udah siap: OrganisasiTimUndangan emailRole-based access control (RBAC) 📊 Admin Dashboard UI dashboard untuk mengelola pengguna, menganalisis data login, dan memantau aktivitas secara real-time. 🔗 Ekosistem Plugin Ingin nambah fitur? Tinggal pakai plugin: OTPSingle Sign-On (SSO)Audit LogWebhookTracking Login Location 📮 Email & SMS Support Integrasi untuk transactional email & SMS langsung dari auth system. Cocok buat verifikasi akun atau notifikasi keamanan. 🤖 Deteksi Bot & Penipuan Algoritma built-in untuk mendeteksi aktivitas mencurigakan, bot, dan abuse. 💾 Penyimpanan Sesi Global Mau user login di banyak device tapi tetap aman? Bisa. Sistem session global-nya udah siap pakai. ✨ DX yang Nyaman Dengan dokumentasi rapi, dukungan TypeScript penuh, helper functions, dan CLI tooling — pengalaman developernya berasa “smooth”. 💡 Bagaimana Cara Kerjanya? Kamu bisa integrasikan Better Auth langsung ke backend kamu (misalnya di API route, server action, atau handler langsung). Kamu juga bisa koneksikan langsung dengan database milikmu sendiri: PostgreSQL, MySQL, SQLite, Supabase, dsb. Dan karena semuanya berjalan di sisi server, data login, session, dan akun benar-benar ada di bawah kendalimu. 🧠 Kelebihan Better Auth Kenapa banyak developer mulai pindah ke Better Auth? Karena mereka nggak cuma cari “bisa login”, tapi ingin sistem autentikasi yang kuat, fleksibel, dan tetap nyaman dipakai. Berikut beberapa kelebihan utama yang bikin Better Auth menonjol di antara solusi lainnya: 🛡️ Kontrol Penuh Semua data autentikasi dan otorisasi disimpan dan diproses di backend kamu sendiri. Nggak perlu bergantung ke layanan cloud pihak ketiga.📦 Komprehensif Mulai dari login biasa, social login, sampai fitur lanjutan seperti 2FA, SSO, dan multi-tenant — semuanya tersedia langsung out of the box.🔄 Fleksibel Better Auth bisa dipakai di hampir semua framework JavaScript modern seperti React, Vue, Next.js, Astro, Nuxt, Hono, TanStack Start, dan lainnya.🧑‍💻 Developer Friendly Didesain dengan pengalaman developer sebagai prioritas. API-nya type-safe, dokumentasinya rapi, dan punya tooling CLI yang memudahkan setup.🧩 Open Source Dirilis di bawah lisensi MIT — bebas digunakan, dimodifikasi, dan dikembangkan bersama komunitas.🔌 Ekosistem Plugin yang Luas Kamu bisa dengan mudah menambahkan fitur lanjutan melalui plugin: mulai dari autentikasi OTP, webhook, verifikasi email, sampai audit log. 🤔 Jadi Kenapa Banyak Developer Pindah ke Better Auth? Karena mereka capek bikin ulang sistem auth tiap kali mulai project baru. Karena mereka pengen kontrol penuh atas data user. Karena mereka butuh fitur enterprise — tapi gak mau bayar mahal atau lock-in ke vendor. “Dengan Better Auth, gue ngerasa bisa bangun sistem login kayak Auth0 atau Clerk... tapi dengan harga nol dan fleksibilitas penuh.” 📌 Penutup Better Auth bukan sekadar library login. Dia adalah tooling autentikasi serius yang bisa kamu pakai untuk semua jenis aplikasi TypeScript — dari project hobi, startup, sampai produk enterprise. Dan di 2025 ini, makin banyak tim developer mulai berpindah dari solusi “sekedar cukup” ke solusi “benar-benar mantap” seperti Better Auth. 🧬 4. Drizzle ORM — SQL Rasa TypeScript, Ringan Tapi Kuat Homepage Drizzle ORM Pernah ngerasa bosan nulis query SQL mentah? Atau justru pernah frustrasi sama ORM yang terlalu banyak “magic” dan nge-blur logika database kamu? Di 2025 ini, banyak developer akhirnya nemu jalan tengah: Drizzle ORM. Drizzle bukan ORM yang ngumpet-ngumpetin SQL kamu. Bukan juga yang ngebuat semuanya terlalu otomatis sampai susah ditelusuri. Dia hadir buat satu misi besar: "Bikin kerja dengan database terasa natural di TypeScript — tanpa kehilangan kekuatan SQL yang sebenarnya." 🔍 Apa Itu Drizzle ORM? Secara sederhana, Drizzle adalah Object-Relational Mapper (ORM) modern, ringan, dan type-safe — dan yang paling penting: dibuat khusus untuk TypeScript. Drizzle membantu kamu berinteraksi dengan database relasional (PostgreSQL, MySQL, SQLite) dengan sintaks yang bersih, aman, dan familiar buat penggemar SQL. Tapi yang bikin beda: setiap query kamu akan auto-dapat dukungan type-checking, auto-complete, dan deteksi error di waktu compile. 🧠 Kenapa Drizzle Bisa Jadi Favorit Banyak Developer? Drizzle muncul dari keresahan banyak dev: ORM lama terlalu besar dan beratSQL mentah terlalu berisiko dan susah dijagaKebutuhan akan tooling modern yang ngikutin ekosistem TypeScript Dan ini dia alasan kenapa Drizzle makin naik daun: ✅ 1. TypeScript-First, Type-Safe Banget Semua definisi skema database kamu ditulis dalam TypeScript. Drizzle bakal auto-generate tipe untuk setiap query. Hasilnya? Auto-complete di VSCodeDeteksi error pas ngetik, bukan pas runtimeKode yang jauh lebih kokoh dan gampang dipelihara 🧾 2. SQL-like Syntax Kalau kamu udah familiar sama SQL, kamu bakal cepat nyatu dengan Drizzle. Contoh: const users = await db.select().from(userTable).where(eq(userTable.email, "[email protected]")) Readable, aman, dan tetap powerful. ⚡ 3. Super Ringan dan Cepat Tanpa dependency besar. Tanpa layer abstraksi berlebihan. Cocok banget buat: Proyek kecil sampai besarLingkungan serverless (seperti Vercel, Cloudflare, Bun)Dev yang pengen kontrol penuh tapi gak mau ribet 🔄 4. Relational Query Builder (RQB) Ambil data relasi antar tabel (misalnya user dan posts) dalam satu query SQL bersih. Bukan 2 query, bukan N+1 query — tapi satu kueri yang optimal. 🛠️ 5. Migrasi Database Modern Pakai drizzle-kit, kamu bisa generate file migrasi langsung dari definisi skema TypeScript. Mau rollback atau deploy migrasi ke staging/production? Tinggal jalanin CLI. 🧩 6. Fleksibel Banget Kamu bebas pakai di runtime dan framework manapun: Node.js, Deno, BunNext.js, SvelteKit, Astro, SolidStart Gak dipaksa pakai struktur tertentu. Gak dikunci ke platform tertentu. ✍️ 7. Bisa Tulis SQL Mentah Kalau Perlu Ada query yang kompleks banget? Butuh raw performance? Drizzle tetap kasih jalan: await db.execute(sql`SELECT * FROM users WHERE created_at > NOW() - INTERVAL '7 days'`) Kamu punya kuasa penuh atas semua query. 🔧 Komponen Inti Drizzle ORM Drizzle itu bukan satu file doang, tapi terdiri dari tiga bagian penting: drizzle-orm (Core Library): Tempat semua definisi skema dan query builder hidup.Database Driver: Kamu pakai driver eksternal sesuai database:PostgreSQL: pg / @neondatabase/serverlessMySQL: mysql2SQLite: better-sqlite3drizzle-kit (CLI Tool): Untuk generate migrasi, snapshot skema, dan tooling pengembangan lainnya. 🛠️ Cara Kerja Drizzle: Dari Skema sampai Query 1. Definisi Skema Kamu bikin skema langsung di TypeScript: import { pgTable, serial, text } from 'drizzle-orm/pg-core' export const users = pgTable('users', { id: serial('id').primaryKey(), name: text('name').notNull(), email: text('email').unique().notNull(), }) 2. Koneksi ke Database Gunakan driver sesuai kebutuhan: import { drizzle } from 'drizzle-orm/node-postgres' import { Pool } from 'pg' const pool = new Pool({ connectionString: process.env.DATABASE_URL }) export const db = drizzle(pool) 3. Menulis Query CRUD operation-nya mirip SQL, tapi full TypeScript: await db.insert(users).values({ name: 'Jabir', email: '[email protected]' }) const data = await db.select().from(users).where(eq(users.id, 1)) await db.update(users).set({ name: 'Jabir Dev' }).where(eq(users.id, 1)) await db.delete(users).where(eq(users.email, '[email protected]')) 4. Migrasi Otomatis npx drizzle-kit push Drizzle akan generate file migrasi SQL berdasarkan perubahan skema kamu. 🔁 Dibanding Prisma? Kalau kamu masih mikir, “Bedanya Drizzle sama Prisma apa sih?”, berikut perbandingannya dalam bentuk poin biar lebih jelas: 📚 Style Drizzle ORM punya sintaks yang lebih dekat ke SQL. Cocok buat yang suka eksplisit dan ngerti cara kerja database. Sementara Prisma lebih abstrak, dengan style coding yang lebih "terbungkus" dan terstruktur.⚡ Kecepatan Drizzle jauh lebih ringan dan cepat — cocok banget buat aplikasi serverless atau yang perlu cold start cepat. Prisma performanya cukup baik, tapi sering terasa berat di environment yang butuh efisiensi tinggi.🧠 Type-Safety Keduanya type-safe, tapi Drizzle benar-benar TypeScript-first. Artinya, setiap bagian dari skema dan query otomatis dapat dukungan penuh dari compiler.🔧 Migrasi Database Drizzle menggunakan CLI drizzle-kit untuk generate dan menerapkan migrasi. Prisma punya sistem migrasi bawaan yang lebih terintegrasi, dengan tooling visual seperti Prisma Studio.🌩️ Serverless Ready Drizzle unggul karena nggak tergantung proses background atau binary berat. Cocok buat Vercel, Cloudflare, dan Bun. Prisma kadang punya kendala performa dan cold start saat dipakai di lingkungan serverless.🎛️ Kustomisasi Query Drizzle ngasih kamu kebebasan penuh. Mau SQL-like, raw SQL, atau query builder kompleks, semua bisa. Prisma lebih terbatas dan kadang perlu workaround untuk query-query spesifik. Kalau kamu pengen ORM yang ringan, fleksibel, dan bisa diajak ngoding modern di TypeScript, Drizzle layak banget dicoba. Tapi kalau kamu pengen solusi “semua sudah tersedia” dan siap pakai secara end-to-end, Prisma juga tetap jadi pilihan bagus. Tinggal kamu sesuaikan aja dengan kebutuhan dan gaya kerja timmu 😉 🎯 Kapan Pakai Drizzle? Drizzle ORM cocok banget kalau kamu: Ingin kontrol penuh atas SQLSuka kerja dengan type-safe querySedang bangun proyek di Next.js, Bun, atau DenoIngin ORM yang ringan dan bisa diskalakanBikin aplikasi di serverless environment dengan cold start rendah 📌 Penutup Drizzle ORM bukan ORM biasa. Dia menjembatani dunia SQL yang efisien dengan dunia TypeScript yang strict dan modern. Kalau kamu selama ini ngerasa ORM terlalu mengekang, tapi juga ogah nulis SQL mentah terus-terusan — Drizzle adalah sweet spot-nya. Modern, ringan, dan bikin ngoding ke database terasa jauh lebih menyenangkan. Di 2025 ini, Drizzle ORM mulai jadi default buat banyak tim modern. Dan setelah nyoba sendiri, mungkin kamu bakal ngerasa hal yang sama: ini ORM yang akhirnya beneran ngerti developer. ⚡ 5. Bun + Vite — Toolchain Super Cepat untuk Frontend Masa Kini Homepage Bun Kalau kamu pernah merasa npm install terlalu lama, atau webpack bikin kamu nunggu ngopi dulu sebelum preview jalan, kamu gak sendiri. “Zaman sekarang, developer butuh alat yang cepat, ringan, dan gak ribet. Nah, di 2025 ini, kombinasi Bun + Vite jadi pasangan paling ngebut buat pengembangan frontend.” Bun fokus di kecepatan eksekusi dan instalasi dependensi. Vite fokus di kecepatan HMR dan pengalaman dev server. Digabungin? Rasanya kayak naik motor listrik tapi jalurnya tol kosong. 🥖 Apa Itu Bun? Bun itu bukan roti. Tapi dia bisa jadi fondasi runtime, package manager, bahkan bundler dan test runner — semua dalam satu alat. 🧠 Fitur-Fitur Bun: 🔥 Super Cepat: Dibuat dengan Zig, Bun punya kecepatan instalasi dan eksekusi yang bikin npm dan yarn keliatan lemot.📦 Package Manager: bun install bisa 10x lebih cepat dari npm.🔧 Runtime JavaScript & TypeScript: Bun bisa jalanin skrip langsung tanpa Babel, tanpa ts-node.🛠️ Bundler Bawaan: Bisa nge-bundle kode frontend/backend tanpa setup ribet.🧪 Test Runner Built-in: Kayak Jest, tapi lebih cepat.💻 Built-in Fetch/Web API: Mirip Deno. Gak perlu banyak polyfill. “Kalau kamu suka hal yang simple dan cepat, Bun akan terasa menyenangkan banget.” 🚀 Apa Itu Vite? Homepage Vite Vite (dibaca: vit, artinya “cepat” dalam bahasa Prancis) adalah alat yang ngubah cara kita ngebuild aplikasi frontend — terutama React, Vue, Svelte, dan kawan-kawan. ⚙️ Fitur-Fitur Vite: ⚡ Dev Server Ngebut: Gak ada bundling pas development. Semua langsung pake ESM. Hasilnya? Startup instan.🌡️ HMR Super Cepat: Ganti file? Halaman gak reload total, cuma bagian yang berubah.📦 Produksi Pake Rollup: Untuk build akhir, Vite pake Rollup yang hasilnya kecil dan efisien.🔌 Plugin-Friendly: Banyak plugin bawaan dan komunitas. Mau Tailwind, PWA, sampai Markdown? Tinggal tambah.📘 Support JSX & TS Out of the Box: Gak perlu config ribet. “Vite itu kayak Rolls Royce-nya dev server: cepat, halus, dan fleksibel.” 🧪 Kombinasi Bun + Vite: Kok Bisa Barengan? Meskipun Bun punya bundler sendiri, banyak dev tetap milih Vite buat bagian frontend karena: Ekosistem Vite sudah kaya dan matangHMR dan plugin system-nya sangat powerfulBuild production dari Vite (dengan Rollup) tetap juara Nah, Bun dipakai buat semua yang ada di luar dev server, seperti: 🧩 Peran Bun: bun install: super cepat gantiin npm installbun run dev: jalanin server Vitebun run build: build projectbun run lint/test: jalanin lint atau test pake CLI 🧩 Peran Vite: Serve app frontend dengan dev server ngebutHMR kilat tanpa reload seluruh halamanBuild final untuk produksi (Rollup under the hood)Integrasi framework (React, Vue, Svelte, dll.) 🧪 Contoh Alur Kerja bun create vite my-app --template react-ts cd my-app bun install bun run dev Semua serba cepat. Mulai project baru? Gak sampai 30 detik udah jalan di browser. 🧠 Kenapa Harus Coba Kombinasi Ini? ✅ Kecepatan di Semua Sisi: Install dependensi? Sekejap.Start dev server? Nyala langsung.Perubahan file? Langsung kelihatan.Build produksi? Gak makan waktu lama. ✅ Toolchain yang Modern & Ringkas: Gak perlu banyak konfigurasi aneh.Gak tergantung pada Node.js (kalau gak mau).Semuanya bisa jalan dengan satu runtime: Bun. ✅ Ekosistem Tetap Kuat: Masih bisa pake plugin Vite.Kompatibel sama banyak framework modern. 📌 Kapan Kombinasi Bun + Vite Cocok Banget? 🚀 Bikin proyek frontend baru dengan React, Vue, Svelte, dll.🧪 Eksperimen proyek pribadi atau MVP.🏢 Tim yang butuh efisiensi tinggi dan cepat prototyping.📦 Proyek dengan banyak dependensi tapi pengen waktu install super singkat.🧰 Dev yang suka terminal bersih dan proses simple. 🧾 Penutup Kalau tahun-tahun lalu kamu masih stuck dengan npm install lama, webpack yang berat, atau proses build yang bikin nunggu lama… “2025 adalah waktunya pindah ke combo Bun + Vite.” Toolchain ini bukan cuma trend, tapi bukti nyata kalau pengalaman dev modern bisa cepat, ringan, dan menyenangkan. Satu klik jalan. Satu detik nyala. Satu kata: puas. 🧠 Stack Ini Cocok Buat Siapa? “Oke, stack-nya keren. Tapi... cocok buat gue gak, ya?” Nah, itu pertanyaan yang bagus — karena gak semua teknologi cocok buat semua orang. Tapi kabar baiknya: stack frontend 2025 ini cukup fleksibel buat berbagai tipe developer. 🚀 Freelancer & Indie Dev Kalau kamu seorang solo developer, freelancer, atau orang yng hobi bikin side project malam-malam sambil ngopi — stack ini ibarat senjata serbaguna. Kenapa cocok? Cepat disiapkan: Kombinasi Bun + Vite bikin inisialisasi proyek kayak nyalain lampu.Ringan: Gak banyak config ribet, gak makan resource berat.Fleksibel: Gak terikat satu vendor. Mau deploy ke Vercel, Netlify, atau self-host — semua bisa.Full kontrol: Tools kayak Drizzle ORM dan Shadcn UI ngasih kamu kendali penuh, jadi UI dan data flow bisa kamu custom sesuai visi pribadi. “Gue pernah bangun MVP buat klien UKM, dari nol sampai online dalam 2 hari. Semua pakai stack ini. Gak ngecewain.” 🧑‍💼 Dev Kantoran (Team-Based Project) Kalau kamu kerjaa di tim, entah itu startup, perusahaan besar, atau agensi digital — stack ini juga main aman. Kenapa? Scalable: Next.js 15 + Better Auth bisa ngurus SEO, autentikasi, caching, dan routing dengan gaya enterprise.Type-safe: Drizzle ORM bikin kerja tim lebih tenang. Satu tipe berubah, semua kena warning.Collab-friendly: Shadcn UI dan Tailwind bikin desainer dan dev ngomong dalam “bahasa yang sama”.Dev tools modern: Bun install dependensi dalam hitungan detik. No more wasting CI/CD time. “Gue kerja di tim 5 orang, semua full remote. Stack ini bikin kita kerja lebih cepat dan minim error.” 🎓 Pemula / Career Switcher Baru masuk dunia frontend? Atau lagi pindah karier dari jurusan lain? Tenang — stack ini gak nyeremin, kok. Kenapa? Dokumentasi solid: Next.js, Vite, Drizzle, Better Auth — semua punya docs yang jelas dan enak dibaca.Komunitas aktif: Cari error tinggal Google atau tanya di Discord, langsung ada yang bantu.Learning path jelas: Dari UI (Shadcn), ke logic (Next.js), ke database (Drizzle), semuanya nyambung dan bisa dipelajari bertahap.Lebih future-proof: Belajar stack ini = siap masuk job market 2025. “Awalnya gue pikir stack ini cuma buat yang udah jago. Tapi pas dicoba, ternyata belajar 1 demi 1, semuanya masuk akal.” Jadi, Stack Ini Buat Siapa? Jawaban singkatnya: Buat kamu yang pengen produktif, tapi tetap modern. Gak peduli kamu ngoding sendirrian, bareng tim, atau baru mulai — stack ini punya tempat buatmu. 🎯 Kesimpulan: Nggak Harus Ikut Tren, Tapi Tahu Tren Itu Penting “Frontend itu ibarat kota yang nggak pernah tidur. Hari ini ramein library A, besok udah pindah ke framework B.” Dunia frontend tuh bergerak cepat banget. Hari ini orang rame pakai sesuatu, minggu depan udah pada pindah haluan. Teknologi baru muncul tiap saat, dari framework, library, sampai tool dan build system. Kalau kamu ngerasa ketinggalan tren? Tenang, semua orang pernah ngerasa kayak gitu. 🔑 Tapi Gimana Harus Nyikapinnya? Jawabannya bukan ikut-ikutan — tapi peka. Kamu nggak harus ngikutin semua tren, apalagi sampai eksperimen ke semua hal. Tapi ngerti tren? Tau mana yang mulai rame dan mana yang pelan-pelan ditinggalin? Itu penting. Banget. Kenapa? 💡 1. Buat Nentuin Fokus Belajar Image by Freepik Misalnya kamu bingung mau fokus ke mana: React? Svelte? Vue? Astro? Kalau kamu ngerti tren, kamu jadi bisa milih belajar yang relevan buat karier atau proyekmu. Contoh: Tailwind makin banyak dipakai di industri → worth it buat kamu dalemin.Remix mulai sepi, sementara Next.js makin ke arah App Router → berarti kamu bisa alokasikan energi ke Next.js dulu. 🔧 2. Buat Milih Stack Proyek Image by Freepik Punya side project atau freelance job? Kalau kamu ngerti tren, kamu jadi bisa pilih teknologi yang modern, didukung komunitas, dan nggak bikin susah di masa depan. Cotnoh: Pilih Vite daripada Webpack buat dev server → karena lebih cepat dan simpel.Pakai Shadcn UI daripada Chakra/MUI → karena lebih fleksibel, modern, dan gampang dikustom. 💼 3. Buat Ngejar Peluang Karier Image by Freepik Recruiter nggak bakal nyari orang yang tahu semua hal. Tapi mereka cari orang yang peka terhadap teknologi yang lagi tumbuh. Contoh: “Oh, kandidat ini udah familiar dengan Bun dan Vite. Menarik nih, karena tim kita lagi migrasi ke stack itu.”“Dia tahu App Router di Next.js, bukan cuma Pages Router doang.” Tren itu bisa jadi tiket kamu masuk ke wawancara, atau bahkan dilirik tim engineer yang butuh talenta up-to-date. 🧠 4. Belajar dari Pengalaman Image by Freepik "Gue dulu skeptis sama Tailwind. Ngeliat utility class semua rasanya berantakan. Tapi ternyata makin ke sini, makin banyak tim yang pakai. Untung gue nyoba lebih awal, jadi nggak kagok pas harus make." Kalau kamu pernah punya pengalaman kayak gitu, kamu pasti ngerti rasanya. Nggak ikut tren = nggak apa-apa. Tapi kalau paham arahnya, kamu bisa siap lebih awal. 🧭 5. Intinya: Jangan Asal Tren, Tapi Tahu Arah Tren Image by Freepik Frontend bukan tentang jadi orang pertama yang coba semua hal baru. Tapi tentang jadi orang yang tahu kapan harus mulai belajar sesuatu — dan kapan nggak usah peduli. “Mau ngikutin semua tren itu kayak coba pake semua baju fashion minggu ini sekaligus — malah pusing sendiri. Tapi ngerti gaya mana yang lagi naik? Itu bikin kamu tetap tampil siap — di interview, di proyek, dan di pasar kerja.” Kalau kamu udah sampai sini, berarti kamu udah satu langkah lebih peka daripada kebanyakan developer lain. Terus jaga awareness-mu — dan jangan lupa, nggak ngikutin tren bukan berarti ketinggalan. Tapi nggak tahu tren sama sekali? Itu yang bahaya. 🔁 Bonus: Tips Ngikutin Tren Tanpa Overwhelmed "Tren teknologi tuh kayak ombak — seru kalau bisa naik, tapi bisa capek juga kalau terus-terusan ngejar." Frontend sekarang kayak pasar malam — ramai, terang, tapi kadang bikin pusing. Tiap minggu ada aja istilah baru yang lewat: dari server actions, islands architecture, edge function, sampai AI-powered CLI. Ngikutin semua? Mustahil. Tapi peka sama arah tren? Bisa banget. Nah, biar tetap waras tapi tetap update, ini dia tips simpel tapi manjur: 🧑‍💻 1. Follow Developer yang Aktif di Twitter/X atau YouTube Kalau kamu scroll medsos, kenapa nggak sekalian isi dengan konten yang bermanfaat? Follow beberapa developer yang rutin ngebahas tren — mereka biasanya jadi orang pertama yang bongkar teknologi baru, ngasih tutorial, atau sekadar bagi opini menarik. Contoh akun luar: @leerob (Vercel, banyak insight seputar Next.js & DX)@shadcn (pencipta Shadcn UI, sering bahas styling modern)Theo - @t3dotgg (pedas tapi insightful, bahas stack modern) Contoh akun lokal: @BuildWithAngga — update tren dan tips belajar yang relatable banget@KampusCoding — kadang share job, tips, dan roadmap yang cocok buat pemula "Gue sering dapet bocoran fitur baru Next.js bukan dari changelog, tapi dari retweet developer yang gue follow." Tips: Buat list khusus di Twitter atau subscribe YouTube-nya biar timeline kamu nggak cuma meme 😄 📬 2. Subscribe Newsletter Teknologi (Kurasi Mingguan) Kalau kamu nggak sempat mantengin Twitter tiap hari, coba cara ini. Langganan newsletter teknologi itu ibarat punya asisten pribadi yang tiap minggu ngirimin rangkuman berita penting: Rekomendasi: Frontend Focus – update frontend terbaru tiap mingguBytes.dev – bahas ringan, sering nyeleneh, tapi tetap informatifTLDR.dev – tech headlines singkat & padat Cukup buka email seminggu sekali sambil ngopi, dan kamu udah update tren tanpa ribet. “Gue nemu fitur baru di Vite dari newsletter, padahal belum ada di dokumentasi resmi waktu itu.” 🌐 3. Gabung Komunitas Online: Discord, GitHub, Dev.to Komunitas itu sumber insight paling hidup. Kalau kamu belum gabung ke komunitas teknologi, kamu kehilangan banyak hal. Di sana bukan cuma bisa tanya jawab, tapi juga: Dapat bocoran stack baruLihat project seru dari developer lainIkut diskusi atau live coding bareng Tempat rekomendasi: Discord Astro → astro.build/chatShadcn UI → server komunitas open sourceGitHub Discussions → cari repo favoritmu, cek bagian diskusiDev.to → artikel dan komentar dari developer real-world “Gue tahu tentang Bun dan kenapa dia cepet banget — bukan dari blogpost, tapi dari diskusi random di Discord Shadcn UI.” 🔧 4. Belajar Stack Baru Lewat Mini Project atau Clone App Tren itu nggak bakal ‘nempel’ cuma dari baca teori. Harus dicoba. Tapi tenang, kamu nggak perlu bikin full e-commerce app buat belajar stack baru. Cukup mulai dari: Mini Project → misalnya dark mode toggle, search bar dengan debounce, atau animasi scroll.Clone App Sederhana → bikin Twitter clone pakai Next.js, atau Notion clone pakai React + Supabase. "Gue baru ngerti App Router Next.js pas bikin project sendiri. Ternyata beda banget sama Pages Router." Selain belajar stack-nya, kamu juga dapat skill tambahan kayak manajemen state, struktur folder, dan pattern best practice. 🧘‍♂️ 5. Santai Aja, Jangan Buru-Buru "Gue baru ngerti Tailwind pas 2023, padahal hype-nya udah dari 2020. Tapi nggak apa-apa — pas guenya siap, belajarnya malah lebih gampang." Tren itu bukan lomba lari. Kadang ada hal yang kelihatan hype banget, tapi belum relevan buat kamu sekarang. It’s okay. Lebih baik konsisten belajar sedikit demi sedikit, daripada sok ikut tren tapi akhirnya capek sendiri dan mental drop.

Kelas 12 Plugin Figma Paling Membantu untuk UI/UX Designer di BuildWithAngga

12 Plugin Figma Paling Membantu untuk UI/UX Designer

Hai, para desainer UI/UX! Mau desain lebih cepat, rapi, keren, dan bikin klien melongo? Nah pas banget nih di artikel ini, kita bakal kupas tuntas 12 plugin Figma paling keren yang bisa kamu coba dan siap jadi jagoanmu di setiap project biar makin kece dan cepat selesai. Let’s Go kita kupas! 1. Foundation : Colors Generator Foundation : Color Generator cover Pernah stress sampai capek nentuin warna apa yang cocok untuk design kamu? atau capek banget kasih nama satu-satu color style yang ada di project kamu? Nah tenang aja kita kasih nih solusi yang bikin hidup jadi UI/UX Designer jadi mudah! Say hello to Foundation : Colors Generator. Dengan Foundation : Colors Generator ini bikin kerjaan kita makin satset! Nggak perlu lagi ribet nyari warna satu-satu terus mikirin namanya. Plugin ini bisa otomatis generate warna sekaligus bikin color style yang rapi dan konsisten. Cocok banget buat kamu yang pengen desain lebih cepat, hemat tenaga, dan pastinya nggak bikin pusing. Sekali klik, semua warna langsung tertata rapi. Praktis banget buat project besar maupun kecil! Kenapa Harus Coba Foundation: Colors Generator? Cepat & Efisien. Bikin color style dalam hitungan detik.Nama Otomatis. Gak usah capek-capek mikirin nama warna, semua diurusin otomatis!Konsisten. Warna dan penamaan yang seragam bikin desainmu lebih rapi.Gratis. Kita gaperlu bayar buat semua kemudahan yang ditawarin. Cara Menggunakan Plugin Foundation: Colors Generator Buka file Figma kamu.Save dulu plugin dari Figma CommunityAkses panel plugin dan cari "Foundation: Colors Generator".Klik plugin dan pilih warna yang ingin di generate.Klik Pallete untuk generate Color Pallete otomatis.Klik Create Styles untuk generate Color Styles otomatis.Foundation : Colors Generator secara otomatis akan menyesesuaikan semua dengan warna yang telah kita pilih. Contoh Pemakaian Plugin Foundation : Colors Generator 2. Unsplash Unsplash Figma Cover Lagi ngerjain desain tapi stuck karena butuh gambar yang bagus, estetik, dan nggak mau ribet urus lisensi atau cari-cari di internet? Tenang, nggak usah buka tab baru, download satu-satu, terus drag-drop ke Figma lagi, langsung aja pakai plugin Unsplash di Figma. Plugin ini ngebantu banget buat kamu dapetin ribuan foto berkualitas tinggi, gratis, dan pastinya bebas hak cipta langsung ke dalam workspace Figma kamu. Mau gambar cityscape, orang main game, furniture, atau suasana malam yang dreamy? Tinggal ketik kata kuncinya, klik, dan foto langsung masuk ke canvas. Cepat, praktis, gak habisin storage buat menyimpan gambar dan hasilnya juga bikin desain kamu makin keren. Nggak cuma buat mockup atau placeholder aja, gambar dari Unsplash juga cocok buat presentasi, header, hero image, bahkan buat mood board di awal proses desain. Dan karena lisensinya aman, kamu nggak perlu khawatir soal hak pakai karena semua foto yang ada bisa kamu gunakan untuk kebutuhan personal maupun komersial. Kenapa kamu harus coba pakai Unsplash? Nggak perlu pindah-pindah aplikasi atau browser. Semua langsung dari dalam Figma. Praktis banget.Gambar bebas hak cipta. Foto-foto dari Unsplash gratis dipakai buat proyek desain, presentasi, sampai prototype klien.Pilihannya banyak & berkualitas tinggi. Mau gambar gaya minimalis, aesthetic, atau real-life? tenang semua ada. Cara Menggunakan Plugin Unsplash Akses panel plugin dan cari "Unsplash".Klik plugin dan pilih search.Ketik gambar yang ingin dicari dan klik search.Klik gambar yang ingin di pakai.Gambar otomatis akan muncul pada canvas. Contoh Pemakaian Plugin Unsplash 3. Content Reel Content Reel Figma Cover Capek lihat tulisan 'Lorem ipsum' di mana-mana tapi belum punya konten beneran? atau capek insert gambar untuk foto profil satu-satu? Tenang aja ada plugin figma yang bisa bantu kita yang namanya Content Reel. Content Reel adalah plugin Figma yang bisa bantu kamu ngisi konten-konten dummy di desainmu mulai dari nama orang, nomor telepon, email, kota, foto profil, bahkan kalimat lengkap. Semua langsung bisa di-input ke elemen teks atau image di canvas Figma kamu. daripada ngetik satu-satu atau copy paste dari google dengan plugin ini kita tinggal pilih jenis konten yang dibutuhin lalu dengan sekali klik semua langsung selesai. Kenapa kamu harus coba pakai Content Reel? Gak perlu lagi “Lorem ipsum” atau copy-paste data dummy dari Google. Cukup klik dan langsung isi teks, gambar, atau info pengguna dengan konten yang realistis.Super cepat & hemat waktu. ****Ingin isi nama-nama user di satu list? Pilih elemen, klik satu tombol, dan semuanya langsung terisi otomatis.Gratis & langsung bisa dipakai di Figma. Tidak butuh setup aneh-aneh. Tinggal cari plugin, buka, dan langsung pakai dan yang terpenting gratis. Cara Menggunakan Plugin Akses panel plugin dan cari "Content Reel".Pilih content yang ingin di isi misalnya “avatars”.Klik object di canvas yang ingin di isi dengan data dummy.Klik gambar yang ingin di pakai atau klik apply all untuk random data dummy.data dummy otomatis akan terisi pada object Contoh Pemakaian Plugin Content Reel 4. Iconify Iconify Figma Cover Butuh ikon buat desain? Gak mau ribet buka situs lain, download SVG satu-satu, lalu import ke Figma? Udah, langsung aja gas pakai plugin Iconify. Iconify adalah plugin keren yang pastinya membantu banget buat kita nih para UI/UX Designer karena Iconify kasih kamu akses ke lebih dari 100.000 ikon dari berbagai library populer seperti Material Icons, Tabler, Heroicons, Carbon, Bootstrap Icons, Feather, dan masih banyak lagi semuanya langsung bisa kamu cari dan pakai tanpa keluar dari Figma. Kenapa kamu harus coba pakai Iconify? Cari ikon langsung dari Figma**.** Nggak perlu buka Google, ketik “search icon people SVG”, klik kanan, simpan, lalu import. Di Iconify, cukup cari "search", klik ikon yang cocok, langsung masuk ke canvas. Buat hidup jadi mudah.Satu plugin, ratusan library ikon. Kamu bisa akses ratusan set ikon populer dalam satu tempat. Mau gaya outline, solid, duotone, minimalis dan yang pastinya semua bisa dikustomisasi.Gratis dan open-source. Iconify adalah plugin gratis yang terus dikembangkan, dan kamu bisa pakai sebanyak yang kamu mau, tanpa batasan fitur. Cara Menggunakan Plugin Iconify Akses panel plugin dan cari "Iconify".Search icon yang ingin dipakai pada figma misalnya “delete”Klik icon yang ingin dipakai dan import as frame atau import as componenticon berhasil di import ke file figma Contoh Pemakaian Plugin Iconify 5. Typescales Typescales Figma Cover Pernah bingung bikin ukuran font yang konsisten antara heading, subheading, body, dan caption? Atau suka galau milih font-size karena takut hasilnya nggak rapi dan berantakan? Plugin TypeScales jawabannya. TypeScales adalah plugin Figma yang bantu kamu bikin sistem tipografi otomatis berdasarkan skala yang bisa diatur. Jadi kamu nggak perlu nebak-nebak lagi apakah font 24px cocok dipasang sama 16px, atau apakah ukuran heading kamu terlalu besar dibandingkan body text. Dengan TypeScales, kamu bisa langsung generate hierarchy teks dari H1 sampai caption hanya dengan satu klik. Kenapa kamu harus coba pakai Typescales? Bikin sistem tipografi dalam hitungan detik. Daripada setting satu-satu ukuran teks secara manual, plugin ini langsung generate semua level teks secara otomatis. Bikin hemat waktu.Bisa diatur sesuai kebutuhan. Mau skala besar atau kecil? Mau rasio 1.2, 1.25, sampai Golden Ratio? Semua bisa kamu pilih sendiri.Nggak pusing mikirin angka lagi. Biarin plugin yang hitung. Kamu tinggal fokus ke desain dan visualisasi. Cara Menggunakan Plugin Typescales Akses panel plugin dan cari "Typescales".Setiing angka variabel sesuai keinginan kita misal “basesize : 16, Scale: 1,25. Dst.”Klik generateText beserta ukuran yang sudah di setting akan muncul otomatis pada canvas. Contoh Pemakaian Plugin Typescales 6. Html.To.Design html.to.design Figma Cover Cape scroll, screenshot, potong, lalu desain ulang? Stop, itu cara lama. HTML.to.Design bantu kamu ‘capture’ tampilan web langsung ke dalam Figma. Praktis banget! HTML.to.Design adalah plugin Figma yang bisa meng-convert tampilan website langsung ke dalam artboard desain di Figma. Cukup tempel URL, klik generate, dan boom! tampilannya langsung muncul di canvas Figma kamu, lengkap sama layout dan elemen-elemennya. Kenapa kamu harus coba pakai html.to.design? Hemat waktu banget. Cocok buat kamu yang mau bikin redesign, moodboard, atau studi kompetitor tanpa buang waktu recreate layout manual.Langsung bisa diedit di Figma. Hasilnya bukan gambar doang! Elemen yang di-convert tetap editable bisa kamu ubah teks, warna, atau posisi sesukamu.Bisa convert banyak jenis website. Dari landing page, blog, hingga dashboard admin semua bisa diubah jadi file Figma dengan rapi. Cara Menggunakan Plugin html.to.design Akses panel plugin dan cari "html.to.desing".masukan url web yang ingin di import ke figma.Klik importplugin akan mengimport web menjadi frame figma yang dapat di edit. Contoh Pemakaian Plugin html.to.design 7. Illustrations Illustrations Figma Cover Desainmu udah oke, tapi masih terasa ‘kosong’ atau kurang hidup? Coba tambahin ilustrasi pakai plugin Illustrations biar makin engaging! Plugin Illustrations satu ini menyediakan akses ke ratusan ilustrasi berkualitas tinggi yang bisa langsung kamu tarik ke canvas. Gak perlu lagi hunting aset ilustrasi di situs lain, download manual, lalu upload ke Figma. Semua jadi praktis dan pastinya hemat waktu. Kenapa kamu harus coba pakai Illustrations? Ribuan ilustrasi gratis. Mulai dari gaya flat, outline, kartun, sampai karakter keren semua masuk ke koleksi plugin ini .Gratis & tanpa ribet lisensi. Semua ilustrasi dilisensi dengan creative commons, aman dipakai untuk desain personal maupun bisnisBikin desainmu lebih hidup & engaging Ilustrasi cocok dipakai di hero section, empty state, onboarding, hingga mood board bikin UI terlihat lebih keren. Cara Menggunakan Plugin Illustrations Akses panel plugin dan cari "Illustrations".Pilih dan klik ilustrasi yang ingin dimasukkan kedalam canvasplugin akan memasukan gambar ilustrasi yang dipilih kedalam canvas Contoh Pemakaian Plugin Illustrations 8. Beautiful Shadows Cover Figma Beautiful Shadows Kalau kamu capek utak-atik drop shadow satu‑satu di Figma tapi hasilnya masih kurang hidup? Coba pakai Beautiful Shadows! Beautiful Shadows adalah plugin yang membantu kamu membuat shadow secara realistis dan smooth layaknya cahaya nyata, bukan sekadar efek blur standar. Dengan mengatur sumber cahaya secara intuitif plugin ini menghasilkan gradien bayangan yang halus dan alami Kenapa Beautiful Shadows wajib kamu coba? Bayangan tampak lebih realistis. Plugin ini membuat bayangan yang menyerupai efek cahaya alami, bukan sekadar blur acak. Hasil akhirnya akan terlihat seperti ada sumber cahaya nyata yang mengenai objek sehingga membuat desainmu lebih terasa nyata.Gak perlu trial-error ngatur offset dan blur manual. Dengan plugin ini, kamu gak harus bolak-balik coba angka sampai hasilnya pas. Cukup atur sudut cahaya dan jarak, bayangan langsung menyesuaikan otomatisHasil desain kamu langsung naik level karena lebih eye-catching, modern, dan ‘niat’. Dengan shadow yang halus dan profesional, desain kamu akan terlihat jauh lebih meyakinkan, bahkan tanpa perlu banyak tambahan elemen lain. Cara Menggunakan Plugin Beautiful Shadows Akses panel plugin dan cari "Beautiful Shadows".Select objec kemudian setting arah pencahayaan dengan cara mengatur bulatan yang berada diluar kotak.Klik apply maka shadows akan otomatis terlihat pada objek. Contoh Pemakaian Plugin Beautiful Shadows 9. Google Map - Map Maker (Free) Cover FigmaMap Maker (free) Desain perlu peta tapi kamu enggak mau repot screenshot Google Maps atau ribet import manual? Pakai nih plugin Google Map - Map Maker (Free), peta mu langsung siap pakai di canvas Figma. Google Map - Map Maker (Free) adalah plugin Figma gratis yang memungkinkan kamu menyisipkan peta berkualitas tinggi ke desain, langsung dari layanan seperti Google Maps. Tinggal pilih area, sesuaikan zoom & style, dan yak! peta siap digunakan di artboard kamu, simple kan!. Kenapa Google Map - Map Maker (Free) wajib kamu coba? Masukkan peta custom kamu sendiri Pilih area spesifik, set zoom level & tipe peta (roadmap, hybrid, terrain...), lalu Map Maker otomatis mengimport peta ke dalam layar figma.Hemat waktu. Tidak perlu capture-crop-tracing manual. Semua langkah tersebut cukup dalam beberapa klik super satset!Bagus untuk UI prototyping. Ingin menampilkan lokasi cabang toko, peta navigasi, atau visual data? Plugin ini bikin prototipe kamu terlihat lebih realistik. Cara Menggunakan Plugin Google Map - Map Maker (Free) Akses panel plugin dan cari " ****Google Map - Map Maker (Free)".Ketik daerah yang ingin dimasukan ke dalam map misal “Jakarta”. lalu setting ingin tampilan roadmap atau hybrid.Klik insert maka map akan langsung tampil. Contoh Pemakaian Plugin Map Maker (free) 10. Icons8 Background Remover Cover Figma Icons8 Remove Background Butuh nge‘hapus’ background foto dengan cepat tanpa harus keluar Figma atau buka Photoshop? gampang banget pakai aja plugin Icons8 Background Remover langsung dari figma. Plugin ini menggunakan AI-powered removal untuk menghilangkan latar belakang gambar secara instan. Cukup pilih foto, klik tombol “Remove Background” gak perlu API key, login, atau repot-repot setup apapun. Kenapa Icons8 Background Remover wajib kamu pakai? Hasil tetap tajam, ukuran gak berubah. Background hilang, tapi ukuran dan kualitas gambar tetap terjaga gak pecah, gak nge-resize. Keren kan?Satu klik, buat banyak gambar. Pilih beberapa gambar sekaligus dan hapus background nya dalam sekali klik. Super cepat dan gak ribet.Bebas registrasi & bebas biaya. Kamu bisa hapus background tanpa login, tanpa bayar dan pastinya unlimited. Cara Menggunakan Plugin Icons8 Background Remover Select gambar yang ingin dihapus background nya. Bisa satu atau banyak gambar sekaligus.Search Icons8 Background Remover lalu klik remove background.Gambar akan diproses menjadi tanpa background. Contoh Pemakaian Plugin Icons8 Background Remover 11. Autoflow Cover Figma Autoflow Buat kamu yang sering bikin user flow, sitemap, atau diagram alur di Figma, tapi males banget bikin garis penghubung satu-satu, geser-geser panah biar lurus, terus harus atur ulang pas layout berubah.Mending pakai Plugin Autoflow Dengan satu klik aja, kamu bisa sambungin antar elemen secara otomatis, rapi, dan tetap fleksibel kalau layout-nya diubah. Gak cuma bikin alur kelihatan lebih profesional, tapi juga ngirit waktu banget pas revisian atau presentasi ke tim dan klien. Kenapa Autoflow wajib kamu coba ? Otomatis sesuaikan koneksi saat elemen digeser. Pindahin frame atau tombol, koneksi panah akan tetap mengikuti. Jadi kamu gak perlu repot edit manual satu per satuIdeal buat presentasi & handoff UI/UX. Alur user atau feature jadi gampang dipahami stakeholder dan developer karena tampilannya clean dan visualnya jelas.Visualisasi flow dalam hitungan detik. Cukup pilih dua elemen yang ingin dikoneksikan dan Autoflow langsung bikin panah koneksi yang bersih dan intuitif Cara Menggunakan Plugin Autoflow Akses panel plugin dan cari " ****Autoflow)"Select 2 objek yang ingin dikoneksikan,Gambar panah akan langsung muncul mengkoneksikan 2 objek. Contoh Pemakaian Plugin Autoflow. 12. Fast Isometric Cover Figma Fast Isometric Pernah kepikiran pengen kasih efek 3D isometrik ke desain flat kamu biar kelihatan lebih dinamis dan ‘hidup’? Tapi pas inget harus edi manual satu-satu, atur sudut, atau pakai tool lain yang ribet langsung keburu males duluan? Nah, plugin Fast Isometric bisa jadi solusi instan. Cukup pilih elemen yang kamu mau, terus klik plugin nya, dalam hitungan detik desain kamu langsung berubah jadi tampilan isometrik yang keren, rapi, dan pastinya bebas dari ribet. Gak perlu skill tambahan. Semua langsung dari dalam Figma. Kenapa Fast Isometric wajib kamu coba ? Ubah 2D jadi isometrik dengan sekali klik. Pilih frame atau shape, jalankan plugin, dan semua lapisan berubah jadi perspektif isometrik dalam hitungan detikIdeal untuk infografis, mockup, atau presentasi. Dengan tampilan 3D halus, materi presentasi & infografis kamu langsung terlihat lebih profesional dan engagingBatch convert banyak elemen sekaligus. Mau ubah seluruh frame atau banyak shape? Sebagai pilihan batch, kamu bisa ubah semuanya sekaligus. Cara Menggunakan Plugin Fast Isometric Akses panel plugin dan cari "Fast Isometric"Select object yang ingin berikan efek isometrik atau bisa juga dengan mengklik objek yang sudah disediakan lalu klik create.Objek akan langsung muncul dalam figma. Contoh Pemakaian Plugin Fast Isometric Penutup Itu dia 12 plugin Figma yang bisa bikin kerjaan kita sebagai UI/UX designer jauh lebih gampang dan efisien. Mulai dari plugin yang bantu cari gambar estetik tanpa ribet lisensi, atur skala typography biar konsisten, bikin user flow tanpa perlu gambar panah satu-satu, sampai kasih efek 3D isometrik dalam sekali klik semuanya ada. Dengan tool-tool ini, kamu gak cuma bisa hemat waktu, tapi juga bisa lebih fokus ke hal-hal penting dalam proses desain. Dan kalau kamu pengen belajar lebih soal plugin-plugin Figma lainnya yang bisa bantu ningkatin workflow desainmu, kamu bisa juga loh langsung join kelas Boost Your Design Work Using Top Figma Plugins di BuildWithAngga. Di sana nanti kamu bakal diajak eksplor plugin-plugin keren lainnya sambil praktik langsung bareng mentor yang udah berpengalaman. Let’s go join!

Kelas Cara Mengubah Desain Figma Menjadi HTML dengan Lovable AI (Step-by-Step) di BuildWithAngga

Cara Mengubah Desain Figma Menjadi HTML dengan Lovable AI (Step-by-Step)

Pernah gak sih kamu lagi dapet desain dari Figma — entah itu dari klien, temen tim, atau hasil desainmu sendiri — terus mikir, "Oke, ini bagus sih... tapi gimana ya mulai ngekodenya?" Yup, dari desain ke kode itu ibarat naik gunung: kelihatannya keren dari jauh, tapi pas mau naik, baru terasa beratnya. Mulai dari nyusun struktur HTML, mikirin class name yang masuk akal, sampai styling CSS yang bikin pixel-nya pas — kadang bisa makan waktu berjam-jam. Belum lagi kalau harus ngebut karena deadline tinggal dua hari 🙃. Dan yang paling ngeselin? Pas udah stuck cuma gara-gara bingung mau kasih nama apa buat class div utama. container, main-wrapper, box1? Ya ampun, capek deh. Nah, di sinilah Lovable AI datang sebagai penyelamat. Bukan buat gantiin kamu sebagai developer, tapi buat bantuin proses konversi dari desain ke HTML/CSS jadi lebih cepat, rapi, dan... manusiawi. Kayak punya partner ngoding yang ngerti estetika dan struktur sekaligus. Jadi kamu bisa lebih fokus ke hal yang penting, kayak logika aplikasi atau nambahin interaksi keren. Di artikel ini, kita bakal bahas gimana caranya kamu bisa ubah desain Figma jadi HTML step-by-step pakai Lovable AI. Tanpa ribet. Tanpa overthinking. Let's go! 🚀 💡 Apa Itu Lovable AI? Bayangin ada asisten pribadi yang ngerti desain dan jago ngoding. Kamu kasih desain, dia langsung bantuin nyusun struktur HTML yang rapi, kasih class name yang masuk akal, dan styling-nya juga udah lumayan oke buat langsung dipake. Nah, itu dia Lovable AI. Lovable AI adalah alat bantu yang didesain khusus buat developer dan designer yang pengen cepet ngejembatani antara dunia visual (kayak Figma) ke dunia kode (HTML & CSS). Tapi bedanya sama tool auto-code biasa, Lovable itu... well, lebih “manusiawi”. Dia nggak asal convert desain jadi kode kaku. Lovable ngerti konteks. Misalnya: kalau kamu upload gambar desain hero section, dia bakal bikin struktur HTML-nya pakai tag semantic kayak <header>, <section>, atau <nav> kalau perlu — bukan cuma <div><div><div>...</div></div></div> kayak bot-bot auto-code zaman dulu 😅. Beberapa fitur unggulan Lovable: ✨ Deskripsi Kode Otomatis: kamu bisa tulis prompt kayak “buat hero section dengan judul besar, CTA, dan gambar di kanan”, dan dia bakal generate HTML/CSS-nya langsung.🧠 Struktur Semantic HTML: bukan cuma div-divan, tapi beneran mikirin struktur yang SEO-friendly dan mudah dipahami.🎨 Inspirasi Naming Class: kamu gak perlu lagi stuck di box1 atau container-main-final-fix-final-revisi3, karena Lovable ngasih nama class yang lebih logis dan clean.⚙️ Support CSS Framework: kamu bisa request output pakai TailwindCSS, Bootstrap, atau bahkan plain CSS. Jadi kalau kamu pernah nyoba tools konversi Figma ke code dan hasilya bikin kamu malah makin pusing, Lovable bisa jadi solusi yang lebih realistis. Dia bukan sulap, tpi cukup cerdas buat bikin kamu bilang, “Akhirnya ada AI yang ngerti kerjaan gue.” Kamu juga bisa baca artikel saya yang judulnya Apa itu Lovable AI? Tools AI yang Bikin Hidup Frontend Developer Lebih Mudah. 🎨 Persiapan Desain Figma Sebelum kita lempar desain ke Lovable AI, ada baiknya kita siapin “makanannya” dulu dengan benar. Soalnya, sebagus apa pun alatnya, kalau input-nya berantakan... ya hasilnya juga bakal setengah hati. Bayangin kamu ngasih desain yang isinya campur aduk: ada frame tapi isinya malah shape acak, ada teks tapi posisinya gak jelas, dan semua layer namanya “Rectangle 123” — duh, AI juga bisa jadi bingung 😅. Nah, biar proses konversinya mulus, kamu bisa ikuti beberapa tips merapikan desain Figma berikut: ✅ Gunakan Frame, Bukan Group Frame itu kayak “panggung utama” buat setiap bagian. Gunakan frame buat layout besar seperti Hero, Footer, atau Card. Hindari pakai Group doang karena frame punya struktur lebih jelas dan bisa dipahami lebih baik oleh AI. ✅ Aktifkan Auto Layout Auto Layout adalah sahabat sejati buat AI (dan juga developer!). Dengan auto layout, ukuran, padding, dan align elemen jadi lebih predictable. Lovable bakal lebih mudah baca hierarki dan struktur desainmu. ✅ Nama-nama Layer yang Masuk Akal Jangan lagi kasih nama “Rectangle 99” atau “Text 14”. Coba kasih nama sesuai fungsinya, misal: Button - CTA, Image - Hero, Text - Subheading. Ini membantu AI nebak maksudmu dan kasih class name yang sesuai. 💡 Tips Tambahan Agar Lebih AI-Friendly: 🔧 Gunakan Komponen: Buat tombol, kartu, input field dalam bentuk component. Ini nunjukkin bahwa elemen itu reusable dan penting.🪆 Hindari Nested Layer Terlalu Dalam: Layer yang terlalu dalam kayak Frame > Group > Group > Group bikin AI (dan kamu) pusing. Keep it simple.🔲 Pakai Sistem Grid & Spacing yang Konsisten: Kalau kamu pakai grid dan spacing yang rapi, Lovable bisa ngerti struktur dan susunan lebih cepat — bahkan bisa kasih layout yang responsive! Kalau kamu belum punya desain siap, kamu bisa coba shaynakit.com di snaa banyak template keren gratis yang siap pakai. Berikut langkah-langkahnya: Buka shaynakit.com kemudian buat akunPilih template yang kamu suka, lalu klik download. Pada tutorial ini saya pakai Learn Coding Landing App Page Figma Template. Shaynakit.com: Download Template Figma Gratis 🚀 Step-by-Step: Mengubah Figma ke HTML Sekarang kita masuk ke bagian paling ditunggu: proses sulapnya! Dari desain Figma, jadi HTML yang siap masuk ke proyekmu. Santai aja, prosesnya gampang banget — bahkan bisa jadi ketagihan karena secepat itu. Buka file dessain Figma yang udah kamu download dari shaynakit.com Import file figma Sekarang gunakan plugins Builder.io, caranya seperti gambar berikt ini: Figma Builder.io Pilih Frame yang akan dieksport, kemudian klik Export Design Builder.io: Export Design Tunggu beberapa saat, jika sudah akan ada dua pilihan hasil desain. Kemudian ppilih hasil yang sesuai. Pilih Desain Setelah itu klik Open in Lovable dan tunggu hingga proses selesai Open in Lovable Setelah itu klik Launch Project maka browser akan buka tab baru Launch Project Tunggu hingga proses selesai, maka hasil desain akan tampil seeperti berikut Lovable Hasil Desain Di sini masih ada yang kurang yaa… yaitu jenis font. Sekarang ketikan prompt ini dan tunggu hasilnya gunakan font paytone one pada h1 dan poppins pada lainnya Maka hasilnya akan seprti ini: Lovable Fix Jenis Font Hasilnya udah cukup bagus ya, kamu bisa lihat kodenya dengan klik Code Viewer di navbar. Sayangnya untuk bisa edit kodenya harus berlangganan dulu. Lovable Code Viewer Jika udaah sesuai, kamu bisa klik Publish biar bisa dilihat oleh orang lain Lovable Publish Kamu bisa cek hasil desain ini di https://figma-ui-mirror-magic-14.lovable.appKamu juga bisa simpan hasilnya ke github, caranya klik logo github lalu klik Connect Github Lovable: Connect GitHub Pilih akun GitHub Pilih Akun GitHub Setelah itu klik Authorize lovable.dev Authorize lovable.dev Kemudian klik Add Organizations Tambah Organization Setelah itu pilih akun atau organisasi Pilih Akun GitHub Kemudian klik Continue Lovable ke GitHub Kemudian klik Connect Lovable ke GitHub Jika sudah maka Lovable akan membaut repositori baru. Kamu bisa akses repositori dari tutorial ini di link ini: cakfan/figma-ui-mirror-magic-14 Lovable GitHub Repository 📌 Tips Tambahan agar Hasil Lebih Maksimal Walaupun Lovable AI itu pintar dan cukup ajaib, tapi tetap aja: hasil terbaik datang dari input yang baik. Jadi, kalau kamu pegen konversi desain ke HTML yang clean, gak ada salahnya kasih dia sedikit bantuan. Ibarat kerja tim — kamu kasih umpan yang enak, Lovable tinggal cetak gol. Berikut beberapa tips biar hasil konversimu makin mantap: 🧼 1. Gunakan Desain yang Clean & Konsisten Kalau desainnya udah rapi dari awal — jarak antar elemen konsisten, font size seimbang, layout nggak acak-acakan — AI juga lebih gampang baca maksudnya. Coba deh bandingin dua desain: Yang satu full padding random, warna bentrok, dan layer nggak jelas.Yang satu lagi clean, spacing konsisten, dan ada hierarchy visual yang jelas. Lovable bakal jauh lebih akurat pas ngerjain yang kedua. ✍️ 2. Tambahkan Deskripsi Prompt yang Jelas Kalau kamu pakai mode “Text to HTML”, jangan pelit kasih konteks. AI bukan cenayang — dia butuh clue biar hasilnya sesuai harapan. Contoh: “Buat hero section dengan heading besar, subheading singkat, tombol CTA warna biru, dan gambar ilustrasi di sisi kanan. Gunakan TailwindCSS.” Daripada cuma: “Hero section aja ya.” Semakin jelas prompt-nya, semakin pas output-nya. 🛠️ 3. Gabungkan dengan Tools Preview Setelah kamu dapet kodenya, kamu bisa langsung tes tampilannya pakai tools kayak: 🌐 Tailwind Play – kalau kamu pakai TailwindCSS🧪 CodePen / JSFiddle – buat preview cepat⚙️ Live Server di VS Code – buat kamu yang mau test langsung di lokal Dengan begitu, kamu bisa lihat hasil real-time, tweak dikit-dikit, dan langsung pakai di project. Sedikit effort di awal bisa bikin hasil akhir jauh lebih solid. Ingat, Lovable itu partner kerja yang handal, tapi tetap butuh arahan dari kamu sebagai pemilik visi desainnya. 🎨💻 🎯 Kesimpulan Kadang, bagian paling berat dari ngoding itu… memulai. Desain udah siap di Figma, tapi kamu masih bengong di depan VS Code, mikir: “Mulainya dari div mana dulu, ya?” Nah, di sinilah Lovable AI jadi penyelamat. Bukan sulap, bukan gantiin kerjaan kamu sepenuhnya, tapi dia bantu banget buat ngelewatin fase awal: dari desain ke HTML/CSS yang rapi, readable, dan bisa langsung dipakai. Jadi kamu gak lagi buang waktu buat bikin struktur dasar atau mikirin class name yang “bagus”. Lovable cocok banget buat: 👩‍💻 Freelancer yang harus gerak cepat🎨 UI Developer yang fokus di tampilan👥 Tim kecil yang butuh efisiensi Intinya, Lovable itu bukan pengganti developer. Tapi dia kayak partner kerja yang sigap, siap bantu kamu jalanin langkah pertama — biar kamu bisa fokus ke yang lebih penting: fungsionalitas, interaksi, dan finishing touch. Karena pada akhirnya, coding itu tetap soal rasa. Dan Lovable cuma bantu kamu sampai ke titik start... dengan lebih cepat dan lebih nyaman. 💖💻 🔁 Bonus: Kapan Sebaiknya Kamu Gunakan Lovable AI? Lovable AI itu kayak temen nongkrong yang jago ngoding — gak selalu kamu ajak, tapi pas dibutuhin, dia selalu siap bantuin. Berikut beberapa momen pas banget buat ngajak Lovable kerja bareng: 💡 Saat Butuh Inspirasi Struktur Kode Pernah bengong lama cuma buat nentuin struktur HTML buat satu section? Lovable bisa kasih gambaran awal yang masuk akal. Cocok banget buat kamu yang suka overthinking mulai dari <div> pertama. 😵‍💫 Ketika Stuck dengan Naming Class Class box-main-wrapper-content-final-fix bikin kamu frustasi? Lovable punya ide nama-nama class yang bersih, konsisten, dan mudah dibaca. Gak perlu ngabisin waktu mikir satu nama tombol. ⚡ Untuk Prototyping Cepat Mau nunjukin hasil desain yang udah bisa diklik atau dilihat klien dengan cepat? Lovable bantu kamu nyusun HTML/CSS tanpa harus ngoding dari nol. Tinggal tweak dikit, langsung jadi prototype ringan yang bisa di-preview. 🧍 Saat Jadi Satu-Satunya Dev di Proyek Desain-Heavy Kalau kamu ngerjain proyek solo, apalagi yang desainnya kompleks, Lovable bisa jadi sidekick setia. Dia bantu kamu jembatani Figma ke kode, biar kamu gak kehabisan tenaga sebelum masuk ke logika atau backend-nya. Intinya: pakai Lovable AI bukan karena kamu malas, tapi karena kamu cerdas memilih alat bantu biar kerjaan lebih efisien. Karena di dunia dev yang serba cepat, kerja cerdas > kerja keras. 😎✨

Kelas Apa itu Lovable AI? Tools AI yang Bikin Hidup Frontend Developer Lebih Mudah di BuildWithAngga

Apa itu Lovable AI? Tools AI yang Bikin Hidup Frontend Developer Lebih Mudah

🧠 Ketika Coding Tak Lagi Sendirian Pernah gak sih kamu lagi ngoding frontend, terus tiba-tiba kejebak bukan karena error, tapi karena hal-hal kecil yang entah kenapa malah nyusahin? Kayak misalnya: Lagi bikin komponen, tapi stuck mikir, “Ini aku kasih nama apa ya? InfoCard, SummaryBox, atau StatContainer?”Mau bikin folder project, tapi bingung: “Layout taruh di components atau pages ya? Atau bikin folder sections aja sekalian?”Atau yang paling klasik: nulis copy buat CTA button. “‘Klik di sini’ terlalu umum, ‘Beli sekarang’ terlalu agresif, ‘Coba gratis’ udah dipake semua orang…” Padahal itu semua kelihatannya remeh, ya. Tapi bisa bikin waktu ngoding molor, mood turun, bahkan kadang ngerasa kayak lagi kerja sendirian di gurun digital. Di tengah hiruk-pikuk tools AI yang makin hari makin banyak, muncullah satu nama yang mencuri perhatian: Lovable AI. Bukan AI yang cuma jago jawab pertanyaan teknis, tapi AI yang ngerti banget kerjaan sehari-hari frontend developer. Yang bisa bantu bukan cuma debug, tapi juga mikirin hal-hal kecil yang sering kita anggap “ah, nanti aja deh”—tapi malah bikin numpuk di kepala. Tapi sebenarnya... Apa itu Lovable AI? Dan kenapa banyak dev mulai bilang, “Ngoding sama Lovable AI itu kayak kerja bareng teman yang ngerti banget kebutuhan kita”? Let’s find out. 💡 Apa Itu Lovable AI? Lovable AI itu bukan pacar virtual buat coder. Tapi jujur aja, dia punya sifat yng nyaris ideal: nggak pernah ngambek, selalu standby 24/7, dan paling ngerti kamu pas lagi mumet ngerjain UI. 😄 Lovable AI adalah sebuah tools berbasis AI yang secara khusus didesain untuk membantu frontend developer. Jadi dia bukn cuma sekadar chatbot pintar atau asisten umum yang bisa jawab “Apa itu React?”, tapi asisten teknis yang ngerti dunia frontend sampai ke akar-akarnya. 💻 “AI-nya Frontend Dev” Gimana maksudnya? Coba bayangin kamu lagi coding: Butuh saran nama komponen → Lovable AI siap kasih beberapa opsi yang kontekstual.Bingung bikin copy tulisan untuk landing page → tinggal ketik deskripsi produknya, dia bantuin nyusun.Mau struktur folder yang rapi dan scalable? Dia bisa bantu nyarikan standar terbaik buat use-case kamu.Butuh desain UI yang readable dan clean? Masukkan perintah, dan Lovable AI bantu generate langsung. Bisa dibilang, dia seperti gabungan dari: 🧠 AI assistant + 🎨 UX copywriter + 🧱 komponen architectSemuanya dibungkus dalam satu antarmuka yang gampang dipakai. 🚀 Dibuat oleh Siapa? Lovable AI dikembangkan oleh tim builder independen yang ngerti banget perasaan dan kebutuhan developer. Meski masih terbilang baru, tools ini cepat viral di kalangan dev Twitter/X karena kemampuannya yang “lebih dari sekadar AI biasa”. Beberapa sumber menyebutkan tools ini dibangun dengan pendekatan “AI yang empatik”—nggak cuma ngasih jawaban, tapi berusaha memahami konteks dan niat pengguna, khususnya dalam pekerjaan kreatif seperti frontend development. 🤔 Kenapa Lovable AI Beda? Ada banyak AI yang bisa bantu nulis kode. Tapi Lovable AI nggak berhenti di situ. Bedanya: 💬 Ngobrolnya natural dan ngerti konteks frontend.🎯 Fokus ke UI/UX dan copywriting, bukan cuma logic programming.🧩 Bisa bantu generate desain, struktur, dan bahkan inspirasi untuk project frontend.🎨 Punya selera desain dan tone yang bisa kamu setel sesuai brand-mu. Dia bukan tools generik yang sok tahu. Lovable AI kayak partner kerja yang “paham banget gimana rasanya jadi frontend dev zaman sekarang”—dari struggle mikirin state sampai gonta-ganti nama ButtonPrimary. Jadi kalau kamu tanya: Lovable AI itu siapa? Jawabannya simple: Dia adalah asisten AI yang ngerti banget frontend—dan hadir bukan buat gantiin kamu, tapi bantuin kamu jadi versi terbaik dari dirimu saat ngoding. ⚙️ Fitur Utama Lovable AI yang Bikin Ngoding Lebih Nyaman Frontend developer itu bukan cuma ngoding. Kita juga mikrin struktur, desain, nama komponen, UX, bahkan copywriting. Dan kadang, hal-hal kecil itu yang bikin kita jadi lelah duluan sebelum sempat ngerjain hal besar. Nah, di sinilah Lovable AI bersinar. Dia bukan cuma pinter secara teknis, tapi juga peka. Berikut beberapa fitur utamanya yang bikin kamu pengin bilang, “Kenapa aku baru kenal dia sekarang ya?” ✨ a. Component Naming Helper “Ini komponen aku kasih nama apa ya? StatBox, SummaryCard, atau InfoDisplay?” Kedengarannya sepele, tapi nyari nama komponen yang pas tuh kadang kayak nyari judul lagu galau: susah banget. Lovable AI bantu kamu kasih nama berdasarkan fungsinya, konteks UI-nya, bahkan konvensi project-mu. Misal kamu jelasin, “Komponen untuk nampilin 3 metrik: pengguna, produk, dan penghasilan”, dia bisa kasih saran nama kayak: DashboardSummaryStatsOverviewCardKPIWidget Dan gak cuma satu — kamu bisa pilih style yang kamu suka. ✨ b. UI Suggestion Generator Kadang ide udah ada, tapi translate ke kode-nya yang bikin stuck. Misalnya kamu mau bikin hero section: “Gambar di kanan, teks judul di kiri, plus tombol CTA warna primer.” Daripada buka Figma dulu atau nyari referensi di Pinterest, kamu cukup kasih deskripsi ke Lovable AI. Dia bakal generate struktur HTML + Tailwind CSS-nya langsung, seperti: <section class="grid grid-cols-2 items-center py-20"> <div> <h1 class="text-4xl font-bold">Tingkatkan Penjualanmu Sekarang!</h1> <p class="mt-4 text-muted-foreground">Bergabunglah bersama 10.000+ bisnis lainnya.</p> <button class="mt-6 btn btn-primary">Coba Gratis</button> </div> <img src="/illustration.svg" alt="Ilustrasi Hero" /> </section> Tinggal tweak dikit, dan voila!—UI kamu langsung siap tampil. ✨ c. Copywriting Assistance “Tombol CTA-nya dikasih teks apa ya? ‘Beli sekarang’? ‘Mulai’? Atau…?” Nah, Lovable AI jago banget dalam hal ini. Kamu tinggal kasih konteks, misalnya: “Ajakan beli untuk produk skincare alami wanita usia 25-35 tahun”, dan dia bisa kasih opsi kayak: “Coba Sekarang, Kulit Lebih Cerah!”“Waktu untuk Glowing Sudah Tiba ✨”“Produk Favoritmu, Kini Lebih Dekat” Dan semua copy-nya bisa kamu atur tone-nya: formal, santai, lucu, atau professional. ✨ d. Konsistensi Desain & Voice Pernah gak kamu nulis teks di UI pakai “kamu” di satu tempat, tapi pakai “Anda” di tempat lain? Atau heading satu huruf besar semua, tapi heading lain huruf kecil? Lovable AI bisa bantu kamu cek dan konsistenin tone, gaya penulisan, bahkan estetika layout. Kayak punya editor UI pribadi yang rajin dan gak rewel. ✨ e. Dokumentasi Otomatis Biasanya setelah bikin komponen atau function, kita bilang: “Nanti aja deh nulis dokumentasinya…” Dan akhirnya? Nggak pernah ditulis. 😅 Lovable AI bisa bantu generate dokumentasi otomatis berdasarkan nama, props, dan isi komponen. Misalnya kamu punya komponen PricingCard, dia bisa bikin dokumentasi kayak: ### `<PricingCard />` Komponen untuk menampilkan paket harga produk, lengkap dengan nama paket, harga, dan daftar fitur. #### Props: - `title` *(string)* – Judul paket. - `price` *(string)* – Harga yang ditampilkan. - `features` *(string[])* – Daftar fitur dalam paket. - `ctaText` *(string)* – Teks tombol ajakan. Keren kan? Coding jalan, dokumentasi aman. Dan itu baru sebagian dari fitur-fitur Lovable AI. Yang bikin seru, semua fitur ini bisa dikombinasikan sesuai kebutuhanmu. Gak perlu buka banyak tools, cukup Lovable AI — kamu bisa brainstorming, ngoding, dan ngerapihin UI tanpa harus lompat ke mana-mana. ⚒️ Studi Kasus: Ngoding Bareng Lovable AI Bayangin gini… Kamu dapet project freelance bikin landing page buat startup edukasi online yang baru launching. Deadlinenya? Besok sore. Kamu buka laptop, buka VSCode, dan mulai dari nol: “Oke, kita mulai dari wireframe dulu…” Kamu tulis di Notion: Hero section dengan judul dan gambar ilustrasiPenjelasan 3 fitur utamaTestimoni 2 userCall to action terakhir Biasanya, dari wireframe ke layout HTML + Tailwind aja bisa makan waktu 1-2 jam. Belum lagi cari copy yang cocok, styling, sampai mikir komponen-komponen reusable-nya. Tapi kali ini kamu lagi pengen coba Lovable AI. 🛠️ Step 1: Bikin Hero Section Kamu ketik ke Lovable AI: “Hero section untuk edukasi online. Judulnya tentang belajar fleksibel, ada ilustrasi di kanan dan tombol ajakan di kiri.” Lovable AI: Hero Section Nggak sampai 10 detik, Lovable AI ngasih struktur HTML lengkap dengan class Tailwind. Ini hasil kodenya: import { Button } from "@/components/ui/button"; import { PlayCircle, BookOpen, Users, Clock } from "lucide-react"; const HeroSection = () => { return ( <section className="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 overflow-hidden"> <div className="container mx-auto px-4 py-12 lg:py-20"> <div className="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center"> {/* Left Content */} <div className="space-y-8 animate-fade-in"> <div className="space-y-6"> <div className="inline-flex items-center px-4 py-2 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"> 🎓 Platform Pembelajaran Terdepan </div> <h1 className="text-4xl lg:text-6xl font-bold text-gray-900 leading-tight"> Belajar <span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"> Fleksibel</span> , Kapan Saja & Dimana Saja </h1> <p className="text-xl text-gray-600 leading-relaxed max-w-lg"> Raih impian karir Anda dengan pembelajaran online yang disesuaikan dengan jadwal dan kebutuhan Anda. Akses ribuan kursus berkualitas dari instruktur terbaik. </p> </div> {/* CTA Buttons */} <div className="flex flex-col sm:flex-row gap-4"> <Button size="lg" className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-8 py-3 text-lg font-semibold rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200" > <BookOpen className="mr-2 h-5 w-5" /> Mulai Belajar Gratis </Button> <Button size="lg" variant="outline" className="border-2 border-gray-300 hover:border-blue-500 text-gray-700 hover:text-blue-600 px-8 py-3 text-lg font-semibold rounded-xl hover:bg-blue-50 transition-all duration-200" > <PlayCircle className="mr-2 h-5 w-5" /> Lihat Demo </Button> </div> {/* Stats */} <div className="grid grid-cols-3 gap-6 pt-8 border-t border-gray-200"> <div className="text-center"> <div className="flex items-center justify-center w-12 h-12 mx-auto mb-2 bg-blue-100 rounded-full"> <Users className="h-6 w-6 text-blue-600" /> </div> <div className="text-2xl font-bold text-gray-900">50K+</div> <div className="text-sm text-gray-600">Siswa Aktif</div> </div> <div className="text-center"> <div className="flex items-center justify-center w-12 h-12 mx-auto mb-2 bg-purple-100 rounded-full"> <BookOpen className="h-6 w-6 text-purple-600" /> </div> <div className="text-2xl font-bold text-gray-900">1,200+</div> <div className="text-sm text-gray-600">Kursus Online</div> </div> <div className="text-center"> <div className="flex items-center justify-center w-12 h-12 mx-auto mb-2 bg-green-100 rounded-full"> <Clock className="h-6 w-6 text-green-600" /> </div> <div className="text-2xl font-bold text-gray-900">24/7</div> <div className="text-sm text-gray-600">Akses Fleksibel</div> </div> </div> </div> {/* Right Illustration */} <div className="relative animate-fade-in animation-delay-200"> <div className="relative z-10"> <img src="<https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?auto=format&fit=crop&w=800&q=80>" alt="Woman learning online with laptop" className="w-full h-auto rounded-2xl shadow-2xl" /> {/* Floating Elements */} <div className="absolute -top-6 -left-6 w-24 h-24 bg-gradient-to-r from-yellow-400 to-orange-500 rounded-full flex items-center justify-center shadow-lg animate-pulse"> <span className="text-white text-2xl">📚</span> </div> <div className="absolute -bottom-6 -right-6 w-20 h-20 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center shadow-lg animate-bounce"> <span className="text-white text-xl">✨</span> </div> <div className="absolute top-1/2 -right-4 bg-white rounded-lg shadow-lg p-4 transform translate-y-[-50%] animate-float"> <div className="flex items-center space-x-3"> <div className="w-3 h-3 bg-green-500 rounded-full"></div> <span className="text-sm font-medium text-gray-700">Pembelajaran Aktif</span> </div> </div> </div> {/* Background Decoration */} <div className="absolute inset-0 bg-gradient-to-r from-blue-200 to-purple-200 rounded-2xl transform rotate-3 scale-105 opacity-20"></div> <div className="absolute inset-0 bg-gradient-to-r from-purple-200 to-pink-200 rounded-2xl transform -rotate-2 scale-110 opacity-10"></div> </div> </div> </div> </section> ); }; export default HeroSection; Tinggal copas → cek di browser → rapikan layout dikit. Udah cakep. 🛠️ Step 2: Section Fitur Kamu lanjut ke bagian fitur. Biasanya kamu nulis satu-satu, tapi sekarang kamu kasih deskripsi ke Lovable AI: “3 fitur: kelas online fleksibel, mentor berpengalaman, sertifikat resmi” Dia generate section-nya lengkap dengan nama komponen, deskripsi yang engaging, dan struktur UI yang responsive. Lovable AI: Feature Section Ini hasil kodenya: import { Clock, Users, Award } from "lucide-react"; const Features = () => { const features = [ { icon: Clock, title: "Kelas Online Fleksibel", description: "Belajar kapan saja dan dimana saja sesuai dengan jadwal Anda. Akses materi pembelajaran 24/7 tanpa batasan waktu.", color: "blue" }, { icon: Users, title: "Mentor Berpengalaman", description: "Dibimbing langsung oleh para ahli dan praktisi berpengalaman di bidangnya masing-masing dengan track record terbukti.", color: "purple" }, { icon: Award, title: "Sertifikat Resmi", description: "Dapatkan sertifikat resmi yang diakui industri setelah menyelesaikan kursus untuk meningkatkan karir Anda.", color: "green" } ]; return ( <section className="py-20 bg-white"> <div className="container mx-auto px-4"> <div className="text-center mb-16"> <h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4"> Mengapa Memilih Platform Kami? </h2> <p className="text-xl text-gray-600 max-w-2xl mx-auto"> Tiga keunggulan utama yang membuat pembelajaran online Anda lebih efektif dan bermakna </p> </div> <div className="grid md:grid-cols-3 gap-8"> {features.map((feature, index) => { const Icon = feature.icon; const colorClasses = { blue: "bg-blue-100 text-blue-600", purple: "bg-purple-100 text-purple-600", green: "bg-green-100 text-green-600" }; return ( <div key={index} className="text-center p-8 rounded-2xl hover:shadow-lg transition-all duration-300 hover:transform hover:scale-105" > <div className={`inline-flex items-center justify-center w-16 h-16 ${colorClasses[feature.color as keyof typeof colorClasses]} rounded-full mb-6`}> <Icon className="h-8 w-8" /> </div> <h3 className="text-xl font-bold text-gray-900 mb-4"> {feature.title} </h3> <p className="text-gray-600 leading-relaxed"> {feature.description} </p> </div> ); })} </div> </div> </section> ); }; export default Features; 🛠️ Step 3: Testimoni & CTA Testimoni user? Lovable AI bantu kamu bikin placeholder lengkap dengan nama, profesi, dan kalimat testimoni yang human-friendly. Bahkan kasih rekomendasi foto dari Unsplash API. CTA terakhir? Tinggal kasih konteks, dan dia bantu susun ulang copy supaya tetap konsisten dengan tone brand. Kamu tinggal ganti warna tombol, simpan file, dan commit ke repo. Lovable AI: Testimonial Section Hasil kodenya: import { Star, Quote } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from "@/components/ui/carousel"; const Testimonials = () => { const testimonials = [ { name: "Sarah Wijaya", role: "Digital Marketing Specialist", image: "<https://images.unsplash.com/photo-1494790108755-2616b612b786?auto=format&fit=crop&w=150&q=80>", content: "Platform ini benar-benar mengubah karir saya! Materi yang diberikan sangat relevan dengan industri dan mentornya sangat berpengalaman. Sekarang saya sudah berhasil mendapat promosi di perusahaan.", rating: 5, course: "Digital Marketing Mastery" }, { name: "Ahmad Rahman", role: "Full Stack Developer", image: "<https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80>", content: "Fleksibilitas belajar di sini luar biasa. Saya bisa belajar sambil bekerja full-time. Sertifikat yang saya dapatkan juga diakui oleh perusahaan-perusahaan besar.", rating: 5, course: "Web Development Bootcamp" }, { name: "Lisa Putri", role: "UI/UX Designer", image: "<https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=150&q=80>", content: "Mentor-mentornya benar-benar ahli di bidangnya. Feedback yang diberikan sangat konstruktif dan membantu saya berkembang dengan cepat. Highly recommended!", rating: 5, course: "UI/UX Design Fundamentals" }, { name: "Budi Santoso", role: "Data Analyst", image: "<https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=150&q=80>", content: "Akses 24/7 ke materi pembelajaran sangat membantu saya yang sering bekerja overtime. Kualitas kontennya juga sangat baik dan up-to-date dengan tren industri.", rating: 5, course: "Data Science Foundation" } ]; return ( <section className="py-20 bg-gray-50"> <div className="container mx-auto px-4"> <div className="text-center mb-16"> <h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4"> Apa Kata Mereka? </h2> <p className="text-xl text-gray-600 max-w-2xl mx-auto"> Ribuan siswa telah membuktikan keefektifan pembelajaran di platform kami </p> </div> <div className="max-w-5xl mx-auto"> <Carousel opts={{ align: "start", loop: true, }} className="w-full" > <CarouselContent> {testimonials.map((testimonial, index) => ( <CarouselItem key={index} className="md:basis-1/2 lg:basis-1/2"> <div className="p-1"> <Card className="h-full hover:shadow-xl transition-all duration-300 hover:transform hover:scale-105 bg-white border-0 shadow-lg"> <CardContent className="p-8"> <div className="mb-6"> <Quote className="h-8 w-8 text-blue-500 mb-4" /> <p className="text-gray-700 leading-relaxed mb-6 text-lg"> "{testimonial.content}" </p> <div className="flex items-center mb-4"> {[...Array(testimonial.rating)].map((_, i) => ( <Star key={i} className="h-5 w-5 text-yellow-400 fill-current" /> ))} </div> </div> <div className="flex items-center"> <img src={testimonial.image} alt={testimonial.name} className="w-14 h-14 rounded-full object-cover mr-4" /> <div> <h4 className="font-bold text-gray-900 text-lg"> {testimonial.name} </h4> <p className="text-gray-600 text-sm"> {testimonial.role} </p> <p className="text-blue-600 text-sm font-medium"> {testimonial.course} </p> </div> </div> </CardContent> </Card> </div> </CarouselItem> ))} </CarouselContent> <CarouselPrevious className="hidden md:flex" /> <CarouselNext className="hidden md:flex" /> </Carousel> </div> </div> </section> ); }; export default Testimonials; CTA Section: Lovable AI: CTA Section Hasil kodenya: import { Button } from "@/components/ui/button"; import { ArrowRight, BookOpen, Users } from "lucide-react"; const CallToAction = () => { return ( <section className="py-20 bg-gradient-to-br from-blue-600 via-purple-600 to-indigo-700 relative overflow-hidden"> {/* Background decoration */} <div className="absolute inset-0 bg-black/20"></div> <div className="absolute top-0 left-0 w-full h-full"> <div className="absolute top-10 left-10 w-20 h-20 bg-white/10 rounded-full animate-pulse"></div> <div className="absolute top-32 right-20 w-16 h-16 bg-white/10 rounded-full animate-pulse animation-delay-500"></div> <div className="absolute bottom-20 left-1/4 w-12 h-12 bg-white/10 rounded-full animate-pulse animation-delay-1000"></div> <div className="absolute bottom-32 right-1/3 w-24 h-24 bg-white/10 rounded-full animate-pulse animation-delay-1500"></div> </div> <div className="container mx-auto px-4 relative z-10"> <div className="text-center max-w-4xl mx-auto"> <div className="mb-8 animate-fade-in"> <h2 className="text-4xl lg:text-5xl font-bold text-white mb-6 leading-tight"> Siap Memulai Perjalanan <span className="block text-yellow-300">Belajar Anda?</span> </h2> <p className="text-xl text-blue-100 leading-relaxed max-w-2xl mx-auto"> Bergabunglah dengan ribuan siswa yang telah mengubah karir mereka. Mulai belajar hari ini dan raih masa depan yang lebih cerah! </p> </div> {/* Stats */} <div className="grid grid-cols-3 gap-8 mb-12 animate-fade-in animation-delay-300"> <div className="text-center"> <div className="text-3xl lg:text-4xl font-bold text-white mb-2">50K+</div> <div className="text-blue-200 text-sm lg:text-base">Siswa Aktif</div> </div> <div className="text-center"> <div className="text-3xl lg:text-4xl font-bold text-white mb-2">1,200+</div> <div className="text-blue-200 text-sm lg:text-base">Kursus Premium</div> </div> <div className="text-center"> <div className="text-3xl lg:text-4xl font-bold text-white mb-2">95%</div> <div className="text-blue-200 text-sm lg:text-base">Tingkat Kepuasan</div> </div> </div> {/* CTA Buttons */} <div className="flex flex-col sm:flex-row gap-6 justify-center items-center animate-fade-in animation-delay-500"> <Button size="lg" className="bg-white text-blue-600 hover:bg-gray-100 px-8 py-4 text-lg font-semibold rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 group" > <BookOpen className="mr-2 h-5 w-5" /> Mulai Belajar Gratis <ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" /> </Button> <Button size="lg" variant="outline" className="border-2 border-white text-white hover:bg-white hover:text-blue-600 px-8 py-4 text-lg font-semibold rounded-xl transition-all duration-200 group" > <Users className="mr-2 h-5 w-5" /> Konsultasi Gratis <ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform" /> </Button> </div> {/* Trust indicators */} <div className="mt-12 pt-8 border-t border-white/20 animate-fade-in animation-delay-700"> <p className="text-blue-200 text-sm mb-4">Dipercaya oleh perusahaan-perusahaan terkemuka</p> <div className="flex justify-center items-center space-x-8 opacity-60"> <div className="text-white font-semibold text-lg">Google</div> <div className="text-white font-semibold text-lg">Microsoft</div> <div className="text-white font-semibold text-lg">Shopee</div> <div className="text-white font-semibold text-lg">Gojek</div> <div className="text-white font-semibold text-lg">Tokopedia</div> </div> </div> </div> </div> </section> ); }; export default CallToAction; Mungkin ada beberapa yang harus diperbaiki. ⏱️ Waktu vs Energi Biasanya, proses ini bisa makan waktu 5–6 jam. Belum termasuk waktu stuck mikir copy, styling, dan nama komponen. Tapi degan Lovable AI? Dari wireframe ke kode hampir siap deploy: 2 jam. Dan yang lebih penting: kamu gak kehabisan energi buat mikirin hal kecil yang berulang-ulang. Hasil akhirnya? Client puas. Kamu gak burnout. Dan kamu sadar, “Wah, ini tools bukan sekadar bantu ngoding. Tapi bantu ngejaga waras juga.” 🔍 Apa yang Membuat Lovable AI Berbeda? Lovable AI Di tengah gempuran tools AI yang makin banyak—dari yng bisa bikin kode sampai yang bantu desain UI—Lovable AI muncul dengan pendekatan yang sedikit… lebih manusiawi. Bukan yang paling pintar bikin kode. Bukan yang paling banyak fitur. Tapi yang paling ngrti kamu sebagai frontend developer. 🤖 Bukan Sekadar Ngasih Kode, Tapi Ngerti Konteks Tools AI lain kadang kayak robot jurus cepat: kamu ketik “buat komponen kartu produk”, langsung keluar 100 baris kode. Tapi Lovable AI beda. Dia nanya balik kalau perlu: “Ini buat e-commerce fashion atau edukasi?”“Pakai Tailwind atau CSS module?”“Style-nya playful atau clean?” Dia ngerti konteks proyek, gaya desain, dan preferensi kamu. Jadi bukan cuma asal jalanin prompt, tapi kasih solusi yang nyambung sama realita frontend-mu. 👩‍🎨 Bukan Pengganti Designer atau Copywriter Lovable AI gak datang buat “menggantikan manusia”. Dia lebih kayak partner kerja yang siap bantu ketika kamu: Bingung nulis CTA yang menarikButuh alternatif nama komponenMikirin layout section testimonial Desainer dan penulis tetap penting. Tapi Lovable AI bantu kamu isi kekosongan saat kamu kerja solo atau buru-buru. 🧠 Fokus ke Usability, Bukan Cuma Kode Valid Banyak AI bisa kasih kamu komponen dengan 100% valid HTML dan CSS. Tapi apakah itu mudah dibaca? Apakah konsisten dengan UI lain? Apakah struktur file-nya scalable? Lovable AI mikirin itu semua. Misalnya, dia bantu: Kasih nama file dan folder yang sesuai konvensi projectSaran struktur komponen yang gampang di-maintainCopy yang ramah pengguna, bukan hasil translate literal Dengan kata lain, Lovable AI bukan sekadar mesin. Dia asisten yang paham ritme kerja frontend developer: yang kadang bingung, kadang ngebut, dan kadang butuh teman brainstorming yang gak ngegas. Kalau kamu udah pernah coba AI tools lain, Lovable AI bakal terasa… beda. Lebih hangat. Lebih lovable. 💙 🔒 Apakah Lovable AI Aman Digunakan? Keamanan dan privasi tentu jadi pertimbangan krusial saat kamu pakai tools AI — apalagi kalau ngebangun aplikasi yang akan dipakai publik. Berikut poin-poin utama soal keamanan Lovable AI: 🛡️ Privasi & Enkripsi Data Semua percakapan, prompt, dan sesi coding kamu di-handle secara terenkripsi end-to-end. Lovable menegaskan menjaga kerahasiaan input–output, dan kamu tetap memegang kendali penuh atas data proyekmu. 🔑 Tidak Menyimpan Kode & Prompt Secara Permanen Lovable tidak mengklaim kepemilikan atas kode atau prompt yng kamu masukkan — semuanya milikmu.Tidak ada proses penyimpanan data kamu kecuali kamu ekspor ke GitHub atau simpan secara eksplisit. 🗝️ Penanganan API Keys yang Aman Kalau kamu paste API key di chat, Lovable akan otomatis mendeteksi dan menasihati untuk menggunakan secrets store (misalnya Supabase secrets).Lebih baik kamu jelaskan apa yang ingin dicapai, dan Lovable akan bantu tunjukkan cara implementasi API key secra aman — misalnya lewat Edge Functions, tanpa memasukkan key langsung ke front-end. 🔎 Scan & Review Keamanan Otomatis Sebelum deploy, Lovable menjalankan scan keamanan otomatis (memeriksa isu seperti RLS, XSS, sanitasi input, dll). Jika ditemukan, kamu akan diminta untuk review dan konfirmasi sebelum lanjut docs.lovable.dev.Kamu juga bisa meminta review manual kapan saja lewat chat atau tombol "Review Security" docs.lovable.dev security. 🧩 Punya Opsi Lingkungan Dev Lokal & Ekspor ke GitHub Proyekmu bisa dipindahkan ke GitHub kapan saja — jadi tetap ada fallback ke version control tradisional.Ini memungkinkan audit manual, automated tests, audit RLS, peninjauan kode, atau keamanan deployment di luar platform. ⚠️ Praktik Keamanan Tambahan yang Disarankan Pakai Supabase RLS untuk mengatur akses data sesuai peran pengguna.Jalankan scan keamanan eksternal (misalnya Snyk, Semgrep, Aikido.dev) sebelum publikasi.Pahami juga teknik prompt-hardened: minta Lovable langsung untuk menambahkan sanitasi input dan rate limiting saat generate kode. ✅ Ringkasan Singkat dalam Bentuk List Enkripsi Data 🔒 Semua data dienkripsi end-to-end, memastikan keamanan saat transfer dan penyimpanan.Kepemilikan Kode 🧾 Kode yang kamu buat tetap milikmu sepenuhnya, tanpa sistem lock-in dari Lovable AI.Penanganan API Key 🔐 Lovable AI memberi saran aman terkait penggunaan API key (misalnya menyarankan Supabase secrets), dan tidak menyimpan API key di front-end.Pemindaian Keamanan (Security Scanning) 🛡️ Lovable AI menjalankan scan otomatis untuk mendeteksi potensi kerentanan, plus kamu juga bisa minta review manual kapan saja.Ekspor ke GitHub & Kontrol Manual 🚀 Kamu bebas mengekspor project ke GitHub untuk audit, testing, atau deployment dengan kontrol penuh di tanganmu. 🧭 Kesimpulan Lovable AI cukup matang dalam hal keamanan: Data terenkripsi,Kode dan kontrol tetap di tangan pengguna,Ada scan keamanan built-in,Dan kamu bebas ekspor untuk audit eksternal. Tapi, tetap penting: Gunakan prompt yang meminta sanitasi dan validasi input.Aktifkan scan keamanan internal dan eksternal.Kelola API key dan secrets dengan cara yang benar. Dengan pendekatan ini, kamu bisa mengandalkan Lovable AI untuk bikin aplikasi frontend dengan lebih cepat — tanpa kompromi pada keamanan. 💸 Gratis atau Berbayar? Lovable AI hadir dengan model freemium, cocok untuk berbagai tipe pengguna — mulai dari indie developer, freelancer, hingga tim kecil atau startup. Berikut penjelasannya: 🎁 Versi Gratis (Free Tier) Fitur:Proyek publik tanpa batas~5 prompt / edit per hariSinkronisasi GitHubOne-click deploy Pas banget untuk eksplorasi, belajar, dan prototipe ringan tanpa biaya. 💡 Pro – $25/bulan Semua yang ada di Free, ditambah:Unlimited private projects100 credits/bulan (~~100 prompt/edit)Cocok untuk indie dev atau hobi, yang butuh privasi dan lebih banyak kuota tanpa komitmen mahal.Proyek privateHapus badge LovableKustom domain3 editor per proyek 📈 Teams**/Pro – $30/bulan** Semua dari Pro, plus:Kuota ~2.5× lipat (~250 credits/bulan)Mode pengembangan lebih intensif (Dev Mode)Penagihan terpusatManajemen akses terpusatTermasuk 20 kursi 👥 Enterprise Harga dan fitur custom, cocok untuk tim:Batas pesan custom, billing terpusat, SSOIntegrasi kustom & dukungan langsungCocok untuk startup atau agensi, yang butuh kontrol tim dan dukungan ekstra. 📊 Apakah Lovable AI Worth It? (Versi List) Untuk Indie Developer atau Hobi ✅ Cocok dengan Pro Plan ($25/bulan)Mendukung proyek pribadi dengan kuota cukup banyak.Harga terjangkau buat yang ingin produktif tanpa keluar banyak biaya.Fitur privat dan praktis untuk side-project.Untuk Freelancer atau Solopreneur ✅ Cocok dengan Launch Plan ($30/bulan)Dapat kuota lebih besar (~2.5x lipat dari Starter).Tools lengkap untuk mengerjakan proyek klien dengan lebih efisien.Cocok buat yang serius membangun MVP atau proyek kecil-menengah.Untuk Coba-Coba / Eksplorasi Awal ✅ Cocok dengan Free Tier (Gratis)Ideal untuk belajar, eksplorasi fitur, dan testing UI/UX ringan.Tanpa komitmen biaya, cukup untuk prototyping dan eksperimen kecil. 💬 Suara dari Pengguna Beberapa non-developer menyebutnya “mind‑blowing” saat coba prototype gratisan, tapi ada juga yang merasa kuota terlalu cepat habis, terutama untuk layout atau apps kompleks. Ada juga pengguna yang bilang free tier cukup baik untuk eksperimen ringan dan belajar — bahkan tanpa biaya sama sekali . 🧭 Kesimpulannya Kalau kamu indie dev atau freelancer yang butuh banyak kuota dan aspek privasi, Pro ($25) atau Teams ($30) sudah sangat memadai.Untuk tim atau startup yang serius membangun banyak fitur, Scale ($100) atau paket Enterprise adalah investasi yang pantas—pastikan volume kuota kamu sesuai dengan kebutuhan. 🎯 Kesimpulan: Coding Lebih Manusiawi Kadang, jadi developer tuh gak cuma soal logic dan semicolon yang benar. Tapi juga soal menjaga energi, kreativitas, dan emosi. Karena, jujur aja… capek gak sih mikirin copy CTA sambil ngurusin bug dan revisi layout? Nah, Lovable AI hadir bukn buat gantiin kamu. Dia gak bakal ambil alih pekerjaanmu justru sebaliknya, dia bantu kamu fokus ke hal-hal yang lebih penting. Hal-hal yang butuh sentuhan manusia: intuisi, estetika, dan konteks. Dengan Lovable AI: Kamu bisa coding lebih cepat, tanpa harus stuck di hal-hal kecil.Bisa jaga konsistensi, meskipun yang ngerjain proyek rame-rame.Dan yang paling kerasa… coding jadi lebih menyenangkan. Kalau kamu adalah seorang frontend developer yang sering kehabisan ide, waktu, atau bahkan motivasi, mungkin inilah saatnya punya tandem yang gak ngeluh, gak tidur, dan gak bnyak tanya tapi selalu siap bantuin: Lovable AI. Karena pada akhirnya, teknologi yang baik bukan yang paling pintar, tapi yang bikin kita tetap merasa manusia. 🔁 Bonus: Kapan Sebaiknya Kamu Gunakan Lovable AI? Lovable AI itu kayak teman yang paling berguna kalau dipanggil di saat yang tepat. Bukan berarti kamu harus pakai dia setiap saat. Tapi ada momen-momen tertentu di mana kehadirannya bisa jadi penyelamat—secara waktu, tenaga, bahkan mood. Nah, berikut beberapa situasi ideal buat kamu manfaatin Lovable AI: 🎨 1. Saat Butuh Inspirasi Desain Cepat Misalnya kamu cuma punya brief singkat: “Landing page produk edukasi untuk anak.” Kamu bisa kasih prompt ke Lovable dan langsung dapat layout HTML + Tailwind yang clean dan usable. 🧱 2. Ketika Stuck Mikir Struktur atau Penamaan Pernah stuck 15 menit cuma mikir: “Ini namanya InfoBox atau StatCard ya?” Lovable AI bisa bantu kasih 3–5 alternatif nama yang relevan, jadi kamu gak perlu overthinking hal kecil. ⚡ 3. Untuk Mempercepat Pengerjaan Proyek Kecil dan MVP Lagi ngejar deadline prototipe? Lovable AI bisa bantu generate komponen, susun copy, bahkan bantuin nulis dokumentasi. Yang tadinya butuh 6 jam, bisa beres dalam 2–3 jam. 👨‍💻 4. Saat Jadi Satu-Satunya Dev di Tim Kalau kamu solo developer, Lovable AI bisa jadi semacam "pair programmer virtual"—bantu validasi ide, menyusun layout, bahkan kasih insight kecil yang kadang luput dari kita. ✨ Pro-Tip: Jangan takut trial & error saat pakai Lovable AI. Anggap aja kayak brainstorming bareng asisten pribadi. Kadang saran pertamanya belum cocok, tapi bisa jadi jalan buat ide lebih bagus.

Kelas Membuat Aplikasi Task Sederhana dengan Next.js 15, Drizzle ORM, Supabase, dan tRPC di BuildWithAngga

Membuat Aplikasi Task Sederhana dengan Next.js 15, Drizzle ORM, Supabase, dan tRPC

Kalau kamu udah ngikutin artikel sebelumnya, kita udah bahas gimana caranya bangun fondasi aplikasi fullstack modern pakai Next.js 15, Shadcn UI, Supabase, dan Better Auth. Project-nya udah clean, login system udah jalan, dan UI-nya pun udah lumayan rapi. Nah, sekarang saatnya kita naik level 🔥 Bukan cuma bikin struktur, tapi bikin sesuatu yang bisa langsung dipakai — sebuah aplikasi task sederhana, alias to-do list modern yang bisa nambah, edit, tandai selesai, bahkan hapus tugas. Ibaratnya, kita pindah dari "ngerancang rumah" ke "ngisi furnitur dan mulai tinggal di dalamnya". Seru, kan? Di artikel ini, kita bakal bareng-bareng bikin aplikasi task dengan fokus ke: ✅ Apa yang bakal kita pelajari? CRUD Task → Bisa nambah, lihat, edit, dan hapus tasktRPC → Supaya client dan server bisa ngobrol langsung tanpa ribetDrizzle ORM → Biar ngoding database jadi lebih santai, typesafe, dan rapiSupabase → Sebagai rumah untuk semua data task yang kita bikin Gak cuma itu, kita juga bakal lihat gimana semua teknologi ini kerja bareng dalam satu alur yang mulus. Mulai dari user ngetik task, dikirim ke server, masuk ke database, dan langsung muncul lagi di layar. Persiapan Proyek Oke, sebelum kita masuk ke bagian koding task-nya, kita mulai dulu dari setup dasar. Karena ini adalah lanjutan dari artikel sebelumnya, aku anggap kamu udah familiar dengan Next.js 15, Shadcn UI, dan Better Auth. Tapi biar kamu nggak mulai dari nol, kamu bisa langsung clone project base-nya dari repo GitHub berikut: 🔗 Repo Starter: https://github.com/cakfan/bwa-auth git clone <https://github.com/cakfan/bwa-auth> cd bwa-auth bun install # atau npm install / pnpm install, sesuaikan dengan package manager-mu Catatan: project ini udah dilengkapi setup auth Supabase, Drizzle ORM better-auth, dan Shadcn UI, jadi kita bisa langsung fokus ke fitur utama: Task App. Ubah file env.example menjadi .env dan sesuaikan konfigurasinya dengan supabase kalian: # Connect to Supabase via connection pooling with Supavisor. DATABASE_URL="postgres://postgres.[DB_PROJECT_ID]:[DB_PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres?pgbouncer=true" # Direct connection to the database. Used for migrations. DIRECT_URL="postgres://postgres.[DB_PROJECT_ID]:[DB_PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:5432/postgres" DB_SCHEMA_NAME="taskify" # Better Auth BETTER_AUTH_SECRET="better_secret" NEXT_PUBLIC_BASE_URL="<http://localhost:3000>" Jalan server dengan perintah ini dan buka browser http://localhost:3000: bun run dev ✅ Cek Tools yang Akan Kita Pakai: Next.js 15 — dengan App Router, file-based routing modernSupabase — sebagai database utama dan sistem authDrizzle ORM — supaya query ke database jadi lebih TypeScript-friendlytRPC — jembatan frontend ↔ backend tanpa bikin file API manual Kalau udah clone dan install dependensi, kita lanjut ke bagian bikin model datanya ya. Kita bakal mulai dari bikin tabel tasks di Drizzle + Supabase! 🛠️ 🗂️ 3. Bikin Schema & Table: tasks Sekarang saatnya kita bikin struktur data buat task-nya. Karena kita pakai Drizzle ORM, proses ini bakal terasa lebih TypeScript banget dan nggak ribet kayak SQL mentah. Kita akan bikin tabel tasks dengan kolom: id → ID uniktitle → Judul taskdescription → (opsional) isi atau penjelasan taskisDone → apakah task sudah selesai atau belumuserId→ terrhubung ke user idcreatedAt → waktu task dibuat ✍️ Tambahkan Skema di Drizzle Baut file src/db/schema/task.ts dan tambahkan kode berikut: import { boolean, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { dbSchema, user } from "."; import { createInsertSchema } from "drizzle-zod"; export const task = dbSchema.table("tasks", { id: uuid("id").primaryKey().defaultRandom(), title: text("title").notNull(), description: text("description"), isDone: boolean("is_done").default(false).notNull(), userId: text("user_id").references(() => user.id), createdAt: timestamp("created_at").defaultNow(), }); export type TaskType = typeof task.$inferSelect; export const TaskInsertSchema = createInsertSchema(task); Kemudian buka file src/db/schema/index.ts dan tambahkan kode berikut diakhir line: export * from "./task"; Jika sudah jalankan perintah ini: bun run db:push Jika tidak ada error maka akan seperti berikut: Push schema ke supabase Pastikan file .env kamu sudah berisi DATABASE_URL dari Supabase ya. Kalau belum, kamu bisa dapatkannya dari dashboard Supabase → Project Settings → Database → Connection String (pilih format URI). ✅ Cek di Supabase Kalau semuanya lancar, kamu bisa cek di Supabase dan harusnya tabel tasks udah muncul otomatis 🎉 Ini artinya database kamu siap dipakai untuk CRUD task yang akan kita bangun. Supabase schema 🔌 4. Setup tRPC Router untuk Task Oke, kita udah punya tabel tasks di database. Sekarang gimana caranya biar data itu bisa kita ambil dari frontend? Nah, di sinilah tRPC masuk! Sebelum langsung ke koding, yuk kita kenalan dulu: 📦 Apa itu tRPC? tRPC adalah singkatan dari TypeScript Remote Procedure Call. Intinya, tRPC memungkinkan kita bikin API tanpa harus nulis file API satu-satu kayak di app/api/task/route.ts. Kita cukup bikin satu router, lalu panggil fungsi-fungsi itu langsung dari frontend seolah-olah kita manggil function biasa. Dan yang paling keren: ✅ TypeScript-nya otomatis sinkron antara client dan server. ✅ Gak perlu bikin API schema manual (gak usah pakai REST atau GraphQL). ✅ Cepat dan fleksibel, apalagi kalau project kamu growing. Ibaratnya, tRPC tuh kayak ngobrol langsung antara frontend dan backend pakai walkie-talkie, tanpa ribet translator (alias REST/GraphQL). 🚀 Kenapa kita pakai tRPC? Typesafe end-to-end Kalau kamu salah kirim tipe data dari frontend, error-nya langsung kelihatan di VSCode — bahkan sebelum dijalankan!Gak Perlu Bikin API Layer Berulang Gak ada lagi tuh nulis GET, POST, PUT, DELETE di banyak file route.Terintegrasi banget sama Next.js App Router Apalagi kalau pakai server actions atau route handlers. 📁 Installasi tRPC di Proyek Ini Pertama install dulu library yang dibutuhkan dengan perintah berikut: bun add @tanstack/react-query @tanstack/react-table @trpc/client @trpc/react-query @trpc/server superjson Struktur folder akan seperti ini: tRPC folder Kita mulai buat file src/trpc/init.ts dan tambahkan kode ini: // src/trpc/init.ts import { getMe, getUser } from "@/actions/user"; import { initTRPC, TRPCError } from "@trpc/server"; import { cache } from "react"; import superjson from "superjson"; export const createTRPCContext = cache(async () => { /** * @see: <https://trpc.io/docs/server/context> */ const user = await getMe(); return { userId: user?.id }; }); export type Context = Awaited<ReturnType<typeof createTRPCContext>>; // Avoid exporting the entire t-object // since it's not very descriptive. // For instance, the use of a t variable // is common in i18n libraries. const t = initTRPC.context<Context>().create({ /** * @see <https://trpc.io/docs/server/data-transformers> */ transformer: superjson, }); // Base router and procedure helpers export const createTRPCRouter = t.router; export const createCallerFactory = t.createCallerFactory; export const baseProcedure = t.procedure; export const protectedProcedure = t.procedure.use(async function isAuthed( opts ) { const { ctx } = opts; if (!ctx.userId) { throw new TRPCError({ code: "UNAUTHORIZED" }); // console.log("TRPC_ERROR:", "UNAUTHORIZED"); } const user = await getUser({ id: ctx.userId }); if (!user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return opts.next({ ctx: { ...ctx, user, }, }); }); Pasti ada yang error kan? 😅 Tenang… sekarang buat file src/actions/user/get-user.ts dan tambahkan kode ini: // src/actions/user/get-user.ts "use server"; import { db } from "@/db"; import { user } from "@/db/schema"; import { eq } from "drizzle-orm"; export async function getUser({ id }: { id: string }) { const [data] = await db.select().from(user).where(eq(user.id, id)); return data ?? null; } Kemudian buat file src/actions/user/index.ts dan tambahkan kode ini: // src/actions/user/index.ts export * from "./me"; export * from "./get-user"; Harusnya udah gak error lagi.. Sekarang buat file src/trpc/server.tsx dan tambahkan kode ini: // src/trpc/server.tsx import "server-only"; // <-- ensure this file cannot be imported from the client import { createHydrationHelpers } from "@trpc/react-query/rsc"; import { cache } from "react"; import { createCallerFactory, createTRPCContext } from "./init"; import { makeQueryClient } from "./query-client"; import { appRouter } from "./routers/_app"; // IMPORTANT: Create a stable getter for the query client that // will return the same client during the same request. export const getQueryClient = cache(makeQueryClient); const caller = createCallerFactory(appRouter)(createTRPCContext); export const { trpc, HydrateClient } = createHydrationHelpers<typeof appRouter>( caller, getQueryClient ); Buat file src/trpc/query-client.ts dan tambahkan kode ini: // src/trpc/query-client.ts import { defaultShouldDehydrateQuery, QueryClient, } from "@tanstack/react-query"; import superjson from "superjson"; export function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, }, dehydrate: { serializeData: superjson.serialize, shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending", }, hydrate: { deserializeData: superjson.deserialize, }, }, }); } Fungsi makeQueryClient ini digunakan untuk membuat custom QueryClient untuk @tanstack/react-query, dengan konfigurasi khusus untuk serialisasi dan deserialisasi data saat server-side rendering (SSR) atau static site generation (SSG) — termasuk pengaturan staleTime. Selanjutnya kitta buat file src/trpc/client.tsx dan tambahkan kode ini: // src/trpc/client.tsx "use client"; // ^-- to make sure we can mount the Provider from a server component import type { QueryClient } from "@tanstack/react-query"; import superjson from "superjson"; import { QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink } from "@trpc/client"; import { createTRPCReact } from "@trpc/react-query"; import { useState } from "react"; import { makeQueryClient } from "./query-client"; import type { AppRouter } from "./routers/_app"; export const trpc = createTRPCReact<AppRouter>(); let clientQueryClientSingleton: QueryClient; function getQueryClient() { if (typeof window === "undefined") { // Server: always make a new query client return makeQueryClient(); } // Browser: use singleton pattern to keep the same query client return (clientQueryClientSingleton ??= makeQueryClient()); } function getUrl() { const base = (() => { if (typeof window !== "undefined") return ""; if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; return "<http://localhost:3000>"; })(); return `${base}/api/trpc`; } export function TRPCProvider( props: Readonly<{ children: React.ReactNode; }> ) { // NOTE: Avoid useState when initializing the query client if you don't // have a suspense boundary between this and the code that may // suspend because React will throw away the client on the initial // render if it suspends and there is no boundary const queryClient = getQueryClient(); const [trpcClient] = useState(() => trpc.createClient({ links: [ httpBatchLink({ transformer: superjson, url: getUrl(), async headers() { const headers = new Headers(); headers.set("x-trpc-source", "nextjs-react"); return headers; }, }), ], }) ); return ( <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}> {props.children} </QueryClientProvider> </trpc.Provider> ); } Kemudian kita buat file API nya src/app/api/trpc/[trpc]/route.ts dan tambahkan kode ini: // src/app/api/trpc/[trpc]/route.ts import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { createTRPCContext } from "@/trpc/init"; import { appRouter } from "@/trpc/routers/_app"; const handler = (req: Request) => fetchRequestHandler({ endpoint: "/api/trpc", req, router: appRouter, createContext: createTRPCContext, }); export { handler as GET, handler as POST }; Buat file src/trpc/routers/task.ts dan tambahkan kode ini: // src/trpc/routers/task.ts import { db } from "@/db"; import { and, desc, eq, lt, or } from "drizzle-orm"; import { task, TaskInsertSchema, TaskUpdateSchema } from "@/db/schema"; import { createTRPCRouter, protectedProcedure } from "@/trpc/init"; import { TRPCError } from "@trpc/server"; import z from "zod"; export const taskRouters = createTRPCRouter({ getMyTask: protectedProcedure .input( z.object({ cursor: z .object({ id: z.string().uuid(), createdAt: z.date(), }) .nullish(), limit: z.number().min(1).max(100), }) ) .query(async ({ ctx, input }) => { const { cursor, limit } = input; const data = await db .select() .from(task) .where( cursor ? or( lt(task.createdAt, cursor.createdAt), and( eq(task.userId, ctx.userId!), eq(task.createdAt, cursor.createdAt), lt(task.id, cursor.id) ) ) : undefined ) .orderBy(desc(task.createdAt), desc(task.id)) .limit(limit + 1); const hasMore = data.length > limit; // Remove the last item if there is more data const items = hasMore ? data.slice(0, -1) : data; // Set the next cursor to the last item if there is more data const lastItem = items[items.length - 1]; const nextCursor = hasMore ? { id: lastItem.id, createdAt: lastItem.createdAt! } : null; return { items, nextCursor }; }), create: protectedProcedure .input( z.object({ title: z.string().min(4, { message: "Title is required" }), description: z.string().min(10, { message: "Description is required" }), isDone: z.boolean(), }) ) .mutation(async ({ ctx, input }) => { const parsedData = TaskInsertSchema.parse(input); if (!parsedData) { throw new TRPCError({ code: "BAD_REQUEST" }); } const [taskCreated] = await db .insert(task) .values({ ...input, userId: ctx.userId }) .returning(); return taskCreated; }), update: protectedProcedure .input( z.object({ id: z.string().min(1, { message: "ID is required" }), isDone: z.boolean(), }) ) .mutation(async ({ ctx, input }) => { const parsedData = TaskUpdateSchema.parse(input); if (!parsedData) { throw new TRPCError({ code: "BAD_REQUEST" }); } const isTaskExist = await db .select() .from(task) .where(and(eq(task.id, input.id), eq(task.userId, ctx.userId!))); if (!isTaskExist) { throw new TRPCError({ code: "NOT_FOUND" }); } const [taskUpdated] = await db .update(task) .set(input) .where(eq(task.id, input.id)) .returning(); return taskUpdated; }), }); Kemudian buat file src/trpc/routers/_app.ts dan tambahkan kode ini: // src/trpc/routers/_app.ts import { createTRPCRouter } from "../init"; import { taskRouters } from "./task"; export const appRouter = createTRPCRouter({ task: taskRouters, }); // export type definition of API export type AppRouter = typeof appRouter; 🧑‍💻 5. Buat UI: List & Form Task Oke, sekarang kita masuk ke bagian yang paling sering dilihat user: UI-nya. Karena kita udah setup Shadcn UI dari awal, kita bisa langsung pakai komponen-komponen kece kayak Card, Input, Button, dll. 🔄 Flow-nya: Tampilkan list taskTambah task via formUpdate status done atau bahkan edit task (opsional, tapi asik) Install library yang dibutuhkan terlebih dahulu dengan printah berikut: bun add react-error-boundary bunx [email protected] add card switch Sekarang buka file src/app/dashboard/page.tsx dan ubah jadi seperti ini: // src/app/dashboard/page.tsx import type { Metadata } from "next"; import { HydrateClient, trpc } from "@/trpc/server"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import AddTaskForm from "./components/add-task-form"; import { TaskList } from "./components/task-list"; export const metadata: Metadata = { title: "Dashboard", }; export default async function DashboardPage() { void trpc.task.getMyTask.prefetchInfinite({ limit: 10, }); return ( <HydrateClient> <div className="space-y-6 p-6"> <AddTaskForm /> <Card> <CardHeader> <CardTitle>Task List</CardTitle> </CardHeader> <CardContent> <TaskList /> </CardContent> </Card> </div> </HydrateClient> ); } Selanjutnya buat file src/app/dsahboard/components/add-task-form.tsx dan tambbahkan kode ini: // src/app/dashboard/components/add-task-form.tsx "use client"; import { trpc } from "@/trpc/client"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; const TaskSchema = z.object({ title: z.string().min(4, { message: "Title is required" }), description: z.string().min(10, { message: "Description is required" }), isDone: z.boolean(), }); type TaskValues = z.infer<typeof TaskSchema>; const AddTaskForm = () => { const utils = trpc.useUtils(); const form = useForm<TaskValues>({ resolver: zodResolver(TaskSchema), defaultValues: { title: "", description: "", isDone: false, }, }); const createTask = trpc.task.create.useMutation({ onSuccess: () => { utils.task.getMyTask.invalidate(); form.reset(); }, }); const addTask = (data: TaskValues) => { const parsed = TaskSchema.safeParse(data); if (!parsed.success) return; createTask.mutate(data); }; return ( <Card className="w-full border-none"> <CardHeader> <CardTitle className="text-4xl leading-1">Buat Task</CardTitle> </CardHeader> <CardContent className="mt-10 space-y-4"> <Form {...form}> <form onSubmit={form.handleSubmit(addTask)} className="grid grid-cols-4 items-center gap-4 gap-y-5" > <FormField control={form.control} name="title" render={({ field }) => ( <FormItem> <FormLabel>Title</FormLabel> <FormControl> <Input type="text" placeholder="Tulis judul" disabled={createTask.isPending} {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="description" render={({ field }) => ( <FormItem> <FormLabel>Deskripsi</FormLabel> <FormControl> <Input type="text" placeholder="Tulis deskripsi" disabled={createTask.isPending} {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="isDone" render={({ field }) => ( <FormItem className="flex flex-col gap-4"> <div className="space-y-0.5"> <FormLabel>Is Done?</FormLabel> </div> <FormControl> <Switch checked={field.value} onCheckedChange={field.onChange} /> </FormControl> </FormItem> )} /> <Button type="submit" className="ml-auto w-fit" disabled={createTask.isPending} > {createTask.isPending ? "Menambahkan..." : "Tambah Task"} </Button> </form> </Form> </CardContent> </Card> ); }; export default AddTaskForm; Kemudian buat file src/app/dashboard/components/task-list.tsx lalu tambahkan kode ini: // src/app/dashboard/compnents/task-list.tsx "use client"; import { Form, FormControl, FormField, FormItem, FormLabel, } from "@/components/ui/form"; import { Switch } from "@/components/ui/switch"; import { trpc } from "@/trpc/client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Suspense } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { useForm } from "react-hook-form"; import z from "zod"; const TaskSchema = z.object({ id: z.string().min(1, { message: "ID is required" }), // title: z.string().min(4, { message: "Title is required" }), // description: z.string().min(10, { message: "Description is required" }), isDone: z.boolean(), }); type TaskValues = z.infer<typeof TaskSchema>; const SwitchIsDone = ({ id, isDone }: { id: string; isDone: boolean }) => { const utils = trpc.useUtils(); const form = useForm<TaskValues>({ resolver: zodResolver(TaskSchema), defaultValues: { id, // title: "", // description: "", isDone, }, }); const updateTask = trpc.task.update.useMutation({ onSuccess: () => { utils.task.getMyTask.invalidate(); }, }); const onUpdate = (data: TaskValues) => { const parsed = TaskSchema.safeParse(data); if (!parsed.success) return; updateTask.mutate(data); }; return ( <Form {...form}> <form onSubmit={form.handleSubmit(onUpdate)}> <FormField control={form.control} name="isDone" render={({ field }) => ( <FormItem className="flex flex-col gap-4"> <div className="space-y-0.5"> <FormLabel>Is Done?</FormLabel> </div> <FormControl> <Switch checked={field.value} onCheckedChange={(value) => { field.onChange(value); // update form state form.handleSubmit(onUpdate)(); // langsung submit form }} /> </FormControl> </FormItem> )} /> </form> </Form> ); }; const TaskItemSuspense = () => { const [tasks] = trpc.task.getMyTask.useSuspenseInfiniteQuery( { limit: 10, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, } ); return ( <div className="flex flex-col gap-2"> {tasks.pages .flatMap((page) => page.items) .map((task) => ( <div key={task.id} className={"flex flex-col gap-1"}> <h2 className="text-3xl font-bold">{task.title}</h2> <p className="text-pretty">{task.description}</p> <SwitchIsDone id={task.id} isDone={task.isDone} /> </div> ))} </div> ); }; export const TaskList = () => { return ( <Suspense fallback={<p>Loading...</p>}> <ErrorBoundary fallback={<p>Error</p>}> <TaskItemSuspense /> </ErrorBoundary> </Suspense> ); }; Jalankan server dan buka halaman http://localhost:3000/dashboard maka tampilan akan sepertit ini: Tampilaan buat task Seelesai juga nih… Fitur yang udah direapkan yaitu menampilkan daftar task, tambah task dan edit task. 🧠 6. Penutup – Simpel Tapi Powerful Yesss, kita udah berhasil bikin aplikasi Task (to-do list) sederhana dengan: ✅ Next.js 15 sebagai fondasi fullstack ✅ tRPC buat komunikasi frontend ↔ backend tanpa repot ✅ Drizzle ORM yang simpel dan aman buat query DB ✅ Supabase sebagai database modern ✅ Dan tentunya Shadcn UI biar tampilannya tetap clean dan minimalis Walaupun sederhana, aplikasi ini udah cukup nunjukin alur kerja modern: mulai dari nge-query data, handle user input, sampai update state secara realtime. Semua tanpa harus repot bikin API berlapis-lapis. 🎯 Yang Bisa Kamu Lakuin Selanjutnya Kalau kamu pengen lanjut eksplorasi, ini beberapa ide: ✏️ Tambahkan fitur edit task🗂️ Filter task berdasarkan status (done / undone)📅 Tambahkan field due date atau kategori📱 Buat versi responsive / mobile first Terima kasih udah ngoding bareng. Sampai jumpa di artikel berikutnya! 🚀

Kelas Bangun Aplikasi Fullstack Modern dengan Next.js 15, Shadcn UI, Supabase & Better-Auth di BuildWithAngga

Bangun Aplikasi Fullstack Modern dengan Next.js 15, Shadcn UI, Supabase & Better-Auth

🧩 Pendahuluan: Saatnya Bikin Aplikasi Fullstack yang Gak Ribet, tapi Powerful Pernah gak sih kamu pengen bikin aplikasi web yang keliatannya “niat banget”, tapi pas ngoding malah overthinking duluan karena bingung harus mulai dari mana? Tenang, kamu gak sendiri. Dulu juga gitu—niatnya cuma pengen bikin to-do list yang bisa login, simpan data, dan tampil keren. Tapi begitu nyentuh setup backend, terus UI-nya gak seragam, terus autentikasi juga ribet... akhirnya nyerah di tengah jalan. 😅 Nah, di sinilah serunya teknologi modern sekarang. Kita bisa bangun aplikasi fullstack tanpa harus mikirin terlalu banyak hal ribet. Cukup pake Next.js 15, terus tinggal kombinasikan dengan Shadcn UI, Supabase, dan Better-Auth, semuanya langsung klik kayak potongan puzzle yang pas. 🚀 Next.js 15: Bukan Sekadar Framework Next.js udah lama jadi sahabat para developer React. Tapi di versi 15 ini, dia makin “dewasa”. Yang paling kerasa itu sih App Router yang super modular, lalu ada Server Actions yang bikin kita bisa kirim data langsung dari form ke server—tanpa harus bikin API route sendiri. 😍 Gak cuma itu, Next.js 15 juga makin cakep dengan fitur Streaming, jadi loading halaman bisa lebih cepat dan interaktif. Cocok banget buat aplikasi yang butuh pengalaman pengguna yang mulus. 🎨 Shadcn UI: Desain Keren Gak Harus Ribet Biasanya, bikin UI yang konsisten itu PR banget. Tapi Shadcn UI hadir sebagai “penyelamat” buat kita yang pengen desain bagus tapi gak mau ribet. Dia pake Tailwind di belakang layar, tapi kasih kita komponen siap pakai yang udah konsisten dan bisa dikustomisasi dengan mudah. Enaknya lagi, kita punya kontrol penuh karena komponen-komponennya bisa kita edit sesuka hati. 🛢️ Supabase: Backend Instant, Tanpa Drama Kalau dulu backend berarti harus setup database, bikin API, auth, dsb, sekarang ada Supabase yang udah siap pakai. Kita bisa bikin tabel lewat dashboard, dapet API otomatis, ada fitur realtime juga, bahkan ada autentikasi. Udah kayak Firebase tapi rasa SQL. 🔐 Better-Auth: Autentikasi yang Beneran Better Nah, urusan login biasanya paling nyebelin. Tapi dengan Better-Auth, semua jadi lebih simpel. Dia dibangun khusus untuk Next.js, udah support login dengan email, Google, dsb. Gak perlu setup ribet, dan yang paling penting: aman dan fleksibel. Jadi, bayangin kalau semua komponen ini digabung: framework modern, UI siap pakai, backend instan, dan autentikasi simpel. Kita bisa bikin aplikasi fullstack yang gak cuma works, tapi juga scalable, maintainable, dan tampil profesional. Yuk, kita lanjut ke tahap persiapan proyek. 🔧 ⚙️ Persiapan Proyek: Bangun Pondasi Aplikasi Modern Kita Oke, jadi kamu udah siap bikin aplikasi fullstack kece dengan Next.js 15, Shadcn UI, Supabase, dan Better-Auth. Sekarang saatnya kita mulai dari nol—beneran nol. Kayak bangun rumah, kita mulai dulu dari pondasinya. Di artikel ini, kita akan menggunakan Bun sebagai package manager. Tapi tenang, kalau kamu lebih nyaman pakai npm atau yarn, tetap bisa kok—semua perintahnya tinggal disesuaikan. 🏗️ 1. Bikin Proyek Baru Pakai Next.js 15 Pertama, kita butuh proyek Next.js yang sudah menggunakan App Router (yang kekinian banget). Di sini, kita akan pakai versi 15.3.3. Jalankan perintah berikut di terminal: bunx [email protected] bwa-auth Nanti akan ditanya beberapa hal. Jawaban yang disarankan: ✔ Would you like to use TypeScript? › Yes ✔ Would you like to use ESLint? › Yes ✔ Would you like to use Tailwind CSS? › Yes (Opsional, tapi bikin form lebih cakep) ✔ Would you like to use `src/` directory? › Yes ✔ Would you like to use App Router? › Yes ✔ Would you like to use Turbopack for `next dev`? › Yes ✔ Would you like to customize the default import alias (@/*)? › No Setlah selesai masuk ke folder proyek: cd bwa-auth Lalu jalankan server: bun run dev Kalau semua berjalan lancar, kamu bisa buka browser dan akses http://localhost:3000 untuk melihat project Next.js kamu tampil dengan halaman default. Tampilan awal Next.js 15 🎨 2. Konfigurasi Tailwind CSS (Udah Auto, Tapi Cek Dulu) Buat mastiin aja nih kalo proyek kita udah pakai Tailwind CSS atau belum, di sini kita pakai tailwind v4. Buka file src/app/globals.css dan cek ada kode ini atau tidak: @import "tailwindcss"; Kalau semuanya oke, kita lanjut. 🧩 3. Install Shadcn UI: UI Keren dalam Sekejap Sekarang, kita install Shadcn UI biar komponen-komponennya bisa langsung dipakai. bunx [email protected] init Jika ada pilihan warna, piilih sesuai keinginan kalian atau bisa ikuti ini: √ Which color would you like to use as the base color? » Neutral Selanjutnya kita tambahkan button, input, dan form dengan perintah berikut: bunx [email protected] add button input form sonner Maka struktur folder akan seperti berikut ini: Struktur folder Buka file globals.css lalu ubah jadi seperti berikut ini: @import "tailwindcss"; @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar: var(--sidebar); --color-chart-5: var(--chart-5); --color-chart-4: var(--chart-4); --color-chart-3: var(--chart-3); --color-chart-2: var(--chart-2); --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); --color-secondary-foreground: var(--secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); } :root { --radius: 0.65rem; --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); --primary: oklch(0.623 0.214 259.815); --primary-foreground: oklch(0.97 0.014 254.604); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); --muted-foreground: oklch(0.552 0.016 285.938); --accent: oklch(0.967 0.001 286.375); --accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); --ring: oklch(0.623 0.214 259.815); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.623 0.214 259.815); --sidebar-primary-foreground: oklch(0.97 0.014 254.604); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.623 0.214 259.815); } .dark { --background: oklch(0.141 0.005 285.823); --foreground: oklch(0.985 0 0); --card: oklch(0.21 0.006 285.885); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.546 0.245 262.881); --primary-foreground: oklch(0.379 0.146 265.522); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); --muted-foreground: oklch(0.705 0.015 286.067); --accent: oklch(0.274 0.006 286.033); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.546 0.245 262.881); --sidebar-primary-foreground: oklch(0.379 0.146 265.522); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.488 0.243 264.376); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } } Sekarang buka file page.tsx dan ganti jadi sepertit ini: import { Button } from "@/components/ui/button"; export default function Home() { return ( <div className="flex flex-col gap-2 min-h-screen items-center justify-center"> <h1 className="text-4xl font-bold">Hello, BuildWithAngga!</h1> <Button>Get Started!</Button> </div> ); } Dan hasilnya akan seperti berikut: Setup shadcn di Next.js Sekarang kita sudah selesai setup shadcn di proyek Next.js. 🔌 4. Setup Supabase: Siapkan Dapur Datanya 🍳 Sekarang kita udah punya UI yang cantik dan fondasi yang kuat. Tapi belum lengkap tanpa tempat nyimpen data, kan? Nah, di sinilah Supabase masuk sebagai dapur utama penyimpanan dan autentikasi aplikasi kita. Kita bakal: Setup database dan tabel user.Ambil API key dan URL Supabase.Integrasi Supabase + Drizzle ORM biar kerjaan backend makin elegan. 🧱 1. Setup Database lewat Supabase Dashboard Supabase Dashboard Pertama, buka https://supabase.com dan login. Setelah itu: Klik "New Project".Isi:Project Name: bwa-auhtDatabase Password: isi dan simpan baik-baikRegion: pilih yang paling dekat dengan target user (biasanya Singapore untuk Indonesia)Jangan lupa untuk copy database password Supabase: buat proyek baru Sekarang buat file .env di root direktori proyek dan tambhakan kode berikut: # Connect to Supabase via connection pooling with Supavisor. DATABASE_URL="postgres://postgres.[DB_PROJECT_ID]:[DB_PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres?pgbouncer=true" # Direct connection to the database. Used for migrations. DIRECT_URL="postgres://postgres.[DB_PROJECT_ID]:[DB_PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:5432/postgres" Maka akan seperti ini: Struktur folder Ganti DB_PROJECT_ID dan DB_PASSWORD sesuai degan proyek kalian. Penjelasan: Supabase menyediakan dua jenis koneksi database untuk kebutuhan yang berbeda: DATABASE_URL → Connection Pooling via Supavisor (port 6543) 🔧 Dipakai untuk: Akses database dari aplikasi (runtime) seperti:API routesServer ActionsCron jobsKoneksi jangka panjang yang lebih efisien (connection pooling)Skalabilitas & performa yang lebih baik ⚡ Kenapa? Koneksi ini menggunakan PgBouncer, yaitu sistem connection pooler yang menghemat jumlah koneksi langsung ke database. Cocok banget buat aplikasi production, karena: Supabase punya batas koneksi langsung (maks 20–100) tergantung plan.PgBouncer bisa multiplex banyak koneksi ringan dari app jadi hanya beberapa koneksi nyata ke database. DIRECT_URL → Direct PostgreSQL Connection (port 5432) 🛠️ Dipakai untuk: Database migration tools seperti:drizzle-kit pushprisma migrateraw SQL CLIAkses langsung yang butuh fitur-fitur database yang tidak didukung PgBouncer (misalnya: prepared statements, session-based commands) ⚠️ Kenapa tidak dipakai terus? Direct connection tidak pakai pooling, jadi lebih berat untuk production.Kalau tiap server instance buka koneksi langsung, bisa cepat over-limit dan menyebabkan error (terutama di plan gratis atau kecil). 🔧 2. Setup Drizzle: ORM Rasa Modern, Simple, dan Type-Safe Setelah kita beres konfigurasi Supabase, sekarang saatnya ngenalin "juru masak" buat urusan database kita — namanya Drizzle ORM. Kenapa pakai Drizzle? Karena dia ringan, simple, dan auto type-safe langsung dari skema. Cocok banget buat Next.js 15 yang modern, dan gak bikin pusing kepala kayak ORM-ORM berat lainnya. 🛠️ 1.Install Drizzle dan Postgres Jalankan perintah ini buat install semua kebutuhan: bun add [email protected] [email protected] [email protected]; bun add -D [email protected] Penjelasan cepat: drizzle-orm: core ORM-nyapostgres: client buat koneksi ke database PostgreSQLdrizzle-kit: CLI buat generate migration & types 📁 2.Setup File Konfigurasi Drizzle Buat file bernama drizzle.config.ts di root project: import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./src/db/schema/index.ts", out: "./drizzle", dialect: "postgresql", schemaFilter: ["public", process.env.DB_SCHEMA_NAME!], dbCredentials: { url: process.env.DIRECT_URL!, }, // Print all statements verbose: true, // Always ask for confirmation strict: true, }); 🧩 3.Definisikan Schema Tabel Pada schema ini kita akan pisahkan dalam beberapa file. Sekarang buat file src/db/schema/index.ts: // src/db/schema/index.ts export * from "./schema"; export * from "./user"; export * from "./account"; export * from "./session"; export * from "./verification"; Buat file src/db/schema/schema.ts: // src/db/schema/schema.ts import { pgSchema } from "drizzle-orm/pg-core"; export const dbSchema = pgSchema(process.env.DB_SCHEMA_NAME!); Buat file src/db/schema/user.ts: import { boolean, text, timestamp } from "drizzle-orm/pg-core"; import { dbSchema } from "."; import { z } from "zod"; export const user = dbSchema.table("user", { id: text("id").primaryKey(), name: text("name").notNull(), username: text("username").unique(), displayUsername: text("display_username"), email: text("email").notNull().unique(), emailVerified: boolean("emailVerified").notNull(), image: text("image"), createdAt: timestamp("createdAt").defaultNow(), updatedAt: timestamp("updatedAt") .defaultNow() .$onUpdate(() => new Date()), }); export type UserType = typeof user.$inferSelect; export const signInSchema = z.object({ username: z.string().min(4, { message: "Username is required" }), password: z .string() .min(6, { message: "Password lenght at least 6 characters" }), }); export type SignInValues = z.infer<typeof signInSchema>; Buat file src/db/schema/account.ts: // src/db/schema/account.ts import { text, timestamp } from "drizzle-orm/pg-core"; import { dbSchema, user } from "."; export const account = dbSchema.table("account", { id: text("id").primaryKey(), accountId: text("accountId").notNull(), providerId: text("providerId").notNull(), userId: text("userId") .notNull() .references(() => user.id), accessToken: text("accessToken"), refreshToken: text("refreshToken"), idToken: text("idToken"), accessTokenExpiresAt: timestamp("accessTokenExpiresAt"), refreshTokenExpiresAt: timestamp("refreshTokenExpiresAt"), scope: text("scope"), password: text("password"), createdAt: timestamp("createdAt").defaultNow(), updatedAt: timestamp("updatedAt") .defaultNow() .$onUpdate(() => new Date()), }); Buat file src/db/schema/session.ts: // src/db/schema/session.ts import { text, timestamp } from "drizzle-orm/pg-core"; import { dbSchema, user } from "."; export const session = dbSchema.table("session", { id: text("id").primaryKey(), expiresAt: timestamp("expiresAt").notNull(), token: text("token").notNull().unique(), createdAt: timestamp("createdAt").defaultNow(), updatedAt: timestamp("updatedAt") .defaultNow() .$onUpdate(() => new Date()), ipAddress: text("ipAddress"), userAgent: text("userAgent"), userId: text("userId") .notNull() .references(() => user.id), }); Buat file src/db/schema/verification.ts: // src/db/schema/verification.ts import { text, timestamp } from "drizzle-orm/pg-core"; import { dbSchema } from "./schema"; export const verification = dbSchema.table("verification", { id: text("id").primaryKey(), identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expiresAt").notNull(), createdAt: timestamp("createdAt").defaultNow(), updatedAt: timestamp("updatedAt") .defaultNow() .$onUpdate(() => new Date()), }); Buat file src/db/index.ts: // src/db/index.ts import * as schema from "./schema"; import postgres from "postgres"; import { drizzle } from "drizzle-orm/postgres-js"; const client = postgres( process.env.NODE_ENV === "production" ? process.env.DATABASE_URL! // Use DATABASE_URL in production for pooling : process.env.DIRECT_URL!, // Use DIRECT_URL in development for direct access ); export const db = drizzle(client, { schema }); Maka struktur proyek aakan seperti ini: Drizzle struktur Sekarang tambahkan baris kode ini di file .env : DB_SCHEMA_NAME="bwa_auth" Buka file package.json lalu tambahkan custom script seperti berikut ini: "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio --port 5555 --verbose" Sehingga akan menjadi seperti ini: { "name": "bwa-auth", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio --port 5555 --verbose" }, "dependencies": {...}, "devDependencies": {...} } Sekarang jalan perintah ini: bun run db:push Maka akan seperti ini: Drizzle push schema 🔐 5. Tambahkan Better-Auth: Login Gak Pake Drama Better-Auth bikin login di Next.js jadi jauh lebih simpel dan modern. Install dulu dependensinya: bun add [email protected] Sekarang buat file src/lib/auth.ts: // src/lib/auth.ts import { db } from "@/db"; import { betterAuth } from "better-auth"; import { username } from "better-auth/plugins"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", }), plugins: [username()], emailAndPassword: { enabled: true, }, }); Kemudian buat file src/lib/auth-client.ts: // src/lib/auth-client.ts import { createAuthClient } from "better-auth/react"; import { usernameClient } from "better-auth/client/plugins"; export const { signIn, signUp, signOut, useSession, getSession } = createAuthClient({ plugins: [usernameClient()], baseURL: process.env.NEXT_PUBLIC_BASE_URL!, }); Tambahkan kode ini di file .env: # Better Auth BETTER_AUTH_SECRET="better_secret" NEXT_PUBLIC_BASE_URL="<http://localhost:3000>" Kalian bisa generate BETTER_AUTH_SECRET di web resmi better-auth. Sekarang buat API untuk better-auth, caranya buat file src/app/api/auth/[...all]/route.ts: // src/app/api/auth/[...all]/route.ts import { auth } from "@/lib/auth"; import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth); Samapi sini sebenerya udah bisa dipake ya, tapi biar makin lengkap kita tambahkan middleware. Middleware di Next.js itu semacam "penjaga gerbang" yang jalan sebelum request masuk ke route atau API handler. Jadi, kita bisa pakai middleware buat: Cek apakah user udah login (autentikasi)Redirect user kalau belum punya aksesTambahin headers, logging, atau trackingAtur locale, timezone, dll Setup Middleware Buat file middleware.ts di root proyek dan tambahkan kode ini: // src/middleware.ts import { NextResponse, type NextRequest } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; import { apiAuthPrefix, authRoutes, DEFAULT_LOGIN_REDIRECT, publicRoutes, } from "./routes"; export async function middleware(request: NextRequest) { const session = getSessionCookie(request); const isApiAuth = request.nextUrl.pathname.startsWith(apiAuthPrefix); const isPublicRoute = publicRoutes.includes(request.nextUrl.pathname); const isAuthRoute = () => { return authRoutes.some((path) => request.nextUrl.pathname.startsWith(path)); }; if (isApiAuth) { return NextResponse.next(); } if (isAuthRoute()) { if (session) { return NextResponse.redirect( new URL(DEFAULT_LOGIN_REDIRECT, request.url) ); } return NextResponse.next(); } if (!session && !isPublicRoute) { return NextResponse.redirect(new URL(authRoutes[0], request.url)); } return NextResponse.next(); } export const config = { matcher: [ // Kecuali untuk static files, image, favicon, dan file gambar "/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", ], }; Agar route lebih fleksibel, kita pisahkan daftar halaman publik dan auth di file routes.ts: // src/routes.ts export const publicRoutes: string[] = ["/"]; export const authRoutes: string[] = ["/signin", "/signup"]; export const apiAuthPrefix: string = "/api/auth"; export const DEFAULT_LOGIN_REDIRECT: string = "/dashboard"; Halaman Sign Up: Saatnya Pengguna Daftar Nah, sekarang kita bikin halaman supaya pengguna bisa daftar akun mereka. Gak perlu ribet kok, apalagi karena kita udah pakai Better-Auth dan Shadcn UI. Tinggal gabungkan aja komponen UI + logic auth. Buat file src/app/(auth)/signup/page.tsx, lalu isi dengan kode ini: import { type Metadata } from "next"; import Link from "next/link"; import SignUpForm from "./form"; export const metadata: Metadata = { title: "Sign Up", }; export default function SignUpPage() { return ( <div className="flex min-h-screen w-full flex-col items-center justify-center p-10"> <div className="flex w-full flex-col rounded-2xl border border-foreground/10 px-8 py-5 md:w-96"> <h1 className="text-3xl font-bold mb-2">Sign Up</h1> <p> Selamat datang di <strong>BWA Auth</strong> </p> <SignUpForm /> <div className="flex items-center justify-center gap-2"> <small>Sudah punya akun?</small> <Link href={"/signin"} className="text-sm font-bold leading-none"> Sign In </Link> </div> </div> </div> ); } Kemudian buat file src/app/(auth)/signup/form.tsx, lalu isi dengan kode ini: "use client"; import { Form, FormControl, FormLabel, FormField, FormItem, FormMessage, } from "@/components/ui/form"; import { useTransition } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { signUp } from "@/lib/auth-client"; import { redirect } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { SignUpSchema, SignUpValues } from "./validate"; import InputStartIcon from "../components/input-start-icon"; import InputPasswordContainer from "../components/input-password"; import { cn } from "@/lib/utils"; import { AtSign, MailIcon, UserIcon } from "lucide-react"; import { Label } from "@/components/ui/label"; export default function SignUpForm() { const [isPending, startTransition] = useTransition(); const form = useForm<SignUpValues>({ resolver: zodResolver(SignUpSchema), defaultValues: { name: "", email: "", username: "", password: "", confirmPassword: "", }, }); function onSubmit(data: SignUpValues) { startTransition(async () => { console.log("submit data:", data); const response = await signUp.email(data); if (response.error) { console.log("SIGN_UP:", response.error.status); toast.error(response.error.message); } else { redirect("/"); } }); } const getInputClassName = (fieldName: keyof SignUpValues) => cn( form.formState.errors[fieldName] && "border-destructive/80 text-destructive focus-visible:border-destructive/80 focus-visible:ring-destructive/20", ); const genderItems = [ { id: "radio-male", value: "male", label: "Male" }, { id: "radio-female", value: "female", label: "Female" }, ]; return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="z-50 my-8 flex w-full flex-col gap-5" > <FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormControl> <InputStartIcon icon={UserIcon}> <Input placeholder="Name" className={cn("peer ps-9", getInputClassName("name"))} disabled={isPending} {...field} /> </InputStartIcon> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormControl> <InputStartIcon icon={MailIcon}> <Input placeholder="Email" className={cn("peer ps-9", getInputClassName("email"))} disabled={isPending} {...field} /> </InputStartIcon> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="username" render={({ field }) => ( <FormItem> <FormControl> <InputStartIcon icon={AtSign}> <Input placeholder="Username" className={cn("peer ps-9", getInputClassName("username"))} disabled={isPending} {...field} /> </InputStartIcon> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <FormControl> <InputPasswordContainer> <Input className={cn("pe-9", getInputClassName("password"))} placeholder="Password" disabled={isPending} {...field} /> </InputPasswordContainer> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="confirmPassword" render={({ field }) => ( <FormItem> <FormControl> <InputPasswordContainer> <Input className={cn("pe-9", getInputClassName("confirmPassword"))} placeholder="Confirm Password" disabled={isPending} {...field} /> </InputPasswordContainer> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" disabled={isPending} className="mt-5 w-full"> Sign Up </Button> </form> </Form> ); } Untuk validasi, buat file src/app/(auth)/signup/validate.ts, lalu isikan kode ini: import { z } from "zod"; const disallowedUsernamePatterns = [ "admin", "superuser", "superadmin", "root", "cakfan", "withcakfan", ]; export const SignUpSchema = z .object({ email: z .string() .min(1, { message: "Email is required" }) .email({ message: "Invalid email address" }), name: z.string().min(4, { message: "Must be at least 4 characters" }), username: z .string() .min(4, { message: "Must be at least 4 characters" }) .regex(/^[a-zA-Z0-0_-]+$/, "Only letters, numbers, - and _ allowed") .refine( (username) => { for (const pattern of disallowedUsernamePatterns) { if (username.toLowerCase().includes(pattern)) { return false; } } return true; }, { message: "Username contains disallowed words" }, ), password: z.string().min(8, { message: "Must be at least 8 characters", }), confirmPassword: z.string().min(8, { message: "Must be at least 8 characters", }), }) .refine((data) => data.password === data.confirmPassword, { message: "Passwords don't match", path: ["confirmPassword"], }); export type SignUpValues = z.infer<typeof SignUpSchema>; Sekarang buat file src/app/(auth)/components/input-start-icon.tsx: // src/app/(auth)/components/input-start-icon.tsx import { LucideIcon } from "lucide-react"; export default function InputStartIcon({ children, icon: Icon, }: { children: React.ReactNode; icon: LucideIcon; }) { return ( <div className="space-y-2"> <div className="relative"> {children} <div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground/80 peer-disabled:opacity-50"> <Icon size={16} strokeWidth={2} aria-hidden="true" /> </div> </div> </div> ); } Kemudian buat file src/app/(auth)/components/input-password.tsx: // src/app/(auth)/components/input-password.tsx "use client"; import { Eye, EyeOff } from "lucide-react"; import { cloneElement, useState, ReactElement, isValidElement } from "react"; interface InputPasswordContainerProps { children: ReactElement<{ type?: string }>; } export default function InputPasswordContainer({ children, }: InputPasswordContainerProps) { const [isVisible, setIsVisible] = useState(false); const toggleVisibility = () => setIsVisible((prevState) => !prevState); return ( <div className="space-y-2"> <div className="relative"> {isValidElement(children) && cloneElement(children, { type: isVisible ? "text" : "password", })} <button className="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-lg text-muted-foreground/80 outline-offset-2 transition-colors hover:text-foreground focus:z-10 focus-visible:outline-2 focus-visible:outline-ring/70 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" type="button" onClick={toggleVisibility} aria-label={isVisible ? "Hide password" : "Show password"} aria-pressed={isVisible} > {isVisible ? ( <EyeOff size={16} strokeWidth={2} aria-hidden="true" /> ) : ( <Eye size={16} strokeWidth={2} aria-hidden="true" /> )} </button> </div> </div> ); } Buka halaman Sign Up di http://localhost:3000/signup maka tampilan akan seperti berikut: Halaman sign up Halaman Sign In: Akses Buat yang Sudah Terdaftar Setelah bisa daftar, sekarang waktunya pengguna bisa login. Kita akan bikin halaman Sign In yang simpel, modern, dan tentu saja aman pakai Better-Auth. Buat file src/app/(auth)/signin/page.tsx: // src/app/(auth)/signin/page.tsx import { type Metadata } from "next"; import SignInForm from "./form"; import Link from "next/link"; export const metadata: Metadata = { title: "Sign In", }; export default function SignInPage() { return ( <div className="flex min-h-screen w-full flex-col items-center justify-center"> <div className="flex w-full flex-col rounded-2xl border border-foreground/10 px-8 py-5 md:w-96"> <h1 className="text-3xl font-bold mb-2">Sign In</h1> <p> Selamat datang di <strong>BWA Auth</strong> </p> <SignInForm /> <div className="flex items-center justify-center gap-2"> <small>Don't have account?</small> <Link href={"/signup"} className="text-sm font-bold leading-none"> Sign Up </Link> </div> </div> </div> ); } Buat file src/app/(auth)/signin/form.tsx: // src/app/(auth)/signin/form.tsx "use client"; import { Form, FormControl, FormField, FormItem, FormMessage, } from "@/components/ui/form"; import { useTransition } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { signIn } from "@/lib/auth-client"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { SignInSchema, SignInValues } from "./validate"; import InputStartIcon from "../components/input-start-icon"; import InputPasswordContainer from "../components/input-password"; import { cn } from "@/lib/utils"; import { AtSign } from "lucide-react"; export default function SignInForm() { const [isPending, startTransition] = useTransition(); const router = useRouter(); const form = useForm<SignInValues>({ resolver: zodResolver(SignInSchema), defaultValues: { username: "", password: "", }, }); function onSubmit(data: SignInValues) { startTransition(async () => { const response = await signIn.username(data); if (response.error) { console.log("SIGN_IN:", response.error.message); toast.error(response.error.message); } else { router.push("/"); } }); } const getInputClassName = (fieldName: keyof SignInValues) => cn( form.formState.errors[fieldName] && "border-destructive/80 text-destructive focus-visible:border-destructive/80 focus-visible:ring-destructive/20" ); return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="z-50 my-8 flex w-full flex-col gap-5" > <FormField control={form.control} name="username" render={({ field }) => ( <FormItem> <FormControl> <InputStartIcon icon={AtSign}> <Input placeholder="Username" className={cn("peer ps-9", getInputClassName("username"))} disabled={isPending} {...field} /> </InputStartIcon> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <FormControl> <InputPasswordContainer> <Input id="input-23" className={cn("pe-9", getInputClassName("password"))} placeholder="Password" disabled={isPending} {...field} /> </InputPasswordContainer> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" disabled={isPending} className="mt-5 w-full"> Sign In </Button> </form> </Form> ); } Buat file src/app/(auth)/signin/validate.ts: // src/app/(auth)/signin/validate.ts import { z } from "zod"; export const SignInSchema = z.object({ username: z.string().min(4, { message: "Username is required" }), password: z .string() .min(6, { message: "Password lenght at least 6 characters" }), }); export type SignInValues = z.infer<typeof SignInSchema>; Maka tampilan akan seperti ini: Halaman sign in Halaman Dashboard: Hanya user yang bisa lihat Buat file src/app/dashboard/page.tsx: // src/app/dashboard/page.tsx import type { Metadata } from "next"; import { Button } from "@/components/ui/button"; import Link from "next/link"; export const metadata: Metadata = { title: "Dashboard", }; export default function DashboardPage() { return ( <div className="flex flex-col py-5 items-center justify-center"> <div className="w-1/2"> <div className="flex items-center justify-between"> <h1 className="text-2xl font-bold mb-4">Dashboard</h1> <Button> <Link href={"#"}>Tambah</Link> </Button> </div> <p>Hanya dapat diakses oleh user</p> <Link href="/">Beranda</Link> </div> </div> ); } Maka tampilan akan seperti ini: Tampilan dashboard Edit Hompage Buka file src/app/page.tsx lalu ubah jadi seperti berikt: // src/app/page.tsx import { getMe } from "@/actions/user/me"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import SignOutButton from "./(auth)/components/button-signout"; export default async function Home() { const me = await getMe(); return ( <div className="flex flex-col gap-2 min-h-screen items-center justify-center"> <h1 className="text-4xl font-bold">Hello, BuildWithAngga!</h1> <div className="flex items-center gap-2"> <Button> <Link href="/dashboard">Dashboard</Link> </Button> {me ? ( <SignOutButton /> ) : ( <Button variant="secondary"> <Link href="/signin">Sign In</Link> </Button> )} </div> </div> ); } Sekarang buat file src/actions/user/me.ts, isi kode berikut: // src/actions/user/me.ts "use server"; import { db } from "@/db"; import { user, UserType } from "@/db/schema"; import { auth } from "@/lib/auth"; import { eq } from "drizzle-orm"; import { headers } from "next/headers"; export async function getMe(): Promise<UserType | null | undefined> { const session = await auth.api.getSession({ headers: await headers() }); if (!session) { return null; } return (await db.select().from(user).where(eq(user.id, session.user.id)))[0]; } Sekarang buat file src/app(auth)/components/button-signou.tsx, lalu salin kode ini: "use client"; import { useState } from "react"; import { signOut } from "@/lib/auth-client"; import { redirect } from "next/navigation"; import { Button } from "@/components/ui/button"; export default function SignOutButton() { const [isPending, setIsPending] = useState(false); const onSignOut = async () => { setIsPending(true); await signOut({ fetchOptions: { onSuccess: () => { setIsPending(false); redirect("/"); }, }, }); }; return ( <Button disabled={isPending} onClick={onSignOut} variant={"destructive"}> Logout </Button> ); } Buka halaman homepage http://localhost:3000 maka tampilan akan seperti berikut: Tampilan ketika belum sign in Saat kita mengakses halaman http://localhost:3000/dashboard tanpa login, kita akan otomatis dialihkan ke halaman sign in. Sebaliknya, jika sudah login lalu mencoba membuka halaman sign in atau sign up, kita akan langsung diarahkan ke halaman dashboard. Halaman home ketika user sudah sign in 🎉 Penutup Sampai di sini, kita sudah berhasil membangun fondasi aplikasi modern dengan Next.js App Router, Tailwind CSS, Shadcn UI, Supabase, Drizzle ORM, dan Better-Auth. Kita sudah bahas cara: Setup proyek dan install dependency penting,Konfigurasi Supabase dan Drizzle ORM,Setup autentikasi dengan pendekatan yang simpel tapi aman, Dengan tools dan teknik yang sudah kamu pelajari di atas, kamu sekarang punya modal kuat untuk membangun berbagai macam aplikasi web modern — entah itu untuk belajar, portofolio, atau bahkan produk beneran. 🔥 Selanjutnya, kamu bisa eksplorasi lebih jauh: Menambahkan fitur filter dan sorting,Implementasi role-based access control,Atau integrasi payment gateway kalau proyekmu butuh monetisasi. Yang penting: teruslah bereksperimen dan jangan takut untuk mencoba hal baru. Dunia web development berkembang cepat, dan kamu sudah berada di jalur yang tepat. Sampai jumpa di artikel atau tutorial selanjutnya. Semangat berkarya! 🚀✨

Kelas Kesalahan Umum Saat Belajar React JS dan Cara Menghindarinya di BuildWithAngga

Kesalahan Umum Saat Belajar React JS dan Cara Menghindarinya

Bayangin kamu baru mulai belajar ngoding front-end. Setelah coba-coba HTML, CSS, dan JavaScript, kamu mulai kepo sama satu nama yang sering banget muncul di forum, video YouTube, sampai lowongan kerja: React JS. “Katanya sih, belajar React itu wajib kalau mau jadi front-end developer masa kini.” Dan emang bener, React bukan cuma populer—dia raja-nya library front-end yang dipakai banyak perusahaan besar kayak Facebook, Netflix, sampe Tokopedia. UI yang interaktif, cepat, dan modular—semua bisa dibuat dengan React. Gak heran kalau banyak pemula langsung lompat belajar React dengan semangat 45. Tapi di sinilah banyak orang mulai salah langkah. Karena semangatnya tinggi, kadang kita suka lewat jalur tikus—ngoding langsung tanpa ngerti konsep dasar. Akibatnya? Error gak jelas, komponen berantakan, dan akhirnya frustrasi sendiri. Nah, biar kamu gak ngalamin drama yang sama, di artikel ini kita bakal bahas kesalahan umum saat belajar React JS, lengkap dengan cara menghindarinya. Supaya proses belajarmu gak cuma cepat, tapi juga lebih seru dan gakk penuh stres. Siap? Yuk lanjut! 💻🔥 Kesalahan Umum Saat Belajar React JS (dan Cara Menghindarinya) Belajar React itu kayak naik roller coaster — seru, menegangkan, tapi kadang bikin mual juga kalau nggak siap. Banyak orang, termasuk saya dulu, datang ke React dengan ekspektasi tinggi: “Dengan React, gue bisa bikin aplikasi kece kayak Instagram!” Tapi kenyataannya? Baru nulis <div>Hello World</div> aja, udah bingung: Ini kenapa JSX mirip HTML tapi gak sepenuhnya HTML?Kenapa error cuma karena lupa return?Kenapa useEffect jalan terus-terusan kayak setan? Jangan khawatir. Semua yang belajar React pasti pernah ngerasain masa-masa frustrasi itu. Dan kebanyakan penyebabnya bukan karena kamu bodoh atau gak bakat ngoding — tapi karena ada pola kesalahan umum yang sering banget diulang para pemula. Makanya, di bagian ini, kita bakal bahas satu per satu kesalahan yang sering dilakukan saat belajar React JS — bukan buat nyalahin, tapi biar kamu bisa belajar dari kesalahan orang lain, dan gak harus jatuh di lubang yang sama. Setiap poin juga bakal dilengkapi dengan cara menghindarinya, jadi kamu bisa belajar React dengan lebih tenang dan percaya diri. Siap? Yuk kita mulai dari akar masalah paling sering: kurang kuat di dasar JavaScript. 1. Mengabaikan Dasar-Dasar JavaScript Mengabaikan Dasar-Dasar JavaScript Image by Freepik Ceritanya gini… Kamu baru kenal React, terus liat tutorial di YouTube. Judulnya catchy: “Bikin Aplikasi Todo List dengan React dalam 15 Menit!”. Kamu langsung klik, buka VSCode, dan mulai ngikutin step by step. Lalu di tengah jalan, muncul baris kayak gini: const { title, description } = props; Dan kamu mikir: “Eh… ini kenapa ada kurung kurawal di dalam const? Ini maksudnya apaan sih?” Lanjut lagi… const handleClick = () => { console.log("Clicked!"); }; Kamu mulai bingung lagi. “Kok function-nya gak pake function? Trus kenapa ada => kayak gitu?” Dan gak lama kemudian kamu menemukan ..., map, filter, dan hal-hal aneh lainnya yng belum pernah kamu lihat sebelumnya waktu belajar JavaScript dasar. 😬 Di sinilah banyak orang kepleset Banyak yang ngira: “React itu library, jadi gak perlu jago JavaScript buat mulai.” Padahal kenyataannya, React itu cuma cara lain untuk menulis JavaScript. Kalau dasarnya belum kuat, kamu bakal kebingungan terus tiap lihat syntax yang “kelihatannya React banget”, padahal itu murni fitur JavaScript modern—alias ES6 dan teman-temannya. Contohnya: Destructuring: bikin kode lebih singkat dan rapi.Arrow function: berguna banget di event handler.Spread & rest operator: wajib saat update state atau merge props.Array methods kayak map, filter, reduce: sering banget dipake buat render list. Kalau kamu belum paham fitur-fitur itu, belajar React bisa terasa kayak masuk hutan tanpa kompas. Setiap error yang muncul bakal bikin kamu panik, padahal cuma karena kamu belum kenal syntax-nya aja. ✅ Cara Menghindarinya Jangan buru-buru masuk ke React. Serius. Luangkan waktu buat benar-benar ngerti JavaScript dulu. Enggak harus jadi master, tapi pastikan kamu paham: Cara kerja function dan scopeBedanya var, let, constCara pakai arrow functionGimana destructuring itu bisa menyederhanakan kodeGunanya spread (...) dan rest (...)Perbedaan antara array vs objectCara kerja callback dan promisePaling penting: array methods kayak map(), filter(), find() Kamu bisa cari playlist “Modern JavaScript” atau “JavaScript ES6+” di BuildWithAngga, atau buka javascript.info buat belajar dari dokumentasi yang jelas dan step-by-step. 🧩 Ingat… React itu ibarat alat tukang. Tapi JavaScript? Itu fondasi rumahnya. Kalau fondasinya rapuh, secanggih apapun alatnya, rumahmu tetap gampang roboh. Jadi, jangan skip dasar. Pelan-pelan aja. Yang penting paham. 💪 📘 Mini Roadmap JavaScript ES6+ untuk Belajar React Belajar JavaScript modern itu bukan soal hapalan, tapi soal paham kenapa dan kapan dipakai — apalagi kalau tujuannya buat React. 🪴 1. Variabel dan Scope Kenapa penting? React sangt bergantung pada variabel yang predictable. 📌 Pelajari: var, let, const → kapan pakai let vs constBlock scope vs function scopeHoisting (biar paham kenapa kadang error undefined) 🧱 2. Arrow Function (=>) Kenapa penting? Arrow function sering dipakai buat event handler dan callback di React. 📌 Pelajari: Sintaks dasar arrow functionPerbedaan this di arrow function vs function biasaReturn implisit vs eksplisit 🧩 3. Destructuring Kenapa penting? Sering banget dipakai buat ambil props, state, dan array dalam React. 📌 Pelajari: Destructuring object: const { name, age } = user;Destructuring array: const [count, setCount] = useState(0); 📦 4. Spread dan Rest Operator (...) Kenapa penting? Wajib untuk update state tanpa mutate langsung (immutable). 📌 Pelajari: Spread object/array: const newArray = [...oldArray]; const newObj = { ...oldObj, name: "Updated" };Rest parameter: const sum = (...args) => args.reduce((a, b) => a + b); 🔁 5. Array Methods Kenapa penting? React serign render list pakai map(), dan manipulasi array lainnya. 📌 Fokus ke: map() → buat list komponenfilter() → menyaring datafind() → mencari item spesifikforEach() → iterasi non-returnsome() dan every() → kondisi data 📜 6. Template Literals Kenapa penting? Buat menyusun string lebih rapi, terutama di JSX atau className dinamis. 📌 Pelajari: const greeting = `Hello, ${name}!`; 🔒 7. Object & Array Manipulation Kenapa penting? State management di React sering melibattkan update object/array. 📌 Pelajari: Menyalin array/object tanpa mutasiMenambah, menghapus, dan mengubah isi array/objectObject.keys(), Object.values(), Object.entries() 🧠 8. Function & Callback Kenapa penting? React heavily depends on passing function as props (event handlers). 📌 Pelajari: Function declaration vs expression vs arrow functionCallback & higher-order functionClosures (optional tapi bagus dipahami) ⏳ 9. Promise dan Async/Await Kenapa penting? Berguna saat ambil data dari API (misal di useEffect atau fetch). 📌 Pelajari: Cara kerja promise dan chaining (.then, .catch)Penulisan async & awaitError handling ✨ Bonus: Optional Chaining & Nullish Coalescing Kenapa pentig? Menghindari error saat akses properti nested yang belum tentu ada. 📌 Pelajari: const name = user?.profile?.name ?? "Guest"; 📌 Tips Belajar Jangan buru-buru! Kuasai per topik dulu, baru lanjut.Bikin mini project kecil per topik, misalnya:Buat function filterData() sendiriRender list pakai map() di HTML biasaUbah object dengan spread dan log hasilnyaGunakan playground seperti CodeSandbox atau JSFiddle untuk latihan ringan. 2. Terlalu Fokus pada Framework, Lupa Konsep Terlalu Fokus pada Framework, Lupa Konsep Image by Freepik Waktu pertama kali belajar React, rasanya pegen langsung "gaspol". Begitu tahu ada useEffect, langsung dipakai. Begitu tahu ada useState, langsung klik-klik tutorial dan copas. Pokoknya asal jalan. Tapi kemudian mulai muncul tanda-tanda: Komponen ngerender berkali-kali dan gak tahu kenapa.Data dari API munculnya telat, terus error.State berubah tapi UI gak update.Atau lebih parah: infinite loop dari useEffect. Dan pas ditanya, “Kenapa kamu pakai useEffect di situ?” Jawabannya: “Soalnya di tutorial begitu, kak 😅.” 🤯 Kenapa Ini Bisa Terjadi? Banyak pemula dan saya dulu juga gitu terlalu fokus ke fitur React, tapi lupa konsep dasarnya. Maksudnya gini: kamu tau useEffect, tapi gak ngerti bahwa itu sebenarnya mirip dengan componentDidMount, componentDidUpdate, atau componentWillUnmount di React versi class component (alias: lifecycle method). Kamu tau useState, tapi gak ngerti bahwa setiap kali setState() dipanggil, React akan re-render komponen. Kamu tau bikin komponen, tapi gak ngerti bedanya controlled vs uncontrolled component. Jadi, karena gak paham “dalemannya”, akhirnya React jadi kayak kotak hitam — cuma bisa dipakai kalau niru. Tapi pas error muncul, bingung sendiri. 🧠 Gimana Cara Menghindari Ini? Belajar Konsep, Bukan Cuma Sintaks Sebelum pakai useEffect, cari tahu dulu: Apa fungsinya? Kapan dia dijalankan? Apa yang terjadi kalau depedency array-nya kosong? Kapan dia bakal dipanggil ulang? Contoh kecil: useEffect(() => { console.log("Komponen mount"); return () => { console.log("Komponen unmount"); }; }, []); Kalau gak ngerti konsep mounting dan unmounting, kode di atas bakal keliatan magis. Jangan Skip Pengetahuan Class Component Meskipun sekarang React pakai functional component + hooks, konsep dasarnya tetap berasal dari class component. Coba baca sekilas soal: componentDidMountcomponentDidUpdatecomponentWillUnmount Setelah itu, kamu bakal ngerti bahwa useEffect adalah cara functional component untuk menjalankan kode lifecycle tadi. Visualisasikan Proses Render React React itu kayak puzzle yang jalan dari atas ke bawah: Komponen direnderState atau props berubah → re-renderEffect dijalankan sesuai dependency Dengan visualisasi ini, kamu akan lebih hati-hati: Kenapa ini rerender terus?Kenapa efek ini jalan terus padahal aku gak mau? Tanya Diri Sendiri: “Kenapa aku pakai hook ini?” Setiap kali kamu nulis useEffect, useState, useRef, dll, coba stop sejenak dan tanya: "Apa yang ingin aku capai?Dan kenapa harus pakai hook ini?" Kalau kamu belum bisa jawab dengan yakin, mungkin kamu belum butuh hook itu. Atau kamu perlu eksplorasi konsepnya dulu. 🎯 Intinya? React itu bukan sekadar kumpulan hook dan component. Dia punya mekanisme internal yang keren banget. Tapi supaya kamu bisa maksimalkan kekuatannya, kamu harus paham "kenapa", bukan cuma "bagaimana". Framework boleh keren, tapi konsep lebih penting daripada fitur. Kalau konsepnya udah kuat, kamu gak cuma jadi pengguna React — kamu jadi developer yang ngerti React. 3. Tidak Memahami State dan Props dengan Benar Tidak Memahami State dan Props dengan Benar Image by Freepik Waktu awal belajar React, kita sering dengar istilah "state" dan "props". Awalnya kelihatan mirip, dan tutorial pun sering pakai keduanya tampa terlalu banyak penjelasan. Sampai akhirnya terjadi hal seperti ini: 😅 "Kak, kenapa aku gak bisa ganti nilai props ini ya?"😓 "Kenapa pas aku ubah state, datanya malah hilang semua?"🤯 "Kok data anaknya gak sinkron sama induknya?" Yup, itu semua adalah gejala dari belum paham benar perbedaan state dan props. 🧠 Bedanya State dan Props Biar gampang, coba bayangin komponen React kayak sebuah mesin kecil. State = memori internal mesin. Bisa berubah seiring waktu. ➤ Kita ubah pakai setState() atau useState().Props = seperti input dari luar. ➤ Gak bisa diubah dari dalam, cuma bisa diterima. Contoh analoginya: Kamu bikin komponen <KartuNama nama="Taufan" />Maka nama itu props — kamu kirim dari luar.Tapi misalnya komponen KartuNama punya fitur toggle untuk tampilkan/semmbunyikan detail, maka isVisible adalah state — karena disimpan dan diubah dari dalam. 💣 Kesalahan Umum yang Sering Terjadi Mencoba Mengubah Props Langsung function Komponen(props) { props.nama = "Ubah Nama"; // ❌ error! } Props itu readonly alias hanya bisa dibaca, gak boleh diubah dari dalam komponen. Kalau kamu ingin ubah data yang dikirim lewat props, kamu harus minta parent component-nya yang ubah. Semua Disimpan di State, Padahal Gak Perlu Kadang ada yang overthinking dan menyimpan data tetap ke dalam useState, padahal gak akan berubah. const [judul, setJudul] = useState("Artikel React"); // ❌ gak perlu state Kalau nilainya gak pernah berubah, cukup pakai variable biasa. Tidak Menyadari Alur Data Mengalir dari Atas ke Bawah (Top → Down) Data dari parent component mengalir ke child lewat props. Tapi kadang pemula berharap bisa "naik" atau ubah data dari bawah ke atas tanpa mekanisme khusus. Contoh: // Parent <Post judul="Belajar React" /> // Child function Post(props) { props.judul = "Judul Baru"; // ❌ Props gak boleh diubah } Solusinya? Harus pakai callback function via props untuk "naik" ke parent. ✅ Cara Menghindarinya Pahami Alur Data React React bekerja secara unidirectional data flow: Dari atas (parent) → ke bawah (child) Belajarlah untuk mengendalikan alur data lewat props dan mengubah data melalui state atau event handler di parent. Latihan Bikin Komponen Sederhana Coba bikin komponen seperti ini: Counter: dengan tombol + dan -Toggle text: klik tombol untuk tampilkan/sembunyikan teksForm input: dan tampilkan data yang diketik Lewat latihan kecil ini, kamu akan terbiasa: Kapan harus pakai stateKapan harus kirim data lewat propsBagaimana cara komunikasi antara parent dan child Jangan Asal useState, Tanya Dulu: “Apakah data ini perlu diubah di dalam komponen?”Jika iya → pakai stateJika tidak → cukup lewat props atau variable biasa ✨ Kesimpulan State dan props itu ibarat jantung dan pembuluh darahnya React. Kalau gak paham cara kerjanya, aplikasi kamu bisa bingung alur datanya, sulit di-debug, atau malah gak jalan seperti yang diharapkan. Dengan latihan dan pemahaman konsep, kamu akan mulai "merasakan" alur data React dan tahu harus ngapain ketika butuh simpan atau kirim data antar komponen. 4. Menulis Komponen Tanpa Struktur atau Konsistensi Menulis Komponen Tanpa Struktur atau Konsistensi Image by Freepik Waktu pertama kali belajar React, kita biasanya semangat banget. Langsung bikin komponen besar… semua logic, tampilan, dan styling disatuin. Hasilnya? 🎩 Komponen 500 baris, isinya campur aduk.🤯 Susah dipahami.🛠️ Mau edit dikit, takut rusak semua. Kalau kamu pernah bikin komponen <Dashboard> yang isinya: sidebar, navbar, konten utama, tabel, dan modal dalam satu file... berarti kamu pernah melewati fase ini 😅 🧱 Masalah Umum: Komponen Gede dan Acak-Acakan Beberapa masalah yang sering terjadi: Semua komponen ditaruh di folder components/ tanpa aturan.Komponen kecil dan besar campur di satu file.Tidak ada pemisahan antara tampilan, logika, dan utilitas.Tidak konsisten dalam penamaan dan struktur. Hasilnya? Saat proyek makin besar, kita jadi bingung sendiri: “Tombol ini ada di mana ya? Kok form-nya rusak pas diganti?” 🧩 Contoh: Komponen Tanpa Struktur // Dashboard.js function Dashboard() { const [users, setUsers] = useState([]); const [isOpen, setIsOpen] = useState(false); useEffect(() => { fetchUsers(); }, []); return ( <div> <h1>Dashboard</h1> <button onClick={() => setIsOpen(true)}>Add User</button> {isOpen && ( <div className="modal"> <form>...</form> </div> )} <table>...</table> </div> ); } Kelihatannya masih oke… tapi bayangin kalau file ini tumbuh jadi 700 baris. 😵 ✅ Cara Menghindari: Terapkan Struktur yang Jelas Kamu bisa mulai dari struktur sederhana tapi rapi, misalnya pakai pendekatan Atomic Design atau minimal punya pemisahan seperti: components/ ├── atoms/ │ └── Button.jsx ├── molecules/ │ └── FormGroup.jsx ├── organisms/ │ └── UserForm.jsx ├── templates/ │ └── DashboardLayout.jsx pages/ └── dashboard.jsx Atomic Design membagi komponen berdasarkan level kompleksitas: Atoms = komponen paling kecil, seperti <Button>, <Input>Molecules = gabungan beberapa atom, seperti <FormGroup>Organisms = bagian kompleks, seperti <UserForm>Templates = layout halamanPages = file halaman utama 💡 Alternatif: Struktur Folder Berdasarkan Fitur Kalau proyek kamu sudah cukup besar dan fokus pada fitur, kamu bisa pakai pendekatan feature-based folder: features/ └── users/ ├── components/ │ └── UserTable.jsx ├── hooks/ │ └── useUsers.js ├── api.js └── index.jsx Pendekatan ini memudahkan kamu mengelola logika dan UI per fitur — cocok banget buat tim yang kerja bareng. 🧰 Tips Tambahan untuk Konsistensi Gunakan konvensi penamaan yang sama (misal: PascalCase untuk komponen, camelCase untuk function)Pisahkan styling (CSS, Tailwind, atau CSS-in-JS) agar tidak campur aduk dengan logicPisahkan helper function ke folder utils/ atau lib/Bikin komponen reusable kalau muncul lebih dari 1x ✨ Kesimpulan React memang fleksibel, tapi tanpa struktur dan konsistensi, proyek bisa jadi monster yang sulit dikendalikan. Dengan menerapkan struktur folder yang rapi, membagi komponen berdasarkan tanggung jawabnya, dan menjaga konsistensi penulisan, kamu akan lebih mudah scaling, debugging, bahkan berkolaborasi bareng tim. Ingat: “Code is read more than it is written.” Jadi bikinlah struktur yang bikin kamu (dan orang lain) betah baca dan ngoprek. 5. Salah Menggunakan useEffect Salah Menggunakan useEffect Image by Freepik Di antara semua hook yang disediakan React, mungkin useEffect adalah yang paling banyak bikin kepala pusing — terutama saat masih awal belajar. Masalahnya bukan karena useEffect itu susah, tapi karena kita belum benar-benar memahami kapan dan bagaimana dia bekerja. 🌀 Contoh Kesalahan Umum: Infinite Loop Tanpa Sadar Coba kamu lihat kode ini: import { useState, useEffect } from "react"; function Example() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }, [count]); return <p>Count: {count}</p>; } Kelihatannya simpel ya? Tapi pas dijalankan... 🚨 “Why is my app crashing?”💥 Karena ini bikin infinite loop! Kenapa? Efek jalan setiap kali count berubah.Di dalam efek, kamu memanggil setCount(), yang bikin count berubah lagi.Karena count berubah → useEffect jalan lagi.Balik lagi ke step 2. 🎢 Selamat datang di loop tak berujung. 🧠 Penyebab Umum Kesalahan dengan useEffect Dependency array tidak sesuaiTerlalu banyak atau terlalu sedikit dependensi bisa bikin efek jalan terus-menerus atau malah nggak jalan sama sekali.Memasukkan fungsi yang berubah-ubah (non-stable) ke dependencyMisalnya, bikin fungsi inline di dalam komponen, lalu masukin ke dependency. Ini bisa bikin useEffect jalan terus setiap render.Mengupdate state dari dalam efek tanpa kondisi yang jelasKalau kamu ngubah state di dalam efek, pastikan itu tidak memicu efek yang sama berulang kali. 🛠️ Cara Menghindari Kesalahan di useEffect ✅ Pahami Kapan useEffect Dipanggil Tanpa dependency array → dijalankan setiap render.Dependency array kosong [] → dijalankan sekali saat mount.Dengan [someVar] → dijalankan saat someVar berubah. ✅ Gunakan console.log untuk debug Contoh: useEffect(() => { console.log("Effect jalan karena count berubah:", count); }, [count]); Ini bisa bantu kamu lihat kapan dan kenapa efek dijalankan. ✅ Jangan Update State Secara Langsung Tanpa Kondisi Kalau butuh update state dalam useEffect, pastikan kamu memberi kondisi eksplisit: useEffect(() => { if (count < 5) { setCount(count + 1); } }, [count]); Ini mencegah efek jalan terus. ✅ Simpan fungsi di luar useEffect kalau tidak perlu berubah-ubah Contoh salah: useEffect(() => { const doSomething = () => console.log("doing..."); doSomething(); }, [doSomething]); // padahal setiap render, fungsi berubah Solusi: Pindahkan doSomething ke luar useEffect atau gunakan useCallback. 🧪 Studi Kasus: Fetch Data useEffect(() => { const fetchData = async () => { const res = await fetch("/api/data"); const data = await res.json(); setData(data); }; fetchData(); }, []); ✅ Ini contoh yang benar: efek hanya jalan sekali saat komponen mount, dan setData() tidak memicu efek baru karena dependensinya kosong. ✅ Solusi: Pahami Kapan dan Bagaimana useEffect Digunakan dengan Benar Oke, jadi gimana sih caranya biar nggak “terjebak nostalgia” alias infinite loop terus saat pakai useEffect? Jawabannya sederhana: pahami kapan dan bagaimana useEffect itu dijalankan. useEffect adalah semacam "asisten pribadi" yang siap ngerjain sesuatu buatmu setiap kali terjadi perubahan tertentu di komponen. Tapi kamu harus ngasih tahu: kapan dia harus kerja, dan kapan cukup diam aja. 🔍 1. Kenali 3 Pola Dasar Penggunaan useEffect 🟦 A. Tanpa Dependency Array useEffect(() => { console.log("Selalu jalan setiap render"); }); Efek akan dijalankan setiap kali komponen dirender ulang.Jarang dibutuhkan, kecuali kamu memang pengen efek itu selalu jalan terus. 🟩 B. Dengan Dependency Kosong [] useEffect(() => { console.log("Cuma jalan sekali (saat mount)"); }, []); Cocok untuk efek yang hanya perlu jalan sekali, seperti fetch data awal, set event listener, dll. 🟨 C. Dengan Dependency Spesifik [something] useEffect(() => { console.log("Jalan saat something berubah"); }, [something]); Efek hanya akan dijalankan saat nilai dalam dependency array berubah.Ini yang paling sering dipakai dalam praktik — dan juga paling rawan kesalahan kalau tidak hati-hati. 🧠 2. Pahami Alur Hidup Komponen (Lifecycle) Versi Function Component useEffect itu mirip gabungan antara componentDidMount, componentDidUpdate, dan componentWillUnmount di React versi class. Contoh: useEffect(() => { console.log("Mount atau update"); return () => { console.log("Cleanup: sebelum unmount atau sebelum efek jalan ulang"); }; }, [data]); Di awal: jalan saat komponen pertama kali dimount.Setiap data berubah: efek lama dibersihkan (cleanup), lalu efek baru dijalankan.Saat unmount: efek dibersihkan satu kali terakhir. 🛡️ 3. Tips Aman Gunakan useEffect 💡 Selalu pikirkan apakah efek ini perlu jalan setiap render, hanya sekali, atau hanya saat sesuatu berubah.🧪 Gunakan console.log() atau React DevTools untuk melacak render dan efek.🧠 Kalau kamu update state di dalam useEffect, pastikan efek itu tidak tergantung state tersebut secara langsung (tanpa kontrol).🔁 Hati-hati dengan fungsi inline dalam dependency, karena mereka dianggap “baru” di setiap render. ✍️ Rangkuman Gampangnya Kalau kamu pakai useEffect tapi hasilnya aneh:Cek dependency array-nya.Kalau dia kosong tapi efek jalan berkali-kali → mungkin ada Strict Mode.Kalau dia isi [state] dan efek update state itu → siap-siap infinite loop.Solusinya? Tambahin kondisi, atau ubah logika update-nya. ✨ Penutup useEffect itu powerful, tapi juga sensitif. Dia bekerja setiap kali ada perubahan dependensi, jadi kamu harus hati-hati banget dengan apa yang kamu masukkan di dalam []. Cara terbaik menghindari jebakan useEffect adalah paham konsep re-render dan dependency, bukan sekadar copy-paste dari StackOverflow. Kalau kamu belajar cara kerjanya dari balik layar, kamu akan lebih percaya diri saat nulis efek-efek kompleks nantinya. 6. Mengabaikan Key Saat Render List Mengabaikan Key Saat Render List Image by Freepik Kamu mungkin pernah lihat kode seperti ini waktu belajar React: {data.map((item) => ( <li>{item.name}</li> ))} Terlihat sederhana, ya? Tapi tunggu dulu — di balik layar, ini bisa bikin React sedikit "bingung" saat harus mengatur ulang elemen-elemen tersebut di DOM. Masalahnya terletak pada tidak adanya key. 🤔 Kenapa key Itu Penting? React menggunakan key sebagai identitas unik untuk setiap elemen dalam list. Ini penting banget saat: Data berubahElemen ditambah/dihapusAtau posisi elemen berubah Tanpa key, React nggak tahu mana item lama dan mana yang baru. Jadi, dia akan merender ulang semuanya dari nol, bahkan ketika kamu hanya mengubah satu elemen kecil. Ini bisa bikin performa drop dan, yang paling sering terjadi: UI jadi aneh, seperti input yang ter-reset, animasi glitch, dan lainnya. 💥 Contoh Masalah Tanpa Key Misalnya kamu punya form dinamis: {fields.map((field) => ( <input value={field.value} onChange={...} /> ))} Kalau kamu gak pakai key, dan user hapus satu field di tengah-tengah, React bakal salah deteksi posisi input — dan nilai input bisa tiba-tiba pindah ke field lain. Auto bikin frustasi! 😩 ✅ Cara Pakai Key dengan Benar Gunakan key yang: Unik untuk setiap itemKonsisten (tidak berubah-ubah) {users.map((user) => ( <li key={user.id}>{user.name}</li> ))} Kalau kamu pakai data dari database, ID biasanya sudah cukup bagus. 🚫 Jangan Gunakan Index Sebagai Key (Kalau Bisa) {items.map((item, index) => ( <li key={index}>{item}</li> ))} Kenapa? Karena kalau urutan list berubah, index akan berubah juga — dan ini menghancurkan performa serta merusak logika React. Gunakan index hanya kalau: Daftarnya statis (tidak bisa diubah)Tidak bisa punya ID unik (misalnya array hasil input user manual) ✨ Bonus Tips Kalau kamu pakai map() untuk render elemen list, ingat: wajib kasih key.Kalau kamu pakai komponen <TransitionGroup> atau animasi, key yang salah akan bikin animasi jadi kacau. 📝 Kesimpulan key itu kayak KTP untuk elemen di list — bantu React mengenali siapa-siapa yang masih sama, siapa yang baru, dan siapa yang udah nggak ada. Tanpa key yang bener, React akan nebak-nebak, dan tebakannya bisa salah. Hasilnya? UI kamu bisa error, performa jadi lambat, dan kamu jadi bingung sendiri. Jadi, next time kamu pakai .map() — jangan lupa kasih key, ya! 💡 7. Tidak Menangani Error dan Loading State Tidak Menangani Error dan Loading State Image by Freepik Bayangin kamu lagi buka aplikasi cuaca buat cek apakah perlu bawa payung hari ini. Tapi pas klik tombolnya... nggak ada apa-apa yang muncul. Diam. Sunyi. Padahal di balik layar, aplikasi lagi fetching data dari API. Nah, itulah kenapa handling loading dan error state itu penting banget. Tanpa ini, pengguna bakal merasa aplikasi “gagal” atau “bermasalah”, padahal kodenya cuma belum kasih tahu apa yang lagi terjadi. 💢 Contoh Kesalahan Umum Misalnya kamu bikin fetch data begini: useEffect(() => { fetch("<https://api.example.com/data>") .then((res) => res.json()) .then((data) => setData(data)); }, []); Selesai? Belum! 🚨 Kalau koneksi lambat? Nggak ada loading spinner. Kalau API error? Pengguna malah bengong ngeliat layar kosong. 🤕 Dampaknya? UX jelek → Pengguna bingung, ngira aplikasi nge-hang.Debugging susah → Kamu juga nggak tahu kalau ternyata API error.Aplikasi nggak kelihatan profesional. ✅ Solusi: Tambahkan Error & Loading State Yuk ubah contoh tadi jadi lebih manusiawi: const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const res = await fetch("<https://api.example.com/data>"); if (!res.ok) throw new Error("Gagal mengambil data"); const json = await res.json(); setData(json); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchData(); }, []); Dan di render-nya: if (loading) return <p>Loading data...</p>; if (error) return <p>Error: {error}</p>; return <DisplayData data={data} />; 🧠 Penjelasan: setLoading(true) → kasih tahu aplikasi bahwa kita mulai fetch data.try/catch → kalau ada error (misal: server down, salah URL, dsb), kita bisa tangani dengan baik.finally → bagian ini akan selalu dijalankan, entah berhasil atau gagal, jadi aman buat setLoading(false). ✨ Bonus Tips Kamu bisa bikin komponen <Loader /> atau <ErrorMessage /> agar bisa digunakan ulang di banyak tempat.Gunakan indikator visual yang familiar, seperti spinner, skeleton loader, atau teks sederhana.Untuk UX yang lebih baik, hindari tampilan loading kosong lebih dari 3 detik tanpa feedback apapun. 📝 Kesimpulan React bukan sulap.Kalau kamu nggak bilang “lagi loading”, dia nggak akan tahu.Kalau kamu nggak tangkap error-nya, dia nggak akan kasih peringatan. Sebagai developer, tugas kita bukan cuma bikin kode yang jalan, tapi juga bikin aplikasi yang nyaman dan terduga. Jadi, jangan skip handling loading & error ya! Kesimpulan Belajar React itu nggak instan. Nggak ada yang langsung jago setelah satu malam ngoding—bahkan developer berpengalaman pun masih suka buka dokumentasi dan debugging berjam-jam hanya untuk satu bug kecil. 😅 Tapi justru di situlah serunya. React ngajarin kita untuk berpikir dalam komponen, memahami alur data, dan lebih sabar menghadapi error. Dan seperti yang udah kita bahas tadi, ada beberapa jebakan yang sering bikin frustrasi di awal—dari lupa dasar JavaScript, terlalu fokus pada tools, sampai nggak nanganin loading state. Semua itu bukan tanda kamu gagal, tapi bagian alami dari proses belajar. Dengan tahu kesalahan-kesalahan umum ini, kamu udah selangkah lebih siap daripada kebanyakan orang. Kamu bisa ngelangkah lebih cepat, lebih mantap, dan lebih tenang. Nggak lagi panik pas useEffect bikin infinite loop, atau bingung kenapa props nggak bisa diubah. 💡 Penutup: Jangan Takut Salah React itu luas, dan kamu akan terus belajar seiring waktu. Jangan minder kalau kadang masih harus googling hal-hal “sepele”. Yang penting bukan soal salah atau bener, tapi soal mau belajar dan terus memperbaiki diri. “Jangan takut salah, asal tahu cara memperbaikinya.” Simpan kalimat itu baik-baik. Karena dalam dunia React — dan dunia coding pada umumnya — itulah skill paling berharga yang bisa kamu punya. Semangat terus belajar! 💪🚀

Kelas Cara Membuat Form Sederhana di React + Validasi Dasar di BuildWithAngga

Cara Membuat Form Sederhana di React + Validasi Dasar

Formulir adalah salah satu komponen yang paling sering kita temui di sebuah website. Entah itu form pendaftaran, login, komentar, atau sekadar form kontak sederhana — hampir semua aplikasi web butuh yang namanya form. Tapi meskipun terlihat sepele, form bisa jadi momok yng bikin pusing kepala, apalagi kalau sudah bicara soal validasi data. Mulai dari "harus diisi", "format email salah", sampai "password minimal 8 karakter" — semua itu perlu dicek biar data yang masuk ke sistem bersih dan valid. Kalau tidak, bisa kacau urusannya: data berantakan, user bingung, dan kita sebagai developer yang kena getahnya 😅 Nah, di artikel ini, kita akan bahas cara membuat form sederhana di React menggunakan Next.js 15, versi terbaru yang sudah memakai App Router dan punya struktur file modern. Selain itu, kita juga akan pakai Zod untuk validasi — ini adalah library validasi schema-based yang simpel tapi powerfull banget. Dan supaya proses input jadi lebih smooth, kita juga akan gabungkan dengan React Hook Form, salah satu library form handling terbaik di dunia React. Kenapa Zod? Karena validasi dengan Zod itu: Gampang ditulisJelas error-nyaBisa dipakai bareng React Hook Form dengan integrasi super mudah Jadi, kalau kamu: Masih baru di dunia React atau Next.jsSering bingung gimana cara validasi input form yang rapiPengen bikin form yang sederhana tapi tetap profesional ...artikel ini cocok banget buat kamu. Kita akan mulai dari nol — setup project, bikin UI form sederhana, pasang validasi dasar, sampai submit dan menampilkan data yang sudah diverifikasi. Tenang aja, semuanya akan dijelaskan pelan-pelan dan step by step. Yuk, kita langsung mulai dari instalasi proyek Next.js-nya! 🚀 Persiapan Proyek Sebelum kita mulai ngoding form dan validasinya, tentu kita harus siapin dulu proyek Next.js-nya. Di sini kita pakai Next.js 15 — versi terbaru yang sudah full power dengan App Router, Server Actions, dan banyak fitur keren lainnya. Kita juga bakal pakai TypeScript biar kodenya lebih aman dan terstruktur. Inisialisasi Proyek Next.js 15 Pertama, buka terminal (atau Command Prompt kalau kamu pakai Windows), lalu jalankan perintah berikut: bunx create-next-app@latest bwa-form Nanti kamu akan ditanya beberapa hal. Jawaban yang disarankan: ✔ Would you like to use TypeScript? › Yes ✔ Would you like to use ESLint? › Yes ✔ Would you like to use Tailwind CSS? › Yes (Opsional, tapi bikin form lebih cakep) ✔ Would you like to use `src/` directory? › Yes ✔ Would you like to use App Router? › Yes ✔ Would you like to use Turbopack for `next dev`? › Yes ✔ Would you like to customize the default import alias (@/*)? › No Setelah selesai, masuk ke folder proyek: cd bwa-form Lalu jalankan server: bun run dev Kalau semua berjalan lancar, kamu bisa buka browser dan akses http://localhost:3000 untuk melihat project Next.js kamu tampil dengan halaman default. Tampilan awal Next.js 15 Install Zod + React Hook Form Sekarang kita install library yang dibutuhkan untuk validasi form dan pengelolaan input: bun add zod react-hook-form @hookform/resolvers Penjelasan singkat: zod: untuk bikin schema validasi yang readable.react-hook-form: buat ngatur input form, handle submit, dan baca error dengan efisien.@hookform/resolvers: ini semacam "jembatan" antara Zod dan React Hook Form, jadi validasi Zod bisa langsung dipakai di form React. Membuat Form Sederhana Sekarang kita akan membuat sebuah form sederhana yang terdiri dari dua input: NamaEmail dan satu tombol submit. Belum ada validasi dulu ya, kita fokus ke tampilan dan logika submit dasar dulu. Validasi Zod-nya akan kita bahas di bagian berikutnya. Buat Halaman Register Buat file page.tsx pada direktori src/app/register/page.tsx seperti pada gambar berikut: Buat halaman register Ubah kode jadi seperti berikut ini: import RegisterForm from "@/components/register-form"; function RegisterPage() { return ( <main className="flex items-center justify-center min-h-screen"> <div className="flex flex-col gap-4 w-1/4"> <h1 className="text-2xl font-bold">Register</h1> <RegisterForm /> </div> </main> ); } export default RegisterPage; Kemdian buat file register-form.tsx padaa direktori src/components. Kalo direktorinya nggak ada, tinggal buat aja. Buat komponen form register dan tambahkan kode berikut: "use client"; import { useState } from "react"; const RegisterForm = () => { const [result, setResult] = useState(""); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const name = formData.get("name"); const email = formData.get("email"); setResult(`Nama: ${name}, Email: ${email}`); }; return ( <> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="name" className="block font-medium"> Nama </label> <input id="name" name="name" type="text" className="w-full border border-gray-300 p-2 rounded" /> </div> <div> <label htmlFor="email" className="block font-medium"> Email </label> <input id="email" name="email" type="email" className="w-full border border-gray-300 p-2 rounded" /> </div> <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" > Kirim </button> </form> {result && ( <div className="mt-4 bg-green-100 text-green-800 p-3 rounded"> {result} </div> )} </> ); }; export default RegisterForm; Penjelasan Singkat Kita pakai useState buat nampung hasil submit.handleSubmit akan dijalankan saat form disubmit, dan kita ambil nilai input dari FormData.Setelah itu, kita tampilkan hasilnya di bawah form.TailwindCSS dipakai buat styling dasar biar form-nya enak dilihat. Kalau kamu buka http://localhost:3000/register, sekarang kamu udah bisa isi form dan lihat hasilnya muncul setelah submit. Tapi ingat, belum ada validasi apa-apa. Jadi kalau kamu isi kosong atu email ngaco, tetap aja dianggap valid. Nah, di bagian berikutnya, kita akan atasi itu dengan Zod + React Hook Form. Register Form Menambahkan Validasi dengan Zod Form udah tampil, bisa diisi, bisa disubmit — tapi belum ada validasi sama sekali. Ini kayak pintu yang dibuka lebar tanpa filter. Sekarang kita akan integrasikan Zod untuk validasi input, dan sambungkan dengan React Hook Form biar prosesnya efisien dan nyaman. Setup Schema dengan Zod Pertama-tama, kita bikin schema validasi menggunakan Zod. Misalnya kita pengen: Nama wajib diisi dan minimal 2 karakterEmail wajib diisi dan harus format email yang benar Tambahkan ini di atas fungsi RegisterForm(): import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" // 1. Buat skema validasi dengan Zod const schema = z.object({ name: z.string().min(2, { message: "Nama minimal 2 karakter" }), email: z.string().email({ message: "Format email tidak valid" }), }) // 2. Tipe otomatis dari Zod type FormData = z.infer<typeof schema> Sehingga menjadi seperti berikut ini: "use client"; import { useState } from "react"; import { z } from "zod" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" // 1. Buat skema validasi dengan Zod const schema = z.object({ name: z.string().min(2, { message: "Nama minimal 2 karakter" }), email: z.string().email({ message: "Format email tidak valid" }), }) // 2. Tipe otomatis dari Zod type FormData = z.infer<typeof schema> const RegisterForm = () => { const [result, setResult] = useState(""); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const name = formData.get("name"); const email = formData.get("email"); setResult(`Nama: ${name}, Email: ${email}`); }; return ( <> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="name" className="block font-medium"> Nama </label> <input id="name" name="name" type="text" className="w-full border border-gray-300 p-2 rounded" /> </div> <div> <label htmlFor="email" className="block font-medium"> Email </label> <input id="email" name="email" type="email" className="w-full border border-gray-300 p-2 rounded" /> </div> <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" > Kirim </button> </form> {result && ( <div className="mt-4 bg-green-100 text-green-800 p-3 rounded"> {result} </div> )} </> ); }; export default RegisterForm; Dengan begini, kita udah punya aturan validasi dan juga tipe datanya. Pakai React Hook Form + Zod Selanjutnya kita ubah RegisterForm() agar pakai React Hook Form untuk handle form-nya: Ganti useState dan handleSubmit lama dengan ini: const { register, handleSubmit, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(schema), }) const onSubmit = (data: FormData) => { setResult(`Nama: ${data.name}, Email: ${data.email}`) } Kemudian ubah bagian <form> jadi seperti ini: <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <div> <label htmlFor="name" className="block font-medium"> Nama </label> <input id="name" {...register("name")} className="w-full border border-gray-300 p-2 rounded" /> {errors.name && ( <p className="text-red-600 text-sm mt-1">{errors.name.message}</p> )} </div> <div> <label htmlFor="email" className="block font-medium"> Email </label> <input id="email" type="email" {...register("email")} className="w-full border border-gray-300 p-2 rounded" /> {errors.email && ( <p className="text-red-600 text-sm mt-1">{errors.email.message}</p> )} </div> <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" > Kirim </button> </form> Sehingga kode lengkapnya akan sepreti ini: "use client"; import { useState } from "react"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; // 1. Buat skema validasi dengan Zod const schema = z.object({ name: z.string().min(2, { message: "Nama minimal 2 karakter" }), email: z.string().email({ message: "Format email tidak valid" }), }); // 2. Tipe otomatis dari Zod type FormData = z.infer<typeof schema>; const RegisterForm = () => { const [result, setResult] = useState(""); const { register, handleSubmit, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(schema), }); const onSubmit = (data: FormData) => { setResult(`Nama: ${data.name}, Email: ${data.email}`); }; return ( <> <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <div> <label htmlFor="name" className="block font-medium"> Nama </label> <input id="name" {...register("name")} className="w-full border border-gray-300 p-2 rounded" /> {errors.name && ( <p className="text-red-600 text-sm mt-1">{errors.name.message}</p> )} </div> <div> <label htmlFor="email" className="block font-medium"> Email </label> <input id="email" type="email" {...register("email")} className="w-full border border-gray-300 p-2 rounded" /> {errors.email && ( <p className="text-red-600 text-sm mt-1">{errors.email.message}</p> )} </div> <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" > Kirim </button> </form> {result && ( <div className="mt-4 bg-green-100 text-green-800 p-3 rounded"> {result} </div> )} </> ); }; export default RegisterForm; Apa yang Berubah? Kita pakai register() dari React Hook Form untuk menghubungkan input dengan state form.Semua validasi ditangani otomatis oleh Zod + zodResolver.Error ditampilkan tepat di bawah input dengan errors.nama?.message dan errors.email?.message. Sekarang kalau kamu isi form dengan kosong atau email ngawur, akan muncul pesan error. Dan kalau datanya valid, hasilnya akan tampil seperti sebelumnya. Contoh validasi gagal: Contoh validasi error Meningkatkan UX: Reset Form dan Loading State Setelah validasi jalan, kita bisa kasih sentuhan UX tambahan: form bisa di-reset setelah berhasil dikirim, dan ada indikator loading pas proses submit berlangsung. Reset Form Setelah Submit React Hook Form sudah menyediakan fungsi reset() yang bisa langsung kita pakai buat membersihkan semua input. Update onSubmit seperti ini: const { register, handleSubmit, reset, formState: { errors }, } = useForm<FormData>({ resolver: zodResolver(schema), }) const onSubmit = (data: FormData) => { setResult(`Nama: ${data.name}, Email: ${data.email}`) reset() // ← bersihkan form setelah submit } Mudah banget kan? Cukup panggil reset() setelah submit sukses, dan semua input akan kembali kosong. Reset field Menambahkan Loading State Sekarang kita tambahkan indikator loading biar user tahu kalau form sedang diproses. Kita pakai state isSubmitting dari formState milik React Hook Form: const { register, handleSubmit, reset, formState: { errors, isSubmitting }, } = useForm<FormData>({ resolver: zodResolver(schema), }) Lalu ubah tombol submit jadi seperti ini: <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 disabled:opacity-50" disabled={isSubmitting} > {isSubmitting ? "Mengirim..." : "Kirim"} </button> Dengan begini: Tombol akan menampilkan "Mengirim..." saat proses berlangsungTombol otomatis nonaktif supaya user nggak bisa klik berkali-kali Untuk testing kitaa perlu mengubah kode ketiak onSubmit menjadi seperti berikut: const onSubmit = async (data: FormData) => { setResult("") // clear result sebelumnya await new Promise((resolve) => setTimeout(resolve, 2000)) // ← delay 2 detik setResult(`Nama: ${data.name}, Email: ${data.email}`) reset() } Maka hasilnya akan seperti ini: Loading state Kenapa Ini Penting? Karena di dunia nyata, proses submit biasanya melibatkan request ke server (misalnya kirim ke Supabase, API Route, atau Server Action). Nah, selama proses itu, user perlu feedback visual bahwa aplikasi sedang "bekerja". Validasi Username: Biar Gak Ada "admin" Palsu 😎 Username adalah salah satu input penting yang sering dipakai buat identitas pengguna. Karena itu, kita perlu pastikan nilai yang dimasukkan: Nggak terlalu pendek,Nggak pakai karakter aneh-aneh,Dan yang paling penting: nggak nyamar jadi akun khusus seperti admin, root, atau superuser. Di bagian ini, kita tambahkan validasi lengkap untuk username menggunakan zod. Validasi ini mencakup: Panjang minimal 4 karakter Supaya nggak ada username kayak a, me, atau xy yang terlalu singkat dan susah dikenali.Hanya boleh huruf, angka, dash (-), dan underscore (_) Untuk menjaga format username tetap konsisten dan aman, tanpa karakter spesial seperti @, #, atau spasi.Larangan pakai kata-kata berbahaya seperti admin, root, dsb. Ini penting untuk menghindari penyalahgunaan identitas. Kita pakai refine() untuk mendeteksi apakah ada kata-kata yang seharusnya tidk boleh ada dalam username. Dengan kombinasi tiga aturan ini, kamu bisa menjaga kualitas dan keamanan input username sejak dari sisi klien — sebelum data dikirim ke server. Pertama, kita tentukan username yang dilarang dipakai, biasanya untuk alasan keamanan (menghindari user yang pura-pura jadi admin, root, dll). Buka file register-form.tsx lalu tambahkan kode berikut di atas schema: // list username yang dilarang const disallowedUsernamePatterns = ["admin", "superuser", "superadmin", "root"]; Lalu kita buat skema validasi untuk field username: username: z .string() .min(4, { message: "Username minimal 4 karakter" }) .regex(/^[a-zA-Z0-0_-]+$/, "Gunakan hanya huruf, angka, - dan _") .refine( (username) => { for (const pattern of disallowedUsernamePatterns) { if (username.toLowerCase().includes(pattern)) { return false; } } return true; }, { message: "Username terdapat kata yang dilarang" } ), Penjelasan Kenapa Ini Penting min(4): mencegah username yang terlalu pendek seperti a, ab, atau xyz.regex: memastikan karakter-karakter aneh (spasi, simbol, dll) tidak bisa masuk.refine: custom logic yang nggak bisa ditangani oleh validator bawaan Zod. Cocok untuk validasi yang butuh loop atau pengecekan substring seperti ini. Pada bagian onSubmit juga ubah jadi sepertii ini: const onSubmit = async (data: FormData) => { setResult(""); // clear result sebelumnya await new Promise((resolve) => setTimeout(resolve, 2000)); // ← delay 2 detik setResult( `Nama: ${data.name}, Email: ${data.email}, Username: ${data.username}` ); reset(); // ← bersihkan form setelah submit }; Terakhir tambahkn input form untuk username, seperti ini: <div> <label htmlFor="username" className="block font-medium"> Username </label> <input id="username" type="text" {...register("username")} className="w-full border border-gray-300 p-2 rounded" /> {errors.username && ( <p className="text-red-600 text-sm mt-1"> {errors.username.message} </p> )} </div> Maka kode lengkapnya akn menjadi seperti ini: "use client"; import { useState } from "react"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; // list username yang dilarang const disallowedUsernamePatterns = ["admin", "superuser", "superadmin", "root"]; const schema = z.object({ name: z.string().min(2, { message: "Nama minimal 2 karakter" }), email: z.string().email({ message: "Format email tidak valid" }), username: z .string() .min(4, { message: "Username minimal 4 karakter" }) .regex(/^[a-zA-Z0-0_-]+$/, "Gunakan hanya huruf, angka, - dan _") .refine( (username) => { for (const pattern of disallowedUsernamePatterns) { if (username.toLowerCase().includes(pattern)) { return false; } } return true; }, { message: "Username terdapat kata yang dilarang" } ), }); type FormData = z.infer<typeof schema>; const RegisterForm = () => { const [result, setResult] = useState(""); const { register, handleSubmit, reset, formState: { errors, isSubmitting }, } = useForm<FormData>({ resolver: zodResolver(schema), }); const onSubmit = async (data: FormData) => { setResult(""); // clear result sebelumnya await new Promise((resolve) => setTimeout(resolve, 2000)); // ← delay 2 detik setResult( `Nama: ${data.name}, Email: ${data.email}, Username: ${data.username}` ); reset(); // ← bersihkan form setelah submit }; return ( <> <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <div> <label htmlFor="name" className="block font-medium"> Nama </label> <input id="name" {...register("name")} className="w-full border border-gray-300 p-2 rounded" /> {errors.name && ( <p className="text-red-600 text-sm mt-1">{errors.name.message}</p> )} </div> <div> <label htmlFor="email" className="block font-medium"> Email </label> <input id="email" type="email" {...register("email")} className="w-full border border-gray-300 p-2 rounded" /> {errors.email && ( <p className="text-red-600 text-sm mt-1">{errors.email.message}</p> )} </div> <div> <label htmlFor="username" className="block font-medium"> Username </label> <input id="username" type="text" {...register("username")} className="w-full border border-gray-300 p-2 rounded" /> {errors.username && ( <p className="text-red-600 text-sm mt-1"> {errors.username.message} </p> )} </div> <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" disabled={isSubmitting} > {isSubmitting ? "Mengirim..." : "Kirim"} </button> </form> {result && ( <div className="mt-4 bg-green-100 text-green-800 p-3 rounded"> {result} </div> )} </> ); }; export default RegisterForm; Dan hasilnya akan seperti ini: Validasi username Validasi Password: Minimal Aman, Gak Asal Asal Saat membuat form pendaftaran, bagian password itu penting banget. Gak cuma asal diisi, tapi juga perlu divalidasi supaya pengguna: Masukkan password yang cukup kuat (minimal panjangnya),Ada huruf besar (A–Z)Ada angka (0–9)Ada simbol (!@#$%^&* dsb.)Dan memastikan mereka mengetik ulang password dengan benar (konfirmasi password). Dengan begini, kita sudah membuat form-nya lebih aman dan lebih user-friendly. Nggak lucu kan, udah bikin akun, eh password-nya salah sendiri 😅 Tambahkan kode berikut pada zod schema: password: z .string() .min(8, { message: "Password minimal 8 karakter", }) .regex(/[A-Z]/, "Password harus mengandung huruf besar (A-Z)") .regex(/[0-9]/, "Password harus mengandung angka (0-9)") .regex(/[^a-zA-Z0-9]/, "Password harus mengandung simbol (!@#$, dll)"), confirmPassword: z.string().min(8, { message: "Password minimal 8 karakter", }), .string()→ Input harus berupa string (teks)..min(8, ...)→ Wajib minimal 8 karakter untuk mencegah password yang terlalu pendek dan mudah ditebak..regex(/[A-Z]/, ...)→ Harus ada setidaknya satu huruf besar (misalnya A, B, Z). Ini bikin password lebih bervariasi..regex(/[0-9]/, ...)→ Harus mengandung angka. Misalnya 1, 2, atau 9..regex(/[^a-zA-Z0-9]/, ...)→ Harus mengandung karakter khusus/simbol, seperti @, !, #, &, dll. Dan juga tambahkan ini untuk validasi kecocokan password: .refine((data) => data.password === data.confirmPassword, { message: "Password tidak cocok", path: ["confirmPassword"], }) Baris ini penting banget karena: .refine() digunakan untuk validasi lintas field (cross-field validation).(data) => data.password === data.confirmPassword → memeriksa apakah password dan konfirmasinya sama.Kalau nggak sama, maka akan muncul error "Password tidak cocok" khusus di bagian confirmPassword. Sehingga kode schema akan menjadi seperti ini: const schema = z .object({ name: z.string().min(2, { message: "Nama minimal 2 karakter" }), email: z.string().email({ message: "Format email tidak valid" }), username: z .string() .min(4, { message: "Username minimal 4 karakter" }) .regex(/^[a-zA-Z0-0_-]+$/, "Gunakan hanya huruf, angka, - dan _") .refine( (username) => { for (const pattern of disallowedUsernamePatterns) { if (username.toLowerCase().includes(pattern)) { return false; } } return true; }, { message: "Username terdapat kata yang dilarang" } ), password: z .string() .min(8, { message: "Password minimal 8 karakter", }) .regex(/[A-Z]/, "Password harus mengandung huruf besar (A-Z)") .regex(/[0-9]/, "Password harus mengandung angka (0-9)") .regex(/[^a-zA-Z0-9]/, "Password harus mengandung simbol (!@#$, dll)"), confirmPassword: z.string().min(8, { message: "Password minimal 8 karakter", }), }) .refine((data) => data.password === data.confirmPassword, { message: "Password tidak cocok", path: ["confirmPassword"], }); Tujuannya Apa? Validasi seperti ini mendorong pengguna membuat password yang: Tidak mudah ditebak,Sulit diretas lewat brute force,Dan lebih sesuai standar keamanan modern (mirip sistem perbankan atau layanan email besar). Tambahkan kode berikut ini utnuk input password: <div> <label htmlFor="password" className="block font-medium"> Password </label> <input id="password" type="password" {...register("password")} className="w-full border border-gray-300 p-2 rounded" /> <div className="mt-1 h-2 w-full bg-gray-200 rounded"> <div className="h-full rounded transition-all" style={{ width: `${(passwordStrength / 4) * 100}%`, backgroundColor: passwordStrength < 2 ? "red" : passwordStrength === 2 || passwordStrength === 3 ? "orange" : "green", }} /> </div> <p className="text-sm mt-1"> Kekuatan:{" "} {passwordStrength < 2 ? "Lemah" : passwordStrength === 2 || passwordStrength === 3 ? "Sedang" : "Kuat"} </p> {errors.password && ( <p className="text-red-600 text-sm mt-1"> {errors.password.message} </p> )} </div> <div> <label htmlFor="confirmPassword" className="block font-medium"> Konfirmasi Password </label> <input id="confirmPassword" type="password" {...register("confirmPassword")} className="w-full border border-gray-300 p-2 rounded" /> {errors.confirmPassword && ( <p className="text-red-600 text-sm mt-1"> {errors.confirmPassword.message} </p> )} </div> Sehingga kode lengkapnya akan menjadi seperti berikut ini: "use client"; import { useState } from "react"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; // list username yang dilarang const disallowedUsernamePatterns = ["admin", "superuser", "superadmin", "root"]; const schema = z .object({ name: z.string().min(2, { message: "Nama minimal 2 karakter" }), email: z.string().email({ message: "Format email tidak valid" }), username: z .string() .min(4, { message: "Username minimal 4 karakter" }) .regex(/^[a-zA-Z0-0_-]+$/, "Gunakan hanya huruf, angka, - dan _") .refine( (username) => { for (const pattern of disallowedUsernamePatterns) { if (username.toLowerCase().includes(pattern)) { return false; } } return true; }, { message: "Username terdapat kata yang dilarang" } ), password: z .string() .min(8, { message: "Password minimal 8 karakter", }) .regex(/[A-Z]/, "Password harus mengandung huruf besar (A-Z)") .regex(/[0-9]/, "Password harus mengandung angka (0-9)") .regex(/[^a-zA-Z0-9]/, "Password harus mengandung simbol (!@#$, dll)"), confirmPassword: z.string().min(8, { message: "Password minimal 8 karakter", }), }) .refine((data) => data.password === data.confirmPassword, { message: "Password tidak cocok", path: ["confirmPassword"], }); type FormData = z.infer<typeof schema>; const RegisterForm = () => { const [result, setResult] = useState(""); const calculatePasswordStrength = (password: string) => { let strength = 0; if (password.length >= 8) strength += 1; if (/[A-Z]/.test(password)) strength += 1; if (/[0-9]/.test(password)) strength += 1; if (/[^a-zA-Z0-9]/.test(password)) strength += 1; return strength; }; const { register, handleSubmit, reset, watch, formState: { errors, isSubmitting }, } = useForm<FormData>({ resolver: zodResolver(schema), }); const passwordValue = watch("password"); const passwordStrength = calculatePasswordStrength(passwordValue || ""); const onSubmit = async (data: FormData) => { setResult(""); // clear result sebelumnya await new Promise((resolve) => setTimeout(resolve, 2000)); // ← delay 2 detik setResult( `Nama: ${data.name}, Email: ${data.email}, Username: ${data.username}` ); reset(); // ← bersihkan form setelah submit }; return ( <> <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <div> <label htmlFor="name" className="block font-medium"> Nama </label> <input id="name" {...register("name")} className="w-full border border-gray-300 p-2 rounded" /> {errors.name && ( <p className="text-red-600 text-sm mt-1">{errors.name.message}</p> )} </div> <div> <label htmlFor="email" className="block font-medium"> Email </label> <input id="email" type="email" {...register("email")} className="w-full border border-gray-300 p-2 rounded" /> {errors.email && ( <p className="text-red-600 text-sm mt-1">{errors.email.message}</p> )} </div> <div> <label htmlFor="username" className="block font-medium"> Username </label> <input id="username" type="text" {...register("username")} className="w-full border border-gray-300 p-2 rounded" /> {errors.username && ( <p className="text-red-600 text-sm mt-1"> {errors.username.message} </p> )} </div> <div> <label htmlFor="password" className="block font-medium"> Password </label> <input id="password" type="password" {...register("password")} className="w-full border border-gray-300 p-2 rounded" /> <div className="mt-1 h-2 w-full bg-gray-200 rounded"> <div className="h-full rounded transition-all" style={{ width: `${(passwordStrength / 4) * 100}%`, backgroundColor: passwordStrength < 2 ? "red" : passwordStrength === 2 || passwordStrength === 3 ? "orange" : "green", }} /> </div> <p className="text-sm mt-1"> Kekuatan:{" "} {passwordStrength < 2 ? "Lemah" : passwordStrength === 2 || passwordStrength === 3 ? "Sedang" : "Kuat"} </p> {errors.password && ( <p className="text-red-600 text-sm mt-1"> {errors.password.message} </p> )} </div> <div> <label htmlFor="confirmPassword" className="block font-medium"> Konfirmasi Password </label> <input id="confirmPassword" type="password" {...register("confirmPassword")} className="w-full border border-gray-300 p-2 rounded" /> {errors.confirmPassword && ( <p className="text-red-600 text-sm mt-1"> {errors.confirmPassword.message} </p> )} </div> <button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700" disabled={isSubmitting} > {isSubmitting ? "Mengirim..." : "Kirim"} </button> </form> {result && ( <div className="mt-4 bg-green-100 text-green-800 p-3 rounded"> {result} </div> )} </> ); }; export default RegisterForm; Dan hasilnya akan menjadi seperti berikut ini: Validasi password Penutup Wah, akhirnya sampai juga ya di ujung artikel ini. Kita udah bareng-bareng bikin form pendaftaran sederhana pakai React dan Next.js 15, terus kita kasih bumbu validasi dari Zod, dan ditambah sentuhan UX kayak loading state, reset, sampai indikator kekuatan password. Lumayan lengkap, kan? Kalau kita ibaratkan form ini kayak pintu masuk ke rumah aplikasi kita, maka validasi itu adalah sistem keamanan biar yang masuk beneran "orang baik-baik". Kita nggak cuma asal tampung data, tapi juga jaga kualitas dan konsistensi sejak dari form paling depan. Menariknya, meskipun form ini terlihat sederhana, banyak banget pelajaran penting yang bisa kita ambil — mulai dari logika validasi dasar, pengelolaan state di React, sampai bagaimana bikin pengalaman pengguna jadi lebih nyaman dan jelas. 🚀 Eksplorasi Lanjutan? Yuk Gas! Kalau kamu udah sampai sini, berarti kamu udah punya pondasi yang solid buat ngembangin form yang lebih kompleks. Beberapa ide buat eksplorasi selanjutnya: 🔢 Multi-step form: cocok buat form panjang biar nggak bikin user kabur⏳ Async validation: misalnya ngecek email/username udah dipakai atau belum📱 Optimasi mobile UX: fokus ke input UX dan auto-capitalize🧠 Integrasi ke backend (misalnya Supabase atau tRPC) Terima kasih udah ngikutin sampai akhir 🎉 Semoga artikel ini bisa bantu kamu bikin form yang lebih baik, lebih aman, dan lebih nyaman buat user kamu. Kalau ada pertanyaan, ide, atau mau share hasil implementasi, jangan sungkan buat ngobrol bareng. Sampai jumpa di tutorial selanjutnya ya! 👋