Skip to main content
BlogWeb Dev

Next.js Partial Prerendering: Shell Statis, Lubang Dinamis

Partial Prerendering memungkinkan satu route mengirim shell statis yang sudah dibangun sebelumnya secara instan, sementara konten dinamis yang bergantung pada waktu permintaan di-streaming secara paralel ke dalam batas Suspense.

Matthews Wong

Software Engineer

Juli 202610 menit baca
Next.js Partial Prerendering: Shell Statis, Lubang Dinamis

Foto oleh markus spiske on flickr

Sebagian besar perdebatan rendering di Next.js berujung pada pilihan biner: render seluruh route saat build demi kecepatan, atau render per permintaan demi kesegaran data. Partial Prerendering, atau PPR, menolak pilihan biner itu. PPR membiarkan satu route menyajikan shell statis secara instan, sementara bagian yang benar-benar membutuhkan data waktu permintaan baru di-streaming belakangan, dalam respons yang sama.

Tulisan ini membahas model mental di balik PPR, bagaimana batas Suspense menentukan bagian mana yang di-prerender versus di-streaming, contoh kerja yang bisa langsung disesuaikan, siklus hidup permintaan di baliknya, jebakan umum yang sering dialami tim, serta trade-off yang perlu dipahami sebelum mengaktifkannya pada sebuah route.

Model mental: shell dulu, lubang belakangan

Bayangkan sebuah halaman dashboard. Navigasi, judul halaman, dan teks pemasaran statis tidak pernah berubah antar permintaan. Namun feed pesanan yang berjalan langsung atau saldo akun pengguna bergantung pada siapa yang meminta dan kapan. Static rendering tradisional memaksa seluruh halaman menjadi sepenuhnya statis atau sepenuhnya dinamis, yang berarti satu widget personal saja bisa diam-diam menyeret seluruh route keluar dari cache statis dan masuk ke rendering dinamis penuh di setiap permintaan. PPR membagi perbedaan itu pada level komponen, bukan pada level route.

Saat build, Next.js menyusuri pohon komponen sebuah route dan mem-prerender semua yang bisa. Setiap kali menemukan komponen yang dibungkus batas Suspense, ia merender UI fallback sebagai gantinya dan meninggalkan sebuah lubang. Hasilnya adalah satu shell HTML, di-cache dan disajikan secara instan, dengan celah bernama tempat konten dinamis nanti disisipkan oleh runtime streaming milik framework itu sendiri. Karena shell tersebut adalah artefak yang di-cache seperti biasa, ia mendapat manfaat dari caching CDN dan edge yang sama seperti halaman statis mana pun, walaupun sebagian dari respons akhirnya bergantung pada pengunjung tertentu.

  • Shell statis — layout, header, dan komponen apa pun yang tidak membaca data waktu permintaan, dibuat sekali dan di-cache seperti halaman statis biasa.
  • Lubang dinamis — komponen yang dibungkus Suspense yang menggunakan API bercakupan permintaan, dirender saat permintaan tiba dan di-streaming ke slot yang sudah ditentukan.
  • Satu respons — shell dan lubang yang di-streaming dikirim lewat satu permintaan HTTP, sehingga tidak ada round trip tambahan untuk bagian dinamisnya.

Membungkus komponen dengan Suspense saja tidak membuatnya menjadi dinamis. Sebuah komponen baru menjadi dinamis ketika ia membaca API waktu permintaan seperti cookies, headers, atau searchParams. Suspense adalah batas yang mengurung perilaku dinamis itu agar bagian lain halaman tetap bisa statis.

Mengaktifkannya: konfigurasi dan opt-in per route

PPR bersifat opt-in di dua tingkat. Pertama, file next.config mengatur opsi ppr menjadi string incremental, yang membuka adopsi per route alih-alih memaksakannya ke seluruh proyek. Kedua, setiap route yang ingin memakai PPR mengekspor konstanta experimental_ppr bernilai true dari file layout atau page-nya. Route tanpa ekspor tersebut secara default bernilai false dan dirender seperti biasa.

