Streaming — Suspense bilan, foydalanuvchi tajribasini yaxshilash
Muammodan boshlaylik
Siz dashboard sahifasini yozdingiz. Sahifada 3 ta qism bor:
- Foydalanuvchi ma'lumotlari — DB dan, 50ms
- So'nggi buyurtmalar — DB dan, 200ms
- Tavsiya etilgan mahsulotlar — tashqi API dan, 800ms
Dynamic Rendering da Next.js hammasi tayyor bo'lguncha kutadi — 800ms. Foydalanuvchi 800ms oq ekran ko'radi. Holbuki foydalanuvchi ma'lumotlari 50ms da tayyor edi.
50ms → foydalanuvchi ma'lumoti tayyor (kutmoqda...)
200ms → buyurtmalar tayyor (kutmoqda...)
800ms → tavsiyalar tayyor → sahifa ko'rinadi
Bu — yomon tajriba. Streaming bu muammoni hal qiladi.
Streaming qanday ishlaydi
Streaming — sahifani bo'laklarga bo'lib yuborish. Tayyor bo'lgan qism darhol foydalanuvchiga yuboriladi, qolganlar kelguncha loading ko'rsatiladi.
50ms → foydalanuvchi ma'lumoti → darhol ko'rinadi
200ms → buyurtmalar → ko'rinadi
800ms → tavsiyalar → ko'rinadi
Foydalanuvchi 50ms da birinchi kontent ko'radi. 800ms kutmaydi.
Suspense bilan Streaming
React ning Suspense komponenti — "bu qism yuklanayotganda fallback ko'rsat" degani. Next.js bu ustiga Streaming qurgan.
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { UserInfo } from './UserInfo'
import { RecentOrders } from './RecentOrders'
import { Recommendations } from './Recommendations'
export default function DashboardPage() {
return (
<div>
{/* 50ms — tez, Suspense shart emas */}
<Suspense fallback={<p>Yuklanmoqda...</p>}>
<UserInfo />
</Suspense>
{/* 200ms */}
<Suspense fallback={<OrdersSkeleton />}>
<RecentOrders />
</Suspense>
{/* 800ms — sekin, lekin boshqalarni to'sib qo'ymaydi */}
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations />
</Suspense>
</div>
)
}
Har bir komponent mustaqil yuklanadi. Recommendations 800ms kutayotganda UserInfo va RecentOrders allaqachon ko'ringan bo'ladi.
Suspense ichidagi komponentlar async bo'lishi kerak
Suspense faqat async Server Component larni "kutadi". Komponent await qilayotganda — fallback ko'rsatiladi, tugaganda — kontent.
// UserInfo.tsx — Server Component
export async function UserInfo() {
const user = await db.getUser() // 50ms
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
}
// Recommendations.tsx — Server Component
export async function Recommendations() {
const items = await fetch('https://api.example.com/recommend', {
cache: 'no-store'
}).then(r => r.json()) // 800ms
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
)
}
Skeleton UI — yaxshi fallback
fallback da oddiy "Yuklanmoqda..." matn yozish mumkin. Lekin Skeleton UI — kontent shakliga o'xshash kulrang bloklar — foydalanuvchi tajribasini yaxshilaydi.
function OrdersSkeleton() {
return (
<div className="animate-pulse">
{[1, 2, 3].map(i => (
<div key={i} className="flex gap-4 mb-4">
<div className="h-12 w-12 bg-gray-200 rounded" />
<div className="flex-1">
<div className="h-4 bg-gray-200 rounded mb-2" />
<div className="h-3 bg-gray-200 rounded w-2/3" />
</div>
</div>
))}
</div>
)
}
animate-pulse — Tailwind klassi, kulrang bloklar yumshoq miltillaydi.
loading.tsx vs Suspense — farqi
Oldingi darsda loading.tsx ni o'rgandik. U ham Streaming ishlatadi — lekin butun sahifa uchun.
loading.tsx → butun page.tsx yuklanguncha
Suspense → faqat o'sha komponent yuklanguncha
// loading.tsx — butun sahifa uchun
export default function Loading() {
return <FullPageSkeleton />
}
// Suspense — faqat bir qism uchun
<Suspense fallback={<SmallSkeleton />}>
<SlowComponent />
</Suspense>
Amalda ikkalasini birga ishlatiladi: loading.tsx — sahifaning tez yuklanadigan qismi uchun, Suspense — sekin qismlarni alohida ajratish uchun.
Parallel ma'lumot olish
Streaming bilan birga parallel fetch ishlatish — eng tez yondashuv:
// ❌ — ketma-ket: 50 + 200 + 800 = 1050ms
export async function DashboardPage() {
const user = await getUser() // 50ms
const orders = await getOrders() // 200ms
const recs = await getRecs() // 800ms
}
// ✅ — parallel: max(50, 200, 800) = 800ms
export async function DashboardPage() {
const [user, orders, recs] = await Promise.all([
getUser(), // 50ms ┐
getOrders(), // 200ms ├ bir vaqtda
getRecs(), // 800ms ┘
])
}
Lekin Promise.all da hammasi tayyor bo'lguncha kutiladi. Streaming bilan birlashtirish — har bir komponentni alohida Suspense ga o'rash — eng optimal:
export default function DashboardPage() {
return (
<div>
<Suspense fallback={<Skeleton />}>
<UserInfo /> {/* o'zi ichida fetch qiladi */}
</Suspense>
<Suspense fallback={<Skeleton />}>
<RecentOrders /> {/* o'zi ichida fetch qiladi */}
</Suspense>
<Suspense fallback={<Skeleton />}>
<Recommendations />{/* o'zi ichida fetch qiladi */}
</Suspense>
</div>
)
}
Har bir komponent mustaqil — biri sekin bo'lsa boshqalarni to'sib qo'ymaydi.
Tez-tez uchraydigan xatolar
1. Client Component ni Suspense ga o'rash
// ❌ — Client Component da useEffect bilan fetch — Suspense kutmaydi
'use client'
export function Orders() {
const [orders, setOrders] = useState([])
useEffect(() => {
fetch('/api/orders').then(r => r.json()).then(setOrders)
}, [])
return <ul>{orders.map(...)}</ul>
}
Suspense faqat async Server Component larni kutadi. Client Component dagi useEffect fetch — Suspense ga ko'rinmaydi.
// ✅ — async Server Component
export async function Orders() {
const orders = await fetch('/api/orders').then(r => r.json())
return <ul>{orders.map(...)}</ul>
}
2. Suspense ni juda yuqorida qo'yish
// ❌ — butun sahifa bitta Suspense ichida — streaming yo'q
<Suspense fallback={<Loading />}>
<UserInfo />
<RecentOrders />
<Recommendations /> {/* 800ms — hammasi shu uchun kutadi */}
</Suspense>
// ✅ — har biri alohida
<Suspense fallback={<Skeleton />}><UserInfo /></Suspense>
<Suspense fallback={<Skeleton />}><RecentOrders /></Suspense>
<Suspense fallback={<Skeleton />}><Recommendations /></Suspense>
Xulosa
Streaming — sahifani bo'laklarga bo'lib yuborish. Tayyor bo'lgan qism darhol ko'rinadi, qolganlar kelguncha skeleton ko'rsatiladi.
loading.tsx → butun sahifa uchun fallback
Suspense → alohida komponent uchun fallback
Eng optimal:
Har bir sekin komponent → alohida Suspense
Har bir komponent → o'zi ichida fetch qiladi
Parallel yuklanadi, biri sekin bo'lsa boshqalarni to'smaydi
Keyingi dars
16-dars: ISR — Incremental Static Regeneration, revalidate, on-demand
Static Rendering ning aqlli versiyasi — sahifa statik, lekin vaqti-vaqti bilan avtomatik yangilanadi.