Nextjs Streaming Suspense Parallel Routes Intercepting Routes
Definisi
Empat fitur App Router ini berurusan dengan satu pertanyaan yang sama: bagaimana menyerahkan UI ke user secepat mungkin sambil tetap menangani data dan layout yang berat.
- Streaming: kemampuan server mengirim HTML secara bertahap. Shell halaman dikirim duluan, bagian yang butuh data menyusul lewat chunk.
- Suspense: boundary React yang menampilkan fallback selama komponen async (biasanya Server Component yang
await) belum selesai. - Parallel Routes: merender beberapa halaman/segments secara bersamaan di dalam satu layout lewat slot bertanda
@folder. - Intercepting Routes: mencegat route dari segment lain sehingga bisa ditampilkan di layout saat ini (pola umum: modal), tanpa pindah halaman penuh.
Keempatnya bukan fitur yang saling ganti. Streaming + Suspense adalah soal kapan konten tampil; Parallel + Intercepting adalah soal bagaimana beberapa route hidup berdampingan.
Kapan dipakai
Gunakan Streaming + Suspense kalau:
- halaman punya data lambat (query DB, API eksternal) yang tidak perlu memblokir seluruh render
- kamu ingin shell navigasi, header, dan konten statis tampil duluan
- ada beberapa bagian halaman dengan kecepatan data berbeda-beda
Gunakan Parallel Routes kalau:
- satu layout harus menampilkan beberapa panel independen (dashboard: grafik + list + aktivitas)
- dua route berbeda harus hidup di area yang sama tanpa saling menunggu
- kamu butuh pola modal yang punya URL sendiri tapi tidak mengganti halaman belakang
Gunakan Intercepting Routes kalau:
- klik dari suatu daftar (feed, grid) harus membuka overlay/modal, tapi URL langsung (share/refresh) harus membuka halaman penuh
- route yang di-intercept sudah ada sebagai halaman normal, dan kamu tidak mau menduplikasi UI
Kalau aplikasi masih satu halaman datar tanpa panel ganda atau modal ber-URL, keempatnya kemungkinan besar belum dibutuhkan.
Cara kerja Streaming dan Suspense
Di App Router, Server Component yang menjalankan await otomatis menjadi titik stream. React membungkus segmen yang belum siap dengan Suspense, mengirim fallback ke klien, lalu menyambungkan hasilnya lewat chunk HTTP ketika data sudah ada.
loading.tsx adalah gula sintaks: Next.js membungkus page.tsx segment itu dengan boundary Suspense secara otomatis. Di balik layar, ini setara dengan menulis <Suspense fallback={<Loading/>}>.
Poin praktis:
- streaming terjadi lewat satu response HTTP yang di-stream, bukan banyak request terpisah
- fallback yang tampil pertama kali adalah milik boundary paling dekat dengan komponen yang suspend
- setelah data tiba, chunk digabung dan hanya bagian itu yang dihydrate, sisanya tetap utuh
Cara kerja Parallel Routes
Parallel Routes memakai konvensi folder @nama (slot). Layout yang membungkusnya menerima tiap slot sebagai prop.
app/
layout.tsx # terima children + @analytics + @team
page.tsx
@analytics/
page.tsx
@team/
page.tsx
dashboard/
layout.tsx
page.tsx
@analytics/
page.tsx// app/layout.tsx
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<div>
{children}
<aside>{analytics}</aside>
<aside>{team}</aside>
</div>
)
}Tiap slot dirender paralel dan independen. Satu slot lambat tidak memblokir slot lain.
default.tsx penting: saat soft navigation dari halaman yang tidak punya route untuk suatu slot, Next.js pakai default.tsx sebagai fallback agar layout tidak hancur. Tanpa itu, slot yang tidak cocok bisa memicu error/404 pada navigasi lunak.
Cara kerja Intercepting Routes
Intercepting dipakai untuk "mencegat" route dari segment lain dan menampilkannya di layout sekarang. Konvensi folder:
(.)cocok di level yang sama(..)naik satu level(..)(..)naik dua level(...)naik ke rootapp
Pola paling umum adalah modal: feed menampilkan daftar, klik item membuka modal lewat intercept, tapi buka URL langsung membuka halaman penuh.
app/
layout.tsx
page.tsx # feed
photo/[id]/page.tsx # halaman penuh (hard nav / share link)
@modal/
(.)photo/[id]/page.tsx # intercept: tampil sebagai modalAturan kuncinya: intercept hanya aktif pada soft navigation (navigasi klien). Reload, share link, atau buka tab baru adalah hard navigation yang tetap merender halaman penuh.
Menyusun Loading UI
Loading UI di App Router tersusun berlapis. Urutannya dari luar ke dalam:
app/loading.tsx— fallback global saat seluruh app sedang transisi awalloading.tsxper segment — fallback untuk segment itu saja<Suspense>eksplisit di dalam komponen — fallback untuk bagian sangat spesifik (misal satu kartu)
Saat nested, boundary paling dalam yang menangani fallback untuk wilayahnya. Begitu data di level dalam siap, hanya wilayah itu yang diganti; fallback parent tetap sampai bagian parent-nya sendiri selesai.
Prinsip menyusunnya:
- taruh
loading.tsxdi segment yang betulan punya data lambat, bukan di setiap folder - pakai
<Suspense>eksplisit untuk bagian kecil agar sisa halaman tidak ikut menunggu - fallback sebaiknya ringan: skeleton, placeholder, shimmer — bukan layar penuh yang berat
- hindari boundary terlalu tinggi (membungkus semua) karena satu komponen lambat akan menahan seluruhnya
Jebakan umum saat nesting route
- Suspense terlalu tinggi: satu boundary di root membuat seluruh halaman menunggu komponen paling lambat. Pecah jadi boundary lebih kecil agar bagian cepat tampil duluan.
- Terlalu banyak
loading.tsxbersarang: tiap transisi menumpuk fallback, memicu flash, layout shift, dan kesan lebih lambat. Susun hanya di level yang butuhnya. - Lupa
default.tsxpada slot Parallel Routes: soft navigation ke halaman yang tak punya route untuk suatu slot akan merusak layout atau memunculkan error/404. Selalu sediakandefault.tsxuntuk tiap slot. - Intercepting hanya jalan di soft navigation: kalau modal tak muncul saat reload/share link, itu wajar — hard navigation memang merender halaman penuh. Jangan pasang logika yang menganggap intercept selalu aktif.
- Parallel + Intercepting salah pasang: pola modal butuh
@modalslot yang berisi route intercept(.)photo/[id], plus route normalphoto/[id]. Kalau salah satu hilang, modal tak bisa dibuka atau halaman penuh tak bisa diakses. - Fetch di layoutmemicu waterfall: kalau
layout.tsxmelakukanawait fetch, semua children menunggu layout selesai. Pindahkan fetch ke segment/page yang butuh, atau pakai Suspense per bagian. params/searchParamsadalah Promise (Next.js 15+): di segment ataugenerateMetadata,paramsharus di-await. Lupa await menghasilkan error atau data kosong.error.tsxharus Client Component: boundary error butuh'use client'. Nested error hanya menangkap subtree-nya sendiri; error di root butuhapp/error.tsx.- Route groups
(folder)bentrok dengan intercept/parallel: grup tidak menambah URL, tapi bisa membingungkan saat dikombinasikan dengan@slotdan(.). Pastikan tidak ada dua route dari struktur berbeda yang menghasilkan URL sama. - Hard vs soft navigation tidak dibedakan saat debug: gejala aneh (modal muncul/ tidak) sering berasal dari perbedaan jenis navigasi, bukan bug render.
Contoh struktur folder (pola modal + parallel)
app/
layout.tsx
page.tsx
loading.tsx
@modal/
default.tsx # slot kosong saat tak ada modal
(.)photo/
[id]/
page.tsx # tampilan modal
photo/
[id]/
page.tsx # halaman penuh
loading.tsxCara membacanya:
app/layout.tsxmembungkuschildrendan slot@modal@modal/default.tsxmencegah layout rusak saat tak ada modal aktif(.)photo/[id]mencegat dari level yang sama dan merender di@modalphoto/[id]adalah tujuan hard navigation (URL langsung/share)
Catatan implementasi
- Pakai
loading.tsxuntuk fallback per-segment; ia otomatis jadi Suspense boundary. - Pakai
<Suspense>eksplisit untuk granularitas lebih halus di dalam komponen. - Pada Parallel Routes, terima slot sebagai prop di layout dan selalu sediakan
default.tsx. - Pada Intercepting Routes, pasangkan dengan Parallel Routes (
@modal) untuk pola modal yang benar. - Ingat perbedaan soft vs hard navigation saat memvalidasi perilaku intercept.
- Di Next.js 15+,
awaitsemuaparamsdansearchParams. - Jangan letakkan fetch berat di layout; biarkan segment/page yang menangani datanya sendiri.
Referensi singkat
loading.tsx= Suspense boundary otomatis per segment<Suspense fallback={...}>= boundary eksplisit untuk bagian spesifik- Parallel Routes: slot
@folder, prop di layout,default.tsxuntuk fallback - Intercepting Routes:
(.),(..),(..)(..),(...)+@modal - Soft navigation = intercept aktif; hard navigation = halaman penuh
Ringkasnya
- Streaming + Suspense dipakai untuk mengirim UI bertahap supaya halaman tidak menunggu data lambat.
- Parallel Routes dipakai untuk merender beberapa panel independen di satu layout lewat slot
@folder. - Intercepting Routes dipakai untuk membuka route di layout saat ini (modal) pada soft navigation, sambil tetap punya halaman penuh pada hard navigation.
- Loading UI disusun berlapis:
loading.tsxglobal → per-segment →<Suspense>eksplisit, dengan boundary paling dalam yang menangani wilayahnya. - Jebakan terbesar saat nesting adalah boundary salah tempat,
default.tsxterlupakan, dan salah paham soal kapan intercept benar-benar aktif.