Next.js - 고급 라우트 기법 (병렬 라우트, 가로채기 라우트)

Stella·2026년 1월 7일

Next.js

목록 보기
7/8

병렬 라우트, 가로채기 라우트라는 고급 라우트 기법

병렬 라우트

하나의 화면에서 여러 페이지를 병렬로 렌더링하는 기능이다.
레이아웃이 복잡하거나 멀티태스킹이 필요한 UI를 구현할 때 매우 유용하다.

하나의 화면에 여러 개의 페이지 컴포넌트를 동시에 렌더링한다.

기존 렌더링 방식의 문제점

기존의 렌더링 방식 단순히 컴포넌트로 구현하면 특정 섹션에서 오류가 발생,
렌더링에 문제가 생길 경우 페이지 컴포넌트 전체에 예외가 발생한다.

= 오류가 없는 섹션에도 영향을 미치는 상황 발생
try-catch문을 사용할 수 있지만 유지보수성이 떨어진다.
예외처리 로직 중복 사용하면 일관성이 저하, 하위 탐색이 불가능한 경우가 많다. (복잡성)

병렬 라우트 대시보드 UI구현

import { ReactNode } from "react";

export default function Layout({ children }: { children: ReactNode }) {
	return <div>{children}</div>;
}

// page.tsx
export default function Page() {
	return <div>관리자 페이지</div>
}

/admin 페이지로 접속하여 관리자 페이지를 확인한다.

- @slot 폴더 생성 = Props의 Key

병렬로 렌더링할 페이지 컴포넌트를 보관하는 폴더이다. @기호를 붙여 특정 폴더를 슬롯으로 지정할 수 있다.

src/app/admin/@notification/page.tsx

export default function Page() {
    return <div>@notification</div>;
}

src/app/admin/@user

export default function Page() {
    return <div>@user</div>;
}

각각의 페이지 컴포넌트는 Next.js가 자동으로 레이아웃 컴포넌트에 Props로 전달한다.
슬롯 이름이 Props의 Key가 된다.

- layout 컴포넌트에서 props를 사용해 병렬로 렌더링

import { ReactNode } from "react";

export default function Layout({
    children,
    notification,
    user,    
}: { children: ReactNode;
    notification: ReactNode;
    user: ReactNode;
}){
    return (
        <div>
            {children}
            {notification}
            {user}
        </div>
    )
}

props로 전달한다.
페이지 컴포넌트가 제공되므로 ReactNode타입으로 정의한다. {notification}{user} 등 페이지 컴포넌트를 렌더링한다.

- 예외 처리하기

@user 슬롯 아래에 error.tsx파일을 생성, 에러가 발생하면 페이지 컴포넌트 대신 렌더링할 컴포넌트를 작성한다.

"use client";

export default function Error() {
	return <div>오류 발생!</div>;
}

해당 슬롯의 페이지 컴포넌트만 마비될 뿐 다른 슬롯이나 레이아웃에는 영향을 미치지 않는다.
오류가 발생한 섹션만 별도로 처리되며, 나머지 섹션과 페이지는 정상적으로 렌더링 된다.

export default function Page() {
	throw new Error();
    
    return <div>@notification</div>;
}

- 섹션별로 하위 탐색 구현하기

병렬 라우트를 활용하여 섹션별로 하위 탐색을 구현할 수 있따.
/admin : 대시보드 페이지, 회원 관리 섹션과 알림 섹션을 렌더링
/admin/archived : 대시보드 페이지로 회원 관리 섹션과 보관된 알림 섹션을 렌더링

알림을 렌더링하는 페이지 컴포넌트를 정의한다.

현재 접속 주소 ~/admin/archived
슬롯별 페이지 컴포넌트
@notification 슬롯 : ~/admin/@notification/archived/page.tsx
@user 슬롯 : ~/admin/@user/archived/page.tsx
children 슬롯 : ~/admin/archived/page.tsx

다른 슬롯에 불필요한 폴더나 페이지를 추가할 필요 없이 문제를 해결할 수 있다.

- default 컴포넌트 추가하기

슬롯 폴더에 default.tsx 파일을 만들고 컴포넌트를 정의하면 된다.

- 섹션별로 하위 탐색할 때 주의할 사항

하나의 슬롯이라도 해당 경로에 렌더링할 페이지 컴포넌트가 없으면 404페이지가 나타난다.
이를 방지하기 위해 경로가 없는 슬롯에는 default 컴포넌트를 정의해야 한다.

= 하드 네비게이션 방식

1) 하드 네비게이션 (SSR) 환경에서 일관된 초기 상태 보장
브라우저의 주소 표시줄에서 URL을 직접 입력하거나 페이지를 새로고침해 이동하는 방식이다.
서버로부터 새로운 페이지를 다시 로드하고 기존의 상태나 UI를 초기화한다.

= 모든 슬롯에서 페이지 컴포넌트 or Default 컴포넌트가 필요하다.
페이지 컴포넌트가 없으면 Default 아니면 404페이지가 나타난다.

2) 소프트 네비게이션
Link컴포넌트 또는 라우터 객체의 navigate메서드 등 클라이언트 사이드 렌더링으로 페이지를 이동하는 방식
기존의 상태를 유지 + 필요한 부분만 업데이트하므로 페이지 이동이 빠르다.

