Frontend/Next.js

[Next.js/App Router] 11. 패럴렐 라우트 (Parallel Route)

ayeongjin 2025. 2. 12. 00:07

패럴렐 라우트(Parallel Route)란?

병렬 라우트: 하나의 화면 안에서 여러 개의 페이지 컴포넌트를 병렬로 렌더링할 수 있는 패턴

 

  • 소셜 미디어 서비스나 관리자 대시보드처럼 복잡한 페이지 구조를 렌더링하는 데 적합합니다.

 

1) 슬롯

패럴렐 폴더 안에 @폴더 이름으로 생성 가능

병렬로 렌더링될 페이지 컴포넌트를 보관하는 폴더

  • app/parallel/@슬롯/page.tsx : 해당 경로 안의 페이지 컴포넌트는 자신의 부모 레이아웃 컴포넌트에서 props로 전달됩니다.
  • @슬롯 : URL 주소에는 영향을 주지 않음. 따라서 parallel/@sidebar로 접속해도 페이지가 렌더링되지 않습니다.
// src/app/parallel/layout.tsx

import Link from "next/link";
import { ReactNode } from "react";

export default function Layout({
  children, // 기본적인 페이지도 props로 전달됨
  sidebar, // 추가적으로 전달 가능한 props
  feed,
}: {
  children: ReactNode;
  sidebar: ReactNode;
  feed: ReactNode;
}) {
  return (
    <div>
      <div>
        <Link href="/parallel">parallel</Link>
        &nbsp;
        <Link href="/parallel/setting">parallel/setting</Link>
      </div>
      <br />
      {sidebar}
      {feed}
      {children}
    </div>
  );
}

 

💡 parrallel 적용 안될 떄
Next.js의 버그로 인해 .next 폴더를 삭제한 후 npm run dev를 실행하면 정상적으로 작동합니다.

 

2) 슬롯 하위 페이지 등록

// src/app/parallel/@sidebar/page.tsx

export default function Page() {
  return <div>@sidebar</div>;
}
// src/app/parallel/@feed/page.tsx

export default function Page() {
  return <div>@feed</div>;
}
// src/app/parallel/@feed/setting/page.tsx

export default function Page() {
  return <div>@feed/setting</div>;
}
  • 이렇게 하면 /parallel/setting으로 이동 시 sidebar와 children 슬롯은 그대로 유지되지만 feed 슬롯만 변경됩니다.
  • 페이지 이동 후 전달받지 못한 슬롯의 상태는 유지됨 (404가 발생하지 않음).
  • 하지만 새로고침하면 404 오류가 발생할 수 있습니다.
    • 슬롯들이 이전의 페이지를 유지하게 되는 건, 링크 컴포넌트를 이용하여 브라우저 측에서 클라이언트 사이드 렌더링 방식으로 페이지를 이동할 때에만 한정되기 때문
  • 이를 방지하려면 각 슬롯별 default 페이지를 설정해야 합니다.
// src/app/parallel/@sidebar/default.tsx
export default function Default() {
  return <div>@sidebar/default</div>;
}

// src/app/parallel/default.tsx
export default function Default() {
  return <div>/parallel/default</div>;
}

이렇게 default.tsx를 추가하면 새로고침해도 404 오류 없이 기본 페이지가 렌더링됩니다.

 

✅ 페럴렐 라우트 공식문서 링크

Rendering: Server-side Rendering (SSR)

 

Rendering: Server-side Rendering (SSR) | Next.js

Use Server-side Rendering to render pages on each request.

nextjs.org

 


 

인터셉팅 라우트 (Intercepting Route)란?

사용자가 특정 경로로 접속할 때 , 요청을 가로채어 원래 페이지 대신 원하는 페이지를 렌더링하는 라우팅 패턴

  • 초기 접속 요청이 아닐 때만 동작합니다.
  • 즉, **클라이언트 사이드 렌더링(CSR)**으로 페이지를 이동할 때 동작합니다.
  • ex. 인스타그램에서 게시글을 클릭하면 모달이 뜨고, 뒤로 가기하면 원래 탐색하던 화면이 유지됨.

 

1) 인터셉팅 페이지 생성

(.)폴더명을 사용하면 해당 경로를 인터셉트할 수 있습니다.

  • (.) 점 한 개 : 동일한 경로에서 인터셉트
  • (..) 점 두 개 : 상위 경로에서 인터셉트
  • (..)(..) 점 두 개 두 번 : 상위의 상위 경로 인터셉트
  • (...) 점 세 개 : app 폴더 바로 아래의 어떤 폴더든 인터셉트
// src/app/(.)book/[id]/page.tsx

import BookPage from "@/app/book/[id]/page";
import Modal from "@/components/modal";

export default function Page(props: any) {
  return (
    <div>
      가로채기 성공!
      <Modal>
        <BookPage {...props} />
      </Modal>
    </div>
  );
}

// src/components/modal.tsx

"use client"; // 클라이언트 컴포넌트로 선언

import { ReactNode, useEffect, useRef } from "react";
import style from "./modal.module.css";
import { createPortal } from "react-dom";
import { useRouter } from "next/navigation";

export default function Modal({ children }: { children: ReactNode }) {
  const dialogRef = useRef<HTMLDialogElement>(null);
  const router = useRouter();

  useEffect(() => {
    if (!dialogRef.current?.open) {
      // 모달이 닫혀있다면 자동으로 열기
      dialogRef.current?.showModal();
      // 모달이 뜨자마자 스크롤을 맨 위로 이동
      dialogRef.current?.scrollTo({ top: 0 });
    }
  }, []);
	
	// createPortal: 브라우저에 존재하는 DOM 요소에 고정적으로 이 모달 요소들을 렌더링
  return createPortal(
    <dialog
      onClose={() => router.back()} // 닫히면 뒤로 가기
      onClick={(e) => {
        if ((e.target as any).nodeName === "DIALOG") {
          router.back(); // 모달 배경 클릭 시 뒤로 가기
        }
      }}
      className={style.modal}
      ref={dialogRef}
    >
      {children}
    </dialog>,
    document.getElementById("modal-root") as HTMLElement
  );
}

 


 

패럴렐 & 인터셉팅 라우트 함께 활용하기

아래와 같은 폴더 구조를 사용하면 패럴렐 라우트와 인터셉팅 라우트를 결합하여 더 유연한 UI를 구현할 수 있습니다.

  • 이렇게 하면 인터셉팅 된 도서 상세 페이지를 모달로 띄우면서도 기존 페이지(피드, 사이드바)를 유지할 수 있습니다.