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

你是否遇到过页面"白屏几秒才出内容"的问题?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

你会看到:

  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(如路由切换时):

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

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


相关推荐
东东51615 分钟前
基于ssm的网上房屋中介管理系统vue
前端·javascript·vue.js
harrain1 小时前
什么!vue3.4开始,v-model不能用在prop上
前端·javascript·vue.js
fanruitian7 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo7 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk7 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
2501_944525548 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
李白你好9 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端
刘一说10 小时前
Vue 组件不必要的重新渲染问题解析:为什么子组件总在“无故”刷新?
前端·javascript·vue.js
徐同保11 小时前
React useRef 完全指南:在异步回调中访问最新的 props/state引言
前端·javascript·react.js
刘一说11 小时前
Vue 导航守卫未生效问题解析:为什么路由守卫不执行或逻辑失效?
前端·javascript·vue.js