diff --git a/docs/02-app/01-building-your-application/01-routing/10-parallel-routes.mdx b/docs/02-app/01-building-your-application/01-routing/10-parallel-routes.mdx index 91261c114031c..0bffa7457777f 100644 --- a/docs/02-app/01-building-your-application/01-routing/10-parallel-routes.mdx +++ b/docs/02-app/01-building-your-application/01-routing/10-parallel-routes.mdx @@ -15,7 +15,7 @@ For example, considering a dashboard, you can use parallel routes to simultaneou srcLight="/docs/light/parallel-routes.png" srcDark="/docs/dark/parallel-routes.png" width="1600" - height="952" + height="942" /> ## Slots @@ -33,28 +33,32 @@ Parallel routes are created using named **slots**. Slots are defined with the `@ 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(props: { +export default function Layout({ + children, + team, + analytics, +}: { children: React.ReactNode analytics: React.ReactNode team: React.ReactNode }) { return ( <> - {props.children} - {props.team} - {props.analytics} + {children} + {team} + {analytics} ) } ``` ```jsx filename="app/layout.js" switcher -export default function Layout(props) { +export default function Layout({ children, team, analytics }) { return ( <> - {props.children} - {props.team} - {props.analytics} + {children} + {team} + {analytics} ) } @@ -71,17 +75,17 @@ However, slots are **not** [route segments](/docs/app/building-your-application/ 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 of 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. +- **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 route that shouldn't be parallel rendered. +> - 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. +Consider the following folder structure. The `@team` slot has a `/settings` page, but `@analytics` does not. Parallel Routes unmatched routes -When navigating to `/dashboard/settings`, the `@team` slot will render the `settings` page while maintaining the currently active page for the `@analytics` slot. +When navigating to `/dashboard/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. @@ -106,10 +110,7 @@ Both [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected import { useSelectedLayoutSegment } from 'next/navigation' -export default function Layout(props: { - //... - auth: React.ReactNode -}) { +export default function Layout({ auth }: { auth: React.ReactNode }) { const loginSegments = useSelectedLayoutSegment('auth') // ... } @@ -120,7 +121,7 @@ export default function Layout(props: { import { useSelectedLayoutSegment } from 'next/navigation' -export default function Layout(props) { +export default function Layout({ auth }) { const loginSegments = useSelectedLayoutSegment('auth') // ... } @@ -130,72 +131,136 @@ When a user navigates to `app/@auth/login` (or `/login` in the URL bar), `loginS ## Examples -### Modals +### Conditional Routes -Parallel Routing can be used to render modals. +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: Parallel Routes Diagram -The `@auth` slot renders a `` component that can be shown by navigating to a matching route, for example `/login`. +```tsx filename="app/dashboard/layout.tsx" switcher +import { checkUserRole } from '@/lib/auth' -```tsx filename="app/layout.tsx" switcher -export default async function Layout(props: { - // ... - auth: React.ReactNode +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`. + +Analytics slot with two subpages and a layout + +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/dashboard/@analytics/layout.tsx" switcher +import Link from 'next/link' + +export default function Layout({ children }: { children: React.ReactNode }) { return ( <> - {/* ... */} - {props.auth} + +
{children}
) } ``` ```jsx filename="app/layout.js" switcher -export default async function Layout(props) { +import Link from 'next/link' + +export default function Layout({ children }: { children: React.ReactNode }) { return ( <> - {/* ... */} - {props.auth} + +
{children}
) } ``` -```tsx filename="app/@auth/login/page.tsx" switcher -import { Modal } from 'components/modal' +### Modals -export default function Login() { - return ( - -

Login

- {/* ... */} -
- ) +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: + +Parallel Routes Diagram + +To implement this pattern, start by creating a `/login` route that renders your **main** login page. + +Parallel Routes Diagram + +```tsx filename="app/login/page.tsx" switcher +import { Login } from '@/app/ui/login' + +export default function Page() { + return } ``` -```jsx filename="app/@auth/login/page.js" switcher -import { Modal } from 'components/modal' +```jsx filename="app/login/page.js" switcher +import { Login } from '@/app/ui/login' -export default function Login() { - return ( - -

Login

- {/* ... */} -
- ) +export default function Page() { + return } ``` -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`. +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() { @@ -209,117 +274,142 @@ export default function Default() { } ``` -#### Dismissing a modal - -If a modal was initiated through client navigation, e.g. by using ``, you can dismiss the modal by calling `router.back()` or by using a `Link` component. +Inside your `@auth` slot, intercept the `/login` route by updating the `/(.)login` folder. Import the `` component and its children into the `/(.)login/page.tsx` file: -```tsx filename="app/@auth/login/page.tsx" highlight="5" switcher -'use client' -import { useRouter } from 'next/navigation' -import { Modal } from 'components/modal' +```tsx filename="app/@auth/(.)login/page.tsx" switcher +import { Modal } from '@/app/ui/modal' +import { Login } from '@/app/ui/login' -export default function Login() { - const router = useRouter() +export default function Page() { return ( - router.back()}>Close modal -

Login

- ... +
) } ``` -```jsx filename="app/@auth/login/page.js" highlight="5" switcher -'use client' -import { useRouter } from 'next/navigation' -import { Modal } from 'components/modal' +```jsx filename="app/@auth/(.)login/page.js" switcher +import { Modal } from '@/app/ui/modal' +import { Login } from '@/app/ui/login' -export default function Login() { - const router = useRouter() +export default function Page() { return ( - router.back()}>Close modal -

Login

- ... +
) } ``` -> More information on modals is covered in the [Intercepting Routes](/docs/app/building-your-application/routing/intercepting-routes) section. +> **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 `` functionality from the modal content (``), 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. -If you want to navigate elsewhere and dismiss a modal, you can also use a catch-all route. +### Opening the modal -Parallel Routes Diagram +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. -```tsx filename="app/@auth/[...catchAll]/page.tsx" switcher -export default function CatchAll() { - return null +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 ( + <> + +
{auth}
+
{children}
+ + ) } ``` -```jsx filename="app/@auth/[...catchAll]/page.js" switcher -export default function CatchAll() { - return null +```jsx filename="app/layout.js" switcher +import Link from 'next/link' + +export default function Layout({ auth, children }) { + return ( + <> + +
{auth}
+
{children}
+ + ) } ``` -> Catch-all routes take precedence over `default.js`. +When the user clicks the ``, 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. -### Conditional Routes +### Closing the modal -Parallel Routes also allows you to conditionally render a slot based on certain conditions, such as authentication state. For example, you can render a `/dashboard` or `/login` page depending on whether the user is logged in: +You can close the modal by calling `router.back()` or by using the `Link` component. -Conditional routes diagram +```tsx filename="app/ui/modal.tsx switcher +'use client' -Parallel Routes can be used to implement conditional routing. For example, you can render a `@dashboard` or `@login` route depending on the authentication state. +import { useRouter } from 'next/navigation' -```tsx filename="app/layout.tsx" switcher -import { getUser } from '@/lib/auth' +export function Modal({ children }: { children: React.ReactNode }) { + const router = useRouter() -export default function Layout({ - dashboard, - login, -}: { - dashboard: React.ReactNode - login: React.ReactNode -}) { - const isLoggedIn = getUser() - return isLoggedIn ? dashboard : login + return ( + <> + +
{children}
+ + ) } ``` -```jsx filename="app/layout.js" switcher -import { getUser } from '@/lib/auth' +```tsx filename="app/ui/modal.tsx switcher +'use client' -export default function Layout({ dashboard, login }) { - const isLoggedIn = getUser() - return isLoggedIn ? dashboard : login +import { useRouter } from 'next/navigation' + +export function Modal({ children }) { + const router = useRouter() + + return ( + <> + +
{children}
+ + ) } ``` -Parallel routes authentication example +> **Good to know:** +> +> - 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. -### Streaming +### Loading and Error UI Parallel Routes can be streamed independently, allowing you to define independent error and loading states for each route: @@ -330,3 +420,5 @@ Parallel Routes can be streamed independently, allowing you to define independen 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. diff --git a/docs/02-app/01-building-your-application/01-routing/11-intercepting-routes.mdx b/docs/02-app/01-building-your-application/01-routing/11-intercepting-routes.mdx index 1895e0105f93f..180e970c828be 100644 --- a/docs/02-app/01-building-your-application/01-routing/11-intercepting-routes.mdx +++ b/docs/02-app/01-building-your-application/01-routing/11-intercepting-routes.mdx @@ -57,14 +57,14 @@ For example, you can intercept the `photo` segment from within the `feed` segmen ### Modals -Intercepting Routes can be used together with [Parallel Routes](/docs/app/building-your-application/routing/parallel-routes) to create modals. +Intercepting Routes can be used together with [Parallel Routes](/docs/app/building-your-application/routing/parallel-routes) to create modals. This allows you to solve common challenges when building modals, such as: -Using this pattern to create modals overcomes some common challenges when working with modals, by allowing you to: +- 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**. -- Make the modal content **shareable through a URL** -- **Preserve context** when the page is refreshed, instead of closing the modal -- **Close the modal on backwards navigation** rather than going to the previous route -- **Reopen the modal on forwards navigation** +Consider the following UI pattern, where a user can open a photo modal from a gallery using client-side navigation, or navigate to the photo page directly from a shareable URL: Intercepting routes modal example -> In the above example, the path to the `photo` segment can use the `(..)` matcher since `@modal` is a _slot_ and not a _segment_. This means that the `photo` route is only one _segment_ level higher, despite being two _file-system_ levels higher. +In the above example, the path to the `photo` segment can use the `(..)` matcher since `@modal` is a _slot_ and not a _segment_. This means that the `photo` route is only one _segment_ level higher, despite being two _file-system_ levels higher. -Other examples could include opening a login modal in a top navbar while also having a dedicated `/login` page, or opening a shopping cart in a side modal. +See the [Parallel Routes](/docs/app/building-your-application/routing/parallel-routes#modals) documentation for a step-by-step example, or see our [image gallery example](https://github.com/vercel-labs/nextgram). -[View an example](https://github.com/vercel-labs/nextgram) of modals with Intercepted and Parallel Routes. +> **Good to know:** +> +> - Other examples could include opening a login modal in a top navbar while also having a dedicated `/login` page, or opening a shopping cart in a side modal.