> 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/pjt-prepare/publish-your-docs/store-front-framewok-next.js/02./6.-parallel-routes-intercepting-routes.md).

# 6. Parallel Routes, Intercepting Routes

이 문서는 Parallel Routes와 Intercepting Routes에 대한 설명과 사용하는 방법에 대해 설명합니다.

***

## Parallel Routes

Parallel Routes는 동시에 또는 조건부로 동일한 레이아웃 내에서 하나 이상의 페이지를 렌더링할 수 있게 해줍니다. 이는 앱의 매우 동적인 섹션인 대시보드나 소셜 사이트의 피드와 같은 경우에 유용합니다.

예를 들어, 대시보드를 고려해보면 병렬 루트를 사용하여 팀 및 분석 페이지를 동시에 렌더링할 수 있습니다.

<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&amp;token=d51372d5-7e2e-4f9a-ae27-dcd53ad5b2ab" alt=""><figcaption></figcaption></figure>

Parallel Routes는 명명된 슬롯을 사용하여 생성됩니다. 슬롯은 @폴더 관례로 정의됩니다. 아래 예시는 @team 및 @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&amp;token=b1f216ac-666e-4b2d-aa15-e5efb4fedc2a" alt=""><figcaption></figcaption></figure></div>

슬롯은 공유 부모 레이아웃에 속성(props)으로 전달됩니다. 위의 예에서는 app/layout.js의 컴포넌트가 이제 @team 및 @user 슬롯 속성을 받아들이고, 이를 자식 속성(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>
  )
}
```

슬롯은 루트 세그먼트가 아니며 URL 구조에 영향을 미치지 않습니다.

### default.js

초기 로드 또는 전체 페이지 다시 로드 중에 일치하지 않는 슬롯에 대한 대체 항목으로 렌더링할 파일을 정의할 수 있습니다.

<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&amp;token=4f18b82a-ce83-428d-b49a-72f3a41e25e6" alt=""><figcaption></figcaption></figure></div>

위와 같은 경로가 있다고 가정했을 경우 @team 폴더에는 settings라는 폴더가 있지만 @user의 경우는 폴더가 없습니다. 그래서 '/dashboard/settings/'에 접근 시 default.js가 없을 시 404에러가 발생하게 되는데 default.js 파일을 만들어줌으로써 이를 방지할 수 있습니다.

### 조건부 경로

Parallel Routes는 특정 조건에 따라 슬롯을 조건부로 렌더링할 수 있도록 허용합니다. 예를 들어 언어가 한국일때만 user page를 보여주고 다른 경우는 team page를 보고 싶을 시 다음과 같이 작성할 수 있습니다.

```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&amp;token=7dfb741c-59e8-44aa-b564-6ec524eccdaf" alt=""><figcaption></figcaption></figure>

### Streaming

Parallel Routes는 독립적으로 스트리밍될 수 있어 각 루트에 대해 독립적인 오류 및 로딩 상태를 정의할 수 있습니다.

<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&amp;token=9fd7cc6a-ea74-4f67-86b4-65af7e5765b4" alt=""><figcaption></figcaption></figure>

## 구현 예시 (Parallel Routes)

{% stepper %}
{% step %}

### 클라이언트 페이지 예시 (공통)

