IlmHamroh
JavaScript Full-stack/23-qism. Next.js16/43-dars19 daqiqa
Mundarija (35)

Next.js streaming va Suspense: sahifa bo'lak-bo'lak keladi

Qisqacha: Streaming (oqim) — server sahifani bir bo'lakda emas, tayyor bo'lgan qismlari bilan yuboradi. Sekin qismni <Suspense fallback={…}> ga o'raysiz. Server avval qolgan sahifani va fallback'ni (masalan "yuklanmoqda…") yuboradi. Sekin qism tayyor bo'lganda uning HTML'i o'sha javobning davomida keladi va kichik skript uni joyiga qo'yadi. loading.tsx — butun sahifa atrofidagi avtomatik Suspense. Promise'ni await siz client komponentga uzatib, u yerda use() bilan o'qish ham mumkin. Narxi: oqim boshlangach status (200) o'zgarmaydi, va JavaScript o'chiq bo'lsa sekin qism ko'rinmaydi.

Bu darsda

  • Sahifaning sekin qismini <Suspense> bilan ajratasiz va qolgan qism kutmasligini haqiqiy o'lchov bilan ko'rasiz.
  • Server yuborgan HTML oqimini bo'lak-bo'lak o'qiysiz: fallback, yashirin div va almashtirish skripti.
  • loading.tsx bilan aniq <Suspense> farqini va qachon qaysi biri kerakligini tushuntirasiz.
  • Promise'ni Server Component'dan Client Component'ga uzatib, use() bilan o'qiysiz.
  • portfolio ning «Men haqimda» sahifasiga GitHub bloki uchun oqim qo'shasiz va holatni commit qilasiz.

Oldin bilishingiz kerak: Serverda ma'lumot olish, Suspense, use(): promise va context'ni o'qish, Maxsus fayllar: loading, error, not-found, Rendering strategiyalari.

1. Nega bu kerak?

Oldingi darsda «Men haqimda» sahifasi GitHub profilini serverda oladigan bo'ldi. Lekin bitta muammo qoldi: sahifa GitHub javobini kutadi. GitHub 1 soniya o'ylasa, foydalanuvchi 1 soniya bo'sh ekranga qaraydi. Holbuki sarlavha, "Men Aziz, frontend dasturchiman" matni, bilimlar ro'yxati — hammasi tayyor, ular GitHub'ga bog'liq emas.

Choyxonani eslang. Siz osh, salat va choy buyurdingiz. Ofitsiant osh pishguncha hamma narsani ushlab turmaydi: avval choy va salatni olib keladi, osh esa tayyor bo'lganda keladi. Stolingiz bo'sh turmaydi.

Streaming — aynan shu. Server tayyor qismni darhol yuboradi, sekin qism uchun esa "joy band qiladi" (fallback) va keyin uni to'ldiradi.

18-qismda Suspense'ni brauzerda ishlatgansiz: lazy komponent yoki use(promise) kutayotganda fallback ko'rinardi. Next.js'da xuddi shu <Suspense> serverda ham ishlaydi — va bu yerda u HTML oqimini boshqaradi.

2. <Suspense> serverda

2.1 Sekin qismni o'rash

Kanon «Men haqimda» sahifasi:

tsx
import { Suspense } from "react";
import {
  GithubBlok,
  GithubBlokSkelet,
} from "./_komponentlar/GithubBlok";

export default function HaqimdaSahifasi() {
  return (
    <main id="asosiy">
      <h1>Men haqimda</h1>
      <p>Men Aziz, frontend dasturchiman. …</p>
      {/* … bilimlar ro'yxati … */}
      <h2>GitHub</h2>
      <Suspense fallback={<GithubBlokSkelet />}>
        <GithubBlok />
      </Suspense>
      <h2>O'qish rejam</h2>
      {/* … jadval … */}
    </main>
  );
}

GithubBlok — oldingi darsdagi async Server Component. U await connection() va await githubProfil(...) qiladi. <Suspense> React'ga aytadi: "bu qism kutayotganda o'rniga fallback ni ko'rsat, qolganini to'xtatma".

Fallback esa oddiy komponent:

tsx
export function GithubBlokSkelet() {
  return <p aria-busy="true">GitHub ma'lumoti yuklanmoqda…</p>;
}

