처음으로 Next.js로 프로젝트를 만들면서 폴더구조를 관리하는데 햇갈리는 부분이 생겼다.
그동안 React로만 프로젝트를 진행했었는데 React는 폴더구조를 내마음대로 만들어도 전혀 문제가 되는 부분이 없었는데 Next.js로 프로젝트를 만들다 보니 문제점이 생겼다.
바로 폴더구조를 어떻게 관리해야 효율적으로 만들 수 있을까? 이다.
Next.js를 잘 사용하기 위해서 routing 방식을 잘 이해하면 폴더구조도 설계하기 쉽지 않을까 생각한다.
이번 글에서는 파일 기반 라우팅
, 동적 라우팅
은 많이 사용해 봤을것 같지만
인터셉팅 라우팅(intercepting routing)
, 병렬 라우팅(parallel routing)
은 생소할 것 같아서 이에 대해 다뤄보려고 한다.
Next.js프로젝트를 만들어 봤다면 익숙한 방식일 것이다.
|-- app | |-- example | | |- page.tsx
위와 같은 폴더 구조를 생성하면, /example
경로로 이동했을 때 example > page.tsx
페이지가 렌더링된다.
파일 기반 라우팅 방식에서 조금더 나아가서 동적으로 생성되는 URL에 의해 페이지 구성이 달라진다면?
이때 사용할 수 있는방식이 동적 라우팅(dynamic routing) 이다.
|-- app | |-- shop | | |- [shopId] | | |- page.tsx
위와 같이 shop 폴더 아래에 [shopId]라는 폴더를 생성하면 해당 경로는 동적인 URL 파라미터를 받을 수 있게된다.
이 경우 [shopId] 폴더 아래에 위치한 page.tsx 컴포넌트는 params라는 객체를 props로 받아, URL에서 전달된 파라미터 값을 사용할 수 있다.
params 객체 안에는 동적 라우팅 폴더 이름과 동일한 키 값이 포함되어 있으며, 이를 통해 URL에서 동적으로 생성된 값을 컴포넌트 내에서 사용할 수 있다.
예를 들어, /shop/1, /shop/2와 같은 경로로 접근할 때, [shopId]는 각각 1, 2 등의 값으로 해석된다.
// URL에 shop/1 을 입력했다면?? type ShopPageProps = {params : {shopId : string }} export default function Shop({params}:ShopPageProps){ console.log(params.shopId) // "1" }
Next.js에서는 layout.tsx
파일을 사용할 수 있고 layout.tsx
은 상위 layout과 중첩된다.
프로젝트를 진행하던 layout에 header컴포넌트
를 배치하고 사용하다가 문제가 발생했었다.
많은 페이지들에서 header컴포넌트를 사용하기에 layout에 header를 배치했는데 특정 페이지에서는 header가 필요 없을 수 있다는 것이었다.
이를 해결하기위해 layout의 유무에 따라 폴더를 그룹화하고 관리하는 방식을 고려했었다. 그러나 폴더의 깊이가 점점 깊어지면서 관리가 복잡해질 것 같다는 생각이 들었다.
이러한 문제를 해결하기 위해 병렬 라우팅(parallel routing)을 알게 되었다.
Next.js 공식문서에는 다음과 같이 나오는데
공식문서에서는 다음과 같이 설명하고 있다.
병렬 라우트(Parallel Routes)는 동일한 레이아웃 내에서 하나 이상의 페이지를 동시에 또는 조건에 따라 렌더링할 수 있게 해줍니다. 이는 대시보드나 소셜 사이트의 피드와 같이 매우 동적인 섹션에서 유용합니다.
예를 들어, 대시보드를 고려해 보았을 때, 병렬 라우트를 사용하면 팀 페이지와 분석 페이지를 동시에 렌더링할 수 있습니다.
@가 붙은 폴더를 slot
이라고 하는데 slot은 가장 가까운 부모 layout에 props로 전달 될 수 있다고 공식문서에서 설명하고 있다.
💥Info
- slot은 경로에는 포함되지 않는다.
- @team > team > page.tsx 경로를 예시로 보면
실제 URL은 /team 으로 작성되며 이때 page컴포넌트를 보여주게 된다.
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
주의 할 점!
병렬 라우팅을 사용할 경우 default.tsx
파일을 생성해 줘야한다.
default파일은 해당 페이지가 render되기 전 혹은 해당 페이지가 없을 경우 보여줄 fallback페이지다.
만약 default파일이 없을 경우 404페이지로 이동되니 주의해야한다.!
fallback페이지를 따로 사용자에게 보여줄 필요가 없다면
export default function Default(){
return null // null 을 return하는 방식을 사용
}
단순히 null을 return해줘도 된다.
공식문서를 읽었을때 언제 사용하면 좋을지 판단하기까지 조금 애매했다.
왜 사용해야 하는지에 대한 설명이 자세하게 안나와 있었기 때문이다.
병렬 라우팅은 언제 사용하면 좋을지 생각해본 결과는 다음과 같다.
- 한 페이지에서 2개 이상의 api를 불러오며 페이지를 구성하는 경우
- 동일한 layout에서 조건에 따라서 rendering 해야할 경우
- modal을 사용하는 경우(인터셉팅 라우팅 방식과 같이사용)
🎉병렬 api호출
병렬 라우팅 방식을 사용해서 성능개선한 글을 봤었는데 왜 병렬 라우팅을 사용하면 좋은지 자세하게 설명해주고 있다.
가장 좋은 점은 api를 각 페이지 컴포넌트에서 다룰 수 있다는 것이었다.
만약 병렬 라우팅을 사용하지 않고 한번에 api를 처리하게 되면 하나의 api호출에서 에러가 발생할 경우 해당 에러가 페이지 전체로 퍼지게 된다.
그렇다면 page전체를 사용자가 볼 수 없게되는 문제점이 생기는데
병렬 라우팅을 사용해서 페이지 단위로 api를 다룬다면 하나의 api가 생겨도 문제되는 컴포넌트만 error ui를 제공해주고 나머지 구성요소들은 정상동작하기 때문에 사용자 경험을 향상 시킬것이다.
또한 api를 병렬로 요청하기 때문에 복잡한 api를 불러올 경우 더 빠른 로딩을 할 수 있다.
🎉같은 layout에서 조건부 render
특정 조건에서만 보여줘야 할 컴포넌트가 있다면 병럴 라우팅을 사용해서 처리하기 좋을 것 같다.
만약 비로그인 사용자일 경우에만 header를 보여주지 말도록 코드를 작성해야할 경우를 생각해보면 적절한 예시일 것 같다.
공식문서에서 intercepting routing에 대해 다음과 같은 설명을 한다.
인터셉팅 라우팅(Intercepting routes)은 현재 레이아웃 내에서 애플리케이션의 다른 부분에서 라우트를 불러올 수 있게 해주는 라우팅 패러다임입니다. 이 방식은 사용자가 다른 컨텍스트로 전환하지 않고도 라우트의 콘텐츠를 표시하고자 할 때 유용합>니다.
예를 들어, 피드에서 사진을 클릭했을 때, 사진을 모달로 표시하여 피드 위에 오버레이할 수 있습니다. 이 경우, Next.js는 /photo/123 라우트를 인터셉트하고 URL을 숨기며, 이를 /feed 위에 오버레이합니다.
공식문서에서는 overlay를 예시로 알려줘서 처음에 좀 혼동이 있었다.
결국 인터셉팅 라우팅을 통해 특정 url접근시 overlay로 보여주는 동작 방식으로 많이 사용할 것이지만 개념에 대해 이해해 보자면 다음과 같다.
|-- app
|-- main
|-- page.tsx
|-- (..sub) // 이 부분이 인터셉팅 라우팅을 설정하는 곳
|-- sub
|-- page.tsx
|-- min
|-- page.tsx
다음과 같은 폴더구조를 가진 프로젝트라면
- main경로에서 sub경로로 이동할 땐 url을 인터셉팅
- min경로에서 sub로 이동할 땐 실제 sub페이지로 이동
사용 방법
.
을 사용해서 폴더구조를 생성하는데
다음과 같은 방식으로 폴더구조를 생성한다.
(.)이 포함된 폴더 아래는 가로채고 싶은 페이지의 컴포넌트와 같은 폴더구조로 생성해주면 된다.
예시를 돕기위해 영상을 첨부해본다.
URL에 직접 경로를 입력해서 이동하는 경우
인터셉팅 라우팅 적용
URL을 확인해 보면 둘다 URL이 변경됐지만 보여지는 ui가 다른걸 확인 할 수 있다.
공식 문서에서 언급한 것처럼, 인터셉팅 라우팅은 주로 모달 형식으로 페이지를 표시할 때 효과적이다.
이 방식은 특정 콘텐츠를 현재 페이지 위에 오버레이로 렌더링할 수 있게 해주며, 사용자 경험을 향상시킬 수 있다.
예를 들어 사용자가 피드에서 사진을 클릭했을 때, 사진을 모달로 띄워서 보여주는 방식이 이에 해당한다.
이 경우, URL은 /photo/123이지만 페이지는 모달 형태로 오버레이되어 보여진다.
사용자는 여전히 피드 페이지를 보고 있으며, 사진을 모달로 열어볼 수 있다.
또한, 병렬 라우팅(parallel routing)과 결합하면 더욱 강력한 효과를 볼 수 있다.
병렬 라우팅을 사용하면 여러 페이지를 동시에 렌더링할 수 있기 때문에
예를 들어 대시보드에서 사이드바와 메인 콘텐츠를 병렬로 렌더링하면서도, 모달을 사용하여 상세 정보를 오버레이로 띄우는 것이 가능하다.
병렬 라우팅, 인터셉팅 라우팅에 대해 알아보면서 이런 생각을 했다.
"굳이 사용안해도 비슷하게 동작시킬 수 있는거 아닌가?"
왜 사용하는가에 대해서 고민해봤는데 Next.js에서 그냥 만들어둔게 아니구나... 라는 생각을 했다.
병렬 라우팅
한페이지에서 보여질 컴포넌트를 독립적으로 처리함으로써 개발과정에서 복합적인 case를 생각할 필요를 줄여줄 수 있다고 생각한다.
또한 성능면에서도 병렬로 api를 다루기에 UX DX향상에 도움을 줄 수 있는 도구 아닌가 생각한다.
인터셉팅 라우팅
병렬 라우팅에 모달을 적용해서 사용한다면 굉장히 효과적일 것같다.
사용자가 특정 행동을 했을 때 새로운 페이지로 전환하는 대신, 현재 페이지에서 오버레이로 추가 정보를 표시할 수 있다. UX면에서 더 좋은 경험일 것이다.
또한 확장성 면에서 효과적이라고 생각한다.
개발과정중에 이러한 고민을 하게되는데
해당 페이지는 다른곳에서 모달로 보여주면 좋을것 같은데?
혹은 , 해당 모달은 나중에 기능이 커지면 페이지로 전환할 여지가 있지 않을까?
위의 도구들을 사용한다면 페이지와 모달의 경계를 생각하지 않고 작업해도 변형하기 좋은 형태기 때문에 DX향상에 도움이 되지않을까 싶다.