Skip to content

Rute Statis Membayangi Rute Dinamis di Next.js App Router

Adityo Guni Waluyo

Satu slug, dua rute, satu pemenang. Kenapa rubrik OLAHRAGA kosong di halaman publik dan cara Next.js 14 memutus rute statis vs dinamis.

Ringkasan

Jadi rubrik OLAHRAGA kosong bukan karena query salah, tapi karena folder statis informasi/olahraga selalu menang lawan rute dinamis, jadi halaman generik nggak pernah jalan. Bug kedua: fetch di Next.js 14 defaultnya force-cache, konten baru nunggu invalidasi manual. Solusinya kasih cache no-store biar pengumuman langsung tampil.

Pemeriksaan paritas konten di project KotaPortal menandai satu anomali: rubrik OLAHRAGA tampil tanpa satu pun pengumuman di sisi publik, sementara enam rubrik lain (UMUM, PROMOSI, WISATA, BUDAYA, KEPEMUDAAN, EKRAF) terisi normal. Data ada di database. API mengembalikannya. Hanya halaman publiknya yang kosong.

Dugaan pertama jatuh ke rute dinamis informasi/[kategori]. Komponen halaman itu dibedah: pemetaan enum, query per kategori, struktur daftar. Semua benar. Untuk slug olahraga, komponen itu ternyata tidak pernah dieksekusi. Mencari cacat di sana memang tidak akan menemukan apa pun, karena kode yang kosong itu bukan kode yang bermasalah melainkan kode yang tidak pernah dijalankan.

Rute yang Menang Tidak Selalu Rute yang Diperiksa

Penyebabnya ada di struktur folder:

frontend/src/app/(public)/informasi/
├── olahraga/page.tsx     # statis, dibuat khusus
└── [kategori]/page.tsx   # dinamis, rubrik generik

Sebuah halaman khusus untuk olahraga pernah dibuat berdampingan dengan rute generik. Sesuai dokumentasi App Router, sebuah segmen "may be static (a literal value matched exactly) or dynamic (a placeholder that captures a value from the URL)" [1]. Ketika keduanya cocok untuk URL yang sama, aturan prioritas berlaku: "Predefined routes take precedence over dynamic routes, and dynamic routes over catch all routes" [2]. Folder literal menang. URL /informasi/olahraga selalu dirender oleh halaman statis, dan daftar pengumuman generik milik rute dinamis tidak pernah mendapat giliran.

Perbaikannya: halaman khusus itu tetap ada, tapi dapat bagian pengumuman yang isinya setara dengan halaman rubrik generik. Satu slug, dua jalur render, wajib satu hasil.

Bug Kedua yang Sama-Sama Tidak Terlihat dari Diff

Paritas struktur diperbaiki, tetapi pengumuman baru masih tidak muncul. Penyebab berikutnya ada di frontend/src/lib/api/endpoints.ts, pada fungsi getAnnouncements. Pada Next.js 14, fetch di server secara default memakai force-cache: "This request should be cached until manually invalidated ... force-cache is the default and can be omitted" [3]. Konten baru tertahan di cache sampai ada invalidasi manual, dan tidak ada satu pun jejaknya di baris kode yang berubah.

Perbaikannya satu opsi:

// getAnnouncements, frontend/src/lib/api/endpoints.ts
fetch(url, { cache: "no-store" })

no-store membuat permintaan "refetched on every request" [3]. Untuk daftar pengumuman yang harus tampil segera setelah dipublikasikan, opsi ini yang sesuai. Sebagai pembanding, Next.js 15 mengubah perilaku ini: "fetch requests are no longer cached by default" [4]. Pada project yang masih berada di Next.js ^14.2.0 dengan React ^18.3.0, default lama masih aktif dan tetap harus disetel eksplisit.

Verifikasi di Proyek Sendiri

Tiga langkah singkat, masing-masing punya hasil yang bisa dibandingkan:

  1. Tambahkan console.log di informasi/[kategori]/page.tsx, jalankan next dev, buka /informasi/olahraga. Jika log tidak pernah tercetak, folder statis yang merender. Ganti URL ke slug lain; log seharusnya muncul.
  2. Bandingkan isi halaman statis dengan halaman rubrik generik pada slug lain. Bagian yang ada di satu halaman tetapi tidak di halaman khusus adalah daftar paritas yang perlu ditutup.
  3. Publikasikan satu item konten baru, muat ulang halaman daftarnya. Jika item baru hanya muncul setelah proses build ulang, fetch masih terkena force-cache.

Dua kegagalan dalam satu commit ini berbagi sifat yang sama: keduanya tidak menghasilkan error. Keputusan resolusi rute dan default cache fetch sama-sama bekerja di luar kode yang ditulis, sehingga diff tampak wajar dan pemeriksaan biasa lolos. Paritas konten akhirnya ketahuan karena satu pemeriksaan yang membandingkan output render antar-rute, bukan isi tiap rute secara terpisah.

---

Sources

[1] Next.js Docs — Dynamic Route Segments (App Router)

[2] Next.js Docs — Dynamic Routes caveats (Wayback snapshot 2023-05-03)

[3] Next.js 14 Docs — fetch

[4] Next.js Docs — How to upgrade to version 15

Artikel terkait