从0死磕全栈之Next.js 平行路由(Parallel Routes)实战指南:轻松实现多区域动态页面

110 阅读5分钟

在开发复杂 Web 应用时,你是否遇到过这样的需求?

  • 在仪表盘中同时显示「用户信息」和「数据分析」两个独立模块;
  • 点击导航栏的“登录”按钮弹出 Modal,但 /login 也有独立页面;
  • 页面中嵌入一个可独立跳转的评论区,不影响主内容。

传统方案往往需要手动管理多个状态、URL 同步、刷新恢复等问题,代码复杂且容易出错。

而 Next.js App Router 提供了一个优雅的解决方案 —— 平行路由(Parallel Routes)。本文将用最简单的方式带你理解它,并通过完整可运行的代码示例,助你快速上手!


一、什么是平行路由?

平行路由允许你在同一个布局中同时渲染多个独立的内容区域,每个区域称为一个 Slot(插槽)

  • 每个 Slot 有自己的页面逻辑,互不影响;
  • URL 只反映主内容(children),Slot 路径不体现在 URL 中
  • 支持软导航(客户端跳转)和硬导航(刷新/直接访问)的状态恢复。

✅ 举个例子:
/dashboard 页面,左侧 @team 显示团队设置,右侧 @analytics 显示访问数据。
点击 /analytics/page-views,只有右侧更新,左侧保持不变。


二、命名规则:@slot 目录

Next.js 使用 @xxx 的目录名定义 Slot:

app/
├── @team/          ← team Slot
│   └── page.tsx
├── @analytics/     ← analytics Slot
│   └── page.tsx
└── layout.tsx      ← 接收 team 和 analytics 作为 props

layout.tsx 中,Slot 会以 props 形式传入

// app/layout.tsx
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    <div style={{ display: 'flex' }}>
      <div style={{ flex: 1 }}>{team}</div>
      <div style={{ flex: 2 }}>{children}</div>
      <div style={{ flex: 1 }}>{analytics}</div>
    </div>
  );
}

⚠️ 注意:

  • children 是隐式 Slot,对应 app/page.tsx
  • Slot 不是路由段,不会出现在 URL 中;
  • 所有同级 Slot 必须同时为静态或动态路由

三、实战:实现一个“社交平台的动态 Feed 流 ”

当然可以!下面我们用一个全新的实战例子来讲解 Next.js 的 平行路由(Parallel Routes),不再使用“登录 Modal”,而是构建一个 社交平台的动态 Feed 流 + 侧边评论面板 的场景。


🎯 场景描述

  • 用户访问 /feed,看到一条条动态(Posts);
  • 点击某条动态的“评论”按钮,右侧弹出评论面板
  • 评论面板内容可通过 URL 分享,例如 /post/3/comments
  • 直接访问 /post/3/comments 时,显示完整评论页
  • 刷新页面后,评论面板自动关闭,只显示 Feed;
  • 浏览器前进/后退能正确控制评论面板的开闭。

这个需求结合了:

  • Parallel Routes(实现独立评论区域)
  • Intercepting Routes(在 Feed 中拦截评论页,以内联形式展示)

📂 项目结构

app/
├── @comments/                  ← 评论 Slot
│   ├── (..)post/
│   │   └── [id]/
│   │       └── comments/
│   │           └── page.tsx    ← 拦截路由:在 Feed 中以内联形式展示
│   ├── default.tsx             ← 默认不显示评论面板
│   └── page.tsx                ← 关闭评论面板(返回 null)
├── feed/
│   └── page.tsx                ← 动态 Feed 主页
├── post/
│   └── [id]/
│       └── comments/
│           └── page.tsx        ← 独立评论页面
└── layout.tsx                  ← 根布局,渲染 Feed + 评论面板

💡 关键点:

  • @comments 是一个 Slot,用于承载评论内容;
  • (..)post/[id]/comments拦截路由,因为 postfeed上一级(同级目录),所以用 (..)
  • default.tsx 确保刷新时不显示残留评论。

✍️ 代码实现

1. Feed 主页:app/feed/page.tsx

// app/feed/page.tsx
import Link from 'next/link';

