Deploy Aplikasi Buatan AI ke Production
Di laptop semuanya jalan. Sekarang aplikasinya akan dipakai orang sungguhan, dengan data sungguhan. Ini cara merilisnya tanpa membuka pintu belakang.
Oleh Afa Hernandes14 menit baca
Daftar isi
- Sebelum mulai
- Langkah 1: Pisahkan development dan production
- Langkah 2: Pindahkan konfigurasi ke environment variable
- Langkah 3: Pasang penjaga kunci rahasia
- Langkah 4: Deploy ke Cloudflare Pages lewat GitHub
- Langkah 5: Atur alamat login di Supabase
- Langkah 6: Uji di production, bukan hanya di laptop
- Langkah 7: Siapkan jalan mundur
- Checklist sebelum link dibagikan
- Error yang sering muncul
- Langkah berikutnya: rilis dan amankan
- Pertanyaan yang sering diajukan
- Apakah publishable key aman walaupun bisa dilihat siapa saja?
- Haruskah memakai dua proyek Supabase? Repot.
- Bisakah memakai Vercel atau Netlify, bukan Cloudflare Pages?
- Aplikasiku dibuat di app builder dan deploy-nya cukup satu klik. Apakah artikel ini berlaku?
- Kesimpulan
Men-deploy halaman statis itu mudah: unggah folder, dapat link, selesai. Kamu sudah melakukannya di deploy pertama ke Cloudflare Pages. Tapi begitu aplikasimu punya database dan login, deploy bukan lagi soal “bisa dibuka atau tidak”. Sekarang ada data orang lain yang harus dijaga, kunci API yang tidak boleh bocor, dan pengguna yang akan kecewa kalau aplikasinya rusak.
Production adalah versi aplikasi yang dipakai pengguna sungguhan. Lawannya adalah development, versi di laptopmu tempat kamu bebas mencoba dan merusak. Tutorial ini membahas cara memindahkan aplikasi buatan AI dari development ke production dengan aman: memisahkan keduanya, menyimpan konfigurasi di tempat yang benar, memasang penjaga yang membatalkan deploy kalau ada kunci rahasia ikut terbawa, lalu menguji di alamat aslinya.
Contohnya tetap aplikasi pencatat pengeluaran dari jalur ini, versi yang sudah memakai Supabase untuk database dan login.
Sebelum mulai
Kamu butuh:
- Aplikasi yang sudah jalan di laptop, idealnya dengan database dan login seperti di tutorial database dan login.
- Proyek yang sudah ada di GitHub. Belum? Ikuti Git dan GitHub untuk kerja tim.
- Akun Cloudflare dan akun Supabase (keduanya punya paket gratis).
- Node.js di laptopmu, untuk menjalankan build dan skrip pemeriksaan.
Diuji dengan Node 24, Vite 8, dan @supabase/supabase-js 2. Aplikasi buatan AI yang memakai database biasanya sudah memakai build tool seperti Vite: alat yang mengubah kode proyekmu menjadi folder dist berisi file siap online. Kalau AI-mu memakai framework lain, prinsipnya sama. Yang berbeda hanya beberapa nama, dan akan disebutkan.
Struktur proyek yang kita pakai:
catatanku/├── index.html├── src/│ ├── main.js│ └── supabase.js ← koneksi ke Supabase├── scripts/│ ├── cek-rahasia.mjs ← dibuat di langkah 3│ └── cek-production.mjs ← dibuat di langkah 6├── .env.example├── .env.local ← tidak masuk Git├── .gitignore└── package.jsonLangkah 1: Pisahkan development dan production
Kesalahan yang paling sering: aplikasi online memakai database yang sama dengan yang kamu pakai untuk mencoba-coba di laptop. Akibatnya, data percobaan bercampur dengan data pengguna, dan satu perintah hapus yang salah saat mencoba fitur baru bisa menghapus data sungguhan.
Buat dua proyek Supabase:
| Development | Production | |
|---|---|---|
| Nama proyek | catatanku-dev |
catatanku |
| Dipakai oleh | Laptopmu dan alamat pratinjau | Alamat utama yang dipakai pengguna |
| Isi data | Data contoh, boleh dihapus kapan saja | Data pengguna sungguhan |
| Boleh dicoba-coba? | Ya | Tidak |
Struktur tabel dan aturan aksesnya harus sama persis di kedua proyek. Caranya: simpan semua SQL yang kamu jalankan (pembuatan tabel dan aturan Row Level Security) sebagai file di repository, misalnya supabase/aturan-akses.sql. Jalankan di proyek development dulu, uji, lalu jalankan file yang sama di SQL Editor proyek production. Jangan pernah mengubah tabel production langsung dari dashboard tanpa mencatatnya di file.
Minta AI membantu dengan prompt seperti ini:
Saya akan memakai dua proyek Supabase: satu untuk development, satu untuk production.Kumpulkan semua SQL pembuatan tabel dan aturan Row Level Security yang pernah kita buatke dalam satu file supabase/aturan-akses.sql, urut dari awal, supaya bisa saya jalankandi proyek baru dan hasilnya sama persis. Jangan sertakan data contoh.Langkah 2: Pindahkan konfigurasi ke environment variable
Alamat dan kunci Supabase berbeda antara development dan production, jadi tidak boleh ditulis langsung di kode. Simpan sebagai environment variable, dan biarkan kodenya membaca dari sana:
import { createClient } from '@supabase/supabase-js';
const url = import.meta.env.VITE_SUPABASE_URL;const key = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY;
if (!url || !key) { throw new Error('VITE_SUPABASE_URL atau VITE_SUPABASE_PUBLISHABLE_KEY belum diisi');}
export const supabase = createClient(url, key);Perhatikan awalan VITE_. Vite hanya memasukkan variabel berawalan VITE_ ke kode yang berjalan di browser. Variabel tanpa awalan itu tidak ikut, dan justru itu yang kita mau untuk kunci rahasia. Framework lain punya aturan serupa dengan awalan berbeda: NEXT_PUBLIC_ di Next.js dan PUBLIC_ di Astro.
Artinya, semua yang berawalan VITE_ bisa dibaca siapa saja yang membuka aplikasimu. Karena itu, di sini kita hanya memakai publishable key, kunci publik yang aksesnya dibatasi aturan akses database. Secret key tidak pernah boleh diberi awalan VITE_.
Untuk development, isi nilainya di file .env.local:
VITE_SUPABASE_URL=https://kode-proyek-dev.supabase.coVITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_isi_kunci_proyek_devNilai aslinya ada di dashboard Supabase proyek development, di bagian pengaturan API. File ini tidak boleh masuk Git. Pastikan .gitignore berisi:
node_modules/dist/.env.env.*!.env.exampleBaris terakhir membuat satu pengecualian: .env.example boleh masuk Git. File ini berisi nama variabel tanpa nilai, supaya kamu (atau developer yang membantumu nanti) tahu apa saja yang harus diisi:
# Salin menjadi .env.local lalu isi nilainya. File ini boleh masuk Git, nilainya jangan.VITE_SUPABASE_URL=VITE_SUPABASE_PUBLISHABLE_KEY=Sebelum lanjut, cek riwayat Git-mu: git log --all --oneline -- .env .env.local. Kalau perintah itu menampilkan commit, berarti file rahasia pernah masuk repository. Ganti (rotate) kunci yang ada di dalamnya, karena menghapus file-nya sekarang tidak menghapusnya dari riwayat.
Langkah 3: Pasang penjaga kunci rahasia
Kamu sudah berhati-hati, tapi AI tidak selalu. Saat memperbaiki bug “data tidak bisa disimpan”, AI kadang mengambil jalan pintas: mengganti publishable key dengan secret key, karena secret key melewati semua aturan akses. Bug-nya hilang, dan seluruh database terbuka untuk umum.
Jangan mengandalkan ingatan untuk menangkap ini. Buat skrip yang memeriksa folder dist (hasil build, persis yang akan online) dan menggagalkan build kalau menemukan sesuatu yang mirip kunci rahasia:
// Memeriksa hasil build (folder dist) sebelum deploy.// Kalau ada yang mirip kunci rahasia, skrip berhenti dengan error dan deploy dibatalkan.import { readdirSync, readFileSync } from 'node:fs';import { join } from 'node:path';
const FOLDER = 'dist';
const pola = [ { nama: 'Supabase secret key', regex: /sb_secret_[A-Za-z0-9_-]{16,}/g }, { nama: 'Private key', regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g },];
// Kunci Supabase versi lama berbentuk JWT. Isi bagian tengahnya menyebut perannya.function jwtServiceRole(teks) { const temuan = []; for (const [jwt] of teks.matchAll(/eyJ[\w-]+\.eyJ[\w-]+\.[\w-]+/g)) { try { const isi = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64url').toString()); if (isi.role === 'service_role') temuan.push(jwt); } catch { // bukan JWT yang valid, lewati } } return temuan;}
function semuaFile(folder) { return readdirSync(folder, { withFileTypes: true }).flatMap((f) => f.isDirectory() ? semuaFile(join(folder, f.name)) : [join(folder, f.name)], );}
let jumlahMasalah = 0;for (const file of semuaFile(FOLDER)) { const teks = readFileSync(file, 'utf8'); for (const { nama, regex } of pola) { for (const [cocok] of teks.matchAll(regex)) { console.error(`BAHAYA: ${nama} di ${file}: ${cocok.slice(0, 14)}...`); jumlahMasalah++; } } for (const jwt of jwtServiceRole(teks)) { console.error(`BAHAYA: kunci service_role (JWT) di ${file}: ${jwt.slice(0, 14)}...`); jumlahMasalah++; }}
if (jumlahMasalah > 0) { console.error(`\n${jumlahMasalah} temuan. Deploy dibatalkan. Pindahkan kunci rahasia ke server.`); process.exit(1);}console.log(`Aman: tidak ada kunci rahasia di folder ${FOLDER}.`);Skrip ini mencari dua bentuk secret key Supabase: format baru yang diawali sb_secret_, dan format lama berupa JWT (teks panjang diawali eyJ) yang isinya menyebut peran service_role. Publishable key dan anon key lama tidak dianggap masalah, karena memang boleh ada di browser. Pola pencariannya sengaja meminta minimal 16 karakter setelah sb_secret_, karena library Supabase sendiri menyimpan teks sb_secret_ (tanpa kunci) untuk mengenali jenis kunci.
Sambungkan skrip ini ke perintah build di package.json, supaya selalu berjalan setelah Vite selesai:
{ "type": "module", "scripts": { "dev": "vite", "build": "vite build && node scripts/cek-rahasia.mjs" }}Tanda && berarti: jalankan perintah kedua hanya kalau yang pertama berhasil, dan anggap seluruh build gagal kalau perintah kedua gagal. Saat dicoba dengan kode yang benar, hasilnya:
$ npm run build✓ built in 263msAman: tidak ada kunci rahasia di folder dist.Lalu kami sengaja meniru kesalahan AI: mengganti VITE_SUPABASE_PUBLISHABLE_KEY dengan VITE_SUPABASE_SECRET_KEY di src/supabase.js. Build-nya langsung ditolak:
$ npm run build✓ built in 245msBAHAYA: Supabase secret key di dist\assets\index-BlsZOCSc.js: sb_secret_iniR...
1 temuan. Deploy dibatalkan. Pindahkan kunci rahasia ke server.Skrip ini adalah jaring pengaman, bukan jaminan. Ia hanya mengenali pola yang ia tahu. Untuk kunci layanan lain (API AI, pembayaran), tambahkan polanya ke daftar pola, dan tetap ikuti checklist keamanan aplikasi buatan AI.
Langkah 4: Deploy ke Cloudflare Pages lewat GitHub
Commit dan push semua perubahan di atas. Lalu, seperti di deploy pertama, buat proyek Pages yang terhubung ke GitHub (Workers & Pages → Create → Pages → hubungkan ke Git). Bedanya ada di pengaturan build:
| Pengaturan | Isi | Penjelasan |
|---|---|---|
| Production branch | main |
Branch yang menjadi versi production |
| Framework preset | None, atau preset framework-mu | Preset hanya mengisi dua baris di bawah. Isian manual tetap yang menentukan |
| Build command | npm run build |
Menjalankan Vite dan penjaga kunci rahasia |
| Build output directory | dist |
Folder hasil build |
Sebelum menekan tombol deploy, isi environment variable di pengaturan proyek (di bagian variabel dan secrets). Cloudflare Pages memisahkan nilai untuk Production dan Preview. Manfaatkan itu:
| Variabel | Production | Preview |
|---|---|---|
VITE_SUPABASE_URL |
URL proyek catatanku |
URL proyek catatanku-dev |
VITE_SUPABASE_PUBLISHABLE_KEY |
Publishable key catatanku |
Publishable key catatanku-dev |
Dengan begitu, alamat pratinjau dari branch lain (misalnya saat kamu mencoba fitur baru) memakai database development, dan hanya branch main yang menyentuh data pengguna.
Dua hal yang perlu diketahui:
- Variabel ini dipakai saat build. Kalau kamu mengubah nilainya, perubahan baru berlaku di deployment berikutnya. Jalankan ulang deployment (retry deployment) atau push commit baru.
- Kunci rahasia tidak perlu diisi di sini sama sekali. Aplikasi kita tidak memakainya, dan memang tidak boleh ada di build yang dikirim ke browser.
Tambahkan juga file .node-version berisi 24 di akar proyek. Cloudflare Pages membaca file ini untuk memilih versi Node saat build, sehingga hasilnya sama dengan di laptopmu.
Langkah 5: Atur alamat login di Supabase
Langkah ini paling sering terlupa, dan gejalanya membingungkan: aplikasi sudah online, pengguna mendaftar, lalu link konfirmasi di email membawa mereka ke localhost, halaman yang tentu tidak bisa dibuka di HP mereka.
Penyebabnya, Supabase perlu tahu alamat aplikasimu untuk mengarahkan pengguna kembali setelah login lewat email, magic link, atau akun Google. Di dashboard proyek production, buka Authentication → URL Configuration:
- Site URL: alamat utama aplikasimu, misalnya
https://catatanku.pages.dev(atau domain sendiri, kalau sudah dipasang). Nilai bawaannya adalah alamatlocalhost, jadi wajib diganti. - Redirect URLs: daftar alamat lain yang boleh dituju setelah login. Isi hanya alamat milikmu. Jangan memakai pola yang terlalu longgar, karena daftar ini mencegah orang lain membelokkan pengguna (lengkap dengan token login-nya) ke situs mereka.
Di proyek development, tambahkan alamat laptopmu (misalnya http://localhost:5173, alamat bawaan Vite) dan pola alamat pratinjau Cloudflare, misalnya https://*.catatanku.pages.dev/**.
Langkah 6: Uji di production, bukan hanya di laptop
Deploy berhasil bukan berarti aplikasinya benar. Uji di alamat aslinya, karena di sinilah konfigurasi yang salah ketahuan. Mulai dengan skrip uji cepat ini:
// Uji cepat setelah deploy: node scripts/cek-production.mjs https://catatanku.pages.dev// Opsional, uji database tanpa login: isi SUPABASE_URL dan SUPABASE_PUBLISHABLE_KEY di terminal.const situs = process.argv[2];if (!situs?.startsWith('https://')) { console.error('Pakai: node scripts/cek-production.mjs https://alamat-situsmu'); process.exit(1);}
let gagal = 0;const lulus = (pesan) => console.log(`LULUS ${pesan}`);const bahaya = (pesan) => { console.log(`GAGAL ${pesan}`); gagal++; };
// 1. Halaman utama terbukaconst utama = await fetch(situs);utama.ok ? lulus(`halaman utama (${utama.status})`) : bahaya(`halaman utama menjawab ${utama.status}`);
// 2. File yang tidak boleh ikut ter-deployfor (const path of ['/.env', '/.env.local', '/.git/config']) { const res = await fetch(new URL(path, situs)); const isi = await res.text(); const miripFileRahasia = /^\s*[A-Z][A-Z0-9_]*\s*=/m.test(isi) || isi.includes('[core]'); if (res.status === 404) lulus(`${path} tidak ada (404)`); else if (!miripFileRahasia) lulus(`${path} dijawab dengan halaman aplikasi (${res.status}), bukan isi file`); else bahaya(`${path} bisa diunduh! Hapus dari folder yang di-deploy`);}
// 3. Database tanpa login tidak boleh mengembalikan dataconst { SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, TABEL = 'pengeluaran' } = process.env;if (SUPABASE_URL && SUPABASE_PUBLISHABLE_KEY) { const res = await fetch(`${SUPABASE_URL}/rest/v1/${TABEL}?select=*&limit=1`, { headers: { apikey: SUPABASE_PUBLISHABLE_KEY }, }); const data = await res.json().catch(() => null); if (Array.isArray(data) && data.length === 0) lulus(`tabel ${TABEL} kosong untuk pengunjung tanpa login`); else if (Array.isArray(data)) bahaya(`tabel ${TABEL} bisa dibaca tanpa login! Periksa Row Level Security`); else console.log(`CEK tabel ${TABEL} menjawab ${res.status}: ${JSON.stringify(data)}`);}
console.log(gagal ? `\n${gagal} uji gagal. Jangan bagikan link-nya dulu.` : '\nSemua uji lulus.');process.exit(gagal ? 1 : 0);Skrip ini memeriksa tiga hal: halaman utama terbuka, file rahasia seperti .env tidak bisa diunduh, dan (kalau kamu mengisi alamat serta publishable key proyek production) tabel tidak mengembalikan data kepada pengunjung yang belum login.
Kenapa bagian kedua tidak cukup memeriksa kode 404? Karena Cloudflare Pages menganggap proyek tanpa file 404.html sebagai single-page application: alamat apa pun yang tidak ada dijawab dengan halaman utama, status 200. Jadi /.env tetap “terbuka”, tapi isinya halaman aplikasimu, bukan file rahasia. Skrip ini membedakan keduanya dari isinya.
Jalankan di terminal Git Bash (atau terminal macOS/Linux):
SUPABASE_URL=https://kode-proyek.supabase.co \SUPABASE_PUBLISHABLE_KEY=sb_publishable_isi_kunci_production \node scripts/cek-production.mjs https://catatanku.pages.devHasil yang diharapkan untuk aplikasi Vite di Cloudflare Pages:
LULUS halaman utama (200)LULUS /.env dijawab dengan halaman aplikasi (200), bukan isi fileLULUS /.env.local dijawab dengan halaman aplikasi (200), bukan isi fileLULUS /.git/config dijawab dengan halaman aplikasi (200), bukan isi fileLULUS tabel pengeluaran kosong untuk pengunjung tanpa login
Semua uji lulus.Kalau ada baris GAGAL, perbaiki dulu sebelum membagikan link. Baris CEK muncul kalau Supabase menjawab dengan pesan, bukan data. Misalnya, nama tabelnya salah: baca pesannya, lalu sesuaikan TABEL.
Setelah skrip lulus, ulangi uji dua akun dari tutorial database dan login di alamat production: daftar sebagai Budi, tambah catatan, lalu login sebagai Sari dan pastikan catatan Budi tidak terlihat. Skrip di atas hanya menguji pengunjung tanpa login. Kebocoran antar-pengguna hanya ketahuan lewat uji dua akun. Hapus kedua akun uji setelah selesai, supaya data production tetap bersih.
Langkah 7: Siapkan jalan mundur
Suatu hari, versi baru akan merusak sesuatu di production. Itu normal. Yang penting kamu bisa memulihkannya dalam hitungan menit, bukan jam.
- Kembalikan versi sebelumnya. Di daftar deployment proyek Pages, setiap deployment lama tersimpan. Pilih deployment terakhir yang sehat, lalu gunakan opsi Rollback untuk menjadikannya production lagi. Setelah itu, perbaiki masalahnya dengan tenang di branch lain.
- Uji di pratinjau dulu. Kerjakan fitur baru di branch terpisah. Cloudflare membuat alamat pratinjau untuk setiap branch, yang terhubung ke database development (langkah 4). Gabungkan ke
mainhanya setelah fiturnya lulus uji di sana. - Cadangkan data. Kode bisa di-rollback, data tidak. Cek fitur backup di paket Supabase-mu dan pahami cara memulihkannya. Kalau paketmu tidak menyediakan backup otomatis, ekspor datamu secara berkala.
- Pasang peringatan biaya dan pemakaian. Paket gratis punya batas pemakaian, dan proyek gratis yang lama tidak dipakai bisa dijeda otomatis. Baca ketentuan terbaru di dashboard layanan yang kamu pakai, dan nyalakan notifikasi email-nya.
Checklist sebelum link dibagikan
- Development dan production memakai proyek database yang berbeda.
- Struktur tabel dan aturan akses production dibuat dari file SQL yang sama dengan development.
-
.env.localtidak ada di repository maupun riwayat commit-nya. -
npm run buildmenjalankancek-rahasia.mjs, dan hasilnya “Aman”. - Environment variable Production dan Preview di Cloudflare Pages menunjuk ke proyek yang benar.
- Site URL dan Redirect URLs di Supabase production sudah diganti ke alamat aplikasimu.
-
cek-production.mjslulus semua. - Uji dua akun lulus di alamat production.
- Kamu tahu cara rollback, dan cara memulihkan data.
Error yang sering muncul
Halaman kosong, dan di Console browser muncul VITE_SUPABASE_URL atau VITE_SUPABASE_PUBLISHABLE_KEY belum diisi.
Environment variable belum diisi di Cloudflare Pages, namanya salah ketik, atau nilainya diisi setelah build terakhir. Periksa nama variabel di pengaturan proyek (harus persis, termasuk awalan VITE_), lalu jalankan ulang deployment.
Link konfirmasi email membuka localhost.
Site URL di Supabase production masih bernilai bawaan. Ganti di Authentication → URL Configuration (langkah 5). Link yang sudah terkirim tetap salah, jadi minta pengguna mendaftar atau meminta link ulang.
Build gagal dengan BAHAYA: Supabase secret key di dist/....
Penjagamu bekerja. Cari di folder src kata SECRET dan service_role, lalu kembalikan kodenya ke publishable key. Kalau sebuah fitur memang butuh secret key, fitur itu harus berjalan di server (misalnya Supabase Edge Functions atau Cloudflare Pages Functions), bukan di browser. Minta AI menjelaskan kenapa ia memakai secret key sebelum kamu menerima perbaikannya. Biasanya masalah aslinya ada di aturan akses.
Di laptop data muncul, di production kosong. Bisa jadi ini benar: production memakai database lain, yang memang masih kosong. Kalau setelah login dan menambah catatan datanya tetap tidak muncul, cek apakah aturan akses (file SQL dari langkah 1) sudah dijalankan di proyek production.
Langkah berikutnya: rilis dan amankan
Aplikasimu sudah online dengan aman. Langkah selanjutnya:
- Pasang domain sendiri lengkap dengan HTTPS, lalu perbarui Site URL di Supabase ke domain baru itu.
- Jalankan checklist keamanan aplikasi buatan AI dari awal sampai akhir, terutama bagian hak akses dan biaya.
- Pelajari celah yang paling umum di OWASP Top 10 untuk pemula, supaya kamu tahu apa yang harus dicari saat meninjau kode buatan AI.
Pertanyaan yang sering diajukan
Apakah publishable key aman walaupun bisa dilihat siapa saja?
Aman, selama aturan akses di setiap tabel benar. Publishable key hanya membuktikan bahwa permintaan datang dari aplikasimu. Yang menentukan data apa yang boleh dibaca adalah Row Level Security. Itu sebabnya skrip di langkah 6 menguji tabel memakai publishable key: siapa pun bisa melakukan hal yang sama.
Haruskah memakai dua proyek Supabase? Repot.
Untuk aplikasi yang dipakai orang lain, ya. Repotnya hanya di awal, dan manfaatnya terasa setiap kali kamu mencoba fitur baru tanpa takut merusak data pengguna. Paket gratis Supabase mengizinkan lebih dari satu proyek (cek batas terbarunya di halaman harga mereka).
Bisakah memakai Vercel atau Netlify, bukan Cloudflare Pages?
Bisa. Langkahnya sama: perintah build, folder output, environment variable terpisah untuk production dan preview, lalu uji di alamat aslinya. Nama menunya saja yang berbeda.
Aplikasiku dibuat di app builder dan deploy-nya cukup satu klik. Apakah artikel ini berlaku?
Prinsipnya tetap berlaku, walaupun langkahnya disederhanakan oleh tool-mu. Cari tahu apakah tool-mu memisahkan database development dan production, di mana environment variable disimpan, dan bagaimana cara kembali ke versi sebelumnya. Uji di langkah 6 tetap bisa kamu jalankan ke alamat aplikasi apa pun.
Kesimpulan
Merilis aplikasi dengan database berarti menjaga tiga hal: data pengguna terpisah dari data percobaan, kunci rahasia tidak pernah sampai ke browser, dan setiap rilis diuji di alamat aslinya. Dengan dua proyek Supabase, environment variable yang benar, penjaga cek-rahasia.mjs, dan uji production, kamu bisa merilis versi baru tanpa deg-degan, dan kembali ke versi lama kalau ada yang salah.
Ini langkah pertama Level 3 jalur Vibe Coding. Begitu aplikasimu dipakai orang, kamu akan menemui bagian yang tidak bisa (dan tidak seharusnya) kamu tangani sendirian. Kenali tandanya di kapan aplikasi vibe coding butuh developer.



