0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · Урок

Границы Suspense и потоковая передача на уровне компонентов

Оберните медленные компоненты данных в Suspense, чтобы передавать их независимо от статической оболочки.

«Границы 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.

Часто задаваемые вопросы

Урок «Границы 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) включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Границы Suspense и потоковая передача на уровне компонентов
  2. Создание содержательных loading.tsx и скелетонов
  3. Частичная предварительная отрисовка: статическая оболочка и динамические области
  4. Проблемы потоковой передачи: смещение компоновки и каскады
← Назад к Next.js 15 Fullstack (App Router + Server Actions)