NextJS - 고급 라우팅 패턴

김명원·2025년 3월 24일

learnNextjs

목록 보기
23/24

고급 라우팅 패턴

Parallel Route

고급 라우팅 패턴 중에 하나로 우니나라 말로는 병렬 라우트라는 뜻을 가지고 있습니다.
말 그대로 화면안에 여러개의 페이지를 병렬로 함께 렌더링 시켜주는 패턴입니다.

하나의 화면에 여러 개의 페이지 컴포넌트들을 한꺼번에 렌더링하는 방법이죠
보통 parallel route는 소셜 미디어 서비스나 또는 관리자의 대시보드 처럼 굉장히 복잡한 구조로 이루어져 있는 서비스들을 구축하는 데에 상당이 유용하게 활용이 됩니다.

활용하기

먼저 Parallel Route를 사용하기 위해서는 슬롯이란 것을 만들어줘야 합니다.
슬롯이란 적용될 폴더에 새로운 폴더로 예시로 @sidebar(파일명) 이러한 폴더를 슬롯이라고 부릅니다.
Slot(슬롯)은 병렬로 렌더링 될 페이지 컴포넌트를 보관하는 폴더 입니다.
폴더 안에 page.tsx를 만들면 됩니다.
그러면 폴더 안에 내용은 자동으로 부모 레이아웃 컴포넌트에게 props로써 전달이 됩니다.

그때의 props 이름은 슬롯의 이름으로 결정이 됩니다. props의 타입은 ReactNode로 결정이 됩니다.
이러한 슬롯의 폴더는 URL 경로에는 아무런 영향을 미치지 않습니다. 마치 라우트 그룹처럼요
그러므로 sidebar를 접속할 수 있는건 안됩니다.

또한 슬롯의 경우 적용이 무한대로 가능하기 때문에 몇개 던지 넣으면 가능하다는 장점이 있습니다.

추가적으로 슬롯안에 페이지를 추가하는 것까지 가능합니다.
슬롯 폴더안에 setting폴더를 만들고 그 안에 page.tsx를 추가해주게 되면
렌더링이 되게 됩니다.
하지만 setting 페이지로 가기 위해서는 Link를 통해서 들어가야만 합니다. 직접 들어가는 것은 404 Page를 return 합니다.
그것을 방지하기 위해 슬롯 폴더안에 default 페이지를 만들어야 하는것 까지 잊으시면 안됩니다.

Intercepting Route

사용자가 특정 경로로 접속해서 새로운 페이지를 요청할 때 원래 렌더링 돼야 되는 페이지가 아닌 원하는 페이지를 대신 렌더링 하도록 설정하는 라우팅 패턴입니다.

즉, 사용자가 동일한 경로에 접속하게 되더라도 특정 조건을 만족하게 되면 그때에는 원래 페이지가 아닌 다른 페이지를 렌더링 하도록 설정하는 기술입니다.

intercepting을 위한 조건은 Next에서 고정적으로 정해준 기준이 있습니다.
초기 접속 요청이 아닐 때에만 intercepting route가 동작하도록 설정이 됩니다.

특히 intercepting을 자주 사용하는 곳이 instagram 입니다.
특정 게시글을 클릭하게 되면 내가 원래 보고 있었던 피드 페이지 위로 게시글의 상세 페이지를 별도로 뛰워 줌으로써 언제든지 뒤로 가기 했을 때 내가 탐색하고 있던 피드로 그대로 다시 돌아오도록 만들어 주는 기능이죠!
저의 포트폴리오 싸이트를 next로 업데이트 할 때 사용할 기능입니다!

활용하기

기존의 intercept할 폴더를 똑같은 폴더명으로 만들어줍니다. 이때 폴더의 이름 앞에 소괄호(.)를 작성하면 됩니다.
예를 들어 book/[id] 폴더에 intercepting Route를 적용하기 위해서
(.)book/[id]폴더를 만들어 주는 것입니다. (.)의 의미는 이제는 뒤에 주소의 경로를 intercept 한다는 뜻이 됩니다.

