Next.js 15 Fullstack (App Router + Server Actions) · Pelajaran

Batas Suspense dan Streaming Tingkat Komponen

Bungkus komponen data yang lambat dalam Suspense agar dapat dialirkan secara independen dari kerangka statis.

Pelajaran 1 dari 413 langkah

Batas Suspense dan Streaming Tingkat Komponen adalah pelajaran Next.js 15 Fullstack (App Router + Server Actions) gratis di CoddyKit. Ini adalah pelajaran 1 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Next.js 15 Fullstack (App Router + Server Actions), dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Next.js 15 Fullstack (App Router + Server Actions) mencakup 4 pelajaran total.

Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.

Why Stream at the Component Level?

In the App Router, a page can contain both fast static content (header, nav, layout shell) and slow data-dependent content (a dashboard widget that hits a third-party API).

Without streaming, the whole page waits for the slowest fetch before anything renders. That hurts perceived performance.

Component-level streaming lets Next.js send the static shell immediately, then stream each slow part in as its data resolves. The tool that enables this is React's <Suspense> boundary.

The Blocking Problem

Here a single async Server Component awaits a slow query. Because the page awaits before returning JSX, the user sees nothing until the 2-second fetch finishes.

The fast parts of the page (title, layout) are held hostage by the slow part.

// app/dashboard/page.tsx
async function getStats() {
  // simulates a slow 2s upstream call
  await new Promise((r) => setTimeout(r, 2000));
  return { revenue: 4200, orders: 87 };
}

export default async function DashboardPage() {
  const stats = await getStats(); // blocks the WHOLE page
  return (
    <main>
      <h1>Dashboard</h1>
      <p>Revenue: {stats.revenue}</p>
      <p>Orders: {stats.orders}</p>
    </main>
  );
}

Extract the Slow Part into Its Own Component

The first step to streaming is isolation: move the awaited data into a separate async Server Component.

The page itself no longer awaits anything, so its static shell can render instantly. The slow work now lives inside <Stats />.

// app/dashboard/stats.tsx
async function getStats() {
  await new Promise((r) => setTimeout(r, 2000));
  return { revenue: 4200, orders: 87 };
}

export async function Stats() {
  const stats = await getStats();
  return (
    <section>
      <p>Revenue: {stats.revenue}</p>
      <p>Orders: {stats.orders}</p>
    </section>
  );
}

Wrap It in a Suspense Boundary

Now wrap the slow component in <Suspense> and give it a fallback. Next.js renders the shell plus the fallback immediately, then streams the real component over the same HTTP response once its data resolves.

  • fallback shows while the boundary's data is pending.
  • Everything outside the boundary is sent right away.
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { Stats } from './stats';

export default function DashboardPage() {
  return (
    <main>
      <h1>Dashboard</h1> {/* sent immediately */}
      <Suspense fallback={<p>Loading stats…</p>}>
        <Stats /> {/* streamed in when ready */}
      </Suspense>
    </main>
  );
}

A Good Fallback Is a Skeleton

The fallback should match the shape of the final content to avoid layout shift. A skeleton placeholder is far better than a bare spinner because it reserves space and signals what's coming.

Keep skeletons as plain, fast Client or Server Components with no data dependencies.

// app/dashboard/stats-skeleton.tsx
export function StatsSkeleton() {
  return (
    <section aria-hidden className="animate-pulse">
      <div className="h-6 w-40 rounded bg-gray-200" />
      <div className="mt-2 h-6 w-32 rounded bg-gray-200" />
    </section>
  );
}

// usage:
// <Suspense fallback={<StatsSkeleton />}>
//   <Stats />
// </Suspense>

Multiple Independent Boundaries

Each <Suspense> streams independently. If you have several slow widgets, give each its own boundary so a slow one never blocks a fast one.

Below, RecentOrders may resolve in 300ms while Revenue takes 2s — and each appears the moment it's ready, in any order.

import { Suspense } from 'react';
import { Revenue } from './revenue';
import { RecentOrders } from './recent-orders';
import { RevenueSkeleton, OrdersSkeleton } from './skeletons';

export default function DashboardPage() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<RevenueSkeleton />}>
        <Revenue />
      </Suspense>
      <Suspense fallback={<OrdersSkeleton />}>
        <RecentOrders />
      </Suspense>
    </main>
  );
}

Boundary Granularity Is a Design Choice

You decide how to group slow components under boundaries:

  • One boundary per widget → each widget pops in on its own (best for unrelated data).
  • One boundary around a group → the group appears together once all its data resolves (good when a coordinated reveal looks cleaner).

A shared boundary streams only when the slowest child inside it is ready, so don't accidentally couple a fast widget to a slow one.

Passing Promises Down with the `use` Hook

