Impact: MEDIUM (improves perceived load time by showing content as it becomes available)
A page that awaits all data before rendering shows a blank screen (or a full-page spinner) until the slowest query completes. If your page fetches user data (50ms), rules (200ms), and analytics from a third-party API (2 seconds), the entire page is blocked for 2 seconds — even though most content was ready in 200ms.
Suspense boundaries let React stream content to the client as each section resolves. Fast sections appear immediately while slow sections show skeleton placeholders, then pop in when ready. This dramatically improves perceived performance without changing your data fetching logic.
Incorrect (all-or-nothing page rendering):
// app/dashboard/page.tsx// ❌ Entire page blocked until slowest fetch completesexport default async function DashboardPage() { const user = await fetchUser() // 50ms const rules = await ruleService.getActiveRules() // 200ms const analytics = await analyticsApi.getSummary() // 2000ms ← blocks everything // User sees nothing for 2+ seconds return ( <div> <UserHeader user={user} /> <RulesSummary rules={rules} /> <AnalyticsDashboard analytics={analytics} /> </div> )}
// ❌ Even with Promise.all, page still blocks until slowest resolvesexport default async function DashboardPage() { const [user, rules, analytics] = await Promise.all([ fetchUser(), // 50ms ─┐ ruleService.getActiveRules(), // 200ms ─┤ Concurrent but page analyticsApi.getSummary(), // 2000ms ┘ still waits for all three ]) return ( <div> <UserHeader user={user} /> <RulesSummary rules={rules} /> <AnalyticsDashboard analytics={analytics} /> </div> )}
Correct (Suspense boundaries for progressive streaming):
// app/dashboard/page.tsx// ✅ Fast sections render immediately, slow sections stream inimport { Suspense } from 'react'export default async function DashboardPage() { // Fast data fetched at page level — available immediately const user = await fetchUser() // 50ms return ( <div> {/* ✅ Renders instantly — data already loaded */} <UserHeader user={user} /> {/* ✅ Renders in ~200ms with its own loading state */} <Suspense fallback={<RulesSummarySkeleton />}> <RulesSummarySection userId={user.id} /> </Suspense> {/* ✅ Renders in ~2s — doesn't block anything above */} <Suspense fallback={<AnalyticsSkeleton />}> <AnalyticsSection userId={user.id} /> </Suspense> </div> )}
// components/RulesSummarySection.tsx// ✅ Async Server Component — fetches its own data independentlyasync function RulesSummarySection({ userId }: { userId: string }) { const rules = await ruleService.getActiveRules(userId) // 200ms return <RulesSummary rules={rules} />}// components/AnalyticsSection.tsx// ✅ Slow third-party call doesn't block the rest of the pageasync function AnalyticsSection({ userId }: { userId: string }) { const analytics = await analyticsApi.getSummary(userId) // 2000ms return <AnalyticsDashboard analytics={analytics} />}