98c3076eb4
This PR document the caching semantics in Next.js, how they interact, and what APIs affect them. We're also taking the opportunity to consolidate terminology, remove duplicate content, and update sections of the docs that relate to caching. ### Documentation - [x] Create a new section for caching - [x] Explain how the different caching mechanisms work - [x] Request Memoization (React Cache) - [x] Persistent Data Cache - [x] Persistent Full Route Cache - [x] In-memory, client-side Router Cache - [x] Document how different APIs affect caching - [x] Document cache interactions - [x] Clean up stale information in the other docs sections - [x] Routing Section - [x] Move advanced navigation topics from fundamentals to **How Navigation Works** section - [x] Rewrite the **How Navigation Works** section - [x] Rendering Section - [x] Simplify fundamentals page - [x] Rewrite the **Static and Dynamic Rendering** pages - [ ] ~Create a page to explain how **Client and Server Components** are rendered~. Moved to this PR: https://github.com/vercel/next.js/pull/51579 - [x] Data fetching section - [x] Consolidate data fetching story for fetching, caching, and revalidating - [x] Clarify data fetching story with 3rd party libraries and React `cache` - [x] Create **Data Fetching Patterns** page - [x] Document other related behaviors: - [x] Update information on scroll position for back/forward navigation - [x] Remove the concepts of **soft and hard navigation** - [x] Remove the concepts of **static and dynamic data fetching** - [x] Use consistent terminology **runtime** 👉🏼 **request time**. Runtime for Edge and Node.js, request time to describe when dynamic stuff happens - [x] `generateStaticParams` being able to seed the Full Route Cache - [x] Polish 💅🏼 --- ### Related PRs: - Diagrams: https://github.com/vercel/front/pull/24142 - Redirects: https://github.com/vercel/front/pull/24179
326 lines
8.7 KiB
Text
326 lines
8.7 KiB
Text
---
|
|
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.
|
|
---
|
|
|
|
Parallel Routing allows you to simultaneously or conditionally render one or more pages in the same layout. For highly dynamic sections of an app, such as dashboards and feeds on social sites, Parallel Routing can be used to implement complex routing patterns.
|
|
|
|
For example, you can 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="952"
|
|
/>
|
|
|
|
Parallel Routing allows you to define independent error and loading states for each route as they're being streamed in independently.
|
|
|
|
<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"
|
|
/>
|
|
|
|
Parallel Routing also allows you to conditionally render a slot based on certain conditions, such as authentication state. This enables fully separated code on the same URL.
|
|
|
|
<Image
|
|
alt="Conditional routes diagram"
|
|
srcLight="/docs/light/conditional-routes-ui.png"
|
|
srcDark="/docs/dark/conditional-routes-ui.png"
|
|
width="1600"
|
|
height="898"
|
|
/>
|
|
|
|
## Convention
|
|
|
|
Parallel routes are created using named **slots**. Slots are defined with the `@folder` convention, and are passed to the same-level layout as props.
|
|
|
|
> Slots are _not_ route segments and _do not affect the URL structure_. The file path `/@team/members` would be accessible at `/members`.
|
|
|
|
For example, the following file structure defines two explicit 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"
|
|
/>
|
|
|
|
The folder structure above means that 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(props: {
|
|
children: React.ReactNode
|
|
analytics: React.ReactNode
|
|
team: React.ReactNode
|
|
}) {
|
|
return (
|
|
<>
|
|
{props.children}
|
|
{props.team}
|
|
{props.analytics}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
export default function Layout(props) {
|
|
return (
|
|
<>
|
|
{props.children}
|
|
{props.team}
|
|
{props.analytics}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
> **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`.
|
|
|
|
## Unmatched Routes
|
|
|
|
By default, the content rendered within a slot will match the current URL.
|
|
|
|
In the case of an unmatched slot, the content that Next.js renders differs based on the routing technique and folder structure.
|
|
|
|
### `default.js`
|
|
|
|
You can define a `default.js` file to render as a fallback when Next.js cannot recover a slot's active state based on the current URL.
|
|
|
|
Consider the following folder structure. The `@team` slot has a `settings` directory, 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"
|
|
/>
|
|
|
|
#### Navigation
|
|
|
|
On navigation, Next.js will render the slot's previously active state, even if it doesn't match the current URL.
|
|
|
|
#### Reload
|
|
|
|
On reload, Next.js will first try to render the unmatched slot's `default.js` file. If that's not available, a 404 gets rendered.
|
|
|
|
> The 404 for unmatched routes helps ensure that you don't accidentally render a route that shouldn't be parallel rendered.
|
|
|
|
## `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`, which allows you read the active route segment within that slot.
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
'use client'
|
|
|
|
import { useSelectedLayoutSegment } from 'next/navigation'
|
|
|
|
export default async function Layout(props: {
|
|
//...
|
|
auth: React.ReactNode
|
|
}) {
|
|
const loginSegments = useSelectedLayoutSegment('auth')
|
|
// ...
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
'use client'
|
|
|
|
import { useSelectedLayoutSegment } from 'next/navigation'
|
|
|
|
export default async function Layout(props) {
|
|
const loginSegments = useSelectedLayoutSegment('auth')
|
|
// ...
|
|
}
|
|
```
|
|
|
|
When a user navigates to `@auth/login`, or `/login` in the URL bar, `loginSegments` will be equal to the string `"login"`.
|
|
|
|
## Examples
|
|
|
|
### Modals
|
|
|
|
Parallel Routing can be used to render modals.
|
|
|
|
<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"
|
|
/>
|
|
|
|
The `@auth` slot renders a `<Modal>` component that can be shown by navigating to a matching route, for example `/login`.
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
export default async function Layout(props: {
|
|
// ...
|
|
auth: React.ReactNode
|
|
}) {
|
|
return (
|
|
<>
|
|
{/* ... */}
|
|
{props.auth}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
export default async function Layout(props) {
|
|
return (
|
|
<>
|
|
{/* ... */}
|
|
{props.auth}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/@auth/login/page.tsx" switcher
|
|
import { Modal } from 'components/modal'
|
|
|
|
export default function Login() {
|
|
return (
|
|
<Modal>
|
|
<h1>Login</h1>
|
|
{/* ... */}
|
|
</Modal>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/@auth/login/page.js" switcher
|
|
import { Modal } from 'components/modal'
|
|
|
|
export default function Login() {
|
|
return (
|
|
<Modal>
|
|
<h1>Login</h1>
|
|
{/* ... */}
|
|
</Modal>
|
|
)
|
|
}
|
|
```
|
|
|
|
To ensure that the contents of the modal don't get rendered when it's not active, you can create a `default.js` file that returns `null`.
|
|
|
|
```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
|
|
}
|
|
```
|
|
|
|
#### Dismissing a modal
|
|
|
|
If a modal was initiated through client navigation, e.g. by using `<Link href="/login">`, you can dismiss the modal by calling `router.back()` or by using a `Link` component.
|
|
|
|
```tsx filename="app/@auth/login/page.tsx" highlight="5" switcher
|
|
'use client'
|
|
import { useRouter } from 'next/navigation'
|
|
import { Modal } from 'components/modal'
|
|
|
|
export default async function Login() {
|
|
const router = useRouter()
|
|
return (
|
|
<Modal>
|
|
<span onClick={() => router.back()}>Close modal</span>
|
|
<h1>Login</h1>
|
|
...
|
|
</Modal>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/@auth/login/page.js" highlight="5" switcher
|
|
'use client'
|
|
import { useRouter } from 'next/navigation'
|
|
import { Modal } from 'components/modal'
|
|
|
|
export default async function Login() {
|
|
const router = useRouter()
|
|
return (
|
|
<Modal>
|
|
<span onClick={() => router.back()}>Close modal</span>
|
|
<h1>Login</h1>
|
|
...
|
|
</Modal>
|
|
)
|
|
}
|
|
```
|
|
|
|
> More information on modals is covered in the [Intercepting Routes](/docs/app/building-your-application/routing/intercepting-routes) section.
|
|
|
|
If you want to navigate elsewhere and dismiss a modal, you can also use a catch-all route.
|
|
|
|
<Image
|
|
alt="Parallel Routes Diagram"
|
|
srcLight="/docs/light/parallel-routes-catchall.png"
|
|
srcDark="/docs/dark/parallel-routes-catchall.png"
|
|
width="1600"
|
|
height="768"
|
|
/>
|
|
|
|
```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
|
|
}
|
|
```
|
|
|
|
> Catch-all routes take precedence over `default.js`.
|
|
|
|
### Conditional Routes
|
|
|
|
Parallel Routes can be used to implement conditional routing. For example, you can render a `@dashboard` or `@login` route depending on the authentication state.
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
import { getUser } from '@/lib/auth'
|
|
|
|
export default function Layout({
|
|
dashboard,
|
|
login,
|
|
}: {
|
|
dashboard: React.ReactNode
|
|
login: React.ReactNode
|
|
}) {
|
|
const isLoggedIn = getUser()
|
|
return isLoggedIn ? dashboard : login
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
import { getUser } from '@/lib/auth'
|
|
|
|
export default function Layout({ dashboard, login }) {
|
|
const isLoggedIn = getUser()
|
|
return isLoggedIn ? dashboard : login
|
|
}
|
|
```
|
|
|
|
<Image
|
|
alt="Parallel routes authentication example"
|
|
srcLight="/docs/light/conditional-routes-ui.png"
|
|
srcDark="/docs/dark/conditional-routes-ui.png"
|
|
width="1600"
|
|
height="898"
|
|
/>
|