平行路由
平行路由允许您在同一布局中同时或有条件地渲染一个或多个页面。它们对于应用的高度动态部分非常有用,例如社交网站的网站主页和源。
例如,考虑到仪表板,您可以使用平行路由来同时渲染团队和分析页面:
插槽
平行路由是使用命名的插槽创建的。插槽按@folder约定来定义。例如,下列文件结构定义了两个插槽 :@analytics 和 @team:
插槽作为属性传递到共享的父布局。对于上面的示例,app/layout.js中的组件目前接收@analytics 和 @team插槽属性,并能够将它们与children属性进行平行渲染:
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是插槽。
您需要知道:
children属性是不需要映射到文件夹的内置插槽。这意味着app/page.js等同于app/@children/page.js。
活跃状态和导航
默认情况下, Next.js 跟踪每个插槽的活跃状态(或子页面)。然而, 插槽中渲染的内容取决于导航类型:
- 软导航: 在客户端导航期间, Next.js 将执行局部渲染, 更改槽内的子页面,同时保持另一个槽的活动子页面,即使它们与当前 URL 不匹配。
- 硬导航: 在整页加载后(浏览器刷新), Next.js 不能为不匹配当前 URL 的插槽确定活跃状态。相反, 它会为未匹配的插渲染
default.js文件, 如果default.js文件不存在会出现404。
您需要知道:
- 对于不匹配的路由,
404有助于确保您不会意外地在页面上渲染不打算渲染的平行路由。
default.js
您可以定义一个default.js文件,作为初始化加载或整页重新加载期间不匹配插槽的备用。
请考虑以下文件结构,@team插槽有一个/settings页面,但是@analytics没有。
当导航到/settings时, @team插槽会渲染/settings页面,同时保持@analytics插槽的当前活动页面。
刷新时, Next.js 将为@analytics渲染default.js。如果default.js不存在,页面会呈现404。
此外, 因为 children是隐式插槽, 您也需要在 Next.js 无法恢复父页面的活动状态时,为children创建一个渲染备用的default.js文件。
useSelectedLayoutSegment(s)
useSelectedLayoutSegment 和 useSelectedLayoutSegments 都接收一个 parallelRoutesKey 参数, 它允许您读取插槽中的活动路由段。
"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 (或路由栏的/login)时,loginSegment将相当于字符串"login"。
例子
条件路由
您能够使用平行路由根据某些条件来按条件渲染路由(比如用户角色)。例如,为/admin 或 /user 角色渲染不同的网站主页。
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,以允许用户用户独立导航插槽。这对于创建 tabs 非常有用。
例如, @analytics 插槽有两个子页面: /page-views 和 /visitors。
在 @analytics中, 创建一个layout 文件以在两个页面之间共享 tabs。
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 }: { children: React.ReactNode }) {
return (
<>
<nav>
<Link href="/page-views">Page Views</Link>
<Link href="/visitors">Visitors</Link>
</nav>
<div>{children}</div>
</>
);
}
Modals
平行路由可能与拦截路由一起使用来创建支持深度链接的 modals。这允许您解决构建时的常见问题, 例如:
- 使模态内容可通过 URL 共享。
- 在页面刷新时保留上下文, 而不是关闭 modal。
- 回退导航时关闭 modal,而不是返回到上一个路由。
- 继续导航时重新打开 modal.
请考虑以下的 UI 模式, 这里用户可以使用客户端导航打开 layout 的登录 modal, 或者访问单独的/login页面:
要实施这个模式,首先创建一个渲染主登录页面的/login路由。
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插槽内, 添加 returns null的default.js文件。这会确保 modal 在不活跃的状态下不会渲染。
export default function Default() {
return "...";
}
export default function Default() {
return "...";
}
在 @auth 插槽内部, 通过更新/(.)login文件夹来拦截/login路由。在/(.)login/page.tsx中引入<Modal>组件及其 children:
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>
);
}
您需要知道:
- 用于拦截路由的约定, 例如
(.), 取决于您的文件系统结构。参阅拦截路由约定。- 通过将
<Modal>功能与 modal 内容(<Login>)分离, 您能够确保 modal 内的任何内容, 如forms, 都是服务器组件。参阅拦截客户端和服务器组件来获取更多信息。
打开 modal
现在, 您能够使用 Next.js 路由来开关 modal。这确保在 modal 打开时或导航前进或后退时,正确地更新 URL。
为了打开 modal,将@auth 插槽作为属性传递给父布局,将其与 children属性一起渲染。
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>时, modal 将会打 开而不是导航到/login页面。不过,在刷新或初始加载时, 导航会将用户带到主登录页/login。
关闭 modal
您能够通过调用router.back() 或通过使用Link组件来关闭 modal。
"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插槽的页面时,我们需要确保平行路由匹配 returns null的组件 。例如, 导航回到根页面时, 创建一个@auth/page.tsx组件:
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 "...";
}
export default function Page() {
return "...";
}
或者如果导航到其它页面时(例如 /foo, /foo/bar, 等), 你能够使用 catch-all 插槽:
export default function CatchAll() {
return "...";
}
export default function CatchAll() {
return "...";
}
您需要知道:
Loading 和 Error UI
平行路由可以独立流式传输, 允许您为每个路由定义 error and loading 状态:
参阅Loading UI 和 错误处理 文档获取更多信息。