Flag experimental_ppr berlaku turun ke setiap layout dan page bersarang di bawah segmen route tersebut, sehingga hanya perlu diatur sekali di bagian atas segmen yang diadopsi. Segmen anak bisa keluar lagi dengan mengekspor experimental_ppr sebagai false, yang berguna ketika sebuah halaman bersarang tertentu benar-benar dinamis sepenuhnya dan tidak mendapat manfaat dari shell statis.

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',
  },
}

export default nextConfig

// app/dashboard/page.tsx
import { Suspense } from 'react'
import { StaticHeader } from './static-header'
import { LiveOrderFeed, FeedSkeleton } from './live-order-feed'

export const experimental_ppr = true

export default function DashboardPage() {
  return (
    <>
      <StaticHeader />
      <Suspense fallback={<FeedSkeleton />}>
        <LiveOrderFeed />
      </Suspense>
    </>
  )
}

Bagaimana sebuah permintaan sebenarnya mengalir

Setelah sebuah route mengaktifkan PPR dan memiliki setidaknya satu batas Suspense di sekitar konten dinamis, permintaan ke route tersebut mengikuti urutan yang bisa diprediksi. Memahami urutan ini penting karena menjelaskan mengapa PPR bisa terasa cepat walaupun bagian dinamis halaman sedang melakukan pekerjaan nyata, seperti kueri basis data atau fetch yang terautentikasi.

  1. Server langsung mengembalikan shell statis yang sudah di-prerender, termasuk UI fallback Suspense untuk setiap lubang dinamis, sehingga browser bisa menampilkan sesuatu yang bermakna seketika.
  2. Secara paralel, server mulai merender setiap komponen dinamis yang bergantung pada data waktu permintaan, seperti sesi berbasis cookie atau pembacaan basis data yang khusus untuk pengguna saat ini.
  3. Setiap bagian dinamis yang selesai akan di-streaming ke dalam lubangnya lewat koneksi yang sama, menggantikan fallback tanpa memuat ulang halaman atau permintaan jaringan kedua.

PPR masih berstatus fitur eksperimental, dan Next.js secara eksplisit menyarankan untuk tidak mengandalkannya di produksi sampai fitur ini stabil. Perlakukan setiap route yang mengadopsinya sebagai sesuatu yang bisa dibatalkan dengan bersih, dan uji ulang setiap kali meng-upgrade Next.js karena perilaku dasarnya bisa berubah antar rilis.

Contoh kerja lengkap

Potongan kode berikut menunjukkan hal minimum yang dibutuhkan untuk mengadopsi PPR pada satu route: flag konfigurasi, opt-in di level route, dan batas Suspense yang memisahkan header statis dari feed pesanan dinamis per pengguna.

BagianLetaknyaYang dikendalikan
ppr: incrementalnext.config.tsMembuka adopsi per route alih-alih pengaturan semua-atau-tidak-sama-sekali untuk seluruh proyek.
experimental_ppr = truelayout.tsx atau page.tsxMengaktifkan partial prerendering untuk segmen route ini beserta turunannya.
Batas SuspenseKomponen apa pun yang menggunakan cookies, headers, atau searchParamsMenandai lubang dinamis dan menyediakan fallback yang ditampilkan di shell statis.

Di mana PPR sebenarnya membantu, dan di mana tidak

PPR pantas dengan kompleksitasnya pada route yang menggabungkan permukaan besar yang sebagian besar statis dengan sepotong kecil konten yang benar-benar personal. Dashboard yang sarat konten pemasaran, halaman produk dengan deskripsi statis ditambah jumlah stok yang berjalan langsung, atau layout blog dengan artikel statis dan jumlah komentar dinamis adalah kandidat yang baik.

  • Paling cocok: halaman sarat konten di mana hanya satu widget kecil yang membutuhkan data langsung per permintaan, karena shell mendominasi waktu muat yang dirasakan pengguna.
  • Paling cocok: route yang saat ini sepenuhnya dinamis hanya karena satu panggilan API dinamis kecil, di mana mengisolasi panggilan itu di belakang Suspense membuka shell yang bisa di-cache untuk sisanya.
  • Kurang cocok: route yang dinamis hampir di semua tempat, di mana shell statisnya akan sangat kecil dan tambahan kompleksitas mengelola batas Suspense hanya memberi sedikit manfaat.

Perlu diingat juga bahwa PPR melengkapi, bukan menggantikan, alat rendering Next.js lainnya. Static rendering dengan revalidasi, dynamic rendering, dan PPR adalah tiga titik pada spektrum yang sama, dan memilih yang tepat adalah keputusan per route, bukan keputusan global.

