학습 Next.js - Day 24 / Parallel Route, Intercepting Route

이유승·2024년 10월 13일

Next.js 학습

목록 보기
25/27



1. Parallel Route 패럴렐 라우트

  • 화면 내부에 여러 개의 레이아웃들을 렌더링하는 패턴.

  • Parallel Route는 복수의 '페이지들'을 한 화면에 렌더링되는 기법이다. 컴포넌트들이 아니다. 여러 컴포넌트들이 한 화면에 렌더링 되는건 굳이 따로 다룰 필요도 없다.



2. Parallel Route 실습

  • 기존 학습 프로젝트에서는 굳이 Parallel Route을 사용할 내용이 없어, Parallel Route 실습만을 위한 임시 폴더와 페이지를 구성하여야 한다.



slot 슬롯.

  • 병렬로 렌더링되어야할 페이지 컴포넌트가 보관될 폴더를 뜻한다.

  • @feed, @sidebar 같이 @ 문자를 이름 가장 앞에 작성하여 구분한다.

  • App Router에서 폴더 내부에 page 파일을 두는것과 동일하지만, Parallel Route를 사용하는 경우에는 폴더 이름 제일 앞에 @ 문자가 추가되는 것.

  • slot에는 갯수 제한이 없다. 원하는 만큼 작성해서 사용하면 된다.

  • slot 내부에 또 다른 페이지를 작성하는 것도 가능하다. 위 이미지의 경우 @feed는 slot으로써 URL 경로에 영향을 미치지 않지만, setting은 slot이 아니므로 경로에 영향을 받는다.
    -> parallel/@feed은 동작하지 않지만, parallel/setting은 동작한다.

  • 이런 식으로 slot 내부의 다른 페이지를 추가할 경우. 이 페이지 경로로 이동하였을 때 다른 Parallel Route 페이지들은 그대로 유지되지만, @feed의 페이지 내용이 하위 폴더의 setting 폴더의 페이지로 대체된다.

  • Parallel Route의 적용을 받은 layout의 경우, Route 적용을 위해 Props로 받아들이는 children는 해당 페이지로 진입하기 위한 URL 값이다.

  • parallel 폴더의 페이지들이니, URL 경로는 '/parallel'를 입력할 경우, children, sidebar, feed가 모두 존재하기 때문에 정상적으로 렌더링된다.

  • 그런데 '/parallel/setting'의 경로를 입력해보면, feed 이외에는 해당되는 페이지가 존재하지 않기 때문에 이전 '/parallel'의 상태를 유지하게 된다.

  • 따라서 '/parallel/setting' 경로를 입력하더라도, feed 이외의 부분들은 이전 경로의 상태를 유지하고. feed 부분만 하위 setting 폴더의 페이지로 대체된다.

  • 이런 현상은 Link 태그를 이용하여 경로를 이동하였을 때만 발생한다. '/parallel'를 입력하지 않고, 바로 '/parallel/setting'를 입력하면 children, sidebar가 존재하지 않기 때문에 404 페이지로 이동하게 된다.

  • 무조건 404 페이지로 이동하는게 싫다면, default.tsx 파일을 작성해주면 이 파일이 404 상황에서 이동될 페이지로 간주된다.



Parallel Route의 적용

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

export default function Layout({
  children,
  sidebar,
  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>
  );
}
  • Slot 폴더 내부의 page 파일은 자동으로 부모 layout 컴포넌트로 Props의 형태로 전달된다.

  • (with-searchbar) Route Group과 동일하게, Slot 폴더는 URL 경로에는 아무 영향도 미치지 않는다. parallel/@feed와 같은 경로를 입력해봐야 404 페이지로 이동될 뿐.

  • Parallel Route는 Next 개발 모드에서 다소 불안정하게 동작한다. 문제가 발생할 경우, 개발 모드를 중단하고 프로젝트 폴더 내부의 .next 폴더를 삭제한 뒤, 재실행하면 보통 해결된다.