aria-busy="true" — ekran o'quvchiga "bu joy hali yuklanmoqda" degan belgi (Malika uchun).

2.2 Natija oynasida: Suspense va use()

Natija oynasida server yo'q, lekin Suspense'ning xulqi aynan bir xil. Serverdagi so'rov o'rnida 800 ms dan keyin tugaydigan Promise'ni olamiz. Sahifa ochilganda fallback ko'rinadi, keyin profil:

tsx
import { Suspense, use } from "react";

type Profile = { login: string; repos: number };

// Serverdagi GitHub so'rovi o'rnida: 800 ms dan keyin javob
const profilePromise = new Promise<Profile>((resolve) =>
  setTimeout(() => resolve({ login: "octocat", repos: 8 }), 800),
);

function GithubBlock({ profile }: { profile: Promise<Profile> }) {
  const p = use(profile);
  return (
    <p>
      @{p.login}: {p.repos} ta ochiq repo
    </p>
  );
}

function GithubSkeleton() {
  return <p aria-busy="true">GitHub ma'lumoti yuklanmoqda…</p>;
}

export default function App() {
  return (
    <main>
      <h1>Men haqimda</h1>
      <h2>GitHub</h2>
      <Suspense fallback={<GithubSkeleton />}>
        <GithubBlock profile={profilePromise} />
      </Suspense>
      <h2>O'qish rejam</h2>
    </main>
  );
}

Ekranda:

text
Men haqimda
GitHub
@octocat: 8 ta ochiq repo
O'qish rejam

ekran(...) qadami birinchi lahzani tekshiradi: "yuklanmoqda…" bor edi, va «O'qish rejam» sarlavhasi allaqachon ekranda turgan — u GitHub'ni kutmagan. 800 ms dan keyin fallback profil bilan almashdi.

Promise komponentdan tashqarida yaratilgan. Ichida yaratilsa, har render'da yangi Promise bo'lardi va use() cheksiz kutardi (use() darsidagi qoida). Next.js'da bu muammo yo'q: Server Component bir marta ishlaydi.

Tekshirib ko'ring: <Suspense> ni olib tashlasak, natija oynasida nima bo'ladi? Haqiqiy Next.js sahifasida-chi?

Javob

Natija oynasida: eng yaqin Suspense chegarasi yo'q — butun ilova 800 ms "to'xtab" turadi, keyin hammasi birdan chiqadi. Next.js'da: sahifa GitHub javobini kutadi, birinchi bayt ham shu vaqtgacha kechikadi. Cache Components yoqilgan loyihada esa build uncached or runtime data xatosi bilan to'xtaydi — Next.js connection() li blokni Suspense'siz qoldirishga ruxsat bermaydi (Rendering strategiyalari darsidagi [stream]/[cache]/[block] tanlovi).

3. Oqim ichida nima bor?

3.1 Biz o'lchadik: bo'laklar va vaqt

Haqiqiy Next.js'da nima bo'lishini ko'rish uchun biz kanon holati 16 ni next build + next start bilan ishga tushirdik. GitHub o'rniga har javobni 800 ms kechiktiradigan lokal soxta server qo'ydik (GITHUB_API_URL), va /haqimda javobini kichik skript bilan bo'lak-bo'lak o'qidik. Qisqartirilgan chiqish:

text
status 200, sarlavhalar 42 ms
--- 1-bo'lak: 45 ms, 4146 belgi
<!DOCTYPE html><html lang="uz"><head><meta charSet="utf-8"/> …
--- 2-bo'lak: 47 ms, 9612 belgi
--- 3-bo'lak: 849 ms, 506 belgi
--- 4-bo'lak: 850 ms, 73 belgi
--- 5-bo'lak: 855 ms, 1184 belgi
<div hidden id="S:0"><p><a href="https://github.com/octocat">@ …
--- 6-bo'lak: 857 ms, 843 belgi
--- 7-bo'lak: 861 ms, 8453 belgi
--- 8-bo'lak: 864 ms, 14 belgi
</body></html>

Ikki to'lqin ko'rinadi. Birinchisi — 45–47 ms: sahifaning deyarli hammasi (13 758 belgi). Ikkinchisi — 849–864 ms: GitHub bloki va oxiri. Ya'ni foydalanuvchi sarlavha, matn va jadvalni 800 ms oldin ko'radi.

