🔵 Layout 数据(慢速:2秒)

数据获取时间:2025-12-17T09:54:11.734Z

数据获取耗时:2001ms

Layout 总耗时:2001ms

Layout 和 Page 并行渲染测试

🔀 并行渲染是什么?

在 Next.js App Router 中,layout 和 page 的数据获取是并行的, 而不是串行等待。这意味着如果 layout 需要 2 秒,page 需要 3 秒, 总渲染时间接近 3 秒(取最长的),而不是 5 秒(2 + 3)。

本次测试配置:

  • Layout 数据获取:2 秒
  • Page 数据获取:3 秒
  • 如果是串行:总时间 = 5 秒
  • 如果是并行:总时间 ≈ 3 秒

🟢 Page 数据(慢速:3秒)

数据获取时间:2025-12-17T09:54:12.735Z

数据获取耗时:3001ms

Page 总耗时:3001ms

⏱️ 渲染时间轴

串行渲染(传统方式)❌

0ms ─────────────────────────────────────────────── 开始
0ms → 2000ms ████████████ Layout 获取数据(2秒)
2000ms → 5000ms ██████████████████ Page 获取数据(3秒)
5000ms ───────────────────────────────────────────── ✅ 完成(总耗时 5秒)

并行渲染(Next.js App Router)✅

0ms ─────────────────────────────────────────────── 开始
0ms → 2000ms ████████████ Layout 获取数据(2秒)
0ms → 3000ms ██████████████████ Page 获取数据(3秒)
3000ms ───────────────────────────────────────────── ✅ 完成(总耗时 3秒)

⚡ 节省时间:5秒 - 3秒 = 2秒(40% 性能提升)

🧪 如何验证并行渲染?

  1. 查看终端日志:

    刷新页面后,观察终端输出:
    [LAYOUT] 开始获取数据
    [PAGE] 开始获取数据
    这两个日志的时间戳几乎相同 → 证明是并行启动

  2. 对比完成时间:

    - Layout 完成:约 2000ms
    - Page 完成:约 3000ms
    - 总渲染时间:约 3000ms(而不是 5000ms)

  3. 观察页面加载:

    打开 Network 面板,刷新页面:
    • 等待约 3 秒后,页面一次性显示(包括 Layout 和 Page)
    • 如果是串行,需要等待 5 秒

  4. 查看日志时间差:

    计算 [LAYOUT] 开始 [PAGE] 开始 的时间差
    → 时间差接近 0ms = 并行 ✅
    → 时间差接近 2000ms = 串行 ❌

🔍 技术原理

1️⃣ React 18 的并发特性

Next.js App Router 基于 React 18 的并发渲染能力, 可以同时启动多个异步操作,而不需要等待前一个完成。

2️⃣ 数据获取的并行化

当 Next.js 开始渲染一个路由时,会:

  • 同时启动 layout 的数据获取
  • 同时启动 page 的数据获取
  • 等待所有数据获取完成
  • 一次性渲染完整页面

3️⃣ 嵌套 Layout 也支持并行

如果有多层嵌套的 layout,它们的数据获取也会并行进行:

app/layout.tsx          (1秒) ┐
app/dashboard/layout.tsx (2秒) ├─ 并行
app/dashboard/page.tsx   (3秒) ┘
总耗时 ≈ 3秒(而不是 6秒)

💡 实际应用场景

✅ 并行获取的好处

  • 用户导航栏数据(Layout)
  • 页面主要内容(Page)
  • → 同时获取,节省时间

📊 典型场景

  • Layout:用户信息、通知数量
  • Page:文章内容、评论列表
  • → 并行加载,提升性能

💻 代码示例

// app/dashboard/layout.tsx
export default async function DashboardLayout({ children }) {
  // Layout 数据获取(例如:用户信息)
  const user = await fetchUser(); // 1秒
  
  return (
    <div>
      <nav>Welcome, {user.name}</nav>
      {children}
    </div>
  );
}

// app/dashboard/page.tsx
export default async function DashboardPage() {
  // Page 数据获取(例如:仪表盘数据)
  const stats = await fetchStats(); // 2秒
  
  return <div>Stats: {stats.total}</div>;
}

// 渲染流程:
// 0ms:  同时启动 fetchUser() 和 fetchStats()
// 1000ms: fetchUser() 完成
// 2000ms: fetchStats() 完成 → 页面渲染完成
// 总耗时:2秒(而不是 3秒)✅

⚠️ 注意事项

  • 总渲染时间 = max(layout 时间, page 时间)

    页面会等待最慢的那个完成

  • 子 layout 会等待父 layout

    但同级的 layout 和 page 是并行的

  • 开发环境可能有差异

    建议在生产环境(pnpm build && pnpm start)测试