병렬 라우트 (Parallel Routes)
병렬 라우트는 동일한 레이아웃 내에 여러 페이지를 동시에 렌더링할 수 있도록 한다. 예를 들어 대시보드 내에 여러 섹션을 구성하거나 소셜앱의 피드와 같은 곳에 사용할 수 있다.@폴더명으로 경로를 설정하여 해당 페이지 내의layout.js에서 이 폴더명을 props로 받아 사용한다.
위 사진과 같이 app 하위에@team과 @analytics폴더를 생성하여 경로의 layout.js에서 children뿐만 아니라 team과 analytics를 받아 한 페이지 내에 여러 페이지 UI를 구성할 수 있다. 경로에는 영향을 주지 않으므로app/@폴더명의 경로로는 접근할 수 없다.
만약 병렬 라우트 하위에 여러 페이지가 존재하고 같은 레이아웃을 설정하고 싶다면 @폴더명/layout.js를 생성해 레이아웃을 공유할 수 있다.
병렬 라우트는 navigation이 어떤 방식으로 발생하느냐에 따라 페이지를 찾을 수도, 찾지 못할 수도 있다. 페이지 이동 방식에는 Soft Navigation과 Hard Navigation이 있다. Hard Navigation이 발생하면 병렬 라우트가 페이지를 찾지 못하는 경우가 발생하는데 이 때 default.js파일이 필요하다.
💡 Soft Navigation: 클라이언트 측에서 발생하는 url 이동으로 <Link>와 같은 컴포넌트를 사용하여 경로를 이동한 경우를 말한다.
💡 Hard Navigation: 전체 페이지를 새로고침하거나 url을 직접 입력해서 들어가는 경우이다.
아래 예시를 통해 default.js파일이 필요한 경우를 자세히 알아보자.
app/
├─ archive/
│ ├─ @archive/
│ │ ├─ page.js
│ ├─ @latest/
│ │ ├─ page.js
│ ├─ layout.js
// app/archive/layout.js
export default function ArchiveLayout({ archive, latest }) {
return (
<>
<section>{archive}</section>
<section>{latest}</section>
</>
);
}
위와 같은 폴더구조에서 /archive로 이동 시 layout.js에 설정한 병렬 라우트로 인해 @archive 와 @latest 페이지가 동시에 보여지게 된다.
app/
├─ archive/
│ ├─ @archive/
│ │ ├─ year/
│ │ │ ├─ page.js
│ │ ├─ page.js
│ ├─ @latest/
│ │ ├─ page.js
│ ├─ layout.js
상단 @archive가 있는 페이지 하위에 페이지를 생성하고 싶어 year페이지를 생성했다. 이 때 폴더 구조는 위와 같다. 병렬 라우트는 url에 영향을 미치지 않으므로 year페이지의 경로는 /archive/year이 될 것이다.
<Link href=’/archive/year’>To year</Link>
이제 Soft Navigation으로 코드를 작성해 페이지를 이동하면 올바르게 year페이지가 뜬다. 그러나 /archive/year에서 새로고침 또는 외부 링크를 통해 들어오게 된다면 404 페이지를 반환하게 된다.
이렇게 되는 이유는 layout.js에서 두 개의 슬롯을 렌더링하는데 @archive에는 year경로가 존재하지만, @latest에는 year경로가 없기 때문에 /archive/year로 처음 진입 시 해당 라우터를 인식하지 못해서 발생하게 되는 문제이다. 따라서 @latest하위에 default.js파일을 설정해주어 Hard Navigation 발생 시 default.js페이지가 뜨도록 설정해줘야 한다.
app/
├─ archive/
│ ├─ @archive/
│ │ ├─ year/
│ │ │ ├─ page.js
│ │ ├─ page.js
│ ├─ @latest/
│ │ ├─ default.js
│ │ ├─ page.js
│ ├─ layout.js
즉, Soft Navigation이 발생하면@latest/page.js페이지가 뜨고 Hard Navigation이 발생하면@latest/default.js페이지가 뜨게 된다.
💡 만약 두 페이지의 내용이 같다면 default.js파일만 생성해주면 된다.
Catch-All 라우트
[폴더명]은 하나의 동적 라우트만 받을 수 있지만, 동적 라우트 밑에 모든 하위 경로들의 처리가 필요하다면 Catch-All 라우트를 사용해 동적 라우팅을 더욱 유연하게 처리할 수 있다.
- Catch-All 라우트 : [...slug].js형태의 파일을 생성하여 동적 세그먼트 값을 배열 형태로 받을 수 있다.
- Optional Catch-All 라우트 : Catch-All 라우트와 유사하지만, 옵셔널하므로 매개변수가 없는 경로도 처리할 수 있게 해준다. [[...slug]].js파일을 생성하여 slug라는 변수에 URL의 하위 경로들이 배열로 전달되며, 경로가 없는 경우에는 undefined를 반환한다.
인터셉팅 라우트 (Intercepting Routes)
특정 경로를 가로채서 다른 경로를 표시하고 싶은 경우에 사용할 수 있는 라우팅 기능이다. 현재 페이지의 컨텍스트를 유지하면서 경로를 표시하려는 경우에 유용하다.
이 기능은 페이지 내부 링크를 통한 탐색 여부에 따라 보여지는 UI가 다르다. 즉, 해당 경로로 진입 시에 클라이언트 측에서 발생한 동작은 인터셉팅 라우가 활성화되고 서버 측에서 요청을 처리하여 보여지게 되는 페이지에서는 기존에 설정한 페이지가 뜨게 된다. ()기호를 사용하여 경로를 표시하고 ()옆에 가로챌 라우트의 이름을 넣는다. 경로는 같은 폴더에 있다면 (.), 하나 상위에 있다면 (..)와 같이 상대 경로로 표시하면 된다.
병렬 라우트와 인터셉터 라우트 결합
인터셉팅 라우트는 병렬 라우트와 결합하여 유용하게 사용할 수 있다. 예를 들면 병렬 라우트에 인터셉팅 라우트가 있다면 모달의 콘텐츠를 보여주고, 새로고침이나 링크를 직접 입력하여 외부에서 해당 모달 콘텐츠에 진입하는 경우에 페이지 전체를 보여주는 방식으로 구현이 가능하다. 이는 모달 콘텐츠가 하나의 페이지가 되면서 이를 공유할 수도 있고, 해당 모달을 새로고침을 해도 콘텐츠가 남아있다는 여러 장점이 있다.
아래 예시를 통해 살펴보자
// app/news/[slug]/image/page.js
export default function ImagePage({ params }) {
const slug = params.slug;
const newsItem = await getNewsItem(slug);
return (
<div className="fullscreen-image">
<img src={`/images/news/${newsItem.image}`} alt={newsItem.title} />
</div>
);
}
/news/post1이라는 동적 경로 하위에 이미지 모달을 띄우고 싶다
먼저 image 폴더로 이미지 모달에 대한 경로를 생성한다. 이 때 이미지 콘텐츠에 대한 url경로는 /news/post1/image가 된다.
// app/news/[slug]/@modal/(.)image/page.js
export default function InterceptedImagePage({ params }) {
const slug = params.slug;
const newsItem = await getNewsItem(slug);
return (
<dialog open>
<img src={`/images/news/${newsItem.image}`} alt={newsItem.title} />
</dialog>
);
}
그 다음 동적 경로 하위에 병렬 라우트로 @modal폴더를 생성한 후, 다시 하위에 인터셉팅 라우트로 (.)image 폴더를 생성해 인터셉팅되는 페이지를 만든다.
// app/news/[slug]/layout.js
export default function ItemLayout({ children, modal }) {
return (
<>
{/* 이미지 모달 */}
{modal}
{/* 아이템 상세 */}
{children}
</>
);
}
상세페이지의 layout.js에 modal과 children을 작성하여 상세페이지에서 이미지를 클릭하면 이미지 모달도 함께 뜰 수 있도록 한다.
// app/news/[slug]/@modal/default.js
export default function Default() {
return null;
}
@modal하위에 default.js를 생성하여 새로고침 시에도 병렬 라우트가 페이지를 찾을 수 있도록 한다.
// app/news/[slug]/page.js
export default async function NewsItem({ params }) {
const slug = params.slug;
const newsItem = await getNewsItem(slug);
return (
<article className="news-article">
<Link href={`/news/${slug}/image`} scroll={false}>
<img src={`/images/news/${newsItem.image}`} alt={newsItem.title} />
</Link>
</article>
);
}
이제 상세페이지인 post1에서 <Link>클릭 시 이미지 모달 콘텐츠가 뜨게 되고, 해당 url을 새로고침 또는 외부에서 진입하게 되면 이미가 있는 모달 콘텐츠가 InterceptedImagePage컴포넌트가 렌더링 되어 모달이 아닌 풀스크린 페이지로 뜨게 된다.
라우트 그룹 (Route Groups)
route를 그룹화하여 폴더를 논리적으로 구성할 수 있도록 도와준다. 경로에는 영향을 미치지 않으나 각 그룹에 대해 공통된 레이아웃을 설정할 수 있고 체계적으로 관리할 수 있으므로 유지보수에 용이하다.
유데미 Next.js 14 & React - 완벽 가이드를 수강하고 직접 정리한 내용입니다.
'개발 > 프론트엔드' 카테고리의 다른 글
[React] 리액트로 스톱워치 Chrome Extension 만들기 (0) | 2024.08.29 |
---|---|
[NextJS]App Router - 앱 최적화와 캐싱 (0) | 2024.08.01 |
[NextJS] 14버전 앱 라우터에 대해 알아보자 (0) | 2024.06.25 |
[WIL] ReactJS 함수형과 클래스형 (0) | 2022.08.07 |
[WIL] ReactJS 기초 (1) | 2022.07.31 |