一、Next.js 路由的核心特性
Next.js 提供了一种基于文件系统的声明式路由机制,将目录结构与 URL 路径直接关联。其核心设计特点包括:
1. 静态路由
app/blog 目录下的文件结构自动映射为 URL 路径(如 app/blog/page.js → /blog)
layout 是整个页面布局
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<main>{children}</main>
</body>
</html>
)
}
2. 嵌套路由
通过子目录实现层级结构(如 app/blog/[slug]/page.js → /blog/[slug])
3、动态路由
如上文也提到过 /blog/[slug] -> /blog/123,[slug] 就是动态段,
动态段作为 params 道具传递给layout、page、route 和 generateMetadata 函数。
// 获取参数
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug
return <div>My Post: {slug}</div>
}
通配符段
| 路由 | 示例 URL | 参数 |
|---|---|---|
app/shop/[...slug]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[...slug]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[...slug]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
可选通配符段
| 路由 | 示例 URL | 参数 |
|---|---|---|
app/shop/[[...slug]]/page.js | /shop | { slug: undefined } |
app/shop/[[...slug]]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[[...slug]]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[[...slug]]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
区别在于,可选,也会匹配不带参数的路由,如/shop
跳转方式
使用 Link 跳转
import Link from 'next/link'
export default async function Post({ post }) {
const posts = await getPosts()
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
还可以使用编程式导航 useRouter(客户端组件)
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
redirect函数(服务端组件)跳转
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id: string) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidatePath('/posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}
注意 hooks 一般都是客户端组件!!!
- 使用原生 History API
windows.history.pushState,window.history.replaceState
路由和导航原理
Next.js 的 App Router 使用 hybrid 进行路由和导航,包括代码分割、 预取、prefetching、缓存、部分渲染、soft navigation、back-and-forward-navigation
- 代码分割:可减少数据传输和执行时间,背后原理是使用 ES 模块的动态导入,这是代码分割的关键技术
- Preteching: Link 组件可在用户访问前预加载路由,两种方式可以预取路由
<Link>组件:当路由在用户的视口中变得可见时,会自动对其进行预取。预取发生在页面首次加载时或通过滚动进入视图时router.prefetch():useRouter钩子可以用来以编程方式预取路由
- 缓存: 当用户在应用程序中导航时,Preteching 的路由段和已访问路由的 React 服务器组件有效负载将存储在缓存中
- 部分渲染: 只重新渲染变化的路由片段,
- 软导航: 确保只有变化的路由片段被重新渲染 浏览器在页面之间导航时执行“硬导航”。Next.js App Router 能够在页面之间实现“软导航”,确保只有发生更改的路由段重新渲染(部分渲染)。这使得可以在导航期间保留客户端 React 状态
- 前后导航:可保持滚动位置和重用路由片段,
4、 加载 UI 和 流式传输
加载 UI
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <LoadingSkeleton />
}
流式传输
使用 SSR,用户能够查看到页面需要完成这些步骤
- 首先,在服务器上获取给定页面的所有数据。
- 然后,服务器渲染页面的 HTML。
- 页面的 HTML、CSS 和 JavaScript 发送到客户端。
- 使用生成的 HTML 和 CSS 显示非交互式用户界面。
- 最后,React Hydrates用户界面以使其具有交互性。
这些步骤是顺序和阻塞的,这意味着服务器只能在获取所有数据后才能渲染页面的 HTML。而且,在客户端,React 只能在下载页面中所有组件的代码后才能 水合 UI。
流式传输允许您将页面的 HTML 分解成更小的块,并逐步将这些块从服务器发送到客户端。
可以让页面一部分更快的显示,不用等待所有的数据加载后才能渲染 UI
防止长时间的数据请求阻塞页面渲染时,流式传输特别有用,因为它可以减少首字节时间 (TTFB)和首屏内容绘制 (FCP)。它还有助于改善到达交互状态时间 (TTI),尤其是在较慢的设备上。
流式传输是在服务端完成的,不影响 SEO
5、错误处理
Next.js 使用错误边界来处理未捕获的异常。错误边界捕获其子组件中的错误,并显示回退 UI 而不是崩溃的组件树
"use client";
export default function Error({ reset }: { reset: () => void }) {
return (
<div className="mx-auto my-4 flex max-w-xl flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 dark:border-neutral-800 dark:bg-black">
<h2 className="text-xl font-bold">Oh no!</h2>
<p className="my-2">Oops! Something went wrong.</p>
<button
className="mx-auto mt-4 flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90"
onClick={() => reset()}
>
Try Again
</button>
</div>
);
}
6、重定向
中间件中的 NextResponse.redirect
中间件允许您在请求完成之前运行代码。然后,根据传入请求,使用 NextResponse.redirect 重定向到不同的 URL。如果您想根据某个条件(例如身份验证、会话管理等)重定向用户或有 大量重定向,这将非常有用。
例如,如果用户未经身份验证,则将其重定向到 /login 页面。
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
7、路由组
加个括号,表示分组,不影响路径
添加骨架屏,我自己添加骨架屏写在了 page.tsx 里面...
创建多个根布局,我就说呢 ,单是一个layout.js 怎么能解决多个根布局
8、并行路由(App Router):允许同时渲染多个独立页面模块
同一布局中同时或有条件地渲染一个或多个页面
使用 @ 来表示并行路由, 不影响 url 结构, 个人觉得并行路由怎么没有应用场景呢
9. 拦截路由
拦截路由可以使用 (..) 约定来定义,这与相对路径约定 ../ 类似
(.)匹配同一级别上的段(..)匹配上一级的段(..)(..)匹配上两级的段(...)匹配来自根app目录的段
10、中间件
可以处理渲染页面之前很多事情, 如 设置 请求头、响应头、CORS 等
二、Pages Router vs App Router 对比
| 特性 | Pages Router (v12-) | App Router (v13+) |
|---|---|---|
| 目录结构 | /pages | /app |
| 布局系统 | 通过_app.js全局控制 | 支持层级布局模板(Layout.js) |
| 数据获取 | getStaticProps等函数 | async组件直接获取数据 |
| 嵌套路由 | 需手动配置 | 原生支持文件夹嵌套 |
| 加载状态 | 需自定义 | 内置Loading组件 |
三、常见问题解决方案
- 404 处理:创建
not-found.js自定义页面 - 路由冲突:静态路由优先级高于动态路由
- 多布局切换:通过
useSelectedLayoutSegment()获取当前路由段 - 服务端组件限制:动态路由参数需在
generateMetadata中处理
附:路由调试技巧
1. 使用 `NEXT_DEBUG_ROUTING=1` 环境变量查看路由匹配过程
2. 通过 `router.refresh()` 强制刷新客户端缓存
3. 用 `usePathname()` 监控当前路径变化