병렬(parallel) 라우팅은 하나의 레이아웃에 하나 이상의 페이지를 동시에 혹은 조건부로 렌더링 할 수 있는 방법입니다.
대시보드, SNS의 피드 같은 굉장히 다이나믹한 섹션이 있는 앱을 위해 병렬 라우팅은 복잡한 라우팅 패턴을 구현할 수 있게 도와줍니다.
예를 들어 team 페이지와 analytics 페이지를 동시에 렌더링 할 수 있습니다.
병렬 라우팅은 각각의 라우트가 독립적으로 스트리밍 되기 때문에 독립적인 에러와 로딩 스테이트를 정의할 수 있도록 합니다.
병렬 라우팅은 회원 인증 스테이트 같은 특정 조건에 따라 결정 되는 슬롯을 조건부로 렌더링 할 수 있도록 도와줍니다.
이는 동일 URL에서 완벽하게 분리 된 코드를 사용할 수 있도록 합니다.
병렬 라우팅은 이름 있는 슬롯(names slots) 으로 만들 수 있습니다.
슬롯은 @folder
컨벤션으로 정의 되고 동일 레벨 레이아웃에 프롭으로 전달 됩니다.
슬롯은 라우트 세그먼트가 아니며 URL 구조에 영향을 미치지 않습니다.
/@team/members
파일 패스는/members
로 접근 가능합니다.
예를 들어 다음 정의 된 폴더 구조는 @analytics
, @team
라는 2개의 슬롯을 가지고 있습니다.
위의 폴더 구조는 app/layout.js
컴포넌트는 @analytics
와 @team
슬롯을 프롭으로 접근할 수 있고 children
프롭과 더불어 병렬 렌더링을 처리할 수 있습니다.
export default function Layout(props: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{props.children}
{props.team}
{props.analytics}
</>
)
}
참고 사항:
children
프롭은 암묵적 슬롯으로 폴더에 매핑 처리할 필요가 없습니다. 다시 말해app/page.js
컴포넌트는app/@children/page.js
와 동일합니다.
기본적으로 슬롯에서 렌더링 되는 컨텐츠는 현재 URL과 매치 됩니다.
매치 되지 않는 슬롯의 경우 Next.js가 렌더링 한 컨텐츠는 라우팅 테크닉과 폴더 구조에 따라 달라질 수 있습니다.
default.js
default.js
컴포넌트를 정의하여 Next.js가 현재 URL을 기반하여 슬롯의 활성화 상태를 처리하지 못하는 경우 fallback 컴포넌트로 사용할 수 있습니다.
다음의 폴더 구조를 생각해 봅시다.
@team
슬롯은 settings
디렉토리를 가지고 있지만 @analytics
는 가지고 있지 않습니다.
루트 /
에서 /settings
로 이동한다면 렌더링 된 컨텐츠는 네비게이션 타입과 default.js
파일에 따라 달라지게 됩니다.
@analytics/default.js 가 존재하는 경우 | @analytics/default.js 가 존재하지 않는 경우 | |
---|---|---|
소프트 네비게이션 | @team/settings/page.js 그리고 @analytics/page.js | @team/settings/page.js 그리고 @analytics/page.js |
하드 네비게이션 | @team/settings/page.js 그리고 @analytics/default.js | 404 |
소프트 네비게이션에선 Next.js는 현재 URL과 맞지 않더라도 슬롯의 이전 활성 스테이트를 렌더링 합니다.
하드 네비게이션에선 네비게이션 발생시 전체 페이지 로딩이 발생합니다.
Next.js는 먼저 매치 되지 않는 슬롯의 defualt.js
컴포넌트를 렌더링 합니다.
만약 존재하지 않는다면 404 컴포넌트를 렌더링 합니다.
매치 되지 않는 요소에 404 컴포넌트를 배치하는 건 병렬 렌더링 되지 않아야 할 라우트를 실수로 렌더링 하지 않도록 도와줍니다.
useSelectedLayoutSegment(s)
useSelectedLayoutSegment
와 useSelectedLayoutSegments
모두 해당 슬롯 내에서 활성 라우트 세그먼트가 무엇인지 알 수 있는 parallelRoutesKey
값을 받습니다.
'use client'
import { useSelectedLayoutSegment } from 'next/navigation'
export default async function Layout(props: {
//...
auth: React.ReactNode
}) {
const loginSegments = useSelectedLayoutSegment('auth')
// ...
}
유저가 @auth/login
혹은 주소창에 /login
으로 접근하면 loginSegments
값은 "login"
스트링 값으로 변하게 됩니다.
병렬 라우팅은 모달을 렌더링 하는데 사용 될 수 있습니다.
@auth
슬롯은 <Modal>
컴포넌트를 렌더링 하는데, 이 컴포넌트는 /login
같은 매칭 되는 라우트에 네비게이션 할 때 보여집니다.
app/layout.tsx
export default async function Layout(props: {
// ...
auth: React.ReactNode
}) {
return (
<>
{/* ... */}
{props.auth}
</>
)
}
app/@auth/login/page.tsx
import { Modal } from 'components/modal'
export default function Login() {
return (
<Modal>
<h1>Login</h1>
{/* ... */}
</Modal>
)
}
모달의 컨텐츠가 비활성화시에 렌더링 되지 않기 위해 default.js
컴포넌트를 만들어 null
을 리턴하도록 합니다.
app/@auth/default.tsx
export default function Default() {
return null
}
<Link href="/login">
를 사용하여 클라이언트 네비게이션을 통해 모달이 활성화 된다면 Link
컴포넌트를 사용하거나 router.back()
을 호출하여 모달을 취소할 수 있습니다.
app/@auth/login/page.tsx
'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'
export default async function Login() {
const router = useRouter()
return (
<Modal>
<span onClick={() => router.back()}>Close modal</span>
<h1>Login</h1>
...
</Modal>
)
}
모달에 대한 더 자세한 정보는 Intercepting Routes 페이지에서 소개 합니다.
다른 곳에서도 모달을 취소하고 싶다면 catch-all 라우트를 사용하면 됩니다.
app/@auth/[...catchAll]/page.tsx
export default function CatchAll() {
return null
}
catch-all 라우트는
defualt.js
보다 우선 순위를 가집니다.
병렬 라우팅은 조건부 라우팅을 구현하는데 사용 될 수 있습니다.
예를 들어 회원 인증 스테이트에 따라 @dashboard
혹은 @login
라우트를 렌더링 할 수 있습니다.
app/layout.tsx
import { getUser } from '@/lib/auth'
export default function Layout({
dashboard,
login,
}: {
dashboard: React.ReactNode
login: React.ReactNode
}) {
const isLoggedIn = getUser()
return isLoggedIn ? dashboard : login
}