> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/developer-guide-en/pjt-prepare/publish-your-docs/store-front-framework-next.js/02.-coding-guide-and-essential-packages/6.-parallel-routes-intercepting-routes.md).

# 6. Parallel Routes, Intercepting Routes

This document explains Parallel Routes and Intercepting Routes and how to use them.

***

## Parallel Routes

Parallel Routes let you render one or more pages simultaneously or conditionally within the same layout. This is useful for highly dynamic sections of an app, such as dashboards or feeds on a social site.

For example, consider a dashboard: you can use parallel routes to render a team page and an analytics page at the same time.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FvsQTG3zcgRVR67Lbioqz%2Fimg%20(7).png?alt=media&#x26;token=d51372d5-7e2e-4f9a-ae27-dcd53ad5b2ab" alt=""><figcaption></figcaption></figure>

Parallel Routes are created using named slots. Slots are defined with the @folder convention. The example below defines two slots, @team and @user.

<div align="left"><figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FZeu487EvN4pVai7VfoER%2Fimg%20(8).png?alt=media&#x26;token=b1f216ac-666e-4b2d-aa15-e5efb4fedc2a" alt=""><figcaption></figcaption></figure></div>

Slots are passed as props to the shared parent layout. In the example above, the component in app/layout.js now accepts the @team and @user slot props and can render them in parallel along with the children prop.

```tsx
export default function Layout({
  children,
  user,
  team,
}: {
  children: React.ReactNode
  user: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <section>
      {children}
      {user}
      {team}
    </section>
  )
}
```

Slots are not route segments and do not affect the URL structure.

### default.js

You can define a file to render as a fallback for slots that don't match during initial load or a full page reload.

<div align="left"><figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FEA1HRKvPCSJCcwSDdNn5%2Fimg%20(9).png?alt=media&#x26;token=4f18b82a-ce83-428d-b49a-72f3a41e25e6" alt=""><figcaption></figcaption></figure></div>

Assuming a route structure like the one above, the @team folder has a folder called settings, but @user has no such folder. So, when accessing '/dashboard/settings/', a 404 error occurs if there's no default.js, and you can prevent this by creating a default.js file.

### Conditional Routes

Parallel Routes allow you to conditionally render a slot based on a specific condition. For example, if you want to show the user page only when the language is Korean, and the team page otherwise, you can write it as follows.

```tsx
export default function Layout({
  children,
  user,
  team,
  params,
}: {
  children: React.ReactNode
  user: React.ReactNode
  team: React.ReactNode
  params: { lang: string }
}) {
  // default는 team page
  let page = team

  // lang이 ko면 user page
  if (params.lang == 'ko') {
    page = user
  }

  return (
    <section>
      {children}
      {page}
    </section>
  )
}
```

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FmTQArKyOLG3tCM0mh4Si%2Fimg%20(10).png?alt=media&#x26;token=7dfb741c-59e8-44aa-b564-6ec524eccdaf" alt=""><figcaption></figcaption></figure>

### Streaming

Parallel Routes can be streamed independently, so you can define independent error and loading states for each route.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FuR3aTUG6krZQeSiHU0iW%2Fimg%20(11).png?alt=media&#x26;token=9fd7cc6a-ea74-4f67-86b4-65af7e5765b4" alt=""><figcaption></figcaption></figure>

## Implementation Example (Parallel Routes)

{% stepper %}
{% step %}

### Client Page Example (Shared)

File example: create the same page component in @user and @team

```tsx
'use client'
import { useState } from "react";

export default function Page() {
  const [backgroundColor, setBackgroundColor] = useState('blue');

  const handleColorChange = () => {
    // 랜덤한 배경색을 생성하기 위한 함수
    const getRandomColor = () => {
      const letters = '0123456789ABCDEF';
      let color = '#';
      for (let i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }
      return color;
    };

    // 새로운 랜덤한 배경색으로 설정
    const newColor = getRandomColor();
    setBackgroundColor(newColor);
  };

  return (
    <>
      <div className="w-1/2">
        <button className="w-full" onClick={handleColorChange}>change button</button>
        <div className="w-full h-full">
          <div
            style={{
              backgroundColor: backgroundColor,
              padding: '20px',
              textAlign: 'center',
              cursor: 'pointer',
            }}
            className="w-full h-96"
          >
            team page
          </div>
        </div>
      </div>
    </>
  );
}
```

(Place the file above in @user and @team respectively)
{% endstep %}

{% step %}

### Add the Slots to the Layout

File example: app/layout.tsx (or app/\[lang]/layout.tsx, etc.)

```tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function Layout({
  children,
  user,
  team,
  params,
}: {
  children: React.ReactNode
  user: React.ReactNode
  team: React.ReactNode
  params: { lang: string }
}) {
  return (
    <section>
      {children}
      <div className="flex">
        {team}
        {user}
      </div>
    </section>
  )
}
```

Then, when you run it, clicking the button at the top triggers each route event, and you can confirm that the color changes.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FtHZZmqZuHcm4CSJco5Ax%2Fimg%20(12).png?alt=media&#x26;token=e7840eea-355c-47aa-98d9-2d5926c9b076" alt="" width="563"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

