Next.js 공부를 하다가 새로운 라우팅 패턴에 대해 알게 되었습니다. 이런게 가능하다니? 신기한 마음에 블로그로 정리해 보려고 합니다!
발음하기도 어려운(?) 패럴렐 라우트는 병렬 라우트를 뜻합니다. 즉, 하나의 화면 안에 여러 페이지 컴포넌트를 한번에 렌더링 시켜주는 패턴입니다. 주로 트위터와 같은 소셜 미디어, 어드민 대시보드 등 복잡한 구조에 사용됩니다.
패럴렐 라우트에는 슬롯 이라는 개념이 등장하는데요. 이는 병렬로 렌더링 될 페이지들을 보관하는 폴더 역할을 합니다. 원하는 페이지 폴더 아래 @
를 붙여서 슬롯을 만들어 주면 됩니다. 이때, 생성된 슬롯은 라우트 그룹처럼 URL경로에는 아무런 영향을 주지 않습니다.
슬롯의 특징에 대해서 몇가지 더 살펴보자면,
layout.tsx
)에 자동으로 props(슬롯에 생성한 컴포넌트
)로 전달됩니다.export default function Layout({
children,
sidebar,
feed,
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
feed: React.ReactNode;
}) {
return (
<div>
{feed}
{sidebar}
{children}
</div>
);
}
3번 특징에 대해 좀더 자세히 알아보겠습니다.
@feed
슬롯 안에 setting
이라는 페이지 폴더를 생성한 뒤 layout.tsx
에 해당 페이지로 이동할 수 있는 테스트 경로를 추가했습니다.
// app/parallel/layout.tsx
import Link from "next/link";
export default function Layout({
children,
sidebar,
feed,
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
feed: React.ReactNode;
}) {
return (
<div>
<div>
<Link href={"/parallel"}>parallel</Link>
<Link href={"/parallel/setting"}>parallel/setting</Link>
</div>
<br />
{feed}
{sidebar}
{children}
</div>
);
}
<Link>
를 통해 진입한 경우즉, 현재 layout.tsx
가 유지된 상태에서 패럴렐 라우트 내부만 업데이트 되기 때문에 정상적으로 동작합니다.
Next.js는 페이지 진입시 해당 페이지가 독립적인 페이지인지를 확인하는데 슬롯 안에 등록된 페이지는 직접 페이지로 등록하지 않기 때문에 존재하지 않는 경로라고 판단하는 것입니다.
즉, 페러렐 라우트 내부 페이지들은 기본적으로 단독으로 접근할 수 없고 반드시 상위 layout.tsx
가 존재해야 정상적으로 렌더링됩니다.
위와 같은 오류를 해결하는 방법은 간단합니다. 페럴렐 라우트가 비어있을 때 기본적으로 보여줄 페이지인 default.tsx
를 만들어 주면 됩니다.
export default function Default() {
return <div>/parallel/default</div>;
}
인터셉팅 라우트는 intercept 의 낚아채다 라는 뜻대로 경로를 낚아채는 것을 말합니다. 즉, 사용자가 동일한 경로로 접속하게 되더라고 특정 조건에 만족하면 다른 페이지를 렌더링하도록 설정하는 기술 을 말합니다.
그런데 여기서 말하는 조건은 개발자가 직접 설정할 수 있는 것은 아니고, Next.js 에서 지정된 조건으로 초기 접속이 아닐 때 를 말합니다. 예를 들면 클라이언트 사이드 렌더링 방식 으로 <Link>
혹은 Router.push()
을 말합니다.
(.)
를 붙여줍니다.()
뒤에 나오는 경로를 인터셉트 하라는 의미입니다..
은 상태 경로를 나타냅니다. (동일한 경로라는 뜻)(..)
인터셉트를 적용시킬 페이지의 경로가 한 단계 위에 있을 때(..)(..)
인터셉트를 적용시킬 페이지의 경로가 두 단계 위에 있을 때(...)
app폴더 바로 아래에 있는 페이지를 인터셉트하겠다는 의미이 두 가지 원리를 사용해서 인스타그램의 상세 페이지를 선택했을 때 동작하는 방식을 구현할 수 있습니다.
예시로 인스타그램(pc)에서 내 프로필에서 피드를 선택했을 때 피드 상세가 모달로 노출되지만, 다시 새로 고침을 하게되면 피드 상세 페이지로 보여지고 있습니다. 이와 같은 동작을 패럴렐 라우트와 인터셉팅 라우트를 사용해서 구현해 보도록 하겠습니다.
// app/page.tsx
export default function Home() {
return (
<div className={style.container}>
<section>
<h3>지금 추천하는 도서</h3>
<div>
{recoBooks.map((book) => (
<Link href={`/book/${id}`} className={style.container}>
...
</Link>
))}
</div>
</section>
</div>
);
}
// app/@modal/(.)book/[id]/page.tsx
import BookPage from "@/app/book/[id]/page";
import Modal from "@/components/modal";
export default function Page(props: any) {
return (
<Modal>
<BookPage {...props} />
</Modal>
);
}
// app/book/[id]/page.tsx
export default function Page({ params }: { params: { id: string } }) {
return (
<div className={style.container}>
<BookDetail bookId={params.id} />
<ReviewEditor bookId={params.id} />
<ReviewList bookId={params.id} />
</div>
);
}
패럴렐 라우트와 인터셉팅 라우트를 함께 사용하면, 기존 페이지에서 모달을 띄우는 사용자 경험을 제공하면서도 새로 고침 시 개별 상세 페이지로 자연스럽게 이동하는 동작을 구현할 수 있습니다.
<Modal>
로 감싸서 모달 형태로 표시됩니다. /
로 접근하거나 /book/[id]
에 직접 접근했을 경우를 위해 default.tsx
을 만들어 줘야합니다.export default function Default() {
return null;
}