Akses kelas selamanya

Ambil Promo
flash sale
hamburger-menu

Tips All

Meningkatkan skills menjadi 1% lebih baik

Reset
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! 👋

Kelas Perbandingan Platform Kerja Remote 2025: Upwork, Contra, Fiverr, dan Lainnya di BuildWithAngga

Perbandingan Platform Kerja Remote 2025: Upwork, Contra, Fiverr, dan Lainnya

Tahun 2025, kerja remote udah bukan tren sementara udah jadi gaya hidup. Banyak orang mulai sadar bahwa kerja dari rumah (atau dari mana aja) itu bukan cuma mungkin, tapi juga menyenangkan dan produktif. Apalagi setelah baca artikel sebelumnya, 25 Pekerjaan Remote yang Bisa Kamu Lakukan dari Rumah (Tanpa Harus Jago Coding), pasti kamu makin semangat nyari peluang kerja online yang sesuai dengan skill kamu. Tapi, setelah tahu jenis-jenis pekerjaannya, pertanyaan selanjutnya pasti muncul: “Platform mana yang paling cocok buat aku?” Yup, sekarang platform kerja remote jumlahnya makin banyak. Mulai dari yang sistemnya bidding kaya Upwork dan Freelancer, sampai yang simpel dan langsung to the point kayk Contra dan Fiverr. Masing-masing punya kelebihan, kekurangan, dan karakteristik sendiri. Nah, di artikel ini, kita bakal bahas dan bandingin beberapa platform kerja remote yang paling populer di tahun 2025. Tujuannya simpel: biar kamu nggak bingung lagi milih tempat buat mulai (atau upgrade) karier freelance-mu. Kriteria Perbandingan (Biar kamu gampagn nentuin mana platform yang paling pas) Sebelum kita masuk ke detail tiap platform, penting banget buat tahu kriteria apa aja yang kita pakai buat bandingin mereka. Jadi kamu nggak asal daftar karena ikut-ikutan, tapi bisa milih yang beneran cocok sama gaya kerja dan tujuan kamu. Berikut ini beberapa poin yang bakal kita pakai buat menalai dan membandingkan platform-platform kerja remote di 2025: Jenis Pekerjaan yang Tersedia Apakah platform ini fokus di pekerjaan kreatif kayak desain dan tulis-menulis? Atau justru banyak proyek teknis sepeti data entry, coding, atau virtual assistant? Penting banget buat tahu karena tiap platform punya “niche” masing-masing. Cara Kerja (Bidding vs Direct Hire) Ada platform yang pakai sistem bidding, di mana kamu harus bersaing dengan freelancer lain buat dapetin proyek (contohnya: Upwork). Tapi ada juga yang pakai sistem direct hire, klien tinggal pilih kamu dari profil atau katalog jasa yang kamu tawarkan (contohnya: Fiverr dan Contra). Tipe yang mana yang kamu suka? Proses Pendaftaran & Verifikasi Ada platform yang simpel banget cukup daftar, bikin profil, langsung bisa jual jasa. Tapi ada juga yang perlu verifikasi identitas, portofolio, bahkan sesi wawancara singkat. Kita bakal bandingin seberapa ribet (atau gampang) proses awalnya. Potensi Penghasilan Berapa kisaran harga proyek yang biasa muncul di sana? Apakah kamu bisa pasang tarif sendiri? Apakah klien di platform itu lebih suka harga murah atau kualitas tinggi? Ini penting buat kamu yang pengin dapet penghasilan sesuai skill dan waktu yang kamu keluarkan. Metode Pembayarana Bisa tarik ke rekening lokal? Butuh akun PayPal atau Wise? Berapa lama proses pencairannya? Kita bakal lihat platform mana yang paling fleksibel dan cepat buat urusan bayar-membayar. Kelebihan dan Kekurangan Setiap platform punya plus dan minusnya. Ada yang punya komunitas aktif, tapi fee-nya tinggi. Ada juga yang cocok buat pemula tapi proyeknya terbatas. Kita bahas dua sisi biar kamu bisa ambil keputusan yang adil. Cocok untuk Siapa? Terakhir, kita simpulkan: platform ini cocoknya buat siapa? Apakah buat pemula, pekerja kreatif, spesialis IT, atau orang yang baru pindah ke dunia freelance? Bagian ini bakal bantu kamu ngelihat “fit” antara platform dan profil kamu. Perbandingan Platform Populer 1. Upwork Tampilan website Upwork Upwork adalah “raksasa” di dunia kerja remote platform global yang sudah eksis sejak penggabungan Elance dan oDesk di 2015. Mulai dari pekerjaan entri data sampai analisis data tingkat lanjut, kamu bisa menemukan proyek dari seluruh penjuru dunia. Banyak perusahaan besar dan startup memilih Upwork sebagai tempat rekrutmen freelancer andal. Pokoknya, peluangnya luas banget, asal kamu tahu cara mainnya. Sistem Kerja: Bidding & Proposal Di Upwork, kamu harus “nawar” proyek alias bidding. Kamu bikin proposal yang menjelaskan kenapa kamu cocok untuk proyek itu. Tapi, kamu nggak bisa ngirim proposal sembarangan. Kamu butuh yang namanya Upwork Connects, semacam koin digital yang dibutuhkan setiap kali kamu melamar kerja. Biasanya dikasih gratisan tiapp bulan, tapi kalau habis, kamu bisa beli. Jadi, selain skill, kamu juga butuh strategi buat menonjol di antara banyaknya pelamar. Kelebihan: Pasar global luas: ribuan proyek anyar setiap hari dari berbagai industri.Pembayaran aman: escrow memastikan kamu dibayar sesuai kesepakatan.Peluang jangka panjang: klien loyal bisa menghasilkan income stabil.Badge & reputasi: Top Rated & Expert-Vetted bikin profilmu makin unggul upwork.com.Spesialisasi moncer: area seperti AI, data analytics, atau generative AI sedang naik daun .Perusahaan besar membuka job: Microsoft, Airbnb, Glassdoor, GoDaddy aktif post di Upwork Kekurangan: Persaingan super ketat, terutama di kategori umum seperti penulis, desain, atau VA.Butuh waktu bangun reputasi—mulai dari tarif masuk rendah dan review minim.Fee freelancer hingga 10%; ada tambahan biaya untuk withdraw.Connects bisa bikin boros kalau kamu kirim banyak proposal tanpa strategi.Proyek bisa tiba-tiba ditutup, walaupun kamu sudah submit proposal; kadang bikin frustasi. Cocok untuk: Profesional remote full-time, yang siap fokus bikin karier.Pemula serius yang ingin investasi waktu untuk reputasi jangka panjang.Strategic thinkers, yang mau riset klien dan bikin proposal menarik.Spesialis niche (AI, data, bahasa, legal) yang bisa tawarkan keahlian spesifik.Freelancer yang hype dengan badge Top Rated & Expert-Vetted Profil Sukses: Jacqueline Ann DeStefano‑Tangorra (AS) Profil Jacqueline Ann DeStefano‑Tangorra Mantan auditor di PwC yang hijrah ke Upwork selama pandemi 2020. Dia membangun jasa data analytics & AI lewat platform ini, launching agensi Omni BI Solutions, dan berhasil meraup ≈ US $470.000 dari 131 proyek dalam 3,5 tahun. **Kurniawan S.** (Indonesia) Profil Kurniawan S. Translator & content writer Indonesia–Inggris, dengan profil: 235 jobs, +9.600 jam kerja, rating 5.0, rate US 25/jam. Konsisten, rapi, dan banyak melayani klien besar. Stephan B. (Indonesia) Profil Stephan B. Top Rated Translator & Data Specialist, menyelesaikan 75+ jobs dengan rating 4.9 dan rate US 8–20/jam. Kalau kamu termasuk orang yang tekun, suka riset, dan nggak takut bersaing, Upwork bisa jadi jalan panjang yang menguntungkan. Tapi kalau kamu tipe yang lebih suka ditawari proyek langsung tanpa ribet, jangan khawatir di bagian selanjutnya kita bahas Contra, platform yang lebih simpel dan cocok buat kreator masa kini. 2. Contra Tampilan website Contra Kalau Upwork itu kayak pasar kerja global yang rame dan kompetitif, Contra lebih kayak cozy studio kreatif yang modern dan rapi. Contra adalah platform kerja remote yang tanpa potongan fee, jadi kamu dapet 100% dari bayaran klien. Nggak ada sistem bidding di sini. Semuanya langsung dari klien ke freelancer, alias direct client. Platform ini memang didesain buat para kreator modern: desainer, penulis, content creator, UI/UX, bahkan video editor. Cara kerjanya lebih simpel dan minim drama. Sistem Kerja & Fitur Andalan: Direct hire: klien langsung menghubungi kamu lewat profil atau service catalog.Portofolio terintegrasi: kamu bisa tampilkan desain, studi kasus, dan hasil nyata dengan template yang estetis .Testimoni & Discover tab: review dari klien muncul di profil, membantu kamu tampil terpercaya.Kalendar booking & manajemen kontrak: atur jadwal pekerjaan langsung lewat platform.Payments escrow & invoice built-in: pembayaran aman tanpa potongan, langsung ke rekening kamu.Pro features (Premium $29/bulan): kustom domain, analytics lanjutan, visibilitas ekstra. Satu hal yang bikin Contra beda: kamu bisa kelihatan lebih profesional meskipun baru mulai, asal portofoliomu ditata rapi. Kelebihan: 0% commission fee—kamu dapat semua penghasilan, hanya dipotong biaya transfer/stripe.Profil keren & profesional berkat portofolio custom & testimoni.Prospek klien lebih berkualitas—Contra kurat job untuk hindari proyek “goceng”.Pembayaran cepat & aman: escrow aktif dan langsung cair ke rekening/invoice fitur .Komunitas progresif: sering ada event & dukungan dari tim, bahkan founder sendiri (Ben Huffman) aktif terlibat.Skalabilitas: kamu bisa tumbuh lewat portofolio dan pola pendapatan berulang tanpa mengurangi income. Kekurangan: Volume job masih terbatas, terutama untuk niche yang bukan kreatif; job list baru rilis berkali sehari .Kurang pas untuk pemula total; kamu butuh portofolio dulu untuk menarik klien .Payout belum tersedia di semua negara, tapi Stripe, PayPal, dan bank transfer sudah banyak didukung .Biaya Pro (premium) diperlukan untuk akses fitur seperti custom domain dan visibilitas di Discover.Marketplace masih berkembang, belum sebesar Upwork atau Fiverr. Cocok Untuk: Desainer grafis / UI‑UX yang ingin tampil profesional dan work‑based branding.Writer / Copywriter degan portofolio rapi dan klien ideal.Kreator digital seperti video editor, motion designer, atau brand strategist.Freelancer yang ingin kontrol penuh harga dan format kontrak.Orang yang care dengan branding, keamanan, dan profesionalisme—apoteker project‑by‑project. Profil Sukses: Julia Sit (Graphic Designer, Indonesia) Porfil Julia Sit Fokus di brand e‑commerce, ia mengungkapkan bahwa 99% income-nya datang dari Contra, dengan klien yang konsisten dan aman secara payment. Dengan kisah nyata ini, Contra jadi pilihan cerdas buat freelancer yang ingin tampil premium dan profesional. Bila kamu siap membangun branding lewat kualitas dan bukan kuantitas bidding, platform ini layak dicoba. 3. Fiverr Tampilan website Fiverr Kalau Upwork itu seperti kantor profesional tempat kamu harus ngelamar kerja, dan Contra seperti studio pribadi yang rapi, maka Fiverr itu ibarat pasar digital yang rame dan penuh warna. Di sini, kamu bisa jual jasa mulai dari yang umum sampai yang super niche—dari desain logo, voice over, penulisan artikel, sampe bikin jingle lucu buat ulang tahun. Uniknya, kamu nggak perlu melamar ke klien. Sebaliknya, kamu yang bikin "toko" sendiri dan pasang “produk” atau layanan yang bisa dibeli klien kapan aja. Fiverr berasal dari ide “semua bisaa dimulai dari $5”, tapi sekarang udah jauh berkembang. Banyak freelancer bisa dapet ratusan hingga ribuan dolar per project, tergantung skill dna reputasi. Sistem Kerja: Jualan Gigs ala Marketplace Di Fiverr, kamu akan membuat “Gig”—layanan yang kamu tawarkan, lengkap dengan harga, deskripsi, dan contoh hasil kerja. Misalnya: “Tulis artikel SEO 1000 kata dalam 3 hari”“Edit video TikTok profesional dengan transisi kekinian”“Desain logo minimalis + revisi tak terbatas” Kamu bisa bikin 3–7 paket layanan per kategori (Basic, Standard, Premium) dengan harga dan fitur berbeda. Klien tinggal klik, bayar, dan kamu langsung mulai kerja setelah sistem escrow Fiverr aktif. Semua komunikasi dan pembayaran terjadi di platform—jadi kamu nggak perlu kirim invoice atau negosiasi lewat email. Kelebihan: Nggak perlu kirim proposal, tinggal pasang gig dan tunggu orderan datangProses simpel dan cocok buat kamu yang suka jualan sistem paketCocok untuk layanan cepat dan project kecil-menengahAda fitur “Fiverr Pro” untuk freelancer top yang pengen dapet klien premiumTampilannya mirip marketplace, gampang dipahamiAda aplikasi mobile buat pantau order sambil rebahanBisa dijadikan passive client income kalau gig kamu udah mulai rame Kekuragan: Potongan fee cukup besar: 20% dari penghasilanPersaingan cukup ketat, apalagi di kategori populer seperti desain dan penulisanAlgoritma platform bisa berubah, mempengaruhi visibilitas gigKlien cenderung minta “banyak” dengan harga seminim mungkin (harus tegas & pintar atur deskripsi)Harus aktif dan fast response biar rating tetap bagus Cocok Untuk: Freelancer pemula yang mau langsung action tanpa proposal ribetKreator dengan layanan spesifik yang bisa dipaketkanVideo editor, voice over artist, penulis artikel, designer, hingga animatorOrang yang pengen bangun “toko jasa digital” secara konsisten Profil Sukses: Charmaine Pocek (AS) – Profil Fiverr Profil Charmaine Pocek Mantan recruiter yang jadi full-time resume writer di Fiverr ProUdah dapat lebih dari 15.000 review positifPenghasilannya tembus $300.000 per tahun hanya dari FiverrSering diwawancarai media seperti CNBC dan Forbes Fiverr itu cocok buat kamu yang suka sistem langsung jualan, nggak mau ribet proposal, dan punya layanan yang bisa dikemas seperti produk. Tapi tetap butuh strategi, branding, dan konsistensi buat bisa bersaing dan bertahan. Kalau kamu udah punya skill dan tinggal bingung “nawarin ke siapa ya?”, Fiverr bisa jadi tempat mulai yang menyenangkan. 4. Freelancer.com Tampilan website Freelancer.com Bayangin Freelancer.com seperti ajang global: kamu bisa ikut bidding project, ikutan lomba desain, atau pasang service—semuanya di satu tempat. Sejak 2009, platform ini tumbuh pesat dengan ~60 juta pengguna dari s***evarnya dunia. Pekerjaannya sangat beragam: IT, desain, penulisan, hingga engineering—semua ada, dan ga sedikit yang pakai sistem milestone untuk keamanan bayarannya . Kalau kamu suka tantangan, kompetisi, dan variety pekerjaan, Freelancer bisa jadi taman bermainmu—walau persaingannya lumayan sengit. Sistem Kerja & Fitur Unggulan: Bidding & Kontes: Lamar langsung ke proyek atau ikuti kontes (termasuk design & naming).Proposal & Milestone: Taruhan kayak Upwork, tapi kamu juga bisa minta dibayar sebagian sebelum mulai.Skill Test & badges: naikin trust klien lewat lencana Verified & Preferred Freelancer.Showcase & Jobs_feed: Menang lomba? Karyamu bisa tampil khusus di halaman.Keanggotaan Premium: Ada paket bulanan untuk bid lebih banyak & fitur tambahan. Kelebihan: Komunitas global besar = bukan hanya punya banyak klien, tapi juga banyak kategori project.Kontes bisa bikin portofolio cepat terpampang dan reputasi moncer.Milestone payments bikin payment process jadi lebih aman & sistematis freelancer.com.Tools lengkap: skill-test, showcase, preferred badge—semuanya untuk meningkatkan visibilitasmu.Pembayaran mendunia: dukungan multiple currency & withdraw ke banyak metode. Kekurangan: Persaingan sengit, terutama di kategori luas seperti design atau penulisan.Fee 10%, bisa turun dengan membership tapi tetap ada minimal $5.Bid terbatas: kamu butuh membership untuk bid lebih sering.Algoritma yang kadang sulit dipahami—gig kamu bisa saja tenggelam.Risiko proyek batal: kalau klien cabut, biddingmu bisa sia-sia. Ccocok Untuk: Freelancer yang siap bersaing habis-habisan, ikut bidding & kontes.Orang yang butuh exposure banyak project sekaligus.Freelancer ingin nyetart skill tanpa takut bayar upfront.Spesialis teknis (IT, engineering, data analysis) yang bikin bid kompetitif. Profil Sukses: Matt dari BrightDock LLC Matt dari BrightDock LLC Seorang freelancer skala enterprise, klaim sudah menghasilkan $1,5 juta lewat platform ini. **Andrea Reggio (Venezuela)** Andrea Reggio Jadi jurnalis freelance global tanpa repot, modal lulusan dan gig konsisten. 5. Sribulancer (sebelumnya Sribulancer/Sribu) Tampilan website Sribulancer Kalau Freelancer.com adalah pasar global, maka Sribu/Sribulancer adalah pasar lokal premium Indonesia sejak 2011. Fokus utamanya di design, branding, video, tulisan, dan digital marketing—dengan sistem kontes dan gig ala Shopify lokal. Didukung investasi dari East Ventures dan Mynavi Jepang, platform ini didesain buat mendekatkan antara brand/UMKM lokal dengan freelancer yang profesional. Sistem Kerja & Fitur: Gig dan kontes desain: upload portofolio & ikut kompetisi logo, packaging, dll.Level system (Newcomer–Grand Master): level kesuksesan freelancer otomatis naik berdasarkan rating & jumlah proyek.Platform lokal: pembayaran dalam Rp, support tim lokal, dan laporan sesuai regulasi Indonesia.Comunication selalu via platform: chat + milestone payments di sistem built-in Kelebihan: Sistem pembayaran lokal, nggak ribet konversi mata uang.Feedback & rating berbasis level, bikin freelancer lebih terlihat profesional.Komunitas lokal aktif, dengan support lebih personal.Kontes menarik: banyak UMKM minta desain logo, menjadikan ini tempat belajar sekaligus unjuk karya.Fee transparan antara 10–20% Kekurangan: Kurang terhubung ke klien global; job terbatas di pasar domestik.Kompetisi ketat di kategori populer seperti logo dan branding.Fitur gig kurang "product-based", lebih banyak proyek per-job.Pembayaran bulanan, tidak instan seperti escrow global. Cocok Untuk: Talenta kreatif lokal: desainer grafis, video editor, copywriter.Freelancer yang ingin awal mula dalam sistem lokal.Orang yang ingin portfolio kuat buat pasar Indonesia pusat.Freelancer yang nyaman dengan pembayaran Rupiah. Reviews: @dodpop8 – Graphic Designer: “Sribu provides opportunities … safe and reliable … communication … comfortable”@Hardianto81 – Graphic Designer: “By using Sribu, we can make a living as long as we are consistent …”@wiellyam – Web Developer: “As a freelancer … opportunity to find clients and gain direct trust from business owners.” Dengan dua platform ini, kamu bisa milih mau berkarya di panggung global (Freelancer.com) atau panggung nasional (Sribulancer)—keduanya lengkap dengan cerita sukses nyata dari freelancer yang gigh. 6. Projects.co.id Tampilan website Projects.co.id Kalau Series kita ini adalah cerita soal platform lokal Indonesia, Projects.co.id bisa dibilang versi “gabungan e-commerce dan freelance marketplace” yang unik. Sejak diluncurkan tahun 2014 oleh PT Panonpoe Media, platform ini memungkinkan kamu tidak hanya menerima proyek, tapi juga menjual produk digital seperti template, e-book, atau desain siap-pakai. Bayangin kamu buka toko dan studio sekaligus di satu tempat: klien datang, kamu bid proyek, selesai—atau klien langsung beli jasa atau produk yang sudah kamu pajang. Semua bisa di-handle di dashboard yang sederhana dan mudah dipahami. Sistem Kerja & Fitur Andalan: Bidding & Services hybrid: kamu bisa kirim penawaran untuk proyek tertentu, atau bikin produk/jasa fixed-price yang siap dibeli langsung.Escrow built-in: dana klien ditahan dulu sampai tugas selesai, lalu otomatis dialirkan setelah konfirmasi—aman buat freelancer dan klien .Marketplace digital products: kamu bisa jual asset seperti mockup, planner, atau e-book sambil freelance job.Integrasi chat & notifikasi real-time: langsung bisa komunikasi dengan klien lewat app.Laporan & riwayat transaksi: semua transparan, bisa dilihat dalam dashboard secara mudah. Kelbihan: Pembayaran dalam Rupiah dan transfer bank lokal → gampang dan hemat biaya.Hybrid job-dan-product marketplace membantu kamu diversifikasi pendapatan dengan cepat.Platform yang user-friendly → review di Play Store menyebut aplikasi “mudah dan legit”.Cocok untuk freelancer pemula karena pasar lokalnya mulai berkembang dan mudah dimengerti.Escrow terintegrasi bikin aman walau kontak langsung dengan klien.Peluang jual aset digital jadi nilai tambah yang unik dibanding tempat lain. Kekurangan: Kadang bugs & lag—menurut review di Play Store banyak yang mengeluhkan blank chat atau crash saat bidding.Iklan dalam aplikasi mengganggu menurut beberapa pengguna.Kasus penipuan pernah terjadi, terutama soal DP atau progress tak lanjut.Pasar lokal berarti akses ke proyek global lebih terbatas.Fiturnya belum selengkap platform global—adventure masih terasa untuk yang teknikal. Cocok Untuk: Freelancer digital asal Indonesia seperti penulis, penerjemah, desain grafis, virtual assistant, dan admin marketplace.Kreator asimetris yang juga ingin jual asset digital (template, e-book, mockup).Pemula freelance yang butuh latihan bidding dan interaksi lokal.Freelancer yang nyaman dengan pembayaran lokal dan aplikasi mobile.Orang yang ingin diversifikasi antara service dan produk digital. Dengan semua fasilitas dan potensi yang ditawarkan, Projects.co.id merupakan pilihan menarik bagi freelancer di Indonesia yang ingin mulai dari lokal, sambil belajar, dan punya ruang untuk tumbuh. Tips Memilih Platform yang Tepat Jadi, setelah lihat berbagai platform tadi, dari yang penuh strategi kayak Upwork, yang estetik kayak Contra, sampai yang lokal banget kayak Projects.co.id, mungkin kamu mulai mikir: “Terus, aku harus mulai dari mana dong?” Nah, sebelum asal daftar dan buang waktu (atau Connects 😅), ini beberapa tips yang bisa bantu kamu milih platform freelance yang paling pas buat kamu: 1. Kenali Dulu Skill & Gaya Kerjamu Setiap platform punya “ritme” kerja yang beda. Misalnya: Kamu suka ngobrol, nego, dan jago bikin proposal? ➤ Cocok banget main di Upwork.Kamu introvert yang pengen klien tinggal klik dan bayar? ➤ Contra atau Fiverr bisa jadi tempat nyaman.Kamu suka bantuin bisnis lokal, ngerti cara kerja orang Indonesia? ➤ Projects.co.id atau Sribulancer bisa jadi pintu awal. Dan yang paling penting, kamu harus jujur sama dirimu sendiri: Apakah kamu nyaman pitching? Atau lebih suka ditawari kerja berdasarkan portfolio? 2. Mulai dari yang Paling Sesuai dengan Pengalaman Kalau kamu: Belum punya klien sama sekali → Mulai dari platform lokal bisa lebih mudah masuk.Sudah pernah freelance tapi pengen serius → Coba platform global seperti Contra atau Upwork.Punya banyak portfolio digital (desain, tulisan, video) → Langsung listing service di Fiverr atau Contra. Jangan buru-buru nargetin platform “besar” kalau pondasimu belum siap. Mulai dari yang realistis lebih baik daripada burnout di tengah jalan. 3. Jangan Terpaku Satu Platform—Diversifikasi! Banyak freelancer pemula mikir, “Aku bakal all-in di satu platform biar fokus.” Nggak salah. Taapi, realitanya: Klien bisa naik-turun tergantung musim.Algoritma bisa berubah.Persaingan makin ketat setiap tahun. Solusinya? Pakai strategi multichannel: Bisa mulai dari Projects.co.id + Contra.Atau Fiverr untuk layanan cepat + Upwork buat project gede.Atau Contra buat portfolio + LinkedIn untuk networking langsung. Jangan taruh semua telur di satu keranjang. Diversifikasi itu penyelamat! 4. Portofolio & Profil = Senjata Utama Bayangin kamu masuk toko sepatu, terus nggak ada contoh sepatu yang dipajang. Males, kan? Sama halnya dengan klien: Mereka nggak peduli kamu lulusan mana—mereka cuma pengen tahu: “Kamu bisa bantu saya atau nggak?” Jadi: Susun portfolio yang fokus ke solusi: tampilkan hasil nyata, bukan cuma “desain bagus”.Kalau belum punya klien? Bikin dummy project, kerjain proyek fiktif, atau bantuin teman/UMKM lokal.Perbarui deskripsi diri di profil: siapa kamu, keahlianmu, gaya kerjamu, dan apa yang bikin kamu beda. Bonus Tips: Aktif di komunitas freelance bira nggak ketinggalan info dan dapet support mental.Belajar terus: soft skill (komunikasi, negosiasi) itu sama pentingnya dengan hard skill.Jangan bandingin perjalananmu sama orang lain. Semua butuh proses. Kesimpulan Kalau kamu udah nyimak dari awal sampai sini—keren banget! 🚀 Berarti kamu beneran serius pengen nyemplung (atau makin dalam) di dunia kerja remote. Dan satu hal yang pasti: Nggak ada platform freelance yang “terbaik untuk semua orang.”Yang ada, platform yang paling cocok buat kamu. Mau kamu ibu rumah tangga yang nyari penghasilan sambil jaga anak, fresh graduate yang baru lulus dan bingung mulai dari mana, atau pekerja kantoran yang pengen banting setir—masing-masing punya jalannya sendiri. Intinya… Upwork cocok buat kamu yang tahan uji, sabar bangun reputasi, dan jago bikin strategi proposal.Contra pas banget kalau kamu suka desain estetis, personal branding, dan kerjaan berbasis hasil akhir.Fiverr cocok buat yang pengen “jualan jasa cepat” tanpa ribet kirim proposal.Projects.co.id & Sribulancer bisa jadi batu loncatan pertama, apalagi kalau kamu lebih nyaman pakai Bahasa Indonesia dan paham pasar lokal. Di 2025 ini, kerja remote bukan lagi mimpi. Teknologi makin gampang diakses. Klien dari seluruh dunia buka peluang. Platform freelancing makin beragam. Tapi yang paling penting bukan platform-nya... Yang paling penting adalah: kesiapan kamu. Siap untuk terus belajar. Siap bangun portfolio. Siap gagal, bangkit, dan nyoba lagi. Karena freelance itu bukan sekadar cari uang—tapi juga soal membangun reputasi, relasi, dan rasa percaya diri. Dan kamu bisa mulai dari hari ini. Dari satu akun. Satu project. Satu klien. Terus belajar, terus melangkah, dan jangan lupa istirahat juga. Karena dunia remote nggak ke mana-mana—tapi kesehatanmu harus tetap dijaga 😄

