Files
d93e68fbb8 "const loginSegment =" should be singular (#64029)
"const loginSegment=" should be singular, because there is
**useSelectedLayoutSegment** in the example, not
**useSelectedLayoutSegments**

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Adding or Updating Examples

- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Closes NEXT-
Fixes #

-->

---------

Co-authored-by: JJ Kasper <[email protected]>
2024-04-03 10:11:44 -07:00

465 lines
14 KiB
Plaintext

---
title: Parallel Routes
description: Simultaneously render one or more pages in the same view that can be navigated independently. A pattern for highly dynamic applications.
related:
links:
- app/api-reference/file-conventions/default
---
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.
For example, considering a dashboard, you can use parallel routes to simultaneously render the `team` and `analytics` pages:
<Image
alt="Parallel Routes Diagram"
srcLight="/docs/light/parallel-routes.png"
srcDark="/docs/dark/parallel-routes.png"
width="1600"
height="942"
/>
## Slots
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`:
<Image
alt="Parallel Routes File-system Structure"
srcLight="/docs/light/parallel-routes-file-system.png"
srcDark="/docs/dark/parallel-routes-file-system.png"
width="1600"
height="687"
/>
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:
```tsx filename="app/layout.tsx" switcher
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
```
```jsx filename="app/layout.js" switcher
export default function Layout({ children, team, analytics }) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
```
However, slots are **not** [route segments](/docs/app/building-your-application/routing#route-segments) and do not affect the URL structure. For example, for `/@analytics/views`, the URL will be `/views` since `@analytics` is a slot.
> **Good to know**:
>
> - The `children` prop is an implicit slot that does not need to be mapped to a folder. This means `app/page.js` is equivalent to `app/@children/page.js`.
## Active state and navigation
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:
- [**Soft Navigation**](/docs/app/building-your-application/routing/linking-and-navigating#5-soft-navigation): During client-side navigation, Next.js will perform a [partial render](/docs/app/building-your-application/routing/linking-and-navigating#4-partial-rendering), changing the subpage within the slot, while maintaining the other slot's active subpages, even if they don't match the current URL.
- **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`](#defaultjs) file for the unmatched slots, or `404` if `default.js` doesn't exist.
> **Good to know**:
>
> - 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`
You can define a `default.js` file to render as a fallback for unmatched slots during the initial load or full-page reload.
Consider the following folder structure. The `@team` slot has a `/settings` page, but `@analytics` does not.
<Image
alt="Parallel Routes unmatched routes"
srcLight="/docs/light/parallel-routes-unmatched-routes.png"
srcDark="/docs/dark/parallel-routes-unmatched-routes.png"
width="1600"
height="930"
/>
When navigating to `/settings`, the `@team` slot will render the `/settings` page while maintaining the currently active page for the `@analytics` slot.
On refresh, Next.js will render a `default.js` for `@analytics`. If `default.js` doesn't exist, a `404` is rendered instead.
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)`
Both [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment) and [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments) accept a `parallelRoutesKey` parameter, which allows you to read the active route segment within a slot.
```tsx filename="app/layout.tsx" switcher
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }: { auth: React.ReactNode }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
```
```jsx filename="app/layout.js" switcher
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function Layout({ auth }) {
const loginSegment = useSelectedLayoutSegment('auth')
// ...
}
```
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
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:
<Image
alt="Conditional routes diagram"
srcLight="/docs/light/conditional-routes-ui.png"
srcDark="/docs/dark/conditional-routes-ui.png"
width="1600"
height="898"
/>
```tsx filename="app/dashboard/layout.tsx" switcher
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}</>
}
```
```jsx filename="app/dashboard/layout.js" switcher
import { checkUserRole } from '@/lib/auth'
export default function Layout({ user, admin }) {
const role = checkUserRole()
return <>{role === 'admin' ? admin : user}</>
}
```
### Tab Groups
You can add a `layout` inside a slot to allow users to navigate the slot independently. This is useful for creating tabs.
For example, the `@analytics` slot has two subpages: `/page-views` and `/visitors`.
<Image
alt="Analytics slot with two subpages and a layout"
srcLight="/docs/light/parallel-routes-tab-groups.png"
srcDark="/docs/dark/parallel-routes-tab-groups.png"
width="1600"
height="768"
/>
Within `@analytics`, create a [`layout`](/docs/app/building-your-application/routing/pages-and-layouts) file to share the tabs between the two pages:
```tsx filename="app/@analytics/layout.tsx" switcher
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>
</>
)
}
```
```jsx filename="app/@analytics/layout.js" switcher
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
Parallel Routes can be used together with [Intercepting Routes](/docs/app/building-your-application/routing/intercepting-routes) to create modals. This allows you to solve common challenges when building modals, such as:
- 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**.
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:
<Image
alt="Parallel Routes Diagram"
srcLight="/docs/light/parallel-routes-auth-modal.png"
srcDark="/docs/dark/parallel-routes-auth-modal.png"
width="1600"
height="687"
/>
To implement this pattern, start by creating a `/login` route that renders your **main** login page.
<Image
alt="Parallel Routes Diagram"
srcLight="/docs/light/parallel-routes-modal-login-page.png"
srcDark="/docs/dark/parallel-routes-modal-login-page.png"
width="1600"
height="768"
/>
```tsx filename="app/login/page.tsx" switcher
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
```
```jsx filename="app/login/page.js" switcher
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
```
Then, inside the `@auth` slot, add [`default.js`](/docs/app/api-reference/file-conventions/default) file that returns `null`. This ensures that the modal is not rendered when it's not active.
```tsx filename="app/@auth/default.tsx" switcher
export default function Default() {
return null
}
```
```jsx filename="app/@auth/default.js" switcher
export default function Default() {
return null
}
```
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:
```tsx filename="app/@auth/(.)login/page.tsx" switcher
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'
export default function Page() {
return (
<Modal>
<Login />
</Modal>
)
}
```
```jsx filename="app/@auth/(.)login/page.js" switcher
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](/docs/app/building-your-application/routing/intercepting-routes#convention).
> - By separating the `<Modal>` functionality from the modal content (`<Login>`), you can ensure any content inside the modal, e.g. [forms](/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms), are Server Components. See [Interleaving Client and Server Components](/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props) for more information.
#### Opening the modal
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.
To open the modal, pass the `@auth` slot as a prop to the parent layout and render it alongside the `children` prop.
```tsx filename="app/layout.tsx" switcher
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>
</>
)
}
```
```jsx filename="app/layout.js" switcher
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>
</>
)
}
```
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
You can close the modal by calling `router.back()` or by using the `Link` component.
```tsx filename="app/ui/modal.tsx" switcher
'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>
</>
)
}
```
```jsx filename="app/ui/modal.js" switcher
'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>
</>
)
}
```
When using the `Link` component to navigate away from a page that shouldn't render the `@auth` slot anymore, we use a catch-all route that returns `null`.
```tsx filename="app/ui/modal.tsx" switcher
import Link from 'next/link'
export function Modal({ children }: { children: React.ReactNode }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
```
```jsx filename="app/ui/modal.js" switcher
import Link from 'next/link'
export function Modal({ children }) {
return (
<>
<Link href="/">Close modal</Link>
<div>{children}</div>
</>
)
}
```
```tsx filename="app/@auth/[...catchAll]/page.tsx" switcher
export default function CatchAll() {
return null
}
```
```jsx filename="app/@auth/[...catchAll]/page.js" switcher
export default function CatchAll() {
return null
}
```
> **Good to know:**
>
> - We use a catch-all route in our `@auth` slot to close the modal because of the behavior described in [Active state and navigation](#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 returns `null` to close the modal.
> - 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](https://github.com/vercel-labs/nextgram) of modals with Intercepted and Parallel Routes.
### Loading and Error UI
Parallel Routes can be streamed independently, allowing you to define independent error and loading states for each route:
<Image
alt="Parallel routes enable custom error and loading states"
srcLight="/docs/light/parallel-routes-cinematic-universe.png"
srcDark="/docs/dark/parallel-routes-cinematic-universe.png"
width="1600"
height="1218"
/>
See the [Loading UI](/docs/app/building-your-application/routing/loading-ui-and-streaming) and [Error Handling](/docs/app/building-your-application/routing/error-handling) documentation for more information.