= 현재 경로에 맞는 페이지 컴포넌트가 존재하는 슬롯만 업데이트되며 페이지 컴포넌트가 없는 슬롯은 이전 상태를 그대로 유지한다.
/admin 페이지의 레이아웃 컴포넌트에서 소프트 네비게이션 방식을 사용한다.

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

export default function Layout({
    children,
    notification,
    user,    
}: { children: ReactNode;
    notification: ReactNode;
    user: ReactNode;
}){
    return (
        <div>
            <header style={{ display: "flex", gap: "10px" }}>
                <Link href={"/admin"} style={{ color: "blue"}}>
                    /admin
                </Link>
                <Link href={"/admin/archived"} style={{ color: "blue"}}>
                    /admin/archived
                </Link>
            </header>
            <br />
            {children}
            {notification}
            {user}
        </div>
    );
}

가로채기 라우트 (하드 or 소프트) 고르기

특정 경로에 사용자가 소프트 네비게이션 방식으로 접근했을 때, 해당 요청을 가로채 원래 렌더링할 페이지 컴포넌트 대신 다른 페이지 컴포넌트를 렌더링하는 기능이다.

동일한 페이지라도 사용자의 접근 방식에 따라 다른 UI제공할 수 있다.
피드 형식의 SNS 서비스에서 자주 활용된다.

가로채기 라우트 적용 방법

app 폴더에 가로채는 폴더 생성 -> page.tsx 생성 -> 원래 페이지 컴포넌트 대신 화면에 렌더링할 페이지 컴포넌트를 만들어야 한다.

= (.)폴더는 가로채기 폴더로 인식

  • 소괄호 : 가로채기 라우트를 위한 폴더임 명시
    photo/[id]/page.tsx
    (.)photo/[id]/page.tsx

  • 마침표(.) : 동일한 경로에 있는 폴더의 가로채기임을 명시한다.
    (..)photo/ 한단계 위에 있는 app 폴더를 가로채기 하는 방법
    (..)(..)photo/ 두 단계 위에 위치한 페이지를 가로채기 하는 방법
    (...)photo/ 루트 폴더를 기준으로 가로채기 할 수 있다.

도서 상세 페이지에 가로채기 라우트

book/[id] 경로의 page.tsx를 가로채기 위해
src/app/(.)book/[id]/page.tsx 로 도서 상세페이지에 접근한다.

모달 구현하기

가로채기 라우트가 동작했을 때 기존 페이지 컴포넌트를 모달 형태로 렌더링한다.

Modal 컴포넌트는 일반적으로 독립적이기 때문에 최상단에 렌더링하는 것이 바람직한다. 루트 레이아웃에서 렌더링하면 레이아웃 스타일에 영향을 받거나 의도치 않은 스타일 충돌을 일으킬 수 있다.

- createPortal 메서드를 이용

특정 컴포넌트를 트리에섯 분리 HTML구조의 원하는 위치에 렌더링한다.
app/layout.tsx

return createPortal (
        <div className={style.backdrop} onClick={(e) => {
            if (e.target === e.currentTarget) {
                router.back();
            }
        }}>
            <div className={style.modal}>{children}</div>
        </div>,
        document.getElementById("modal-root") as HTMLElement
);

createPortal 메서드를 불러온다. 첫 번째 인수 : 렌더링하려는 UI 요소
두 번째 인수 : 첫 번째 인수로 전달하는 UI 요소를 렌더링 할 위치 (modal-root)

가로채기 라우트와 병렬 라우트 함께 사용하기

모달을 렌더링해도 종전에 탐색하던 페이지를 뒷배경에 나오도록 구현하려면 모달과 기존 페이지를 동시에 렌더링하는 방식으로 구조를 수정해야 한다.

= 두 개 이상의 페이지를 동시에 렌더링하도록 설정해야 함

도서 상세 페이지 @modal
종전 페이지 children슬롯으로 설정해 하나의 페이지에서 동시에 렌더링 가능

src/app/@modal/(.)book/[id]/page.tsx
@modal에서 보여줄 default 컴포넌트 생성

src/app/book/[id]/page.tsx

  • app/layout.tsx
    children, modal이라는 두 슬롯의 페이지 컴포넌트가 props으로 제공된다.
    {modal} children 슬롯의 페이지 컴포넌트와 함께 렌더링한다.

도서 상세 페이지 로딩 UI설정

로딩중 표시하는 로딩 UI를 추가하고 스트리밍 방식으로 페이지를 로딩하는 동안 사용자에게 로딩 UI를 보여 주도록 설정한다.

도서 상세 페이지를 Suspense컴포넌트로 감싸고, fallback Prop의 값으로 '로딩 중'이라는 div태그 전달

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

export default function Page(props: any) {
    return (
        <Modal>
            <Suspense fallback={<div>로딩 중</div>}>
                <BookPage {...props}/>;
            </Suspense>
        </Modal>
    )
}

- skeleton UI 설정하기

fallback prop의 값을 BookPageSkeleton 컴포넌트로 설정한다.

<Suspense fallback={<BookPageSkeleton />}>
	<BookPage {...props}/>
</Suspense>
profile
공부 기록

0개의 댓글