D
P
0
← Semua artikel Read in English

Next.js & React di Produksi

Route Detail 500 Cuma di Produksi, `pnpm dev` Aman? `DYNAMIC_SERVER_USAGE` dari `generateStaticParams` Kosong yang Baca `searchParams`

· · 3 menit baca
Route Detail 500 Cuma di Produksi, `pnpm dev` Aman? `DYNAMIC_SERVER_USAGE` dari `generateStaticParams` Kosong yang Baca `searchParams`

Sebuah situs klien berbasis Next.js 16 punya halaman detail koin di route /coins/[slug]. Tiap koin dapat halamannya sendiri: harga, statistik, dan sebuah chart yang bisa diganti rentang waktunya lewat tombol 24 jam, 7 hari, 30 hari. Di lokal semua mulus. Saya jalankan pnpm dev, buka /coins/bitcoin, dapat 200 dengan render penuh. Saya push ke Railway, build sukses, buka URL produksi yang persis sama, dan dapat Internal Server Error. HTTP 500. Bukan sekali, tapi konsisten, dan selalu setelah loading sekitar lima detik.

Yang bikin ini menonjol: bukan cuma satu koin. Semua route /coins/[slug] 500 di produksi. Sementara itu route lain, homepage, halaman list, halaman about, semuanya 200 dan sehat. Jadi ada sesuatu yang spesifik di template detail koin ini yang tumbang, dan hanya di build produksi.

Jalan buntu pertama

Tebakan pertama saya salah, dan saya buang waktu di situ. Karena halaman detail baca banyak field dari API, saya curiga ada null-deref. Salah satu field yang mencurigakan adalah athDate, tanggal all-time-high yang kadang belum tersedia untuk koin baru. Saya pikir render meledak karena akses properti pada nilai null, jadi saya tambal dengan optional chaining di mana-mana.

const ath = coin.athDate?.toLocaleDateString();

Push lagi. Masih 500. Tentu saja, karena null-deref itu tidak pernah benar-benar terjadi. Kalau memang ada TypeError, dev juga akan meledak, dan dev bersih. Saya menembak gejala yang salah karena saya belum membaca log yang sebenarnya. Pelajaran klasik: jangan menambal sebelum tahu error-nya.

Membaca error yang sebenarnya

Dev hijau, produksi merah, kode yang sama persis di keduanya. Pola ini sudah saya bongkar di tulisan sebelumnya soal DYNAMIC_SERVER_USAGE yang dipicu draftMode() di rantai fetch komponen bersarang: bedanya ada di jalur optimasi statis yang cuma dijalankan next build. Jadi saya berhenti menebak dan langsung mereproduksi kondisi produksi di mesin sendiri:

pnpm build && pnpm start

500 itu langsung muncul di lokal. Sekarang saya bisa baca stderr asli, bukan menebak dari halaman error klien. Digest-nya gamblang:

Error: digest 'DYNAMIC_SERVER_USAGE'

Digest yang sama, route yang beda. Sisanya tinggal mencari API dinamis mana yang tersentuh kali ini.

Akar masalah: searchParams plus generateStaticParams kosong

Route detail koin punya dua hal yang, sendiri-sendiri, tampak tidak berbahaya.

Pertama, generateStaticParams()-nya mengembalikan array kosong:

export async function generateStaticParams() {
  return [];
}

Niatnya sama seperti dulu, jangan pre-render koin apa pun saat build. Jebakannya juga sama: array kosong bukan cara mematikan optimasi statis, dan di Next.js 16 route ini tetap masuk hitungan sebagai kandidat statis.

Kedua, page-nya membaca searchParams untuk tahu rentang chart yang aktif:

export default async function CoinPage({ searchParams }) {
  const range = searchParams.range ?? "24h";
  const chart = await getCoinChart(slug, range);
  // ...
}

searchParams adalah API dinamis. Membacanya menandai render sebagai bergantung pada request. Di sinilah konfliknya: Next.js mencoba meng-optimasi route secara statis karena generateStaticParams kosong, tapi render-nya menyentuh searchParams yang menuntut konteks request. Statis versus dinamis bertabrakan, dan tabrakan itu meledak jadi DYNAMIC_SERVER_USAGE di request time, cuma di build produksi. Di dev tabrakan itu tidak pernah terjadi karena searchParams selalu punya request untuk disandarkan.

Bedanya dengan kasus draftMode() tadi: di sini tidak ada yang bersarang. searchParams dibaca langsung di baris paling atas page. Justru karena kelihatan biasa, ia gampang terlewat.

Perbaikannya

Satu baris. Buang generateStaticParams kosong itu, ganti dengan deklarasi dinamis eksplisit:

export const dynamic = "force-dynamic";

searchParams kembali punya konteks request yang sah. 500 hilang, dan tombol rentang chart bekerja seperti seharusnya.

Pelajaran