← [ BACK_TO_NOTES ]

Next.js Loading 与 Streaming

Next.js App Router 的 loading.tsx 以 Suspense 为基础建立路由级 Pending Boundary,并与流式渲染配合让共享布局先显示、慢数据完成后再逐步补齐。

#concept #growing #frame #react

[!info] related notes

Next.js Loading 与 Streaming

一句话定义

loading.tsx 是 Next.js App Router 为某个 route segment 提供的路由级 Pending UI;它建立在 React Suspense 之上,让页面在数据尚未准备完成时先显示 fallback,并允许后续内容继续流式补齐。

基本模型

目录:

dashboard/
├── layout.tsx
├── loading.tsx
└── page.tsx

如果 page.tsx 里的数据获取很慢:

export default async function Page() {
  const data = await slowQuery()
  return <Dashboard data={data} />
}

用户可以先看到:

Dashboard Layout
├── Sidebar
├── Header
└── Loading Skeleton

等数据完成后再替换成真实 Page。

与 Suspense 的关系

可以先用这个心智模型理解:

loading.tsx
≈ route segment 的 Suspense fallback

也就是框架把 React 的 Suspense primitive 变成文件系统约定。

手工组件级 Suspense:

<Suspense fallback={<RevenueSkeleton />}>
  <RevenueChart />
</Suspense>

Next.js 路由级:

loading.tsx
+ page.tsx

二者目标相同,但粒度不同。

Streaming

没有流式边界时,传统心智可能是:

等待所有数据
    ↓
完整页面
    ↓
一次性返回

有 Suspense / Streaming 后,可以变成:

Layout / Header / Sidebar
        ↓
      先到达

Loading fallback
        ↓
      先显示

慢数据完成
        ↓
真实内容继续流入

因此 Streaming 的价值不只是“服务器更快”,而是让快的内容不必被慢的内容阻塞。

Layout 为什么不会因为 Loading 消失

loading.tsx 主要替代的是其路由内容边界,而外层仍然有效的 Layout 可以继续保留。

AppLayout
├── Sidebar       ← 保留
├── Header        ← 保留
└── Route Content
    ├── Loading   ← pending
    └── Page      ← ready 后替换

因此如果 Sidebar 的客户端状态位于被保留的 Layout 中,例如:

collapsed = true

子页面等待两秒并不会自动让它丢失。

Route-level 与 Component-level Loading

Route-level loading.tsx

适合:

  • 整个页面主体都需要等待
  • 希望快速给用户统一 Skeleton
  • 路由切换时提供即时反馈

Component-level <Suspense>

适合:

  • 只有局部数据慢
  • 希望其他内容先完成
  • 不想让整个页面都进入同一个 loading 状态

例如:

<DashboardSummary />

<Suspense fallback={<RevenueSkeleton />}>
  <RevenueChart />
</Suspense>

<Suspense fallback={<ActivitySkeleton />}>
  <RecentActivity />
</Suspense>

这体现一个通用设计原则:

Boundary 应尽量贴近真正会慢的区域,而不是机械放得越高越好。

Route Group 可以控制 Loading Boundary 范围

如果:

dashboard/loading.tsx

影响范围太大,可以利用 Next.js Route Groups 重新组织:

dashboard/
├── (overview)/
│   ├── loading.tsx
│   └── page.tsx
├── tasks/page.tsx
└── settings/page.tsx

这样 URL 不变,但 route boundary 可以更精确。

Streaming 与 LLM Streaming 不是一回事

Next.js 页面 Streaming 主要讨论:

React/HTML/RSC UI 如何分段到达并逐步展示。

而 LLM Streaming 常讨论:

Token、事件或模型输出如何通过 SSE/WebSocket 等协议持续传输。

两者都叫 Streaming,但流的对象和协议层次不同,不应混为一谈。

常见误区

loading.tsx 不是“全局 Spinner 开关”

它属于 route segment boundary,有明确作用范围。

Streaming 不等于数据请求一定更快

慢查询仍然可能很慢;Streaming 优化的是等待组织方式和感知性能。

有 loading.tsx 以后仍然需要局部 Suspense

路由级 fallback 粒度较大,复杂页面经常同时使用更细的组件级 Suspense。

最短记忆方式

loading.tsx
= route-level pending boundary
+ Suspense fallback
+ streaming entry point

它主要回答:这一层内容还没准备好时,用户先看到什么?

创建于 2026/9/15 更新于 2026/9/15
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本