Kelas 25 Pekerjaan Remote yang Bisa Kamu Lakukan dari Rumah (Tanpa Harus Jago Coding) di BuildWithAngga

25 Pekerjaan Remote yang Bisa Kamu Lakukan dari Rumah (Tanpa Harus Jago Coding)

Bayangin begini: pagi-pagi kamu bangun tanpa suara alarm yang bikin kaget, nggak perlu buru-buru mandi, dandan, atau rebutan kendaraan sama orang rumah. Sambil ngopi santai, kamu buka laptop, duduk di meja kerja favorit, dan mulai kerja dari ruang tamu, teras, atau bahkan dari kamar. Nggak ada bos yang ngawasin dari balik meja, nggak ada meeting dadakan di kantor yang bikin stres. Kedengarannya kayak mimpi, ya? Tapi nyatanya, kerja remote udah jadi kenyataan buat banyak orang. Apalagi sejak pandemi kemarin, dunia kerja berubah drastis. Banyak perusahaan sadar kalau karyawan tetap bisa produktif walaupun kerja dari rumah. Bahkan sekarang, banyak perusahaan luar negeri yang membuka peluang kerja remote untuk siapa aja, dari mana aja. Tapi mungkin kamu berpikir, "Kerja remote kan buat orang-orang yang bisa ngoding, ngerti IT, atau lulusan teknik komputer." Nah, ini dia kesalahan yang sering banget terjadi. Banyak orang merasa nggak cukup “pintar teknologi” buat kerja remote, padahal faktanya banyak pekerjaan remote yang bisa dikerjakan tanpa skill coding sama sekali. Aku pun dulu berpikir gitu. Ngerasa kerja remote itu cuma buat programmer. Tapi setelah cari tahu, ternyata ada begitu banyak peluang buat yang latar belakangnya non-IT. Mulai dari penulis konten, admin sosial media, sampai virtual assistant—semuanya bisa dikerjakan dari rumah, bahkan dari desa sekalipun, selama ada koneksi internet. Di artikel ini, aku bakal share ke kamu 25 jenis pekerjaan remote yang bisa kamu lakuin tanpa harus jadi ahli teknologi. Pekerjaan ini cocok buat kamu yang pengen ganti suasana kerja, mulai karier baru, atau sekadar cari penghasilan tambahan dengan cara yang lebih fleksibel. Siapkan cemilan dan kopi kamu, karena kita bakal bahas satu per satu—siapa tahu salah satunya cocok buat kamu. 😉 Apa Itu Kerja Remote & Kenapa Banyak Dicari? Apa itu kerja remote & kenapa banyak dicari? Image by freepik Sebelum kita bahas daftar pekerjaannya, yuk kenalan dulu sama istilah yang lagi naik daun ini: kerja remote. Kerja remote itu sederhananya kerja yang nggak harus dilakukan dari kantor. Artinya, kamu bisa kerja dari mana aja—rumah, warung kopi, coworking space, bahkan dari kota atau negara lain sekalipun. Yang penting kamu tetap bisa komunikasi sama tim dan menyelesaikan tugasmu tepat waktu. Dulu, kerja remote tuh dianggap “tidak normal”. Kalau bilang ke orang tua, “Aku kerja dari rumah aja,” mungkin dibalas dengan, “Lho, kok nggak ke kantor? Emang itu beneran kerja?” Tapi sekarang, kerja remote udah makin diterima, bahkan jadi pilihan utama banyak orang. Apalagi generasi sekarang makin sadar bahwa hidup itu bukan cuma soal kerja 9 to 5 dan terjebak macet setiap hari. Bukan cuma kita yang butuh fleksibilitas. Perusahaan juga mulai sadar bahwa dengan sistem remote, mereka bisa rekrut talenta dari mana aja tanpa harus nyiapin kantor fisik. Lebih hemat biaya, dan kadang justru lebih produktif. Nah, tren ini makin kuat sejak pandemi 2020. Banyak perusahaan akhirnya "dipaksa" buat coba kerja jarak jauh, dan ternyata... berhasil! Sejak saat itu, lowongan kerja remote terus meningkat, termasuk untuk pekerjaan non-IT. Dan yang menarik, sekarang banyak kerja remote yang nggak butuh kamu jadi expert teknologi atau lulusan informatika. Punya kemampuan dasar seperti komunikasi yang oke, manajemen waktu, dan sedikit skill digital udah cukup buat mulai. Tapi pertanyaannya: emangnya ada kerjaan kayak gitu? Jawabannya: ADA! Banyak malah. Yuk kita intip daftar 25 pekerjaan remote yang bisa kamu mulai dari sekarang—tanpa harus jadi jago ngoding atau ngerti IT tingkat dewa. 25 Pekerjaan Remote untuk Pemula, Ibu Rumah Tangga, dan Fresh Graduate (Tanpa Harus Jago Coding!) Kerjaan ini bisa kamu mulai walau belum punya banyak pengalaman. Bahkan banyak yang bisa dikerjakan paruh waktu atau fleksibel, cocok buat kamu yang masih kuliah, baru lulus, atau punya kesibukan di rumah. Pekerjaan Remote Buat yang Suka Nulis dan Komunikasi Content Writer Image by Freepik Content Writer Bayangin kamu duduk di teras rumah, ditemani secangkir teh hangat, dan mulai nulis artikel tentang tips menata dapur kecil biar tetap kece. Itulah kerjaan seorang content writer. Tugas utamanya adalah menyampaikan informasi lewat tulisan yang ringan dan menarik. Nggak perlu jadi sastrawan—yang penting kamu bisa riset, nulis dengan gaya yang asyik, dna tahu siapa yang baca. Rutinitas sehari-hari: Cek brief dari klien atau tim editorialRiset topik dan cari referensi dari berbagai sumberBuat kerangka atau outline artikelMenulis artikel sesuai gaya bahasa target audiensEdit tulisan agar lebih enak dibaca dan bebas typoTambahkan elemen pendukung seperti gambar, infografis, atau kutipanSubmit artikel ke Google Docs, CMS, atau langsung ke klien Copywriter Image by Freepik Copywriter Kamu pernah baca iklan simpel tapi langsung bikin pengen beli? "Diskon cuma hari ini!" misalnya. Nah, itu kerjaan copywriter. Tugasnya bikin tulisan singkat tapi "nendang" buat promosi produk. Bisa berupa slogan, email marketing, caption, atau landing page. Cocok buat kamu yang suka main kata dan mikir out-of-the-box. Rutinitas sehari-hari: Menganalisis brief dan tujuan iklan/tulisanRiset target audiens dan produkMenulis headline dan copy untuk iklan, landing page, email, dllMerevisi berdasarkan feedback klien atau timMenyesuaikan tone & voice sesuai brand Transcriber Image by Freepik Transcriber Bayangin kamu lagi dengerin podcast tentang parenting, sambil ngetik apa yang mereka omongin. Transcriber kerjaannya seperti itu: mendengarkan audio (kadang video), lalu mengetik ulang isi pembicaraannya dengan rapi. Cocok buat kamu yang teliti, sabar, dan nyaman bekerja sambil pakai headset. Rutinitas sehari-hari: Menerima file audio/video dari klienMendengarkan rekaman dan mengetik isi percakapanMenandai bagian tidak terdengar jelas (jika perlu)Memastikan ejaan dan tanda baca sesuai standarMengedit hasil transkrip sebelum dikirim Translator Image by Freepik Translator Kamu jago bahasa Inggris, Korea, Jepang, atau bahasa lain? Jadi translator remote bisa jadi ladang cuan! Misalnya, kamu nerjemahin artikel resep dari Bahasa Inggris ke Bahasa Indonesia. Atau subtitle drama Korea buat channel YouTube. Asal ngerti dua bahasa dan tahu konteks, kamu udah bisa mulai. Rutinitas sehari-hari: Menerima dokumen atau file dari klienMenerjemahkan teks sesuai konteks dan budayaMengecek akurasi terjemahan secara menyeluruhMemastikan konsistensi istilah teknisMengirim hasil akhir dalam format yang diminta Customrer Support Image by Freepik Customer Support (Live Chat/Email) Bayangin kamu jadi "penolong pertama" buat pelanggan toko online yang bingung soal pengiriman barang. Kamu bantu mereka lewat chat atau email, menjawab pertanyaan, dan bikin pengalaman mereka lebih nyaman. Cocok buat kamu yang sabar, komunikatif, dan suka bantuin orng. Rutinitas sehari-hari: Mengecek tiket pertanyaan pelangganMenjawab pertanyaan via chat, email, atau media sosialMenyampaikan laporan masalah ke tim teknisUpdate knowledge base atau FAQRekap interaksi harian ke sistem/CRM Pekerjaan Kreatif yang Bisa Dipelajari Cepat Desain Grafis Image by Freepik Desain Grafis Pernah iseng bikin quote pakai Canva terus dipuji temen karena estetik? Itu bisa jadi pintu masukmu ke dunia desain grafis. Banyak bisnis butuh desain buat poster, feed Instagram, sampai kemasan produk. Nggak harus jago Photoshop kok—mulai aja dari tool gratis kayak Canva atau Figma, lalu terus eksplor. Rutinitas sehari-hari: Menerima brief dari klien/tim marketingMembuat sketsa konsep visualMendesain konten visual (poster, feed IG, banner, dll)Melakukan revisi sesuai masukanMenyimpan file desain dalam format siap pakai Social Media Manager Image by Freepik Social Media Manager Bayangin kamu pegang akun Instagram sebuah brand makanan, terus tiap hari bikin konten, upload story, dan ngobrol sama followers. Seru, kan? Nah, kerjaan ini cocok buat kamu yang aktif di medsos dan ngerti tren. Kamu bisa bantu UMKM atau brand bikin strategi konten biar makin dikenal. Rutinitas sehari-hari: Menyusun kalender konten mingguan/bulananMenulis caption dan menjadwalkan postingMembalas komentar dan DM dari audiensAnalisis performa konten (engagement, reach)Kolaborasi dengan desainer atau tim kreatif Video Editor Image by Freepik Video Editor (Dasar) Kamu sering bikin video lucu buat status WA atau TikTok keluarga? Kamu udah punya modal jadi video editor! Banyak kreator butuh bantuan edit video ringan: potong klip, tambah musik, kasih teks. Pakai aplikasi kayak CapCut atau VN aja dulu, lalu pelan-pelan belajar software yang lebih kompleks. Rutinitas sehari-hari: Menerima footage mentah dari klien atau timMenyusun timeline dan mengedit video (potong, transisi, efek)Menambahkan backsound, subtitle, dan elemen grafisMengekspor video sesuai spesifikasi platformMenyimpan backup file proyek Illustrator/Digital Artist Image by Freepik Illustrator / Digital Artist Suka gambar digital? Kamu bisa jual jasa ilustrasi buat buku anak, komik, bahkan stiker WhatsApp. Nggak harus gambar realis kok—gaya kawaii, doodle, atau minimalis juga banyak peminatnya. Banyak platform seperti Fiverr atau Instagram yang bisa jadi etalase karyamu. Rutinitas sehari-hari: Membaca brief atau skrip dari klien/editorMembuat sketsa kasar untuk ilustrasiMelakukan revisi berdasarkan feedbackMenyelesaikan ilustrasi dengan detail warna dan shadingMengirimkan hasil dalam format digital UI/UX Designer Image by Freepik UI/UX Designer UI/UX Designer fokus bantu bikin tampilan dan pengalaman aplikasi atau website jadi mudah dan menarik. Meskipun ini bagian dari dunia IT, banyak orang belajar dari nol lewat kursus online. Kalau kamu suka ngulik aplikasi dan punya rasa ingin tahu tinggi, ini bidang yang menjanjikan dan bisa dipelajari step-by-step. Kalo kamu mau belajar UI/UX berikut beberapa rekomendasi kelas yang bisa kamu ikuti dari BuildWithAngga: Kelas Online Gratis UI UX Figma to No-Code Lovable AI: Bikin Landing PageUltimate UX Guide: Focus on User-Centered DesignMastering Mobile UI Design for Beginners with FigmaMastering Figma: Modern UI Dashboard Design Rutinitas sehari-hari: Menganalisis kebutuhan pengguna atau client briefMembuat wireframe dan prototipe (Figma/Adobe XD)Kolaborasi dengan developer dan stakeholderMelakukan usability testingMerevisi desain berdasarkan hasil testing Pekerjaan yang Butuh Skill Riset & Organisasi Data Entry Image by Freepik Data Entry Bayangin kamu bantuin satu usaha kecil buat masukin data penjualan mereka ke Excel. Kerjaan ini nggak ribet, asal kamu teliti dan rapi. Cocok banget buat kamu yang suka kerja sistematis dan nggak keberatan ngulang hal yang sama. Rutinitas sehari-hari: Menerima data mentah dari klienMemasukkan data ke sistem/spreadsheet dengan akuratMengecek ulang data untuk menghindari kesalahanMengelompokkan atau mengkategorikan dataBackup data secara rutin Virtual Asisstant Image by Freepik Virtual Assistant (VA) Kamu pernah bantuin temen ngatur jadwal arisan, bikin daftar belanja, atau cek email masuk? Kalau iya, kamu udah punya modal jadi VA. Tugas VA itu macam-macam: mulai dari ngatur kalender, balas email, sampai nyusun laporan ringan. Bisa bantuin pebisnis, freelancer, atau konten kreator. Rutinitas sehari-hari: Mengecek dan membalas email klienMenjadwalkan meeting dan pengingat tugasMengatur file dan dokumen digitalMembuat laporan sederhana (Excel, Google Docs)Koordinasi dengan tim atas nama klien Research Asisstant Image by Freepik Rersearch Assistant Suka penasaran sama sesuatu dan doyan cari info sampai ke akar? Jadi research assistant mungkin cocok buatmu. Misalnya, kamu bantuin influencer cari referensi buat konten edukasi, atau bantu startup riset tren pasar. Kerjaan ini fleksibel dan cocok buat kamu yang suka ngulik. Rutinitas sehari-hari: Mencari informasi dari sumber akademik atau webMerangkum dan menyusun hasil risetMenyusun daftar pustaka atau referensiMengisi spreadsheet riset dan insightPresentasi hasil riset ke tim atau atasan Project Coordinator Freelance Image by Freepik Project Coordinator Freelance Kalau kamu suka ngatur-ngatur, jadi project coordinator bisa cocok. Tugasnya bantu tim freelance agar kerjaan berjalan sesuai timeline. Meski nggak coding, kamu tetap berperan penting buat koordinasi antar anggota tim. Rutinitas sehari-hari: Membuat jadwal dan timeline kerja timMengecek progres tugas dari freelancer lainKomunikasi rutin dengan klien dan timMenyusun laporan proyekMengatur revisi atau penyesuaian kerja Moderator Komunitas Online Image by Freepik Moderator Komunnitas Online Bayangin kamu jadi penjaga ketertiban di grup parenting Facebook atau komunitas belajar online. Tugasnya ngejaga suasana tetap nyaman, bantu jawab pertanyaan anggota, dan lapor kalau ada postingan yang nggak sesuai aturan. Cocok buat kamu yang sabar dan suka berinteraksi di forum. Rutinitas sehari-hari: Memantau komentar dan postingan anggotaMenghapus konten yang melanggar aturanMenjawab pertanyaan umum anggota komunitasMenyusun peraturan komunitas atau FAQMelaporkan aktivitas harian ke tim admin Pekerjaan Remote di Dunia Edukasi & Kursus Online Tutor Online (Non-Akademik) Image by Freepik Tutor Online (Non-Akademik) Kamu jago main gitar, masak, atau bikin kerajinan tangan? Coba deh buka kelas online kecil-kecilan. Banyak orang nyari tutor buat skill non-akademik. Bisa mulai dari WhatsApp group, Zoom, atau platform kursus. Cocok buat kamu yang sabar dan seneng ngajarin. Rutinitas sehari-hari: Menyiapkan materi pembelajaran untuk sesi onlineMengajar lewat Zoom atau platform lainMenjawab pertanyaan murid di luar sesiMemberi tugas dan nilaiMenyimpan catatan perkembangan siswa Bantu Bikin Modul/Konten Belajar Image by Freepik Bantu Bikin Modul/Konten Belajar Kalau kamu suka bikin rangkuman atau presentasi, kerjaan ini cocok banget. Banyak guru, pelatih, atau institusi yang butuh bantuan bikin materi belajar. Misalnya, bikin slide Canva buat pelajaran matematika anak SD. Asal kamu rapi dan bisa riset, bisa banget dicoba. Rutinitas sehari-hari: Menentukan topik berdasarkan silabusMenulis materi pembelajaran dengan struktur jelasMenambahkan ilustrasi, gambar, atau video pendukungMembuat soal latihan atau kuisMenyesuaikan modul sesuai jenjang/tingkatan Proofreader Materi Belajar Image by Freepik Proofreader Materi Belajar Kamu suka baca dan jeli banget kalau ada typo? Jadi proofreader bisa jadi opsi menarik. Tugasnya ngecek tulisan—misalnya soal latihan, e-book, atau artikel—biar bebas dari salah ketik dan jelas dibaca. Cocok buat kamu yang perfeksionis dan detail. Rutinitas sehari-hari: Membaca ulang materi dari penulis/modulMemperbaiki typo, ejaan, dan tanda bacaMemastikan kalimat mudah dipahamiKonsistenkan gaya penulisanTandai bagian ambigu atau perlu direvisi Pengisi Kuis dan Latihan Soal Image by Freepik Pengisi Kuis dan Latihan Soal Pernah bikin kuis seru buat grup keluarga atau komunitas? Kamu bisa ubah itu jadi kerjaan, lho! Banyak platform belajar atau guru privat yang butuh bantuan bikin soal latihan, lengkap sama jawaban. Bisa untuk pelajaran SD sampai SMA. Rutinitas sehari-hari: Menyusun soal pilihan ganda, isian, atau uraianMenulis kunci jawaban dan pembahasanMemastikan soal sesuai standar/topikMengecek duplikasi atau kemiripan soalMengelompokkan soal berdasarkan level kesulitan Narator Konten Edukassi Image by Freepik Narator Konten Edukasi Bayangin kamu jadi suara di balik video pembelajaran anak-anak, atau jadi narator di podcast edukasi. Modalnya mirip kayak voice over—cukup suara jelas, artikulasi bagus, dan semangat ngajarin yang tinggi. Cocok buat kamu yang pengen tetap berkontribusi tanpa harus tampil langsung. Rutinitas sehari-hari: Membaca naskah konten dengan intonasi jelasMerekam suara dengan alat yang layakMengedit hasil rekaman untuk kejernihan audioMenyesuaikan durasi sesuai naskahMengirim hasil akhir dalam format audio Pekerjaan Remote Tambahan yang Fleksibel & Santai Microtask Worker (Pekerjaan Mikro) Image by Freepik Microtask Worker (Pekerjaan Mikro) Pernah denger soal kerjaan kecil seperti ngetik captcha, ngisi survei, atau nge-tag gambar? Ini disebut microtask. Cocok buat kamu yang pengen kerja santai, tanpa tekanan. Lumayan buat nambah penghasilan tanpa skill khusus. Rutinitas sehari-hari: Mengerjakan tugas-tugas kecil (labeling, tagging, dsb)Memastikan akurasi data sesuai petunjukSubmit pekerjaan dalam waktu singkat Tester Produk (Product Reviewer) Image by Freepik Tester Produk (Product Reviewer) Kamu bisa dibayar buat nyobain produk dan kasih review. Misalnya, coba skincare, alat rumah tangga, atau aplikasi baru. Bisa dalam bentuk tulisan atau video. Selain dapat produk gratis, kamu juga bisa dapet fee! Rutinitas sehari-hari: Mencoba produk digital atau fisikMencatat pengalaman penggunaan (user experience)Melaporkan bug atau errorMengisi form evaluasi produkMemberi saran perbaikan jika diminta Penjual Online (Reseller/Dropshipper) Image by Freepik Penjual Online (Reseller/Dropshipper) Kalau kamu suka jualan tapi nggak punya modal buat stok barang, jadi dropshipper bisa jadi solusi. Kamu tinggal pasarkan produk dari supplier dan mereka yang kirim ke pembeli. Bisa dikerjakan dari HP aja, cocok buat ibu rumah tangga. Rutinitas sehari-hari: Upload produk ke marketplace atau toko onlineBalas pertanyaan dan chat calon pembeliMengemas dan mengirim barang (jika bukan dropship)Cek stok dan update hargaBuat promo atau konten jualan Pengisi Kuesioner / Survey Online Image by Freepik Pengisi Kuesioner / Survey Online Cuma jawab pertanyaan dari laptop atau HP, kamu bisa dapat uang atau voucher. Memang bayarannya kecil, tapi kalau rutin bisa lumayan. Cocok buat pengisi waktu luang di sela aktivitas harian. Rutinitas sehari-hari: Mendaftar di platform surveiMengisi survei sesuai profil atau minatMenjawab jujur sesuai panduanMelengkapi survei untuk mendapatkan poin/imbalanMenyimpan riwayat survei selesai Penulis Ulasan (Review Writer) Image by Freepik Penulis Ulasan (Review Writer) Kamu suka nulis ulasan makanan, film, atau produk? Banyak platform yang nyediain tempat buat kamu berbagi review dan dapat imbalan. Cocok buat kamu yang suka nulis santai dan jujur dari pengalaman pribadi. Rutinitas sehari-hari: Mencoba produk, aplikasi, atau layananMenulis review jujur berdasarkan pengalamanMenyertakan kelebihan dan kekuranganUpload ulasan di blog, marketplace, atau platform reviewTambahkan foto atau tangkapan layar bila perlu Tips Memulai Kerja Remote Bayangin kamu lagi duduk di depan laptop butut yang udah menemani bertahun-tahun. Kopi hitam buatan sendiri masih mengepul, dan kamu lagi scroll lowongan kerja remote di HP. Rasanya campur aduk—penasaran, tapi juga ragu. “Bisa nggak ya aku mulai dari nol?” Jangan khawatir. Semua yang udah sukses kerja remote juga mulai dari titik itu. Mulailah dengan kenali apa yang kamu suka. Misalnya kamu ibu rumah tangga yang suka ngeblog resep masakan, berarti kamu udah punya modal untuk jadi content writer. Atau kamu fresh graduate yang jago desain feed Instagram? Itu bisa jadi tiket ke dunia desain grafis. Bikin portofolio sederhana. Nggak perlu ribet—cukup pakai Canva atau Notion. Upload hasil desainmu, tulisanmu, atau test project yang kamu bikin sendiri. Ini semacam “kartu nama digital” biar calon klien percaya. Coba bantu UMKM sekitar rumah. Bantuin mereka buat caption, desain, atau ngurus katalog. Ini bisa kamu masukin ke portofolio juga. Sambil bantu orang, kamu juga latihan. Terakhir, gabung komunitas. Di grup Facebook, Telegram, Discord—di sana kamu bisa nemu mentor, job listing, dan semangat baru dari teman seperjuangan. Tools yang Sering Dipakai Dulu kamu mungkin cuma tahu Microsoft Word dan WhatsApp. Tapi begitu masuk dunia kerja remote, kamu akan kenal sahabat baru. 📞 Kalau mau meeting, kamu akan sering buka Zoom atau Google Meet. Tapi jangan khawatir, nggak harus jago ngomong di awal—yang penting sopan dan jelas. 📝 Kalau mau nulis atau edit dokumen bareng tim? Google Docs bisa jadi penyelamat. Lengkap dengan fitur komen dan revisi. 📋 Butuh ngatur to-do list biar kerjaan nggak berantakan? Coba Trello atau Notion—kayak papan tempel digital buat otakmu. 🎨 Kamu orang desain? Canva bisa jadi sahabat terbaik untuk pemula. Tapi kalau mau naik level, coba Figma buat kolaborasi desain yang lebih rapi. 🤖 Suka ngulik dan riset? ChatGPT bisa bantu kamu nulis, ngide, atau cari jawaban cepat. Tinggal tanya, dia jawab. Cara Terhindar dari Penipuan Kerja Remote Bayangin kamu dapet pesan di WhatsApp, “Kamu terpilih jadi admin marketplace, gajinya 7 juta per bulan! Tapi transfer dulu Rp200.000 buat aktivasi akun.” Waspadalah! Ini modus yang sering kejadian. Kerjaan remote yang asli nggak akan minta kamu bayar di awal. Kalau mereka bilang “deposit dulu”, langsung tinggalkan. Sebelum daftar kerja, coba cari nama perusahaannya di Google. Kalau nggak ada jejak, atau banyak review negatif, mending cari yang lain. Jangan asal kasih KTP, nomor rekening, atau data pribadi lengkap sebelum ada perjanjian kerja resmi. Lebih aman kalau kamu pakai platform resmi seperti Upwork atau Fiverr yang punya sistem escrow—uang klien ditahan dulu, baru dibayar kalau kerjaan selesai. Dan ingat: kalau tawarannya terdengar terlalu indah, mungkin memang bohong. Kalau kamu ibu rumah tangga yang lagi nyuapin anak sambil riset lowongan kerja, atau fresh graduate yang bingung arah hidup, percayalah—selama ada niat dan koneksi internet, kamu bisa punya karier hebat dari rumah. Dunia kerja remote itu luas, dan kamu punya tempat di dalamnya. 💻🌏✨ Rekomendasi Kelas Buat Belajar Kerja Remote Kalau kamu masih bingung harus mulai dari mana, ini beberapa kelas online yang relevan, praktis, dan cocok buat pemula yang pengen kerja remote tanpa harus jago coding: Belajar Kerja Remote UI/UX Design & No-Code dengan Contra Cocok buat kamu yang pengen masuk ke dunia desain tanpa harus ngerti coding. Belajar tools no-code, cara bangun portofolio, sampai tips dapetin client luar negeri via Contra.Belajar Upwork 2024: Jadi Freelancer Profesional, Hasilkan Ribuan Dollar Buat kamu yang pengen serius mulai di platform Upwork, kelas ini ngajarin strategi dapetin proyek, bikin proposal yang dilirik, dan cara bangun reputasi.Ebook: Petunjuk Pro Freelance Web Developer & Kerja Remote Kalau kamu sedikit banyak udah ngerti web development (HTML, CSS, atau Next.js), ebook ini bantu kamu loncat ke tahap jadi freelancer pro dengan strategi yang terbukti.Upwork Freelancer Mastery: Strategi Terbukti Raih 100 Juta Pertama Buat kamu yang serius dan pengen fokus cari penghasilan besar lewat Upwork. Belajar dari yang udah terbukti berhasil—dari mindset, positioning, sampai closing. Semua kelas di atas pakai bahasa Indonesia, cocok banget buat pemula, dan bisa langsung dipraktikkan! Penutup: Mulai dari Sekarang, Bukan Nanti Kerja remote bukan cuma tren, tapi peluang nyata—terutama buat kamu yang mungkin belum punya pengalaman kerja formal, lagi di rumah, atau sedang cari cara baru untuk mandiri secara finansial. Kamu nggak harus langsung jago. Yang penting, mulai dulu. Bisa dari ikut webinar, gabung komunitas, atau bantuin teman/UMKM sekitar. Setiap langkah kecilmu hari ini bisa jadi batu loncatan buat karier remote yang stabil dan bikin bangga. Ingat: 📌 Nggak semua orang harus bisa ngoding buat kerja online. 📌 Setiap orang punya kelebihan—temukan dan asah itu. 📌 Kerja remote = kerja beneran. Bukan cuma main HP atau kerja iseng. Kamu bisa kerja dari rumah, tetap dekat keluarga, dan tetap produktif. Apapun latar belakangmu, peluang kerja remote itu bisa banget jadi jalan buat masa depan yang lebih fleksibel dan membanggakan.