NextJs 路由管理
Defining Routes
1. Creating Routes
2. Creating UI
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
Route Groups 路由组
1. 在不影响 URL 路径的情况下组织路由
要在不影响 URL 的情况下组织路由,请创建一个组以将相关路由放在一起。括号中的文件夹将从 URL 中省略(例如 (marketing)
或 (shop)
)。
2. 为分组创建不同的布局
即使 (marketing)
和 (shop)
内部的路由共享相同的 URL 层次结构,您也可以通过在组的文件夹中添加 layout.js
文件来为每个组创建不同的布局。
3. 为分组加载骨架
要通过 loading.js
文件将加载骨架应用于特定路由,请创建一个新的路由组(例如,/(overview)
),然后将 loading.tsx
移动到该路由组内。
4. 创建多个根布局
要创建多个根布局,请删除顶级 layout.js
文件,并在每个路由组内添加一个 layout.js
文件。这对于将应用程序划分为具有完全不同 UI 或体验的部分非常有用。需要将 <html>
和 <body>
标记添加到每个根布局中。 示例中,(marketing)
和 (shop)
都有自己的根布局。
注:
包含路由组的路由不应解析为与其他路由相同的 URL 路径。例如,由于路由组不会影响 URL 结构,因此 (marketing)/about/page.js
和 (shop)/about/page.js
都会解析为 /about
并导致错误。
Dynamic Routes 并行路由
1. Convention
可以通过将文件夹名称括在方括号中来创建动态区段:[folderName]。
例如,[id]
或 [slug]。
动态 Segments 作为 params
属性传递给 layout
、page
、route
和 generateMetadata
函数。
例如,博客可以包含以下路由 app/blog/[slug]/page.js
其中 [slug]
是博客文章的动态区段。
文件 app/blog/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug
return <div>My Post: {slug}</div>
}
Route | Example URL | params |
---|---|---|
app/blog/[slug]/page.js |
/blog/a |
{ slug: 'a' } |
app/blog/[slug]/page.js |
/blog/b |
{ slug: 'b' } |
app/blog/[slug]/page.js |
/blog/c |
{ slug: 'c' } |
2. Catch-all Segments
动态区段可以通过在方括号内添加省略号 [...folderName]
的
Route | Example URL | params |
---|---|---|
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'] } |
3. Optional Catch-all Segments
可以通过在双方括号中包含参数来使 catch-all Segments 成为可选的: [[...folderName]]
的
Route | Example URL | params |
---|---|---|
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'] } |
catch-all 和 optional catch-all 段之间的区别在于,使用 optional 时,也会匹配不带参数的路由(上例中为 /shop
)。
TypeScript
app/blog/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
return <h1>My Page</h1>
}
Route | params Type Definition |
---|---|
app/blog/[slug]/page.js |
{ slug: string } |
app/shop/[...slug]/page.js |
{ slug: string[] } |
app/shop/[[...slug]]/page.js |
{ slug?: string[] } |
app/[categoryId]/[itemId]/page.js |
{ categoryId: string, itemId: string } |
Parallel Routes 并行路由
1. Parallel Routes
Parallel Routes 允许您同时或有条件地呈现同一布局中的一个或多个页面。它们对于应用程序的高度动态部分非常有用,例如社交网站上的仪表板和源。
例如,考虑一个仪表板,您可以使用并行路由同时呈现团队
和分析
页面:
2. Slot
并行路由是使用命名槽创建的。插槽是使用 @folder
约定定义的。例如,以下文件结构定义了两个插槽:@analytics
和 @team
:
插槽作为 props 传递给共享的父布局。在上面的例子中,app/layout.js
中的组件现在接受 @analytics
和 @team
插槽 props,并且可以与 children
属性一起并行渲染它们:
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
import { checkUserRole } from '@/lib/auth'
export default function Layout({
user,
admin,
}: {
user: React.ReactNode
admin: React.ReactNode
}) {
const role = checkUserRole()
return <>{role === 'admin' ? admin : user}</>
}
槽不是路由段,不会影响 URL 结构。例如,对于 /@analytics/views,URL
将为 /views
,因为 @analytics
是一个槽。插槽与常规 [Page]组件组合在一起,以形成与路由段关联的最终页面。因此,您不能在同一路由分段级别拥有单独的[静态]和[动态]插槽。如果一个槽是动态的,则该级别的所有槽都必须是动态的。