Kanonning o'z o'lchovi (haqiqiy GitHub bilan, natijalar/16-oqim.txt) ham xuddi shu manzarani beradi:

/haqimda oqimi: qism qachon keldi (2-so'rov)
  • sarlavhalar21 ms
  • 1-bo'lak (qobiq)55 ms
  • GitHub bloki164 ms
  • oxiri169 ms

Manba: IlmHamroh kanoni, portfolio v2 holat 16, next start, haqiqiy GitHub API, 2026-10-21

Ustunlarga qarang: qobiq 55 ms da keldi, GitHub bloki 164 ms da. Sovuq serverda (birinchi so'rov) raqamlar kattaroq — qobiq 354 ms, GitHub bloki 942 ms — lekin tartib o'sha. GitHub ishlamaganda (GITHUB_API_URL=http://127.0.0.1:9) zaxira matn 41 ms da keldi.

3.2 Fallback, yashirin div va almashtirish

1-bo'lakdagi GitHub joyi qanday ko'rinadi? Biz ishga tushirgan javobdan ko'chirma (asli bitta qator, telefonga sig'ishi uchun teglar orasidan bo'lindi):

html
<h2>GitHub</h2>
<!--$?-->
<template id="B:0"></template>
<p aria-busy="true">GitHub ma&#x27;lumoti yuklanmoqda…</p>
<!--/$-->
<h2>O&#x27;qish rejam</h2>

<!--$?--> va <!--/$--> — React'ning izohlari: "bu yerda kutilayotgan Suspense chegarasi bor". <template id="B:0"> — keyin topish uchun belgi. Ichida esa fallback — "yuklanmoqda…".

800 ms dan keyin, o'sha javobning davomida (5-bo'lak):

html
<div hidden id="S:0">
<p><a href="https://github.com/octocat">@<!-- -->octocat</a>:
<!-- --> <!-- -->8<!-- --> ta ochiq repo, <!-- -->20<!-- -->
ta kuzatuvchi.</p> …</div>
<script>$RB=[];$RV=function(a){ … };
$RC("B:0","S:0")</script>

Tayyor HTML hidden atributli div ichida keladi — u ko'rinmaydi. Ortidan kichik inline skript: $RC("B:0","S:0") — "B:0 belgisidagi fallback'ni olib tashla, o'rniga S:0 dagi tarkibni qo'y". Brauzer bu skriptni darhol bajaradi — sahifaning katta JavaScript to'plami yuklanishini kutmasdan.

sequenceDiagram
  participant B as Brauzer
  participant S as Next.js server
  participant G as GitHub
  B->>S: GET /haqimda
  S->>G: profil va repolar
  S-->>B: 1-bo'lak: qobiq + fallback (45 ms)
  Note over B: sarlavha, matn, "yuklanmoqda…"
  G-->>S: javob (800 ms)
  S-->>B: yashirin div S:0 + $RC skripti
  Note over B: fallback o'rniga profil

Diagrammadagi asosiy narsa: brauzer bitta so'rov yuboradi va bitta javob oladi. Javob shunchaki tez boshlanib, kech tugaydi. Qo'shimcha fetch ham, API ham yo'q.

Bo'laklar orasida self.__next_f.push(...) skriptlari ham bor — bu RSC payload (Server Components darsida ko'rgansiz): React keyinchalik sahifani interaktiv qilish (hydration) uchun ishlatadi.

Tekshirib ko'ring: HTML'ni o'qiydigan qidiruv roboti GitHub ma'lumotini ko'radimi?

Javob

Ha. Ma'lumot HTML javobining ichida bor — <div hidden id="S:0"> da. Bundan tashqari, Next.js hujjatiga ko'ra, faqat HTML o'qiydigan botlarni Next.js User-Agent bo'yicha taniydi va ular uchun metadata tayyor bo'lishini kutib yuboradi (htmlLimitedBots sozlamasi). Brauzer va JavaScript bajaradigan Googlebot esa oddiy oqimni oladi.

3.3 Biz topdik: JavaScript o'chiq bo'lsa

Almashtirish skript bilan bo'ladi. JavaScript o'chiq bo'lsa-chi? Bu kanon o'lchovi emas — o'zimiz ishga tushirgan tajriba. Biz o'sha serverda /haqimda ni headless Chrome'da ikki marta ochdik — JS yoqilgan va o'chiq holda — va «GitHub» bo'limidagi ko'rinadigan matnni oldik:

text
JS yoqilgan: "GitHub\n\n@octocat: 8 ta ochiq repo, 20 ta kuzatuvchi.\n\nvazifalar — ★ 3, oxirgi o'zgarish 2026-yil oktabr" | S:0 bor: false
JS o'chiq: "GitHub\n\nGitHub ma'lumoti yuklanmoqda…" | S:0 bor: true
chrome Chrome/155.0.8059.85

JS o'chiq bo'lganda ma'lumot sahifada bor (S:0 bor: true), lekin hidden — foydalanuvchi abadiy "yuklanmoqda…" ni ko'radi. Bu streaming'ning halol narxi. Kanon loyihasida bu muammo keyingi holatda o'z-o'zidan yo'qoladi: Cache Components I darsida GitHub bloki keshlanib, sahifaning HTML'iga build vaqtida tushadi — oqim kerak bo'lmay qoladi.

Shu sababdan muhim kontentni (maqola matni, narx, asosiy CTA) Suspense ortiga yashirmang. Suspense — qo'shimcha, sekin, "bo'lmasa ham bo'ladi" qismlar uchun.

Yana bir bog'liqlik: $RC(...) — inline skript. Qat'iy CSP (script-src 'self') inline skriptni bloklaydi — u holda JS yoqilgan foydalanuvchi ham fallback'da qoladi. Kanon CSP'da 'unsafe-inline' ni qoldirgan: muqobili — nonce — butun saytni dinamik qilardi (next.config chuqur darsida o'lchov bilan).

4. loading.tsx va <Suspense>

4.1 loading.tsx — butun sahifa uchun Suspense

Maxsus fayllar darsida loading.tsx ni ko'rdingiz. Endi uning ichini bilasiz: Next.js page.tsx ni avtomatik <Suspense fallback={<Loading />}> ga o'raydi. Ya'ni:

tsx
// Next.js ichida taxminan shunday quradi:
<Layout>
  <Suspense fallback={<Loading />}>
    <Page />
  </Suspense>
</Layout>

Kanondagi namuna — loyihalar/[slug]/loading.tsx:

tsx
// loading.tsx — sahifa atrofida avtomatik <Suspense> (N-05).
// Faqat build vaqtida yaratilmagan slug'da ko'rinadi.
export default function LoyihaYuklanmoqda() {
  return (
    <main id="asosiy" aria-busy="true">
      <h1>Loyiha yuklanmoqda…</h1>
    </main>
  );
}

Kanon izohi telefonga sig'ishi uchun ikki qatorga bo'lindi.

4.2 Qaysi birini tanlash

loading.tsx <Suspense>
Nimani o'raydi Butun sahifani Siz tanlagan qismni
Fayl Alohida fayl, papkada JSX ichida
Navigatsiyada Layout darhol, sahifa o'rnida loading Sahifaning qolgani darhol
Qachon Sahifa umuman ma'lumotsiz ma'nosiz Sekin qism — qo'shimcha

«Men haqimda» uchun loading.tsx yomon tanlov bo'lardi: GitHub'ni kutib, sarlavha, matn va jadvalni ham yashirardi. Shuning uchun kanon aniq <Suspense> ni GitHub blokining atrofiga qo'ydi. Next.js hujjati ham shuni tavsiya qiladi: Suspense'ni sekin ma'lumotga iloji boricha yaqin qo'ying.

Yana bir nozik joy: layout ichidagi sekin so'rov loading.tsx bilan yopilmaydi — loading.tsx layout'ning ichida turadi. Layout'dagi sekin qismga o'z <Suspense> i kerak.

4.3 Bir nechta Suspense chegarasi

Sahifada ikkita sekin qism bo'lsa — ikkita alohida <Suspense>. Ular mustaqil: qaysi biri oldin tayyor bo'lsa, o'sha oldin chiqadi. Bitta <Suspense> ichida ikkalasi bo'lsa — ikkalasi ham eng sekinini kutadi.

tsx
import { Suspense, use } from "react";

const later = <T,>(ms: number, value: T) =>
  new Promise<T>((r) => setTimeout(() => r(value), ms));

const rate = later(300, "12 650 so'm");
const weather = later(800, "+18°, bulutli");

function Rate() {
  return <p>Kurs: {use(rate)}</p>;
}

function Weather() {
  return <p>Ob-havo: {use(weather)}</p>;
}

export default function App() {
  return (
    <main>
      <h1>Toshkent bugun</h1>
      <Suspense fallback={<p>Kurs: …</p>}>
        <Rate />
      </Suspense>
      <Suspense fallback={<p>Ob-havo: …</p>}>
        <Weather />
      </Suspense>
    </main>
  );
}

Ekranda:

text
Toshkent bugun
Kurs: 12 650 so'm
Ob-havo: +18°, bulutli

Harakat qatoridagi ekran(...) qadamlari oraliq holatni tekshiradi: 400 ms da kurs allaqachon ko'rinadi, ob-havo esa hali "…". Ikkalasini bitta <Suspense> ga qo'ysangiz, kurs ham 800 ms kutardi. <T,> dagi vergul — .tsx faylda generic'ni JSX tegi deb o'ylamaslik uchun (React + TS IV).

Qanchalik mayda bo'lish kerak? Har kichik qismga alohida Suspense chegarasi — sahifa "popkorn" kabi sakrab chiqadi. Ma'noli guruhlar (yon panel, asosiy kontent, izohlar) — odatda yaxshiroq.

5. Skeleton va layout siljishi

Fallback — shunchaki "yuklanmoqda…" matni bo'lishi shart emas. Ko'p saytlar skeleton (kulrang qutilar) ko'rsatadi: kelajakdagi kontent shaklida bo'sh joy.

Yaxshi fallback'ning uchta belgisi:

  1. O'lchami tayyor kontentga yaqin. Fallback 1 qator, kontent 5 qator bo'lsa, ma'lumot kelganda pastdagi hamma narsa sakraydi — bu CLS (Cumulative Layout Shift — kutilmagan siljish, Core Web Vitals).
  2. Ekran o'quvchiga tushunarli. aria-busy="true" yoki ko'rinmas "yuklanmoqda" matni.
  3. Harakat ehtiyotkor. Miltillovchi animatsiya — prefers-reduced-motion ichida.

Kanondagi GithubBlokSkelet — bitta qator matn. GitHub bloki ham 1 qator matn + 3 qatorli ro'yxat. Siljish bor, lekin pastki qism (jadval) kichik. Kanon bu yerda soddalikni tanladi va CLS'ni Performance bosqichida butun sayt bo'yicha o'lchadi.

6. Promise'ni Client Component'ga uzatish

6.1 Naqsh

Ba'zan sekin ma'lumot interaktiv komponentga kerak bo'ladi: masalan, repolar ro'yxatini client tomonda saralash tugmasi bilan. Server Component Promise'ni await qilmasdan, uni prop sifatida beradi. Client Component esa use() bilan o'qiydi:

tsx
// app/haqimda/page.tsx — Server Component (sxema, kanonda yo'q)
import { Suspense } from "react";
import { RepoRoyxati } from "./RepoRoyxati";

export default function Sahifa() {
  const repolar = repolarniOl("octocat"); // await YO'Q
  return (
    <Suspense fallback={<p>Repolar yuklanmoqda…</p>}>
      <RepoRoyxati repolar={repolar} />
    </Suspense>
  );
}
tsx
// RepoRoyxati.tsx — Client Component
"use client";
import { use, useState } from "react";

export function RepoRoyxati({
  repolar,
}: {
  repolar: Promise<{ nomi: string; yulduz: number }[]>;
}) {
  const royxat = use(repolar);
  const [tartib, setTartib] = useState<"nom" | "yulduz">("nom");
  // … saralash tugmasi va ro'yxat
}

Next.js Promise'ni brauzerga "uzatadi": server uni bajarib, natijani oqimda yuboradi, brauzerdagi use() esa shu natijani oladi. Promise natijasi seriyalanadigan bo'lishi shart (oddiy obyekt, massiv, son, satr, sana) — funksiya yoki klass nusxasi o'tmaydi (Client va server kompozitsiyasi).

6.2 Qachon kerak

Variant Qayerda kutadi Qachon
await Server Component'da Serverda Ko'rsatish interaktiv emas (kanondagi GitHub bloki)
Promise → client → use() Brauzer use() da Ma'lumot client komponentga kerak (filtr, saralash)
Client'da TanStack Query Brauzer fetch Tez-tez yangilanadi, client'da qayta so'rash kerak (N-28)

Kanon birinchi variantni tanladi: GitHub bloki faqat matn va havolalar, interaktiv emas. use() naqshi esa mashqda.

7. Oqimning cheklovlari

7.1 Status allaqachon ketgan

Oqim boshlanganda HTTP sarlavhalar va status kodi birinchi bo'lak bilan ketadi. Keyin ularni o'zgartirib bo'lmaydi. Next.js hujjati shunday deydi:

text
You cannot change the status code or headers after streaming starts.

Tarjimasi: "Oqim boshlangach status kodi yoki sarlavhalarni o'zgartirib bo'lmaydi". Shuning uchun Suspense ichidagi notFound() sahifani 404 qila olmaydi — Next.js faqat <meta name="robots" content="noindex"> qo'shadi. Kanon buni Maxsus fayllar darsida o'z ko'zi bilan ko'rgan: noma'lum loyiha birinchi so'rovda 200 qaytargan. Yechim — 404'ni oqimdan oldin hal qilish (proxy.ts).

7.2 Oraliq qatlamlar

Server va brauzer orasidagi proksi (nginx) yoki CDN javobni to'plab, keyin bir martada yuborishi mumkin — shunda oqim foydasi yo'qoladi. Next.js hujjati nginx uchun X-Accel-Buffering: no sarlavhasini tavsiya qiladi. Deploy'da bu tekshiriladigan narsa (Deploy I).

7.3 Xato oqim ichida

GithubBlok xato tashlasa, xato eng yaqin error boundary ga — sahifaning error.tsx iga ketadi va butun sahifa o'rniga xato ekrani chiqadi. Kanon shuning uchun githubProfil ichida xatoni ushlab, null va zaxira matn qaytaradi (oldingi dars). Faqat GitHub joyini xato oynasi bilan almashtirish kerak bo'lsa — Suspense atrofiga alohida error boundary qo'yiladi (Error boundary).

8. Portfolio qadami: oqim va commit

Serverda ma'lumot olish darsida github.ts va GithubBlok ni yozdingiz. Bugun ularni sahifaga ulaymiz.

8.1 O'zgarish

src/app/(sayt)/haqimda/page.tsx ga:

tsx
import { Suspense } from "react";
import {
  GithubBlok,
  GithubBlokSkelet,
} from "./_komponentlar/GithubBlok";

// … <h2>GitHub</h2> ostiga:
<Suspense fallback={<GithubBlokSkelet />}>
  <GithubBlok />
</Suspense>

GithubBlokSkelet — GithubBlok.tsx ning o'zida, blok yonida.

8.2 Build va o'lchov

Kanon build jadvalidan /haqimda qatori (xom natijalar/tajriba/16-build-connection.txt):

text
├ ◐ /haqimda

◐ — Partial Prerender: statik qobiq build vaqtida tayyor, GitHub bloki esa har so'rovda oqim bilan keladi (Rendering strategiyalari darsidagi belgi). Qolgan sahifalar o'zgarmadi.

Kanon o'lchovi: /haqimda TTFB 60 ms, brauzer JS 138.4 KB — o'zgarmadi, chunki GitHub bloki butunlay serverda. axe 0, hydration ogohlantirishi 0. Lint, tip, format — 0 xato.

8.3 Halol narx

Har sahifa ko'rish = GitHub'ga 2 ta so'rov. Kalitsiz limit — IP boshiga soatiga 60 ta. Demak soatiga taxminan 30 ta ko'rishdan keyin hamma tashrif buyuruvchi zaxira matnni ko'radi. Kichik portfolio uchun bu chegaraga yaqin. Yechim — natijani keshlash: Cache Components I darsida.

8.4 Commit

npm run lint, npm run tip, npm run build — toza bo'lsa:

bash
git add -A
git commit

Commit (kanon): Haqimda: GitHub profili serverda (kalitsiz, parallel, zaxira) va Suspense bilan oqim.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Butun sahifa sekin qismni kutadi <Suspense> yo'q yoki juda yuqorida Chegarani sekin komponentga yaqin qo'ying
loading.tsx bor, lekin sekin layout kutiladi loading.tsx layout ichida turadi Layout'dagi sekin qismga o'z <Suspense> i
use() cheksiz "yuklanmoqda" (client'da) Promise render ichida yaratiladi Promise'ni tashqarida yoki serverda yarating
Ma'lumot kelganda sahifa sakraydi Fallback o'lchami kontentdan kichik Skeleton'ni kontent shaklida qiling
Noma'lum sahifa 200 qaytaradi notFound() oqim boshlangandan keyin Tekshiruvni Suspense'dan oldin yoki proxy'da
JS o'chiq — abadiy "yuklanmoqda" Almashtirish inline skript bilan Muhim kontentni Suspense ortiga qo'ymang
Qat'iy CSP bilan fallback almashmaydi $RC inline skripti bloklangan 'unsafe-inline' yoki nonce (N-38)
Deploy'da oqim yo'q, hammasi birdan Proksi javobni to'playdi X-Accel-Buffering: no

10. Mashqlar

Mashqlar kurs/mashqlar/23/16-streaming/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): Suspense chegarasi qayerda?

Sahifada: sarlavha (tayyor), mahsulot tavsifi (tayyor), narx (baza, 50 ms), sharhlar (tashqi API, 1 200 ms), "o'xshash mahsulotlar" (tavsiya xizmati, 900 ms).

  • Narx muhim va tez: Suspense
  • Sharhlar sekin va qo'shimcha: o'z chegarasi
  • O'xshash mahsulotlar sharhlar bilan bitta chegarada bo'lsa, ular ms da chiqadi
Yechim
  • kerak emas — narx tez va muhim; uni Suspense ortiga yashirish JS'siz foydalanuvchidan narxni yashiradi.
  • Suspense — sharhlar sekin, sahifa ularsiz ham foydali.
  • 1200 — bitta Suspense chegarasi ichidagi hamma narsa eng sekinini kutadi. Alohida chegaralarda o'xshash mahsulotlar 900 ms da chiqardi.

2-mashq (o'rta): alohida Suspense chegaralari

«Bir nechta Suspense chegarasi» bo'limidagi misolni oling va unga uchinchi qism qo'shing: News — 500 ms dan keyin "Metro yangi bekat ochdi". Uchala qism alohida Suspense chegarasida bo'lsin. Har komponent chizilganda konsolga nomini yozsin (console.log("Kurs chizildi")) — qaysi tartibda chizilishini ko'ring.

Yechim
tsx
import { Suspense, use } from "react";

const later = <T,>(ms: number, value: T) =>
  new Promise<T>((r) => setTimeout(() => r(value), ms));

const rate = later(300, "12 650 so'm");
const news = later(500, "Metro yangi bekat ochdi");
const weather = later(800, "+18°, bulutli");

function Rate() {
  const value = use(rate);
  console.log("Kurs chizildi");
  return <p>Kurs: {value}</p>;
}

function News() {
  const value = use(news);
  console.log("Yangilik chizildi");
  return <p>Yangilik: {value}</p>;
}

function Weather() {
  const value = use(weather);
  console.log("Ob-havo chizildi");
  return <p>Ob-havo: {value}</p>;
}

export default function App() {
  return (
    <main>
      <h1>Toshkent bugun</h1>
      <Suspense fallback={<p>Kurs: …</p>}>
        <Rate />
      </Suspense>
      <Suspense fallback={<p>Yangilik: …</p>}>
        <News />
      </Suspense>
      <Suspense fallback={<p>Ob-havo: …</p>}>
        <Weather />
      </Suspense>
    </main>
  );
}

Konsolda:

text
Kurs chizildi
Yangilik chizildi
Ob-havo chizildi

Ekranda:

text
Toshkent bugun
Kurs: 12 650 so'm
Yangilik: Metro yangi bekat ochdi
Ob-havo: +18°, bulutli

Chizilish tartibi — Promise'lar tugash tartibi (300, 500, 800 ms), JSX'dagi tartib emas. Ekrandagi joylashuv esa JSX tartibida qoladi: har fallback o'z joyini band qilgan.

3-mashq (qiyin): promise — client komponentga

ProjectList client komponentini yozing: u projects: Promise<{ name: string; stars: number }[]> prop'ini use() bilan o'qiydi va «Yulduz bo'yicha» tugmasi bosilganda ro'yxatni yulduzlar soni bo'yicha kamayish tartibida saralaydi (toSorted — Nusxa bilan o'zgartirish). Promise — 300 ms dan keyin uchta loyiha: vazifalar (3), bahor (7), landing (1). App uni <Suspense> ichida ko'rsatsin.

Yechim
tsx
import { Suspense, use, useState } from "react";

type Project = { name: string; stars: number };

// Server Component'dagi `repolarniOl()` o'rnida
const projectsPromise = new Promise<Project[]>((resolve) =>
  setTimeout(
    () =>
      resolve([
        { name: "vazifalar", stars: 3 },
        { name: "bahor", stars: 7 },
        { name: "landing", stars: 1 },
      ]),
    300,
  ),
);

function ProjectList({ projects }: { projects: Promise<Project[]> }) {
  const list = use(projects);
  const [byStars, setByStars] = useState(false);
  const shown = byStars
    ? list.toSorted((a, b) => b.stars - a.stars)
    : list;
  return (
    <>
      <button onClick={() => setByStars(true)}>
        Yulduz bo'yicha
      </button>
      <ul>
        {shown.map((p) => (
          <li key={p.name}>
            {p.name} — {p.stars}
          </li>
        ))}
      </ul>
    </>
  );
}

export default function App() {
  return (
    <Suspense fallback={<p>Loyihalar yuklanmoqda…</p>}>
      <ProjectList projects={projectsPromise} />
    </Suspense>
  );
}

Ekranda:

text
Yulduz bo'yicha
bahor — 7
vazifalar — 3
landing — 1

Next.js'da projectsPromise Server Component'da yaratiladi va prop bilan uzatiladi — ProjectList faylining boshida esa "use client" turadi. Saralash brauzerda bo'ladi, ma'lumot esa serverdan oqimda keladi.

4-mashq: Portfolio qadami

«Men haqimda» sahifasida GithubBlok ni <Suspense fallback={<GithubBlokSkelet />}> ga o'rang. npm run build jadvalida /haqimda yonida ◐ borligini tekshiring. Keyin next start bilan sahifani oching va DevTools → Network → haqimda so'rovining Timing yorlig'ida "Content Download" qancha davom etganini ko'ring. Toza bo'lsa — commit.

Yechim

Namuna — «Portfolio qadami» bo'limidagi kod. "Content Download" uzun bo'ladi (GitHub javobi vaqti): javob tez boshlanadi, lekin oqim GitHub kutilguncha ochiq turadi. Sinash uchun DevTools'da JavaScript'ni o'chirib (Command Menu → "Disable JavaScript") sahifani qayta yuklang — «Biz topdik: JavaScript o'chiq bo'lsa» bo'limidagi "yuklanmoqda…" holatini o'zingiz ko'rasiz. Commit xabari — kanondagi yoki o'zingizniki.

11. Real ishda

  • Dashboard'lar — klassik streaming joyi: menyu va sarlavha darhol, grafiklar va jadvallar har biri o'z Suspense chegarasida.
  • E-commerce — mahsulot nomi va narxi darhol (oqimsiz), sharhlar va tavsiyalar Suspense'da.
  • AI javoblari — matn bo'lak-bo'lak keladigan chat interfeyslari ham oqimga tayanadi (Route Handlers darsida javob oqimi).
  • Intervyuda: "loading.tsx va <Suspense> farqi?", "Streaming'da nega 404 qaytarib bo'lmaydi?" — App Router bo'yicha tipik savollar.

Xulosa

  • Streaming — bitta javob, bo'laklarda: qobiq darhol, sekin qism tayyor bo'lganda.
  • <Suspense fallback> sekin qismni ajratadi; loading.tsx — butun sahifa uchun avtomatik Suspense.
  • Oqimda tayyor HTML yashirin div da keladi va inline skript uni fallback o'rniga qo'yadi — JS o'chiq bo'lsa ko'rinmaydi.
  • Mustaqil qismlar — alohida Suspense chegaralarida; bitta chegara eng sekinini kutadi.
  • Promise'ni client komponentga uzatib use() bilan o'qish mumkin — interaktiv ko'rinish uchun.
  • Oqim boshlangach status va sarlavhalar o'zgarmaydi; 404 ni oldinroq hal qiling.

Keyingi dars: generateStaticParams va segment konfiguratsiyasi — dinamik sahifalarni build vaqtida oldindan yaratamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js streaming va Suspense: sahifa bo'lak-bo'lak keladi — IlmHamroh