Next.js 15 Fullstack (App Router + Server Actions) · درس

حدود Suspense والبث على مستوى المكوّن

غلّف مكونات البيانات البطيئة في Suspense لبثها بشكل مستقل عن الغلاف الثابت.

الدرس 1 من 413 خطوة

حدود Suspense والبث على مستوى المكوّن درس مجاني في Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Next.js 15 Fullstack (App Router + Server Actions)، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

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.
البدء مجانًا

تعلم TypeScript مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
22
الدروس
88

الأسئلة الشائعة

هل درس «حدود Suspense والبث على مستوى المكوّن» مجاني؟

نعم — نص درس «حدود Suspense والبث على مستوى المكوّن» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Next.js 15 Fullstack (App Router + Server Actions)، انتقل إلى CoddyKit PRO. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.

ماذا ستتعلم في «حدود Suspense والبث على مستوى المكوّن»؟

غلّف مكونات البيانات البطيئة في Suspense لبثها بشكل مستقل عن الغلاف الثابت. تتمرن على Next.js 15 Fullstack (App Router + Server Actions) مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Next.js 15 Fullstack (App Router + Server Actions)؟

لا تُشترط خبرة سابقة. Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «حدود Suspense والبث على مستوى المكوّن»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Next.js 15 Fullstack (App Router + Server Actions) هذا؟

نعم. كل درس في Next.js 15 Fullstack (App Router + Server Actions) يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. حدود Suspense والبث على مستوى المكوّن
  2. إنشاء loading.tsx وعناصر Skeleton ذات معنى
  3. العرض المسبق الجزئي: غلاف ثابت وثقوب ديناميكية
  4. مشكلات البث: تحرك التخطيط والشلالات
← العودة إلى Next.js 15 Fullstack (App Router + Server Actions)