Intercepting Route 공식문서

모달 형태로 띄우는 방법은??

모달 컴포넌트를 만들어 준후 (당연히 클라이언트 컴포넌트)
Modal 함수 내에서 createPortal이라는 React Dom으로부터 제공되는 메서드를 불러와서 그대로 호출 한 후
첫 번째 인수로 모달 컴포넌트의 렌더링 결과로는 모달이니깐 dialog 태그 안에 children을 그대로 렌더링 하도록 설정합니다.
두 번째 인수로는 모달이 렌더링 될 위치인 DOM 요소를 넣어 주셔야 됩니다. document.getElementById('modal-root') 요소를 찾아서 as HTMLElement라는 타입으로 단언을 해줍니다.
예를 들어

"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,
      });
    }
  }, []);

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

이렇게 되면 createPortal 이라는 메서드를 통해서 브라우저에서 존재하는 modal-root라는 아이디를 갖는 DOM 요소 아래에 dialog 태그가 렌더링이 됩니다.
그러므로 Root Layout에서 modal-root의 id를 가진 div 태그를 설정해주면 됩니다.

예시

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <div className={style.container}>
          <header>
            <Link href={"/"}>📚 ONEBITE BOOKS</Link>
          </header>
          <main>{children}</main>
          <Footer />
        </div>
        <div id="modal-root"></div> // 추가
      </body>
    </html>
  );
}

그러면 이제 Intercepting Route를 적용시킨 page에 Modal 컴포넌트를 사용해주시면 됩니다.

예시

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

모달 css 적용 예

.modal {
  width: 80%;
  max-width: 500px;

  margin-top: 20px;
  border-radius: 5px;
  border: none;
}

.modal::backdrop {
  background: rgb(0, 0, 0, 0.7);
}

Parallel 과 Intercepting Route

위와 같이 Intercepting Route만 모달을 사용했을 때 뒷배경에 원래 탐색하고 있던 페이지가 병렬로 함께 나와주는 상황이 아닙니다.
이러한 문제점을 보완하기 위해 Parallel Route와 Intercepting Route를 같이 사용하는 방법이 있습니다.

예를 들어

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

export default function Page(props: any) {
  return (
    <Modal>
      <BookPage {...props} />
    </Modal>
  );
}

Intercepting Route를 적용한 Page가 모달이 띄워지기 이전에 페이지가 함께 렌더링이 되는 또는 병렬로 함께 렌더링이 되는 기능을 추가해야 합니다.

이때 Parallel Route를 이용하면 되겠죠??
app폴더 하단에 @modal 슬롯을 만들어 줍니다. 다음 Intercepting Route를 적용시킨 폴더를 @modal 슬롯 안으로 이동시켜 줍니다!

이렇게 된 경우 modal 안에 Intercepting되는 page 컴포넌트가 들어가 있으니, modal 슬롯에 있는 페이지 컴포넌트가 되는 것입니다!

그러면 page 컴포넌트는 부모 레이아웃 컴포넌트에게 modal 이라는 이름의 props로 전달이 됩니다.

그러면 Layout에서 modal의 타입을 ReactNode로 불러온 후 병렬로 잘 렌더링되게 설정해주면 되겠죠?

export default function RootLayout({
  children,
  modal,
}: Readonly<{
  children: React.ReactNode;
  modal: ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <div className={style.container}>
          <header>
            <Link href={"/"}>📚 ONEBITE BOOKS</Link>
          </header>
          <main>{children}</main>
          <Footer />
        </div>
        {modal}
        <div id="modal-root"></div>
      </body>
    </html>
  );
}

Parallel 할때 예외 처리 페이지 안까먹으셨죠? 그것도 만들어야 합니다!

profile
개발자가 되고 싶은 정치학도생의 기술 블로그

0개의 댓글