Jebakan yang sering dialami tim saat mengadopsi PPR

Kesalahan paling umum adalah meletakkan batas Suspense terlalu tinggi di pohon komponen, mengelilingi seluruh bagian halaman padahal seharusnya hanya membungkus komponen spesifik yang membaca data waktu permintaan. Itu mengubah lubang dinamis yang kecil dan terarah menjadi lubang besar, dan UI fallback akhirnya menutupi konten yang sebenarnya bisa tetap statis. Turunkan batas tersebut ke komponen terkecil yang benar-benar perlu dinamis, dan biarkan semua di sekitarnya tetap menjadi bagian shell.

Jebakan kedua adalah melupakan bahwa data yang diambil di dalam komponen dinamis tetap membutuhkan strategi caching-nya sendiri. PPR mengendalikan kapan sebuah komponen dirender relatif terhadap permintaan, bukan apakah panggilan fetch di dalamnya di-cache. Komponen dinamis yang memanggil endpoint lambat tanpa cache di setiap permintaan akan tetap lambat saat di-streaming, walaupun bagian lain halaman sudah termuat secara instan. Padukan PPR dengan strategi caching fetch yang wajar atau memoisasi permintaan di dalam bagian dinamis itu sendiri.

  • Jaga batas Suspense selalu seketat mungkin, hanya membungkus komponen spesifik yang membaca cookies, headers, atau searchParams.
  • Berikan komponen dinamis loading state yang sesuai dengan ukurannya sendiri, bukan skeleton generik untuk seluruh halaman.
  • Jalankan ulang build setelah mengadopsi PPR dan periksa output build untuk melihat route mana yang di-prerender sebagian versus penuh secara dinamis, karena komponen lebih tinggi di pohon bisa diam-diam membuat seluruh segmen menjadi dinamis penuh.
FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa itu Partial Prerendering di Next.js?

Partial Prerendering, atau PPR, adalah strategi rendering yang menggabungkan shell yang di-prerender secara statis dengan konten dinamis yang di-streaming saat permintaan tiba, semuanya dalam satu route. Bagian statis halaman di-cache dan disajikan secara instan, sementara komponen yang dibungkus Suspense dan bergantung pada data waktu permintaan dirender sesuai kebutuhan dan di-streaming ke halaman lewat respons yang sama.

Bagaimana cara mengaktifkan Partial Prerendering di proyek Next.js?

Atur opsi ppr menjadi incremental di next.config, lalu ekspor experimental_ppr bernilai true dari file layout atau page pada setiap route yang ingin diikutkan. Ekspor tersebut berlaku turun ke layout dan page bersarang di bawah segmen itu, sehingga hanya perlu dideklarasikan sekali per route.

Apakah membungkus komponen dengan Suspense membuatnya menjadi dinamis?

Tidak. Suspense hanya membuat sebuah batas yang bisa menampilkan fallback selama konten dimuat; ia tidak membuat komponen menjadi dinamis dengan sendirinya. Komponen menjadi dinamis ketika membaca API waktu permintaan seperti cookies, headers, atau searchParams, dan Suspense adalah yang memungkinkan perilaku dinamis itu dikurung tanpa memaksa seluruh route dirender secara dinamis.

Bisakah Partial Prerendering dipakai di produksi sekarang?

Next.js saat ini mendokumentasikan PPR sebagai fitur eksperimental dan secara eksplisit menyarankan untuk tidak mengandalkannya di produksi sampai stabil. Tim yang mencobanya sebaiknya memvalidasi dengan cermat dan menguji ulang setiap kali meng-upgrade Next.js, karena perilaku eksperimental bisa berubah antar rilis.

Kapan Partial Prerendering benar-benar membantu performa?

PPR paling membantu pada route di mana sebagian besar halaman statis berisi sepotong kecil konten yang benar-benar personal, seperti dashboard dengan feed pesanan langsung atau halaman produk dengan jumlah stok yang berjalan langsung. Pada route yang dinamis hampir di semua tempat, shell statisnya jadi sangat kecil dan tambahan kompleksitas mengelola batas Suspense hanya memberi sedikit manfaat.