2026-06-06 · 7 min
Bun 1.3 + Supabase Auth: Migrasi SaaS dari Next
Klien SaaS HR (~340 paying company, 4,200 user aktif harian) saya operate sejak 2024. Auth lama: Next.js + Auth.js v5 + custom Postgres adapter. Dua bulan lalu saya migrasi ke Bun + Hono + Supabase Auth. Hasil terukur.
Setup
App lama:
- Next.js 14 di Vercel Pro
- Auth.js v5 dengan Postgres adapter
- Custom JWT signing untuk machine-to-machine
- Self-hosted email via Postmark
App baru:
- Bun 1.3 + Hono v4 di Hetzner VPS (cx41, 4 vCPU, 16GB)
- Supabase Auth (hosted, free tier untuk 50k MAU)
- JWT verify pakai Supabase JWKS endpoint
- Email magic link via Supabase (Postmark integration)
Decision driver: cost + cold start. Vercel function cold start untuk auth endpoint pernah hit 3,1 detik di P99, dan login flow Auth.js lumayan boilerplate-heavy.
Cost real (bulanan)
Sebelum:
- Vercel Pro: $20 base + ~$95 function execution = $115
- Postmark: $25 (50k email/mo)
- Postgres Hetzner: $25
- Total: $165/mo
Sesudah:
- Hetzner cx41: $25 (sama, hosting Bun app)
- Supabase paid tier: $25 (free tier MAU cukup, tapi storage + bandwidth bikin auto-upgrade)
- Postmark: $0 (Supabase yang handle)
- Total: $50/mo
Saving: $115/mo (~Rp 1,8jt). Annualized: Rp 22jt.
Latency
Login flow end-to-end (user click “login” → dashboard ter-render):
Sebelum:
- P50: 1,4 detik
- P95: 2,8 detik (cold start affecting)
- P99: 4,5 detik
Sesudah:
- P50: 380ms
- P95: 720ms
- P99: 1,1 detik
Cold start hilang karena Bun app long-running di VPS. Supabase Auth verification ~80ms tambahan (JWKS fetch + verify), tapi total tetap jauh lebih cepat.
Yang dirombak (5 hari kerja)
Hari 1: Setup Hono + Bun, basic route, integrate Supabase JS client. Replace Auth.js session cookie dengan Supabase session.
Hari 2: Migrate user data. Pakai Supabase Auth Admin API untuk import 8,400 existing user (dengan hashed password, OAuth provider mapping). Script bash + Bun. Run 47 menit. Verify count match.
Hari 3: Rewrite middleware. Pattern lama:
// Next.js middleware.ts
import { auth } from '@/auth';
export default auth((req) => {
if (!req.auth) return NextResponse.redirect('/login');
});
Pattern baru:
// Hono middleware
import { createClient } from '@supabase/supabase-js';
app.use('/api/*', async (c, next) => {
const token = c.req.header('authorization')?.replace('Bearer ', '');
if (!token) return c.json({ error: 'No auth' }, 401);
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
const { data: { user }, error } = await supabase.auth.getUser(token);
if (error || !user) return c.json({ error: 'Invalid' }, 401);
c.set('user', user);
await next();
});
Hari 4: RLS migration. Supabase Postgres native RLS. Saya tulis policy untuk 28 table. Test dengan dummy user dari 3 tenant.
Hari 5: E2E test + canary deploy. 5% traffic ke new auth selama 24 jam. Monitor error rate. OK, full cutover.
Yang break
-
OAuth callback URL: GitHub, Google OAuth app harus diupdate dengan new callback URL. Saya lupa update GitHub OAuth, 12 user login GitHub error selama 40 menit sebelum saya notice. Fix: update + clear comm ke user via in-app notification.
-
Session migration: User yang active session di Auth.js lama tetap valid sampai expire. Saya tidak invalidate manual. Result: 7 hari first week, user pelan-pelan auto re-login. Saya tambah message: “Login ulang untuk fitur baru.” Adoption 92% dalam 5 hari.
-
Machine-to-machine token: Existing M2M token (untuk klien yang pakai webhook outbound) pakai signature lama. Saya keep dual-verify (old JWT + new Supabase JWT) selama 30 hari. Setelah itu deprecate. 4 klien aktif refresh token, 0 issue.
-
Bun + Supabase JS bug: Supabase JS v2.45 punya bug di Bun runtime untuk WebSocket realtime. Saya tidak pakai realtime untuk auth, jadi skip. Tapi catat untuk future.
Memory
Hetzner VPS RSS:
- Bun + Hono app: 145MB stable
- Postgres (auth tables migrated to Supabase): 280MB → 90MB (lebih kecil setelah auth offload)
Total memory usage VPS turun 35%. Bisa downsize ke cx31 ($15/mo) kalau klien mau, tapi saya rekomendasi keep cx41 untuk headroom.
Yang Supabase Auth tidak kasih
- Custom claim di JWT: bisa, tapi via Postgres function. Lebih complex dari Auth.js callback pattern.
- Multi-tenancy native: Supabase Auth single user pool. Kalau butuh hard tenant isolation di level auth: butuh extra work via RLS atau separate Supabase project.
- Granular session management: list active session per user, revoke specific session — limited. Auth.js dulu saya bisa build sendiri di Postgres.
Untuk klien saya: tidak ada blocker. Tapi worth check requirement Anda.
Verdict
Untuk SaaS Indonesia dengan SMB scale dan cost-sensitive: kombinasi Bun + Hono + Supabase Auth sangat compelling. Cost turun 70%, latency turun 70%, security model lebih audit-able.
Bukan untuk fintech yang butuh fully isolated auth stack (audit trail wajib in-house) atau app dengan custom auth flow yang Supabase tidak support (passkey + biometric advanced).
Migration cost (5 hari + 2 minggu monitor): Rp 12jt billed ke klien. Break-even di 7 bulan dari hosting saving. Sekarang sudah 2 bulan, on track.
Bukan magic, butuh perencanaan migration data + dual-verify period. Worth-it untuk most SMB SaaS.
Ditulis oleh Reza Pradipta