Streaming, vízesékek és Suspense
Párhuzamos adatlekérés, React.cache, Suspense határok és kliens navigációs buktatók.
7. Streaming, vízesékek és Suspense
Ha az adatlekéréseket egymás után várod meg, az idő összeadódik (vízesés). Cél: a független munkák párhuzamosan fussanak, és a lassabb részek ne blokkolják az egész oldal első megjelenését.
Vízesés vs párhuzamosság
// ❌ Vízesés – teljes idő ≈ A + B + C
const a = await fetchA()
const b = await fetchB()
const c = await fetchC()
// ✅ Párhuzamos – teljes idő ≈ max(A, B, C)
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()])Ha B csak A eredményétől függ, először await fetchA(), utána indítsd B-t – ne várj feleslegesen olyan dolgokra, amik még nincsenek a kritikus úton.
Korai promise, késői await
Néha érdemes a promise-okat minél hamarabb elindítani, és csak ott await-elni, ahol tényleg kell az érték. Párhuzamos, de külön Suspense szegmensek:
async function HeaderWithUser() {
const user = await getUser()
return <header>{user.name}</header>
}
async function StatsBlock() {
const stats = await getStats()
return <section>{/* … */}</section>
}
export default function Page() {
return (
<div>
<Suspense fallback={<HeaderSkeleton />}>
<HeaderWithUser />
</Suspense>
<Suspense fallback={<StatsSkeleton />}>
<StatsBlock />
</Suspense>
</div>
)
}A lényeg: ne sorolj felesleges await-et egyetlen async komponens tetején, ha a részek külön is megjelenhetnek – bontsd őket, és használj Suspense-t.
Suspense és streaming
A loading.tsx a mappában lévő page.tsx köré automatikusan Suspense-szerű viselkedést ad. Manuális Suspense határokkal a lassú részek külön „foltokban” tölthetők – a felhasználó hamarabb lát valamit a képernyőn.
import { Suspense } from 'react'
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<Suspense fallback={<p>Oldalsáv betöltése…</p>}>
<SlowSidebar />
</Suspense>
<main>{children}</main>
</div>
)
}React.cache() – deduplikáció fetch nélkül
A fetch egy requesten belüli deduplikációját nem kapod meg automatikusan, ha ORM-mel vagy saját függvénnyel hívsz adatbázist. Ilyenkor a React cache() függvényével ugyanazt a lekérést egy render alatt egyszer futtathatod:
import { cache } from 'react'
export const getUserById = cache(async (id: string) => {
return db.user.findUnique({ where: { id } })
})
// Layout és Page ugyanazzal az id-vel: egy DB hívásEz nem helyettesíti a HTTP cache-t vagy a Next cache tag-eket – csak egy kérésen belüli memoizáció.
useSearchParams, usePathname és Suspense
A useSearchParams() (és bizonyos esetekben más navigációs hookok) miatt a gyerek fa kliens oldali renderelést igényelhet. Ha ezt egy Server Component layout/page közvetlenül tartalmazza, build figyelmeztetés vagy felesleges egész oldalas „kliens bailout” jöhet létre.
Gyakorlati szabály: csomagold a hookot használó részt külön Client Componentbe, és azt tedd Suspense fallbackkel:
// layout.tsx – Server Component
import { Suspense } from 'react'
import { SearchParamsConsumer } from './SearchParamsConsumer'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<Suspense fallback={null}>
<SearchParamsConsumer />
</Suspense>
{children}
</>
)
}📝 Összefoglaló – 7. fejezet
- Független
awaithelyettPromise.all(vagy célzott párhuzamosság) Suspense+loading.tsx: részleges tartalom hamarabb, lassú részek különcache(): többszöri azonos adatlekérés egy renderben → egy futás- Navigációs hookok: Suspense a várt minta a layout/page körül