***

## Intercepting Routes

Intercepting Routes let you load a route from another part of the application within the current layout. This routing paradigm can be useful when you want to display the content of a route without the user switching to a different context.

For example, when clicking a photo in a feed, you might want to display that photo in a modal. In this case, Next.js intercepts the /photo/123 route and masks the URL, overlaying it on top of /feed.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FRMfgB60CPa3XyQt6zJwQ%2Fimg%20(13).png?alt=media&#x26;token=f3f0b9d0-5f4a-4c2a-99f1-0486f6f748bb" alt=""><figcaption></figcaption></figure>

However, when accessing the photo by clicking a shareable URL or refreshing the page, the entire photo page should render instead of the modal. In this case, the route interception should not occur.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FWS3tqlLZfLmig93fGgzI%2Fimg%20(14).png?alt=media&#x26;token=5f972866-6532-425d-b2eb-924f197a9541" alt=""><figcaption></figcaption></figure>

Intercepting Routes can be defined using the (..) convention, which is similar to the relative path convention ../ but for segments.

You can use the following:

* (.) : matches segments at the same level
* (..) : matches segments one level above
* (..)(..) : matches segments two levels above
* (...) : matches segments from the root app directory

For example, you can create a (..)photo directory inside the feed segment to intercept the photo segment from within the feed.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FT11fpxPiqxOfgUoVAT8C%2Fimg%20(15).png?alt=media&#x26;token=4569f8bd-28f9-459c-a8dc-068d2e31cb72" alt=""><figcaption></figcaption></figure>

### Modals

You can use Intercepting Routes together with Parallel Routes to build modals.

Building modals with this pattern lets you overcome several common issues associated with modals, enabling functionality such as:

* Making modal content shareable via a URL
* Preserving context instead of closing the modal when the page is refreshed
* Closing the modal instead of navigating to the previous route
* Reopening the modal when navigating forward

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FkqIkwdyJMFUSZExWjpBb%2Fimg%20(16).png?alt=media&#x26;token=98420b4f-b240-4fee-b544-5ca6873cc999" alt=""><figcaption></figcaption></figure>

## Implementation Example (Intercepting Routes + Modal)

<div align="left"><figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FZExxieTONIPtXtKy5b6G%2Fimg%20(17).png?alt=media&#x26;token=ef4c1c8d-e38c-4820-bd2b-fd50d5ad7238" alt=""><figcaption></figcaption></figure></div>

Below is example code for the modal interception pattern.

File: /@modal/(.)photos/\[id]/modal.tsx

```tsx
'use client'
import React, { ElementRef, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { createPortal } from "react-dom";

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const dialogRef = useRef<ElementRef<'dialog'>>(null);

  useEffect(() => {
    if (!dialogRef.current?.open) {
      dialogRef.current?.showModal();
    }
  }, []);

  function onDismiss() {
    router.back();
  }

  return createPortal(
    <div className="modal-backdrop">
      <dialog ref={dialogRef} className="modal" onClose={onDismiss}>
        {children}
        <button onClick={onDismiss} className="close-button" />
      </dialog>
    </div>,
    document.getElementById('modal-root')!
  );
}
```

File: @modal/(.)photos/\[id]/page.tsx

```tsx
import { Modal } from "@/app/[lang]/@modal/(.)photos/[id]/modal";

export default function PhotoModal({
  params: { id: photoId },
}: {
  params: { id: string };
}) {
  return <Modal>{photoId}</Modal>;
}
```

File: photos/\[id]/page.tsx (for full page rendering)

```tsx
import { Modal } from "@/app/[lang]/@modal/(.)photos/[id]/modal";

export default function PhotoPage({
  params: { id: photoId },
}: {
  params: { id: string };
}) {
  return <Modal>{photoId}</Modal>;
}
```

Add the modal slot from Parallel Routes to the layout:

```tsx
<section>
  {children}
  {modal}
  <div id="modal-root" />
</section>
```

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FxsRstjriL04jJVCAxmu4%2Fimg%20(18).png?alt=media&#x26;token=aef19474-a0f8-4c7e-ae76-3823158c1110" alt="" width="356"><figcaption></figcaption></figure>

With this setup, when you click the button in the feed, the request goes to /ko/photos/id, but Intercepting Routes intercepts it as @modal and shows the modal on the same screen.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2F3CgcqKPWQaoKheolSZJD%2Fimg%20(19).png?alt=media&#x26;token=5d95f277-b577-4506-aa7a-8e00ee3982c1" alt="" width="178"><figcaption></figcaption></figure>

If you access /ko/photos/id directly or via a shareable URL, the full photo page renders instead of the modal (interception does not occur).

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FdeJbUVDUqJYqyyrtppgb%2Fimg%20(20).png?alt=media&#x26;token=f073e62e-2097-49f0-b8d0-5628fa559a55" alt=""><figcaption></figcaption></figure>

***

(The example images and code snippets in this document are included for illustration purposes.)
