āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š nextjs/app/api-reference/file-conventions/parallel-routes ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
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="/nextjs/light/parallel-routes.png" srcDark="/nextjs/dark/parallel-routes.png" width="1600" height="942" />
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="/nextjs/light/parallel-routes-file-system.png" srcDark="/nextjs/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:
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}
</>
)
}
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:
- The
childrenprop is an implicit slot that does not need to be mapped to a folder. This meansapp/page.jsis equivalent toapp/@children/page.js.
default.jsYou 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="/nextjs/light/parallel-routes-unmatched-routes.png" srcDark="/nextjs/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.
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:
default.js file for the unmatched slots, or 404 if default.js doesn't exist.Good to know:
- The
404for unmatched routes helps ensure that you don't accidentally render a parallel route on a page that it was not intended for.
useSelectedLayoutSegment(s)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')
// ...
}
When a user navigates to app/@auth/login (or /login in the URL bar), loginSegment will be equal to the string "login".
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="/nextjs/light/conditional-routes-ui.png" srcDark="/nextjs/dark/conditional-routes-ui.png" width="1600" height="898" />
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
}
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="/nextjs/light/parallel-routes-tab-groups.png" srcDark="/nextjs/dark/parallel-routes-tab-groups.png" width="1600" height="768" />
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>
</>
)
}
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:
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="/nextjs/light/parallel-routes-auth-modal.png" srcDark="/nextjs/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="/nextjs/light/parallel-routes-modal-login-page.png" srcDark="/nextjs/dark/parallel-routes-modal-login-page.png" width="1600" height="768" />
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
import { Login } from '@/app/ui/login'
export default function Page() {
return <Login />
}
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
}
Inside your @auth slot, intercept the /login route by importing the <Modal> component and its children into the @auth/(.)login/page.tsx file, and updating the folder name to /@auth/(.)login/page.tsx.
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
(.)is used for intercepting routes. See Intercepting Routes docs for more information.- 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.
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.
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>
</>
)
}
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.
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>
</>
)
}
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
}
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:
- We use a catch-all route in our
@authslot to close the modal because of how parallel routes behave. 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 returnsnullto 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 of modals with Intercepted and Parallel Routes.
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="/nextjs/light/parallel-routes-cinematic-universe.png" srcDark="/nextjs/dark/parallel-routes-cinematic-universe.png" width="1600" height="1218" />
See the Loading UI and Error Handling documentation for more information.
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā