Kenapa Serverless Next.js Lu Tiba-tiba Error 'Too Many Connections'?
Bedah akar masalah connection exhaustion di environment serverless, cara kerja connection pool, dan implementasi PgBouncer atau Neon connection pooling di Prisma.
Bedah akar masalah connection exhaustion di environment serverless, cara kerja connection pool, dan implementasi PgBouncer atau Neon connection pooling di Prisma.
Artikel ini membahas pendekatan praktis yang bisa langsung kamu implementasikan di proyek nyata. Kita akan bedah dari fondasi sampai optimasi, lengkap dengan contoh kode yang bisa langsung di-copy.
Mengapa Hal Ini Penting?
Banyak developer langsung loncat ke kode tanpa memahami konteks arsitektural di baliknya. Padahal, keputusan yang diambil di awal proyek punya dampak besar terhadap maintainability, performa, dan developer experience jangka panjang.
Arsitektur yang baik bukan tentang over-engineering, tapi tentang membuat keputusan yang tepat di waktu yang tepat.
Berikut beberapa masalah umum yang sering muncul ketika fondasi tidak dipikirkan matang:
- Technical debt menumpuk karena quick hack yang jadi permanen
- Onboarding developer baru jadi lambat karena kode tidak terstruktur
- Performa menurun seiring fitur bertambah
Setup Awal & Konfigurasi
Langkah pertama adalah memastikan tooling dan konfigurasi dasar sudah benar. Ini fondasi yang sering dilewati tapi krusial.
Inisialisasi Project
# Buat project baru
npx create-next-app@latest my-project --typescript --tailwind --app
cd my-project
# Install dependencies tambahan
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kitStruktur Folder yang Scalable
src/
├── app/ # Next.js App Router pages
│ ├── (marketing)/ # Public pages group
│ ├── admin/ # Admin dashboard group
│ └── api/ # API routes
├── components/ # Reusable UI components
│ ├── ui/ # Primitives (Button, Input, etc.)
│ └── features/ # Feature-specific components
├── lib/ # Utilities & helpers
├── db/ # Database schema & connection
└── services/ # Business logic layerPemisahan antara components/ui dan components/features kelihatan sepele, tapi ini membuat komponen tetap focused dan mudah di-maintain. Komponen di ui/ tidak boleh punya business logic — murni presentational.
Implementasi Core Logic
Sekarang masuk ke bagian inti. Kita akan implementasi pattern yang solid untuk menangani data flow.
Database Schema
// src/db/schema.ts
import { pgTable, serial, varchar, timestamp, jsonb } from 'drizzle-orm/pg-core'
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title').notNull(),
slug: varchar('slug').notNull().unique(),
content: jsonb('content'),
status: varchar('status').default('draft'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
})Perhatikan penggunaan jsonb untuk kolom content. Ini memberikan fleksibilitas untuk menyimpan rich text dalam format Lexical JSON tanpa perlu migrasi schema setiap kali format konten berubah.
Service Layer
// src/services/posts.ts
import { db } from '../db'
import { posts } from '../db/schema'
import { eq, desc } from 'drizzle-orm'
export async function getPublishedPosts(limit = 10) {
return db
.select()
.from(posts)
.where(eq(posts.status, 'published'))
.orderBy(desc(posts.createdAt))
.limit(limit)
}
export async function getPostBySlug(slug: string) {
const [post] = await db
.select()
.from(posts)
.where(eq(posts.slug, slug))
.limit(1)
return post ?? null
}Pattern ini memisahkan database query dari route handler, sehingga logic bisa di-reuse di berbagai tempat (API route, server component, CLI script) tanpa duplikasi.
Optimasi & Best Practices
Setelah core functionality jalan, berikut beberapa optimasi yang worth dilakukan:
- Caching Strategy: Gunakan
unstable_cacheatau ISR untuk mengurangi beban database pada halaman yang jarang berubah. - Connection Pooling: Di environment serverless, pastikan menggunakan pooled connection (Neon dengan
@neondatabase/serverlesssudah handle ini secara otomatis). - Error Boundary: Implementasikan
error.tsxdi setiap route segment untuk graceful error handling.
Monitoring & Observability
// Middleware untuk logging request time
export async function middleware(request: Request) {
const start = Date.now()
const response = await fetch(request)
const duration = Date.now() - start
console.log(`[${request.method}] ${new URL(request.url).pathname} - ${duration}ms`)
return response
}Kesimpulan
Membangun arsitektur yang solid bukan tentang memilih stack paling keren, tapi tentang membuat keputusan yang tepat untuk konteks project kamu. Beberapa takeaway utama:
- Mulai dari fondasi yang benar — setup project structure, tooling, dan konfigurasi sebelum menulis fitur.
- Pisahkan concern — database, business logic, dan presentasi harus di layer yang berbeda.
- Automate repetitive tasks — gunakan CI/CD pipeline dan code generation untuk mengurangi human error.
- Monitor dari awal — jangan tunggu production issue untuk mulai logging dan monitoring.
Dengan pendekatan ini, kamu tidak hanya membangun produk yang jalan, tapi produk yang scalable, maintainable, dan menyenangkan untuk dikembangkan.
Butuh penyesuaian khusus untuk project Anda?
Jika situasi operasional atau arsitektur sistem bisnis Anda membutuhkan solusi kustom, diskusikan langsung bersama tim engineer kami.
Mulai Project