파일 예시: @user 및 @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>
    </>
  );
}
```

(위 파일을 @user와 @team에 각각 배치)
{% endstep %}

{% step %}

### 레이아웃에 슬롯 추가

파일 예시: app/layout.tsx (또는 app/\[lang]/layout.tsx 등)

```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>
  )
}
```

그 다음 실행하면 상단의 버튼 클릭 시 각각의 route event가 발생하여 색상이 변하는 것을 확인할 수 있습니다.

<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&amp;token=e7840eea-355c-47aa-98d9-2d5926c9b076" alt="" width="563"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

***

## Intercepting Routes

Intercepting Routes는 현재 레이아웃 내에서 애플리케이션의 다른 부분에서 루트를 로드할 수 있게 해줍니다. 이 라우팅 패러다임은 사용자가 다른 컨텍스트로 전환하지 않고 루트의 내용을 표시하고자 할 때 유용할 수 있습니다.

예를 들어, 피드에서 사진을 클릭할 때 해당 사진을 모달에서 표시할 수 있습니다. 이 경우 Next.js는 /photo/123 루트를 가로채고 URL을 마스킹하여 /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&amp;token=f3f0b9d0-5f4a-4c2a-99f1-0486f6f748bb" alt=""><figcaption></figcaption></figure>

그러나 공유 가능한 URL을 클릭하거나 페이지를 새로 고침하여 사진에 접근할 때에는 모달 대신 전체 사진 페이지가 렌더링되어야 합니다. 이때는 라우트 가로채기가 발생하지 않아야 합니다.

<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&amp;token=5f972866-6532-425d-b2eb-924f197a9541" alt=""><figcaption></figcaption></figure>

Intercepting Routes는 (..) 관례를 사용하여 정의할 수 있으며, 이는 상대 경로 관례 ../과 유사하지만 세그먼트를 위한 것입니다.

다음과 같이 사용할 수 있습니다:

* (.) : 동일한 레벨의 세그먼트와 일치
* (..) : 하나 위 레벨의 세그먼트와 일치
* (..)(..) : 두 레벨 위의 세그먼트와 일치
* (...) : 루트 app 디렉토리에서 세그먼트와 일치

예를 들어, 피드 세그먼트 내에서 (..)photo 디렉토리를 생성하여 피드 내부에서 사진 세그먼트를 가로챌 수 있습니다.

<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&amp;token=4569f8bd-28f9-459c-a8dc-068d2e31cb72" alt=""><figcaption></figcaption></figure>

### Modals

Intercepting Routes를 Parallel Routes와 함께 사용하여 모달을 만들 수 있습니다.

이 패턴을 사용하여 모달을 만들면 모달과 관련된 몇 가지 일반적인 문제를 극복할 수 있으며, 다음과 같은 기능이 가능해집니다.

* 모달 콘텐츠를 URL을 통해 공유 가능하게 함
* 페이지를 새로 고침할 때 모달을 닫는 대신 컨텍스트를 보존
* 이전 라우트로 이동하는 대신 모달을 닫음
* 앞으로 이동하는 경우 모달을 다시 열 수 있음

<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&amp;token=98420b4f-b240-4fee-b544-5ca6873cc999" alt=""><figcaption></figcaption></figure>

## 구현 예시 (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&amp;token=ef4c1c8d-e38c-4820-bd2b-fd50d5ad7238" alt=""><figcaption></figcaption></figure></div>

다음은 모달 인터셉트 패턴의 예시 코드입니다.

파일: /@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')!
  );
}
```

파일: @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>;
}
```

파일: photos/\[id]/page.tsx (전체 페이지 렌더링용)

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

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

레이아웃에 Parallel Routes의 modal 슬롯을 추가:

```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&amp;token=aef19474-a0f8-4c7e-ae76-3823158c1110" alt="" width="356"><figcaption></figcaption></figure>

이 구성으로, 피드에서 버튼을 클릭하면 /ko/photos/id로 요청이 가지만, Intercepting Routes가 이를 @modal로 가로채 동일한 화면에서 모달을 띄웁니다.

<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&amp;token=5d95f277-b577-4506-aa7a-8e00ee3982c1" alt="" width="178"><figcaption></figcaption></figure>

직접 /ko/photos/id로 접근하거나 공유 가능한 URL을 통해 접근하면 모달이 아닌 전체 사진 페이지가 렌더링됩니다(가로채기가 발생하지 않음).

<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&amp;token=f073e62e-2097-49f0-b8d0-5628fa559a55" alt=""><figcaption></figcaption></figure>

***

(문서 내 예시 이미지 및 코드 스니펫은 설명을 위해 포함되어 있습니다.)