3. Intercepting Route 인터셉팅 라우트

  • 사용자가 동일한 경로에 접속할 때, 특정 조건 하에서라면 다른 페이지가 렌더링 되도록 하는 기술.

  • Intercepting Route은 초기 접속 요청이 아닐 때 동작한다.

  • 클라이언트 사이드 렌더링, Link 컴포넌트, Router 객체의 push 등에서만 Intercepting Route이 동작할 수 있다.

  • SNS 등지에서 어느 게시물을 클릭하면, 기존에 보고 있던 페이지 위에 모달 형식으로 상세 페이지를 새로 띄워주는데 사용된다.
    -> 상세 페이지를 끄면, 기존 목록 페이지가 다시 보여지는 식.
    -> 이 상태에서 새로고침을 하면, 초기 접속이 되기 때문에 모달이 아니라 하나의 전체 페이지로 렌더링 되도록 하는 식으로 구현하곤 한다.

  • 더 명확한 이해를 위해 공식문서를 참조해봐도 좋다.



4. Intercepting Route 실습

  • 위 book 페이지에 Intercepting Route을 적용하기 위해서는..

  • book 페이지에 해당하는 Route 폴더 이름 그대로, 다만 '(.)' 문자를 제일 앞에 붙여주면 된다.
    -> (.)book/[id] 폴더 내부의 pagebook/[id] 폴더 내부의 page의 Intercepting Route이 되는 것.

  • 다만, (.)의 의미는 두 폴더가 같은 경로에 있다는 것이다. Intercepting의 대상이 상위 경로에 있다면 (..). App 폴더 바로 아래의 page를 Intercepting한다면 (...).
    -> 상위의 상위 경로는? (..)(..).

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>
  );
}
  • book 페이지를 Intercepting하는 book Intercept 페이지. /book 경로로 진입해보면, 기존 book 페이지가 아니라 새롭게 구성한 book Intercept 페이지가 렌더링 된다.

  • 기존 book 페이지를 book Intercept 페이지로 호출해서 사용하는 것도 가능하다.



"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? React-dom에서 제공되는 메소드.

  • 첫번째 인자로는 렌더링 할 컴포넌트 (dialog 태그). 두번째 인자로는 해당 컴포넌트가 어느 위치에 렌더링되는지 기준이 될 DOM 요소를 넣어주면 된다. (document.getElementById("modal-root") as HTMLElement)

  • createPortal을 사용하지 않을 경우, Modal 컴포넌트의 내용은 특정 페이지의 하위 컴포넌트로 간주될 수 밖에 없다. CSS 설정 등에서 문제가 될 소지가 많아지는 것.

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>
  );
}
  • createPortal에서 사용한 modal-root라는 id값을 갖는 HTML 태그는 루트 레이아웃에 배치하는 것이 좋다.

  • Modal UI는 특정 조건 하에서 렌더링되거나 렌더링 되지 않아야 한다. useRef를 이용해서 Modal UI를 제어한 뒤, useEffect과 조건문을 사용해서 이를 제어해주면 된다.

  • onClick을 이용해서 Modal의 뒷배경을 클릭했을 때, Modal이 종료되도록 하는 이벤트를 넣는다.
    -> 클릭한 대상의 nodeName을 기준으로 본문과 배경화면을 구분하면 된다.
    -> 학습 프로젝트를 기준으로 배경화면은 dialog 태그. nodeName === "DIALOG"을 조건으로 사용한다.

  • ESC 키를 입력하거나 하는 상황을 상정하여 onClose 옵션에 Modal이 종료되도록 하는 이벤트를 설정한다.

  • 뒤로가기의 기능은 router.back() 메소드를 사용한다.



5. 심화 실습

  • 기존 학습 프로젝트의 코드 상으로는, Modal이 출력되고 있음에도 기존 book 페이지가 뒷 배경으로 같이 렌더링 되는 문제가 있다.

  • 정상적인 서비스의 모습에서는 book Modal이 출력될 때에는, index 페이지만이 출력되어야 한다.

  • Modal UI에 Parallel Route을 적용한다. Parallel Route에 의해 페이지 컴포넌트의 내용은 부모 layout 컴포넌트로 Props 형태로 전달된다.
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>
  );
}
  • @modal의 부모 레이아웃 컴포넌트는 루트 레이아웃은 컴포넌트. 루트 레이아웃은 기본적으로 다른 페이지 컴포넌트들을 children으로 받아오고 있다. 따라서 modal의 경우 modal이라는 이름으로 가져온 뒤, children과 병렬적으로 렌더링 되도록 구성해주면 된다.

  • index 페이지에서 book UI를 클릭하면, book 페이지로 진입한 뒤 modal을 출력하는 것이 아닌. index 페이지를 배경으로 book 페이지가 modal 형식으로 렌더링 된다.









00. 강의 소개.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글