并行路由
并行路由允许你同时或有条件地渲染同一布局中的一个或多个页面。它们对于应用的高度动态部分非常有用,例如仪表板和社交网站上的提要。
¥Parallel Routes allows you to simultaneously or conditionally render one or more pages within the same layout. They are useful for highly dynamic sections of an app, such as dashboards and feeds on social sites.
例如,考虑仪表板,你可以使用并行路由同时渲染 team
和 analytics
页面:
¥For example, considering a dashboard, you can use parallel routes to simultaneously render the team
and analytics
pages:
插槽
¥Slots
并行路由是使用命名槽创建的。插槽是按照 @folder
约定定义的。例如,以下文件结构定义了两个槽:@analytics
和 @team
:
¥Parallel routes are created using named slots. Slots are defined with the @folder
convention. For example, the following file structure defines two slots: @analytics
and @team
:
插槽作为属性传递给共享父布局。对于上面的示例,app/layout.js
中的组件现在接受 @analytics
和 @team
slot 属性,并且可以与 children
属性一起并行渲染它们:
¥Slots are passed as props to the shared parent layout. For the example above, the component in app/layout.js
now accepts the @analytics
and @team
slots props, and can render them in parallel alongside the children
prop:
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
export default function Layout({ children, team, analytics }) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
但是,槽不是 路由段,不会影响 URL 结构。例如,对于 /@analytics/views
,URL 将为 /views
,因为 @analytics
是一个槽。插槽与常规 页面 组件组合,形成与路由段关联的最终页面。因此,你不能在同一路由段级别拥有单独的 static 和 dynamic 插槽。如果一个插槽是动态的,则该级别的所有插槽都必须是动态的。
¥However, slots are not route segments and do not affect the URL structure. For example, for /@analytics/views
, the URL will be /views
since @analytics
is a slot. Slots are combined with the regular Page component to form the final page associated with the route segment. Because of this, you cannot have separate static and dynamic slots at the same route segment level. If one slot is dynamic, all slots at that level must be dynamic.
很高兴知道:
¥Good to know:
children
属性是一个隐式插槽,不需要映射到文件夹。这意味着app/page.js
相当于app/@children/page.js
。¥The
children
prop is an implicit slot that does not need to be mapped to a folder. This meansapp/page.js
is equivalent toapp/@children/page.js
.
活动状态和导航
¥Active state and navigation
默认情况下,Next.js 跟踪每个槽的活动状态(或子页面)。但是,槽内渲染的内容将取决于导航类型:
¥By default, Next.js keeps track of the active state (or subpage) for each slot. However, the content rendered within a slot will depend on the type of navigation:
-
软导航:在客户端导航期间,Next.js 将执行 部分渲染,更改插槽内的子页面,同时保留其他插槽的活动子页面,即使它们与当前 URL 不匹配。
¥Soft Navigation: During client-side navigation, Next.js will perform a partial render, changing the subpage within the slot, while maintaining the other slot's active subpages, even if they don't match the current URL.
-
硬导航:全页加载(浏览器刷新)后,Next.js 无法确定与当前 URL 不匹配的插槽的活动状态。相反,它将为不匹配的插槽渲染
default.js
文件,如果default.js
不存在,则渲染404
。¥Hard Navigation: After a full-page load (browser refresh), Next.js cannot determine the active state for the slots that don't match the current URL. Instead, it will render a
default.js
file for the unmatched slots, or404
ifdefault.js
doesn't exist.
很高兴知道:
¥Good to know:
不匹配路由的
404
有助于确保你不会意外地在不适合的页面上渲染并行路由。¥The
404
for unmatched routes helps ensure that you don't accidentally render a parallel route on a page that it was not intended for.
default.js
你可以定义一个 default.js
文件来渲染,作为初始加载或整页重新加载期间不匹配插槽的后备。
¥You can define a default.js
file to render as a fallback for unmatched slots during the initial load or full-page reload.
考虑以下文件夹结构。@team
插槽有 /settings
页,但 @analytics
没有。
¥Consider the following folder structure. The @team
slot has a /settings
page, but @analytics
does not.
当导航到 /settings
时,@team
插槽将渲染 /settings
页面,同时维护 @analytics
插槽的当前活动页面。
¥When navigating to /settings
, the @team
slot will render the /settings
page while maintaining the currently active page for the @analytics
slot.
刷新时,Next.js 将为 @analytics
渲染 default.js
。如果 default.js
不存在,则会渲染 404
。
¥On refresh, Next.js will render a default.js
for @analytics
. If default.js
doesn't exist, a 404
is rendered instead.
此外,由于 children
是隐式插槽,因此你还需要创建 default.js
文件,以便在 Next.js 无法恢复父页面的活动状态时渲染 children
的后备。
¥Additionally, since children
is an implicit slot, you also need to create a default.js
file to render a fallback for children
when Next.js cannot recover the active state of the parent page.
useSelectedLayoutSegment(s)
useSelectedLayoutSegment
和 useSelectedLayoutSegments
都接受 parallelRoutesKey
参数,该参数允许你读取槽内的活动航路段。
¥Both useSelectedLayoutSegment
and useSelectedLayoutSegments
accept a parallelRoutesKey
parameter, which allows you to read the active route segment within a slot.
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
当用户导航到 app/@auth/login
(或 URL 栏中的 /login
)时,loginSegment
将等于字符串 "login"
。
¥When a user navigates to app/@auth/login
(or /login
in the URL bar), loginSegment
will be equal to the string "login"
.
示例
¥Examples
条件路由
¥Conditional Routes
你可以使用并行路由根据某些条件(例如用户角色)有条件地渲染路由。例如,要为 /admin
或 /user
角色渲染不同的仪表板页面:
¥You can use Parallel Routes to conditionally render routes based on certain conditions, such as user role. For example, to render a different dashboard page for the /admin
or /user
roles:
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
}
import { checkUserRole } from '@/lib/auth'
export default function Layout({ user, admin }) {
const role = checkUserRole()
return role === 'admin' ? admin : user
}
选项卡组
¥Tab Groups
你可以在插槽内添加 layout
,以允许用户独立导航该插槽。这对于创建选项卡很有用。
¥You can add a layout
inside a slot to allow users to navigate the slot independently. This is useful for creating tabs.
例如,@analytics
插槽有两个子页面:/page-views
和 /visitors
。
¥For example, the @analytics
slot has two subpages: /page-views
and /visitors
.
在 @analytics
中,创建一个 layout
文件以共享两个页面之间的选项卡:
¥Within @analytics
, create a layout
file to share the tabs between the two pages:
import Link from 'next/link'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Link href="/page-views">Page Views</Link>
<Link href="/visitors">Visitors</Link>
</nav>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export default function Layout({ children }) {
return (
<>
<nav>
<Link href="/page-views">Page Views</Link>
<Link href="/visitors">Visitors</Link>
</nav>
<div>{children}</div>
</>
)
}
模态
¥Modals
并行路由可以与 拦截路由 一起使用,以创建支持深度链接的模式。这使你可以解决构建模式时的常见挑战,例如:
¥Parallel Routes can be used together with Intercepting Routes to create modals that support deep linking. This allows you to solve common challenges when building modals, such as:
-
使模态内容可通过 URL 共享。
¥Making the modal content shareable through a URL.
-
刷新页面时保留上下文,而不是关闭模式。
¥Preserving context when the page is refreshed, instead of closing the modal.
-
关闭向后导航的模式,而不是转到上一个路由。
¥Closing the modal on backwards navigation rather than going to the previous route.
-
重新打开向前导航的模式。
¥Reopening the modal on forwards navigation.
考虑以下 UI 模式,用户可以使用客户端导航从布局打开登录模式,或访问单独的 /login
页面:
¥Consider the following UI pattern, where a user can open a login modal from a layout using client-side navigation, or access a separate /login
page:
要实现此模式,首先创建一个 /login
路由来渲染你的主登录页面。
¥To implement this pattern, start by creating a /login
route that renders your main login page.
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
然后,在 @auth
槽内添加返回 null
的 default.js
文件。这可确保模态在不活动时不会渲染。
¥Then, inside the @auth
slot, add default.js
file that returns null
. This ensures that the modal is not rendered when it's not active.
export default function Default() {
return null
}
export default function Default() {
return null
}
在 @auth
插槽内,通过更新 /(.)login
文件夹来拦截 /login
路由。将 <Modal>
组件及其子组件导入到 /(.)login/page.tsx
文件中:
¥Inside your @auth
slot, intercept the /login
route by updating the /(.)login
folder. Import the <Modal>
component and its children into the /(.)login/page.tsx
file:
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
很高兴知道:
¥Good to know:
用于拦截路由的约定,例如
(.)
,取决于你的文件系统结构。参见 拦截路由约定。¥The convention used to intercept the route, e.g.
(.)
, depends on your file-system structure. See Intercepting Routes convention.通过将
<Modal>
功能与模态内容 (<Login>
) 分离,你可以确保模态内的任何内容,例如 forms,是服务器组件。请参阅 交错客户端和服务器组件 了解更多信息。¥By separating the
<Modal>
functionality from the modal content (<Login>
), you can ensure any content inside the modal, e.g. forms, are Server Components. See Interleaving Client and Server Components for more information.
打开模式
¥Opening the modal
现在,你可以利用 Next.js 路由来打开和关闭模式。这可确保在模式打开以及前后导航时正确更新 URL。
¥Now, you can leverage the Next.js router to open and close the modal. This ensures the URL is correctly updated when the modal is open, and when navigating backwards and forwards.
要打开模式,请将 @auth
插槽作为属性传递给父布局,并将其与 children
属性一起渲染。
¥To open the modal, pass the @auth
slot as a prop to the parent layout and render it alongside the children
prop.
import Link from 'next/link'
export default function Layout({
auth,
children,
}: {
auth: React.ReactNode
children: React.ReactNode
}) {
return (
<>
<nav>
<Link href="/login">Open modal</Link>
</nav>
<div>{auth}</div>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export default function Layout({ auth, children }) {
return (
<>
<nav>
<Link href="/login">Open modal</Link>
</nav>
<div>{auth}</div>
<div>{children}</div>
</>
)
}
当用户单击 <Link>
时,模式将打开,而不是导航到 /login
页面。但是,在刷新或初始加载时,导航到 /login
会将用户带到主登录页面。
¥When the user clicks the <Link>
, the modal will open instead of navigating to the /login
page. However, on refresh or initial load, navigating to /login
will take the user to the main login page.
关闭模式
¥Closing the modal
你可以通过调用 router.back()
或使用 Link
组件来关闭模态框。
¥You can close the modal by calling router.back()
or by using the Link
component.
'use client'
import { useRouter } from 'next/navigation'
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter()
return (
<>
<button
onClick={() => {
router.back()
}}
>
Close modal
</button>
<div>{children}</div>
</>
)
}
'use client'
import { useRouter } from 'next/navigation'
export function Modal({ children }) {
const router = useRouter()
return (
<>
<button
onClick={() => {
router.back()
}}
>
Close modal
</button>
<div>{children}</div>
</>
)
}
当使用 Link
组件离开不应再渲染 @auth
插槽的页面时,我们需要确保并行路由与返回 null
的组件匹配。例如,当导航回根页面时,我们创建一个 @auth/page.tsx
组件:
¥When using the Link
component to navigate away from a page that shouldn't render the @auth
slot anymore, we need to make sure the parallel route matches to a component that returns null
. For example, when navigating back to the root page, we create a @auth/page.tsx
component:
import Link from 'next/link'
export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
import Link from 'next/link'
export function Modal({ children }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
export default function Page() {
return null
}
export default function Page() {
return null
}
或者,如果导航到任何其他页面(例如 /foo
、/foo/bar
等),则可以使用 catch-all 插槽:
¥Or if navigating to any other page (such as /foo
, /foo/bar
, etc), you can use a catch-all slot:
export default function CatchAll() {
return null
}
export default function CatchAll() {
return null
}
很高兴知道:
¥Good to know:
由于 活动状态和导航 中描述的行为,我们在
@auth
槽中使用包罗万象的路由来关闭模式。由于客户端导航到不再与插槽匹配的路由将保持可见,因此我们需要将插槽与返回null
的路由相匹配以关闭模式。¥We use a catch-all route in our
@auth
slot to close the modal because of the behavior described in Active state and navigation. Since client-side navigations to a route that no longer match the slot will remain visible, we need to match the slot to a route that returnsnull
to close the modal.其他示例可能包括在图库中打开照片模式,同时还有专用的
/photo/[id]
页面,或者在侧面模式中打开购物车。¥Other examples could include opening a photo modal in a gallery while also having a dedicated
/photo/[id]
page, or opening a shopping cart in a side modal.查看示例 具有拦截和并行路由的模态。
¥View an example of modals with Intercepted and Parallel Routes.
加载和错误 UI
¥Loading and Error UI
并行路由可以独立流式传输,允许你为每个路由定义独立的错误和加载状态:
¥Parallel Routes can be streamed independently, allowing you to define independent error and loading states for each route:
¥See the Loading UI and Error Handling documentation for more information.