Streaming SSR với Suspense: Tăng tốc trang Next.js
Hướng dẫn implement streaming SSR trong Next.js với React Suspense — giảm TTFB, cải thiện Core Web Vitals, và UX loading tốt hơn.
Traditional SSR có một vấn đề lớn: server phải render toàn bộ page trước khi gửi bất kỳ byte nào về client. Nếu có một component chậm (API call mất 3 giây), cả page bị block. Streaming SSR giải quyết điều này.
Streaming SSR hoạt động thế nào?
Thay vì đợi render xong rồi gửi, server gửi HTML theo từng chunk. Phần nào render xong gửi trước, phần chậm gửi sau. Browser bắt đầu paint ngay khi nhận chunk đầu tiên.
Trong Next.js App Router, streaming được kích hoạt tự động qua <Suspense> boundaries.
Implement cơ bản
Wrap slow components trong Suspense với fallback UI:
// app/dashboard/page.tsx
import { Suspense } from "react";
import { DashboardStats } from "@/components/DashboardStats";
import { RecentArticles } from "@/components/RecentArticles";
import { TrendingTags } from "@/components/TrendingTags";
import { Skeleton } from "@/components/ui/skeleton";
export default function DashboardPage() {
return (
<div className="grid grid-cols-12 gap-6">
{/* Stats load nhanh — render đầu tiên */}
<div className="col-span-12">
<Suspense fallback={<StatsSkeletons />}>
<DashboardStats />
</Suspense>
</div>
{/* Articles và Tags stream independent */}
<div className="col-span-8">
<Suspense fallback={<ArticleListSkeleton />}>
<RecentArticles />
</Suspense>
</div>
<div className="col-span-4">
<Suspense fallback={<TagsSkeleton />}>
<TrendingTags />
</Suspense>
</div>
</div>
);
}
Mỗi Suspense boundary là một streaming point. Component nào render xong trước sẽ stream về client trước.
Nested Suspense — Granular loading
Bạn có thể nest Suspense boundaries để tạo loading UI chi tiết hơn. Ví dụ trong article page:
// app/articles/[slug]/page.tsx
export default async function ArticlePage({ params }) {
const { slug } = await params;
return (
<article>
{/* Article content stream đầu tiên */}
<Suspense fallback={<ArticleSkeleton />}>
<ArticleContent slug={slug} />
</Suspense>
{/* Related articles stream sau */}
<Suspense fallback={<p>Đang tải bài liên quan...</p>}>
<RelatedArticles slug={slug} />
</Suspense>
{/* Comments stream cuối cùng */}
<Suspense fallback={<CommentsSkeleton />}>
<CommentsSection slug={slug} />
</Suspense>
</article>
);
}
User thấy article content ngay, trong khi related articles và comments vẫn đang load. Perceived performance tăng đáng kể.
Impact lên Core Web Vitals
TTFB (Time to First Byte) — Giảm mạnh vì server bắt đầu gửi response ngay, không đợi slow queries.
FCP (First Contentful Paint) — Browser nhận HTML sớm hơn, paint shell + fast content trước.
LCP (Largest Contentful Paint) — Nếu LCP element nằm trong chunk đầu tiên, cải thiện rõ rệt.
INP (Interaction to Next Paint) — Ít JS hydration cần chạy cùng lúc, main thread ít bị block.
Tips thực tế
Skeleton UI thay vì spinner — Skeleton giữ layout stability (tránh CLS), spinner thì không. Dùng skeleton matching shape của real content.
Đừng wrap mọi thứ trong Suspense — Chỉ wrap components thực sự cần async data. Static content render bình thường.
loading.tsx = page-level Suspense — File loading.tsx trong App Router tương đương wrap cả page trong Suspense. Dùng cho route transitions.
Combine với PPR — Partial Prerendering (Next.js 15+) kết hợp static shell + streaming dynamic parts. Ultimate performance pattern.
Kết luận
Streaming SSR với Suspense là một trong những cải tiến quan trọng nhất của React ecosystem. Trong Next.js App Router, nó gần như miễn phí — chỉ cần đặt Suspense boundaries đúng chỗ. Hãy bắt đầu với những page có multiple data sources và bạn sẽ thấy sự khác biệt ngay.
Admin
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!