Kodeloka
  1. Kodeloka
  2. Kelas Next.js
  3. Materi

Data & Caching Β· Modul 13 dari 23

Partial Prerendering & Streaming

Menggabungkan kecepatan halaman statis dengan kesegaran data dinamis dalam satu respons: kerangka statis dikirim seketika, bagian dinamis mengalir menyusul.

🎯 Poin-Poin Utama Modul Ini:
  • Sebelumnya satu halaman harus memilih: seluruhnya statis ATAU seluruhnya dinamis
  • PPR mengirim kerangka statis lebih dulu, lalu mengalirkan bagian dinamis
  • Suspense menentukan batas antara bagian statis dan dinamis
  • Fallback Suspense yang buruk menyebabkan pergeseran tata letak

πŸ“Œ Masalah yang Diselesaikan

Halaman produk berisi:
  β”œβ”€ Header, navigasi, footer      β†’ sama untuk semua orang (statis)
  β”œβ”€ Deskripsi & gambar produk     β†’ jarang berubah (statis)
  β”œβ”€ Harga & stok terkini          β†’ harus segar (dinamis)
  └─ "Direkomendasikan untuk Anda" β†’ per pengguna (dinamis)

Model lama: satu bagian dinamis membuat SELURUH halaman
dirender ulang tiap permintaan. Header yang tidak pernah
berubah pun ikut menunggu kueri stok selesai.

Partial Prerendering memecah asumsi itu. Kerangka statis sudah jadi sejak waktu build dan dikirim seketika; lubang-lubang dinamisnya diisi menyusul lewat streaming dalam respons yang sama.

πŸ’» Suspense sebagai Penanda Batas

app/produk/[id]/page.tsx
import { Suspense } from 'react'

export default async function HalamanProduk({ params }) {
  const { id } = await params      // params adalah Promise di Next 15+
  const produk = await ambilProduk(id)   // ber-'use cache' β†’ statis

  return (
    <main>
      {/* Semua ini masuk kerangka statis */}
      <h1>{produk.nama}</h1>
      <GambarProduk src={produk.gambar} />
      <p>{produk.deskripsi}</p>

      {/* Lubang dinamis: dialirkan menyusul */}
      <Suspense fallback={<SkeletonHarga />}>
        <HargaDanStok id={id} />
      </Suspense>

      <Suspense fallback={<SkeletonRekomendasi />}>
        <Rekomendasi untukPengguna />
      </Suspense>
    </main>
  )
}

// Komponen ini membaca data segar tiap permintaan
async function HargaDanStok({ id }: { id: string }) {
  const { harga, stok } = await db.produk.harga(id)
  return <div>Rp{harga} Β· sisa {stok}</div>
}
πŸ’‘ Aturannya sederhana

Apa pun yang berada di dalam <Suspense> dan menyentuh data dinamis β€” cookies(), headers(), searchParams, atau kueri tanpa cache β€” menjadi lubang dinamis. Sisanya masuk kerangka statis. Anda mengendalikan pembagiannya lewat penempatan batas Suspense.

πŸ”‘ API Dinamis Kini Asinkron

// Next 14 β€” sinkron
const cookieStore = cookies()
const token = cookieStore.get('token')

// Next 15+ β€” WAJIB di-await
const cookieStore = await cookies()
const token = cookieStore.get('token')

const headersList = await headers()
const { id } = await params
const { q } = await searchParams
⚠️ Ini perubahan yang merusak kode lama

cookies(), headers(), params, dan searchParams menjadi asinkron sejak Next 15. Perubahan ini yang memungkinkan Next menunda evaluasi bagian dinamis sampai kerangka statis terkirim. Kode dari tutorial Next 14 akan gagal atau memberi peringatan. Tersedia codemod untuk migrasi otomatis:

npx @next/codemod@latest next-async-request-api .

🎨 Fallback yang Baik Menentukan Kualitas

// ❌ Menyebabkan pergeseran tata letak β€” tinggi berubah saat data tiba
<Suspense fallback={<p>Memuat…</p>}>

// βœ… Skeleton berukuran sama dengan konten aslinya
<Suspense fallback={
  <div className="h-14 w-40 animate-pulse rounded bg-neutral-200" />
}>
🚫 Cumulative Layout Shift adalah biaya tersembunyi streaming

Streaming memperbaiki waktu tampil pertama, tetapi bila fallback berukuran berbeda dari konten aslinya, halaman akan "meloncat" saat data tiba. Pengguna yang sedang membaca atau hendak menekan tombol akan terganggu, dan skor Core Web Vitals Anda turun. Buat skeleton dengan dimensi yang sama persis dengan konten yang akan menggantikannya.

βš™οΈ Mengaktifkan & Memeriksa

// next.config.ts β€” nama flag berbeda antar versi,
// periksa dokumentasi untuk versi yang Anda pakai
export default {
  experimental: {
    ppr: 'incremental',   // aktifkan per-rute
  },
}

// lalu pada rute yang diinginkan:
export const experimental_ppr = true
# Melihat rute mana yang statis, dinamis, atau parsial
npm run build

# Keluarannya menandai tiap rute:
#   β—‹  Static     β€” dirender saat build
#   ●  SSG        β€” statis dengan generateStaticParams
#   Ζ’  Dynamic    β€” dirender tiap permintaan
#   ◐  Partial    β€” kerangka statis + lubang dinamis
πŸ“Œ Baca keluaran build setiap kali

Satu pemanggilan cookies() yang tidak sengaja berada di luar Suspense akan mengubah seluruh rute menjadi dinamis, dan Anda kehilangan manfaat prerendering tanpa peringatan apa pun. Tabel rute di akhir next build adalah cara tercepat menangkapnya β€” jadikan kebiasaan memeriksanya sebelum deploy.