你是否遇到过页面"白屏几秒才出内容"的问题?Next.js 的 流式渲染(Streaming) 功能,可以让你的页面像视频一样"边加载边显示",用户无需等待全部数据就看到部分内容。本文通过一个简单例子,手把手教你用 App Router 实现渐进式加载。
一、什么是流式渲染(Streaming)?
在传统 SSR(服务端渲染)中,服务器必须等所有数据加载完,才能返回完整 HTML 给浏览器。用户看到的是"白屏 → 突然全屏内容"。
而 流式渲染 允许服务器分块发送 HTML:
- 先返回页面骨架(如导航栏、标题)
- 再逐步填充数据区域(如文章内容、评论)
✅ 用户感知更快,SEO 友好,体验更流畅。
💡 Next.js App Router 原生支持流式渲染,配合 React 18 的
Suspense
,开箱即用!
二、核心原理:Suspense
+ 异步组件
Next.js 利用 React 18 的 <Suspense>
组件,将页面拆分为多个"可延迟加载"的区块。
结构如下:
jsx
<Suspense fallback="加载中...">
<AsyncComponent /> {/* 这个组件内部可以 await 数据 */}
</Suspense>
当 AsyncComponent
还在 fetch
数据时,先显示 fallback
;数据回来后,自动替换为真实内容。
三、实战例子:新闻详情页的渐进式加载
我们模拟一个新闻页面,包含:
- 固定头部(立即显示)
- 新闻标题(快速显示)
- 新闻正文(较慢,模拟延迟)
- 评论区(最慢,模拟复杂查询)
目标:让用户先看到标题,再看到正文,最后看到评论,而不是干等 3 秒。
第一步:创建 Next.js 项目(使用 App Router)
bash
npx create-next-app@latest streaming-demo
# 选择:Yes (使用 App Router)
cd streaming-demo
第二步:创建新闻详情页
创建文件:app/news/[id]/page.js
jsx
// app/news/[id]/page.js
import { Suspense } from 'react';
import NewsTitle from './NewsTitle';
import NewsContent from './NewsContent';
import Comments from './Comments';
export default function NewsPage({ params }) {
const { id } = params;
return (
<div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
<header style={{ marginBottom: '2rem' }}>
<h1>📰 新闻中心</h1>
</header>
{/* 标题:快速加载,不包裹 Suspense */}
<NewsTitle id={id} />
{/* 正文:中等延迟,用 Suspense */}
<Suspense fallback={<p>正在加载新闻内容...</p>}>
<NewsContent id={id} />
</Suspense>
{/* 评论区:高延迟,用 Suspense */}
<div style={{ marginTop: '2rem', borderTop: '1px solid #eee', paddingTop: '1rem' }}>
<h2>💬 评论区</h2>
<Suspense fallback={<p>正在加载评论,请稍候...</p>}>
<Comments id={id} />
</Suspense>
</div>
</div>
);
}
第三步:创建异步组件(模拟延迟)
1. 新闻标题(快速,0.5秒)
jsx
// app/news/[id]/NewsTitle.js
export default async function NewsTitle({ id }) {
// 模拟快速 API 调用
await new Promise(resolve => setTimeout(resolve, 500));
return <h2>【快讯】第 {id} 号新闻标题</h2>;
}
2. 新闻正文(中等,1.5秒)
jsx
// app/news/[id]/NewsContent.js
export default async function NewsContent({ id }) {
// 模拟较慢的数据获取
await new Promise(resolve => setTimeout(resolve, 1500));
return (
<div>
<p>这是第 {id} 号新闻的详细内容...</p>
<p>数据加载较慢,但用户已看到标题,不会觉得卡顿。</p>
</div>
);
}
3. 评论区(慢,2.5秒)
jsx
// app/news/[id]/Comments.js
export default async function Comments({ id }) {
// 模拟复杂查询(如数据库 JOIN)
await new Promise(resolve => setTimeout(resolve, 2500));
return (
<ul>
<li>用户A:内容很棒!</li>
<li>用户B:期待更多更新。</li>
<li>用户C:加载虽然慢,但体验不差 👍</li>
</ul>
);
}
第四步:添加全局布局(可选)
创建 app/layout.js
让页面更完整:
jsx
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="zh">
<body>
{children}
</body>
</html>
);
}
第五步:运行并观察效果
bash
npm run dev
访问 http://localhost:3000/news/123
你会看到:
- 立即显示"新闻中心"和页面结构
- 约 0.5 秒后显示新闻标题
- 约 1.5 秒后显示新闻正文
- 约 2.5 秒后显示评论区
🔍 打开浏览器开发者工具 → Network → 看 HTML 响应,会发现内容是分块传输的(Chunked Transfer)!
四、流式渲染的优势总结
优势 | 说明 |
---|---|
更快的首屏感知 | 用户无需等待全部数据 |
更好的 SEO | 搜索引擎能更快抓取关键内容 |
降低跳出率 | 用户看到内容后更愿意等待剩余部分 |
天然支持 Suspense | 无需复杂状态管理 |
五、注意事项
- 仅 App Router 支持:Pages Router 不支持流式渲染。
- 服务端组件默认 :上述组件都是服务端组件(无需
'use client'
)。 - 避免过度拆分 :不是每个组件都需要
Suspense
,只对慢速、非关键内容使用。 - fallback 要轻量 :
fallback
内容应简单(如文字、骨架屏),避免复杂逻辑。
六、进阶:配合 loading.js
Next.js 还支持页面级 loading(如路由切换时):
jsx
// app/news/[id]/loading.js
export default function Loading() {
return <p>正在加载新闻页面...</p>;
}
但注意:loading.js
是页面级 loading,而 Suspense
是组件级流式加载,两者互补。
七、结语
流式渲染是现代 Web 应用提升用户体验的利器。Next.js 通过 App Router + React Suspense,让这一高级功能变得简单、直观、开箱即用。
通过本文的新闻页面例子,你已经掌握了:
- 如何用
Suspense
包裹慢速组件 - 如何模拟异步数据延迟
- 如何实现内容的渐进式展示
现在,就去优化你的 Next.js 项目吧!让用户告别"白屏等待" 🚀
流式渲染不是炫技,而是以用户为中心的加载哲学------让用户先看见,再等待,始终掌控体验节奏。