从0死磕全栈之Next.js 流式渲染(Streaming)实战:实现渐进式加载页面,提升用户体验

436 阅读4分钟

你是否遇到过页面“白屏几秒才出内容”的问题?Next.js 的 流式渲染(Streaming) 功能,可以让你的页面像视频一样“边加载边显示”,用户无需等待全部数据就看到部分内容。本文通过一个简单例子,手把手教你用 App Router 实现渐进式加载。


一、什么是流式渲染(Streaming)?

在传统 SSR(服务端渲染)中,服务器必须等所有数据加载完,才能返回完整 HTML 给浏览器。用户看到的是“白屏 → 突然全屏内容”。

流式渲染 允许服务器分块发送 HTML

  • 先返回页面骨架(如导航栏、标题)
  • 再逐步填充数据区域(如文章内容、评论)

✅ 用户感知更快,SEO 友好,体验更流畅。

💡 Next.js App Router 原生支持流式渲染,配合 React 18 的 Suspense,开箱即用!


二、核心原理:Suspense + 异步组件

Next.js 利用 React 18 的 <Suspense> 组件,将页面拆分为多个“可延迟加载”的区块。

结构如下:

<Suspense fallback="加载中...">
  <AsyncComponent /> {/* 这个组件内部可以 await 数据 */}
</Suspense>

AsyncComponent 还在 fetch 数据时,先显示 fallback;数据回来后,自动替换为真实内容。


三、实战例子:新闻详情页的渐进式加载

我们模拟一个新闻页面,包含:

  • 固定头部(立即显示)
  • 新闻标题(快速显示)
  • 新闻正文(较慢,模拟延迟)
  • 评论区(最慢,模拟复杂查询)

目标:让用户先看到标题,再看到正文,最后看到评论,而不是干等 3 秒。


第一步:创建 Next.js 项目(使用 App Router)

npx create-next-app@latest streaming-demo
# 选择:Yes (使用 App Router)
cd streaming-demo

第二步:创建新闻详情页

创建文件:app/news/[id]/page.js

// 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秒)

// 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秒)

// 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秒)

// 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 让页面更完整:

// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="zh">
      <body>
        {children}
      </body>
    </html>
  );
}

第五步:运行并观察效果

npm run dev

访问 http://localhost:3000/news/123

你会看到:

  1. 立即显示“新闻中心”和页面结构
  2. 约 0.5 秒后显示新闻标题
  3. 约 1.5 秒后显示新闻正文
  4. 约 2.5 秒后显示评论区

🔍 打开浏览器开发者工具 → Network → 看 HTML 响应,会发现内容是分块传输的(Chunked Transfer)!


四、流式渲染的优势总结

优势说明
更快的首屏感知用户无需等待全部数据
更好的 SEO搜索引擎能更快抓取关键内容
降低跳出率用户看到内容后更愿意等待剩余部分
天然支持 Suspense无需复杂状态管理

五、注意事项

  1. 仅 App Router 支持:Pages Router 不支持流式渲染。
  2. 服务端组件默认:上述组件都是服务端组件(无需 'use client')。
  3. 避免过度拆分:不是每个组件都需要 Suspense,只对慢速、非关键内容使用。
  4. fallback 要轻量fallback 内容应简单(如文字、骨架屏),避免复杂逻辑。

六、进阶:配合 loading.js

Next.js 还支持页面级 loading(如路由切换时):

// 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 项目吧!让用户告别“白屏等待” 🚀

流式渲染不是炫技,而是以用户为中心的加载哲学——让用户先看见,再等待,始终掌控体验节奏。