export default function FeedPage() {
  const posts = [
    { id: 1, content: '今天天气真好!' },
    { id: 2, content: '刚学了 Next.js 平行路由,太强大了!' },
    { id: 3, content: '求推荐好用的 UI 库' },
  ];

  return (
    <div style={{ maxWidth: '600px', margin: '0 auto' }}>
      <h1>动态 Feed</h1>
      {posts.map((post) => (
        <div key={post.id} style={{ padding: '16px', border: '1px solid #eee', margin: '12px 0' }}>
          <p>{post.content}</p>
          {/* 软导航到 /post/1/comments,触发拦截路由 */}
          <Link href={`/post/${post.id}/comments`}>查看评论</Link>
        </div>
      ))}
    </div>
  );
}

2. 独立评论页:app/post/[id]/comments/page.tsx

// app/post/[id]/comments/page.tsx
export default function CommentsPage({ params }: { params: { id: string } }) {
  return (
    <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
      <h2>完整评论页 - 动态 ID: {params.id}</h2>
      <p>这是独立的评论页面,可直接访问或刷新。</p>
      <ul>
        <li>用户A:写得真好!</li>
        <li>用户B:+1</li>
        <li>用户C:求源码</li>
      </ul>
    </div>
  );
}

3. 拦截路由(Feed 中的评论面板):app/@comments/(..)post/[id]/comments/page.tsx

// app/@comments/(..)post/[id]/comments/page.tsx
'use client';

import { useRouter } from 'next/navigation';

export default function InterceptedComments({ params }: { params: { id: string } }) {
  const router = useRouter();

  return (
    <div
      style={{
        position: 'fixed',
        top: 0,
        right: 0,
        width: '350px',
        height: '100vh',
        background: 'white',
        borderLeft: '1px solid #ddd',
        padding: '20px',
        boxShadow: '-2px 0 10px rgba(0,0,0,0.1)',
        zIndex: 1000,
        overflowY: 'auto',
      }}
    >
      <button
        onClick={() => router.back()}
        style={{ float: 'right', background: 'none', border: 'none', cursor: 'pointer' }}
      >
        ×
      </button>
      <h3>评论面板 - 动态 ID: {params.id}</h3>
      <p>这是在 Feed 中以内联方式展示的评论(通过拦截路由)。</p>
      <ul>
        <li>用户A:写得真好!</li>
        <li>用户B:+1</li>
        <li>用户C:求源码</li>
      </ul>
    </div>
  );
}

🔍 为什么用 (..)post
因为 @comments 是 Slot(不是路由段),所以 /post 实际在 上一级路由,用 (..) 拦截。


4. Slot 默认状态

// app/@comments/default.tsx
export default function CommentsDefault() {
  return null; // 初始不显示评论面板
}

// app/@comments/page.tsx(用于关闭面板)
export default function CommentsPage() {
  return null;
}

5. 根布局:app/layout.tsx

// app/layout.tsx
import Link from 'next/link';

export default function RootLayout({
  children,
  comments,
}: {
  children: React.ReactNode;
  comments: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <nav style={{ padding: '10px', borderBottom: '1px solid #eee' }}>
          <Link href="/feed">返回 Feed</Link>
        </nav>

        <div style={{ display: 'flex' }}>
          <div style={{ flex: 1 }}>{children}</div>
          {/* 评论面板 Slot */}
          <div>{comments}</div>
        </div>
      </body>
    </html>
  );
}

🧪 效果验证

操作行为
/feed 点击“查看评论”URL 变为 /post/2/comments,右侧弹出评论面板
刷新页面评论面板消失,显示完整 /post/2/comments 页面
浏览器后退评论面板关闭,回到 /feed
直接访问 /post/3/comments显示完整评论页(无 Feed)
前进/后退多次评论面板自动开闭,状态精准同步

✅ 完美实现:上下文保持 + URL 可分享 + 刷新安全 + 历史记录兼容


✅ 总结

这个例子展示了平行路由 + 拦截路由在真实业务场景中的强大组合:

  • @comments Slot:隔离评论区域,独立渲染;
  • (..)post/[id]/comments:在 Feed 中“拦截”评论页,以内联形式展示;
  • default.tsx:确保刷新后不残留 UI;
  • URL 语义清晰/post/2/comments 既是独立页,也是 Modal 的入口。

七、总结

平行路由是 Next.js App Router 的核心高级功能,它让复杂页面的构建变得:

  • 结构清晰:每个区域独立管理;
  • 状态可控:软/硬导航行为明确;
  • 用户体验好:支持分享、刷新、前进/后退。

如果你正在开发 Dashboard、社交 Feed、Modal 系统等,强烈推荐使用 Parallel Routes