路由系统基础
深入理解 Next.js 的文件系统路由、动态路由及路由组等核心概念。
📚 学习目标
学完这篇文章后,你将能够:
- 理解 Next.js App Router 的路由原理
- 掌握静态路由、动态路由、路由组的使用
- 了解核心文件约定(page、layout、loading、error、not-found)
- 学会在项目中实现不同的路由功能
- 掌握路由的最佳实践
前置知识
在开始学习之前,建议先阅读:
你需要了解:
- URL 的基本概念
- 文件和目录的基本操作
1️⃣ Next.js App Router 概述
1.1 什么是 App Router?
Next.js 13+ 引入了全新的 App Router,使用 app/ 目录替代了之前的 pages/ 目录。
核心特点:
- 📁 文件系统路由(文件夹和文件自动生成路由)
- 🎯 布局系统(Layouts)
- ⚡ 流式渲染(Streaming)
- 🔄 Server Components(默认)
- 🔌 并行路由和拦截(高级功能)
1.2 App Router vs Pages Router
| 特性 | App Router (app/) | Pages Router (pages/) |
|---|---|---|
| 文件约定 | 文件夹 + 文件 | 文件 |
| 布局系统 | 支持(Layouts) | 不支持 |
| 默认组件 | Server Component | Client Component |
| 数据获取 | async/await | getStaticProps/getServerSideProps |
| 发布版本 | Next.js 13+ | 早期版本 |
本项目使用 App Router:所有路由都在 app/ 目录下。
2️⃣ 文件系统路由基础
2.1 路由规则
Next.js 使用文件夹结构来定义路由:
| 文件/文件夹 | 生成的 URL |
|---|---|
app/page.tsx | / |
app/about/page.tsx | /about |
app/about/team/page.tsx | /about/team |
app/artifact/[id]/page.tsx | /artifact/123 (动态) |
2.2 核心文件约定
Next.js 使用特殊的文件名来定义不同的功能:
| 文件名 | 作用 |
|---|---|
page.tsx | 页面文件(必需) |
layout.tsx | 布局文件(共享 UI) |
loading.tsx | 加载状态 |
error.tsx | 错误处理 |
not-found.tsx | 404 页面 |
route.ts | API 路由 |
3️⃣ 静态路由
3.1 基本静态路由
静态路由是最简单的路由形式,路径固定不变。
文件结构:
app/
├── page.tsx → /
├── login/
│ └── page.tsx → /login
└── deepresearch/
└── page.tsx → /deepresearch示例 1:首页(app/page.tsx)
查看本项目的首页:app/page.tsx
'use client';
import { useRef, useMemo, useState, useEffect } from 'react';
import { ProtectedRoute } from './components/ProtectedRoute';
import SessionSidebar from './components/SessionSidebar';
import { ChatHeader } from './components/ChatHeader';
import { MessageList } from './components/MessageList';
import { ChatInput, type ChatInputHandle } from './components/ChatInput';
/**
* 聊天页面主组件
*/
export default function ChatPage() {
// 组件逻辑...
return (
<div className="flex h-screen">
<SessionSidebar />
<main className="flex-1">
<ChatHeader />
<MessageList />
<ChatInput />
</main>
</div>
);
}说明:
- 这是项目的根页面,访问
/时渲染 - 使用
'use client'标记为 Client Component - 整合了多个子组件
示例 2:登录页(app/login/page.tsx)
'use client';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const router = useRouter();
const handleLogin = () => {
// 登录逻辑...
router.push('/'); // 跳转到首页
};
return (
<div>
<h1>登录</h1>
<button onClick={handleLogin}>登录</button>
</div>
);
}3.2 嵌套静态路由
路由可以嵌套多层:
app/
├── page.tsx → /
├── about/
│ ├── page.tsx → /about
│ └── team/
│ └── page.tsx → /about/team4️⃣ 动态路由
4.1 什么是动态路由?
动态路由允许你创建带有参数的路由,例如 /artifact/123、/user/profile 等。
语法:使用方括号 [param] 标记动态参数。
4.2 基本动态路由
文件结构:
app/
└── artifact/
└── [id]/
└── page.tsx → /artifact/123示例:Canvas 工件详情页(app/artifact/[id]/page.tsx)
'use client';
import { useParams, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function ArtifactPage() {
// 获取路由参数
const params = useParams();
const router = useRouter();
const artifactId = params.id as string;
const [artifact, setArtifact] = useState(null);
useEffect(() => {
// 根据 ID 加载工件
fetch(`/api/artifacts/${artifactId}`)
.then(res => res.json())
.then(data => setArtifact(data))
.catch(() => router.push('/artifact/not-found'));
}, [artifactId, router]);
if (!artifact) {
return <div>加载中...</div>;
}
return (
<div>
<h1>工件 #{artifactId}</h1>
<pre>{artifact.content}</pre>
</div>
);
}说明:
useParams()Hook 获取路由参数params.id对应[id]动态参数- 可以根据参数加载数据
4.3 动态路由在 API 中的应用
API 路由也支持动态参数:
app/api/
└── artifacts/
└── [id]/
└── route.ts → GET /api/artifacts/123查看本项目的实现:app/api/artifacts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withAuth } from '@/app/middleware/auth';
import { artifactService } from '@/app/services';
/**
* GET /api/artifacts/[id]
* 获取指定 ID 的工件
*/
export const GET = withAuth(async (request: NextRequest, auth, { params }) => {
try {
const id = params.id;
const artifact = await artifactService.getArtifactById(auth.client, id);
if (!artifact) {
return NextResponse.json({ error: '工件不存在' }, { status: 404 });
}
return NextResponse.json({ artifact });
} catch (e) {
return NextResponse.json(
{ error: '获取工件失败', detail: String(e) },
{ status: 500 },
);
}
});
/**
* DELETE /api/artifacts/[id]
* 删除指定 ID 的工件
*/
export const DELETE = withAuth(
async (request: NextRequest, auth, { params }) => {
try {
const id = params.id;
await artifactService.deleteArtifact(auth.client, id);
return NextResponse.json({ success: true });
} catch (e) {
return NextResponse.json(
{ error: '删除工件失败', detail: String(e) },
{ status: 500 },
);
}
},
);说明:
- API Route 的
params参数包含动态路由参数 params.id对应[id]动态段
5️⃣ Layout(布局)
5.1 什么是 Layout?
Layout 允许你在多个页面之间共享 UI,例如导航栏、侧边栏等。
特点:
- 布局会在路由切换时保持状态
- 支持嵌套布局
- 不会重新渲染(性能优化)
5.2 本项目的全局布局
查看实现:app/layout.tsx
import type { Metadata } from "next";
import "./globals.css";
import { AuthProvider } from "./contexts/AuthContext";
export const metadata: Metadata = {
title: "LangGraph Chat App",
description: "Chat application powered by LangGraph",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="zh-CN">
<body className="bg-[#050509] text-slate-200 antialiased selection:bg-blue-500/30 overflow-hidden">
<AuthProvider>
{children}
</AuthProvider>
</body>
</html>
);
}代码解析:
metadata导出:设置页面的 SEO 信息AuthProvider:包裹所有子组件,提供全局认证状态{children}:占位符,所有子页面内容会渲染在这里
5.3 嵌套布局
你可以在子路由中定义自己的布局:
app/
├── layout.tsx # 根布局
└── dashboard/
├── layout.tsx # Dashboard 子布局
├── page.tsx # /dashboard
└── settings/
└── page.tsx # /dashboard/settings// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<nav>Dashboard 导航栏</nav>
{children}
</div>
);
}6️⃣ 特殊路由文件
6.1 loading.tsx - 加载状态
定义路由加载时的 UI。
// app/chat/loading.tsx
export default function Loading() {
return <div>加载中...</div>;
}本项目未使用:因为使用了客户端加载状态。
6.2 error.tsx - 错误处理
定义路由出错的 UI。
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>出错了!</h2>
<p>{error.message}</p>
<button onClick={reset}>重试</button>
</div>
);
}6.3 not-found.tsx - 404 页面
定义资源未找到时的 UI。
查看本项目的实现:app/artifact/[id]/ArtifactNotFound.tsx
import Link from 'next/link';
import { ArrowLeft, FileQuestion } from 'lucide-react';
export default function ArtifactNotFound() {
return (
<div className="flex flex-col items-center justify-center h-screen">
<FileQuestion className="w-24 h-24 text-gray-500 mb-4" />
<h1 className="text-2xl font-bold mb-2">工件不存在</h1>
<p className="text-gray-400 mb-4">
该工件可能已被删除或您没有访问权限
</p>
<Link href="/" className="flex items-center text-blue-400 hover:underline">
<ArrowLeft className="w-4 h-4 mr-2" />
返回首页
</Link>
</div>
);
}使用示例:
// app/artifact/[id]/page.tsx
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import ArtifactNotFound from './ArtifactNotFound';
export default function ArtifactPage() {
const params = useParams();
const [artifact, setArtifact] = useState(null);
const [notFound, setNotFound] = useState(false);
useEffect(() => {
fetch(`/api/artifacts/${params.id}`)
.then(res => {
if (!res.ok) {
setNotFound(true);
return null;
}
return res.json();
})
.then(data => {
if (data) setArtifact(data.artifact);
});
}, [params.id]);
if (notFound) {
return <ArtifactNotFound />;
}
if (!artifact) {
return <div>加载中...</div>;
}
return <div>{artifact.content}</div>;
}7️⃣ 路由组(Route Groups)
7.1 什么是路由组?
路由组使用圆括号 (group) 命名,用于组织代码而不影响 URL 路径。
语法:
app/
├── (marketing)/
│ ├── about/
│ │ └── page.tsx → /about
│ └── contact/
│ └── page.tsx → /contact
└── (app)/
└── dashboard/
└── page.tsx → /dashboard说明:
(marketing)和(app)不会出现在 URL 中- 用于组织相关页面
7.2 本项目的应用
本项目使用路由组来组织不同类型的页面:
app/
├── (app)/
│ ├── page.tsx → /
│ ├── login/
│ │ └── page.tsx → /login
│ └── deepresearch/
│ └── page.tsx → /deepresearch
└── (artifact)/
└── artifact/
└── [id]/
└── page.tsx → /artifact/1238️⃣ 路由导航
8.1 Link 组件
使用 Link 组件进行客户端导航(推荐):
import Link from 'next/link';
export default function Navigation() {
return (
<nav>
<Link href="/">首页</Link>
<Link href="/about">关于</Link>
<Link href="/artifact/123">工件详情</Link>
</nav>
);
}优点:
- 客户端导航(无页面刷新)
- 自动预加载(鼠标悬停时)
- 更好的性能
8.2 useRouter Hook
使用 useRouter Hook 进行编程式导航:
'use client';
import { useRouter } from 'next/navigation';
export default function LoginButton() {
const router = useRouter();
const handleLogin = () => {
// 登录成功后跳转
router.push('/');
// 替换当前页面(不添加历史记录)
router.replace('/dashboard');
// 返回上一页
router.back();
};
return <button onClick={handleLogin}>登录</button>;
}方法说明:
router.push(path)- 导航到新页面router.replace(path)- 替换当前页面router.back()- 返回上一页router.forward()- 前进
8.3 本项目中的应用
查看登录页:app/login/page.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useAuth } from '@/app/contexts/AuthContext';
export default function LoginPage() {
const router = useRouter();
const { user } = useAuth();
// 如果已登录,跳转到首页
useEffect(() => {
if (user) {
router.push('/');
}
}, [user, router]);
// ... 登录逻辑
}9️⃣ 路由参数与查询参数
9.1 获取路由参数
使用 useParams Hook 获取动态路由参数:
'use client';
import { useParams } from 'next/navigation';
export default function UserPage() {
const params = useParams();
const userId = params.id; // 对应 [id]
return <div>用户 ID: {userId}</div>;
}9.2 获取查询参数
使用 useSearchParams Hook 获取 URL 查询参数:
'use client';
import { useSearchParams } from 'next/navigation';
export default function SearchPage() {
const searchParams = useSearchParams();
const query = searchParams.get('q'); // ?q=hello
const page = searchParams.get('page'); // ?page=1
return (
<div>
<p>搜索词: {query}</p>
<p>页码: {page}</p>
</div>
);
}9.3 本项目中的应用
查看会话列表 API:app/api/chat/sessions/route.ts
export const GET = withAuth(async (request: NextRequest, auth) => {
const { searchParams } = new URL(request.url);
const type = searchParams.get('type') as SessionType | undefined;
// 根据 type 参数过滤会话
const sessions = await sessionService.getAllSessions(auth.client, type);
return NextResponse.json({ sessions });
});使用示例:
GET /api/chat/sessions- 获取所有会话GET /api/chat/sessions?type=chat- 只获取聊天会话GET /api/chat/sessions?type=deepresearch- 只获取深度研究会话
🔟 实战案例:完整路由实现
10.1 项目路由总览
10.2 核心路由代码
聊天主页(app/page.tsx):
'use client';
export default function ChatPage() {
return (
<div className="flex h-screen">
<SessionSidebar />
<main className="flex-1">
<ChatHeader />
<MessageList />
<ChatInput />
</main>
</div>
);
}会话管理 API(app/api/chat/sessions/route.ts):
import { NextRequest, NextResponse } from 'next/server';
import { withAuth } from '@/app/middleware/auth';
import { sessionService } from '@/app/services';
// GET /api/chat/sessions - 获取会话列表
export const GET = withAuth(async (request: NextRequest, auth) => {
const sessions = await sessionService.getAllSessions(auth.client);
return NextResponse.json({ sessions });
});
// POST /api/chat/sessions - 创建新会话
export const POST = withAuth(async (request: NextRequest, auth) => {
const { name } = await request.json();
const result = await sessionService.createSession(
{ name },
auth.user.id,
auth.client,
);
return NextResponse.json(result);
});
// DELETE /api/chat/sessions - 删除会话
export const DELETE = withAuth(async (request: NextRequest, auth) => {
const { id } = await request.json();
await sessionService.deleteSession({ id });
return NextResponse.json({ success: true });
});
// PATCH /api/chat/sessions - 更新会话名称
export const PATCH = withAuth(async (request: NextRequest, auth) => {
const { id, name } = await request.json();
await sessionService.updateSessionName({ id, name });
return NextResponse.json({ success: true });
});💡 练习题
-
选择题:以下文件会生成什么 URL?
app/ ├── page.tsx ├── product/ │ └── [id]/ │ └── page.tsx └── api/ └── users/ └── route.ts- A.
/,/product/:id,/api/users - B.
/,/product/[id],/api/users - C.
/home,/product/:id,/api/users - D.
/,/product/:id,/api/users/route
- A.
-
代码题:创建一个动态路由
/blog/[slug],显示博客文章详情。 -
分析题:查看本项目的 app/artifact/[id]/page.tsx,说明它如何获取和使用路由参数。
-
实践题:创建一个新的页面
/settings,并添加导航链接。
📚 参考资源
官方文档
本项目相关文件
- app/page.tsx - 主页
- app/layout.tsx - 全局布局
- app/artifact/[id]/page.tsx - 工件详情页
- app/api/chat/sessions/route.ts - 会话管理 API
- app/artifact/[id]/ArtifactNotFound.tsx - 404 页面
✅ 总结
路由规则:
- 文件系统路由:文件夹和文件自动生成 URL
page.tsx→ 页面layout.tsx→ 布局route.ts→ API
路由类型:
- 静态路由:
app/page.tsx→/ - 动态路由:
app/[id]/page.tsx→/123 - 路由组:
app/(group)/page.tsx→/
导航方式:
Link组件:客户端导航(推荐)useRouterHook:编程式导航
本项目核心路由:
/- 聊天主页/login- 登录页/deepresearch- 深度研究页/artifact/[id]- 工件详情页/api/chat/*- 聊天相关 API/api/artifacts/*- 工件管理 API
下一步:阅读下一篇文章《API Routes 完全指南》,学习如何创建和使用 Next.js API。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。