โ† Semua pembelajaran / Astro Nol โ†’ Portal Berita
Fase 1 ยท Routing, Layout & Model Rendering

Rute dinamis & getStaticPaths

getStaticPaths adalah tempat kamu memutuskan berapa banyak halaman yang dirender saat build. Untuk portal berita dengan arsip besar, keputusan itu punya konsekuensi langsung ke lama build dan ke biaya.

Sumber asli docs.astro.build Resmi Rangkuman ~9 menit baca

Intisari

  • getStaticPaths() wajib ada di rute dinamis yang di-prerender, dan dilarang ada di rute on-demand.
  • Ia berjalan sekali saat build. Artikel yang terbit setelah build tidak punya halaman sampai kamu build ulang.
  • Untuk arsip 200 ribu artikel, prerender semuanya berarti build berjam-jam. Pola yang benar: prerender yang panas, on-demand untuk sisanya.
  • props dari getStaticPaths menghindari kueri kedua per halaman โ€” ambil datanya sekalian di sana.
  • paginate() menangani halaman daftar, termasuk menghitung total dan tautan sebelumnya/berikutnya.

Bentuk dasarnya

---
// src/pages/berita/[slug].astro
import type { GetStaticPaths } from "astro";
import { ambilSemuaSlugTerbit, ambilArtikelPublik } from "@/lib/artikel";

export const getStaticPaths: GetStaticPaths = async () => {
  const artikel = await ambilSemuaSlugTerbit();

  return artikel.map((a) => ({
    params: { slug: a.slug },
    props: { artikel: a },     // ikut terkirim, tidak perlu kueri lagi
  }));
};

const { artikel } = Astro.props;
---
<h1>{artikel.judul}</h1>

Nama kunci di params harus sama persis dengan nama di kurung siku pada nama berkas. Berkas [slug].astro butuh params: { slug: โ€ฆ }. Salah nama tidak menghasilkan error yang jelas โ€” halamannya cuma tidak pernah dibuat.

Pakai props, bukan kueri ulang. Tanpa props, tiap halaman akan memanggil ambilArtikelPublik(slug) sendiri โ€” 200.000 halaman berarti 200.001 kueri. Dengan props, datanya sudah ikut dari kueri pertama. Ini perbedaan antara build lima menit dan build lima puluh menit.

Keputusan yang sebenarnya: berapa yang di-prerender?

Portal berita punya distribusi trafik yang sangat timpang. Beberapa ribu artikel terbaru menyerap hampir seluruh pembaca; ratusan ribu artikel arsip diakses sesekali dari pencarian. Merender semuanya saat build berarti membayar waktu build untuk halaman yang mungkin tidak dibuka sebulan sekali.

StrategiWaktu buildArtikel baru munculCocok untuk
Prerender semuaJam-jaman di arsip besarSetelah build berikutnyaSitus < 5.000 halaman
On-demand semuaDetikLangsungPortal berita dengan CDN di depan
HibridaMenitLangsung untuk yang on-demandKalau butuh jaminan latensi di halaman tertentu

Untuk kasusmu, on-demand + cache Cloudflare hampir selalu jawabannya. Halaman dirender sekali saat pembaca pertama membukanya, lalu dilayani dari edge untuk semua orang berikutnya sampai TTL habis atau kamu purge tag-nya. Hasil akhirnya menyerupai statis dari sisi pembaca, tapi artikel baru langsung hidup tanpa build ulang โ€” dan itu syarat mutlak untuk redaksi yang menerbitkan puluhan artikel per hari. Mekanismenya di Fase 8.

Hibrida: prerender yang panas saja

---
// Prerender 500 artikel terbaru; sisanya dirender saat diminta.
export const prerender = true;

export const getStaticPaths: GetStaticPaths = async () => {
  const panas = await ambilArtikelTerbaru("semua", 500);
  return panas.map((a) => ({ params: { slug: a.slug }, props: { artikel: a } }));
};
---

Ini butuh output: "server" plus fallback yang sesuai di adapter โ€” dan jujur saja, kerumitannya jarang sepadan kalau CDN-mu sudah bekerja. Ketahui bahwa polanya ada, lalu pilih on-demand sampai ada pengukuran yang menuntut sebaliknya.

Paginasi

---
// src/pages/berita/[...halaman].astro  โ†’  /berita, /berita/2, /berita/3
import type { GetStaticPaths, Page } from "astro";
import { ambilSemuaTerbit } from "@/lib/artikel";

export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
  const artikel = await ambilSemuaTerbit();
  return paginate(artikel, { pageSize: 20 });
};

const { page } = Astro.props as { page: Page<Artikel> };
---
<h1>Berita โ€” halaman {page.currentPage} dari {page.lastPage}</h1>

<ul>
  {page.data.map((a) => <li><a href={`/berita/${a.slug}`}>{a.judul}</a></li>)}
</ul>

<nav>
  {page.url.prev && <a href={page.url.prev} rel="prev">โ† Sebelumnya</a>}
  {page.url.next && <a href={page.url.next} rel="next">Berikutnya โ†’</a>}
</nav>

Jangan paginasi seluruh arsip. ambilSemuaTerbit() di atas memuat seluruh tabel ke memori โ€” di 200.000 artikel itu ratusan megabita, dan halaman ke-9.000 tidak pernah dikunjungi manusia, hanya crawler. Batasi paginasi ke beberapa ratus halaman pertama dan alihkan sisanya ke pencarian. Google juga tidak akan merayapi paginasi sedalam itu.

Beberapa parameter dan rute rest

---
// src/pages/perusahaan/[kode]/[tab].astro
export const getStaticPaths: GetStaticPaths = async () => {
  const perusahaan = await ambilSemuaPerusahaan();
  const tab = ["ringkasan", "keuangan", "pemegang-saham"];

  return perusahaan.flatMap((p) =>
    tab.map((t) => ({ params: { kode: p.kode, tab: t }, props: { perusahaan: p } })),
  );
};
---
// Rute rest: [...jalur].astro
// /a/b/c  โ†’  Astro.params.jalur === "a/b/c"
// /       โ†’  Astro.params.jalur === undefined  (bukan string kosong)

Perbedaan undefined dan string kosong itu nyata dan menggigit. params.jalur.split("/") akan meledak di rute akar. Selalu tangani undefined-nya.

Kesalahan yang paling sering

GejalaPenyebab
Halaman 404 padahal datanya adaNama kunci params tidak sama dengan nama di kurung siku
getStaticPaths ignoredHalaman itu prerender = false. Di rute on-demand, pakai Astro.params langsung
Build makan RAM sampai OOMSeluruh tabel dimuat ke memori di getStaticPaths
Artikel baru tidak munculHalaman di-prerender; ia butuh build ulang
Angka jadi "1", bukan 1params selalu string. Konversi sendiri, dan validasi hasilnya

Latihan: buat berita/[slug].astro dengan getStaticPaths yang mengembalikan 10 artikel palsu berikut props. Jalankan pnpm build dan hitung berkas HTML di dist/berita/. Lalu hapus props-nya dan ambil ulang data per halaman; tambahkan console.log di fungsi pengambilan dan bandingkan berapa kali ia terpanggil di kedua versi. Terakhir, ubah halamannya jadi prerender = false dan amati apa yang terjadi pada getStaticPaths.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.