An alternative pattern: start the fetch in the parent without awaiting, then pass the promise to a child that unwraps it with React's use hook. The child must be inside a <Suspense> boundary, which suspends on the pending promise.

This lets the parent kick off several requests in parallel before any of them block.

// app/dashboard/page.tsx
import { Suspense } from 'react';
import { Stats } from './stats';

function getStats() {
  return new Promise<{ revenue: number }>((r) =>
    setTimeout(() => r({ revenue: 4200 }), 2000),
  );
}

export default function Page() {
  const statsPromise = getStats(); // NOT awaited
  return (
    <Suspense fallback={<p>Loading…</p>}>
      <Stats statsPromise={statsPromise} />
    </Suspense>
  );
}

The Client Component That Reads the Promise

The child uses use(promise) to read the resolved value. When the promise is pending, use suspends and the nearest <Suspense> shows its fallback.

use can be called in a Client Component, making it the idiomatic way to stream a server-started promise into interactive UI.

// app/dashboard/stats.tsx
'use client';
import { use } from 'react';

export function Stats({
  statsPromise,
}: {
  statsPromise: Promise<{ revenue: number }>;
}) {
  const stats = use(statsPromise); // suspends until resolved
  return <p>Revenue: {stats.revenue}</p>;
}

loading.tsx Is a Route-Level Suspense

A file named loading.tsx in a route segment is sugar: Next.js automatically wraps that segment's page.tsx in a <Suspense> using the loading file as the fallback.

  • loading.tsx → streams the whole page while it loads (one big boundary).
  • Manual <Suspense> → streams parts of the page independently.

Use loading.tsx for the coarse first paint, and inline <Suspense> for fine-grained component streaming inside the page.

// app/dashboard/loading.tsx
export default function Loading() {
  return <p>Loading dashboard…</p>;
}

Don't Forget: Pure Functions Can Be Tested in Isolation

The data-shaping logic that feeds your streamed components is just plain TypeScript — keep it pure so you can unit-test it without a server. Here a standalone summarizer that any judge can run.

type Order = { id: number; total: number };

function summarize(orders: Order[]): { count: number; revenue: number } {
  const revenue = orders.reduce((sum, o) => sum + o.total, 0);
  return { count: orders.length, revenue };
}

const orders: Order[] = [
  { id: 1, total: 1200 },
  { id: 2, total: 3000 },
];

const result = summarize(orders);
console.log(`Orders: ${result.count}, Revenue: ${result.revenue}`);

Quick Check

You have a dashboard page with a fast header and two slow widgets: <Revenue /> (~2s) and <Orders /> (~300ms). You want the header to appear instantly and each widget to appear the moment its own data is ready, independently.

Recap

Key takeaways for component-level streaming:

  • Isolate slow data fetches into their own async Server Components.
  • Wrap each in <Suspense fallback={…}> — the shell and everything outside the boundary stream immediately.
  • Use skeleton fallbacks that match final shape to avoid layout shift.
  • Multiple boundaries stream independently; a shared boundary waits for its slowest child.
  • Pass an un-awaited promise down and read it with the use hook for parallel, streamed data.
  • loading.tsx is an automatic route-level Suspense for coarse first paint; inline <Suspense> handles fine-grained streaming.
Gratis untuk memulai

Belajar TypeScript dengan tutor AI — gratis

Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.

Kursus
22
Pelajaran
88

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Batas Suspense dan Streaming Tingkat Komponen” gratis?

Ya — teks lengkap “Batas Suspense dan Streaming Tingkat Komponen” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Next.js 15 Fullstack (App Router + Server Actions), upgrade ke CoddyKit PRO. Kursus Next.js 15 Fullstack (App Router + Server Actions) mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Batas Suspense dan Streaming Tingkat Komponen”?

Bungkus komponen data yang lambat dalam Suspense agar dapat dialirkan secara independen dari kerangka statis. Kamu berlatih Next.js 15 Fullstack (App Router + Server Actions) dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai Next.js 15 Fullstack (App Router + Server Actions)?

Tidak diperlukan pengalaman sebelumnya. Next.js 15 Fullstack (App Router + Server Actions) di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 1 dari 4.

Berapa lama pelajaran “Batas Suspense dan Streaming Tingkat Komponen” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran Next.js 15 Fullstack (App Router + Server Actions) ini?

Ya. Setiap pelajaran Next.js 15 Fullstack (App Router + Server Actions) menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Batas Suspense dan Streaming Tingkat Komponen
  2. Membuat loading.tsx dan Skeleton yang Bermakna
  3. Prerendering Parsial: Kerangka Statis, Celah Dinamis
  4. Jebakan Streaming: Pergeseran Tata Letak dan Air Terjun
← Kembali ke Next.js 15 Fullstack (App Router + Server Actions)