【Nextjs】 路由篇

324 阅读6分钟

一、Next.js 路由的核心特性

Next.js 提供了一种基于文件系统的声明式路由机制,将目录结构与 URL 路径直接关联。其核心设计特点包括:

1. 静态路由

app/blog 目录下的文件结构自动映射为 URL 路径(如 app/blog/page.js/blog

image.png

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]

image.png

3、动态路由

如上文也提到过 /blog/[slug] -> /blog/123[slug] 就是动态段, 动态段作为 params 道具传递给layoutpageroute 和 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 navigationback-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 />
 }

image.png

流式传输

使用 SSR,用户能够查看到页面需要完成这些步骤

  1. 首先,在服务器上获取给定页面的所有数据。
  2. 然后,服务器渲染页面的 HTML。
  3. 页面的 HTML、CSS 和 JavaScript 发送到客户端。
  4. 使用生成的 HTML 和 CSS 显示非交互式用户界面。
  5. 最后,React Hydrates用户界面以使其具有交互性。

image.png

这些步骤是顺序和阻塞的,这意味着服务器只能在获取所有数据后才能渲染页面的 HTML。而且,在客户端,React 只能在下载页面中所有组件的代码后才能 水合 UI。

流式传输允许您将页面的 HTML 分解成更小的块,并逐步将这些块从服务器发送到客户端。

image.png

可以让页面一部分更快的显示,不用等待所有的数据加载后才能渲染 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>
  );
}

image.png

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、路由组

加个括号,表示分组,不影响路径

image.png

添加骨架屏,我自己添加骨架屏写在了 page.tsx 里面...

image.png

创建多个根布局,我就说呢 ,单是一个layout.js 怎么能解决多个根布局

image.png

8、并行路由(App Router):允许同时渲染多个独立页面模块

同一布局中同时或有条件地渲染一个或多个页面

image.png

使用 @ 来表示并行路由, 不影响 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组件

三、常见问题解决方案

  1. 404 处理:创建 not-found.js 自定义页面
  2. 路由冲突:静态路由优先级高于动态路由
  3. 多布局切换:通过 useSelectedLayoutSegment() 获取当前路由段
  4. 服务端组件限制:动态路由参数需在 generateMetadata 中处理

附:路由调试技巧

1. 使用 `NEXT_DEBUG_ROUTING=1` 环境变量查看路由匹配过程
2. 通过 `router.refresh()` 强制刷新客户端缓存 
3.`usePathname()` 监控当前路径变化