App Router 구조 이해하기
이전 절에서 우리는 App Router가 파일 시스템 기반 라우팅을 사용하며, 폴더가 라우트 세그먼트를 정의하고 page.tsx
파일이 실제 UI를 렌더링한다는 것을 배웠습니다. 또한 동적 라우트를 통해 가변적인 URL 경로를 처리하는 방법도 살펴보았습니다. 이번 절에서는 이 개념들을 더욱 확장하여 중첩 라우트(Nested Routes) 를 구현하는 방법을 심도 있게 다루어 보겠습니다.
중첩 라우트는 UI의 특정 부분이 다른 UI 안에 포함되는 계층적인 구조를 만들 때 사용됩니다. 이는 웹 애플리케이션에서 매우 흔한 패턴으로, 예를 들어 대시보드 내의 여러 서브 페이지들이 공통된 사이드바나 헤더를 공유하는 경우가 대표적입니다.
중첩 라우트의 기본 원리
App Router에서 중첩 라우트는 폴더 구조와 layout.tsx
파일의 조합으로 구현됩니다.
- 폴더의 중첩:
app
디렉터리 내부에 폴더를 중첩하여 생성하는 것 자체가 URL 경로의 중첩을 의미합니다. 예를 들어,app/dashboard/settings
는/dashboard/settings
경로를 나타냅니다. - 레이아웃의 중첩: 각 폴더에
layout.tsx
파일을 정의하면, 해당 레이아웃은 그 폴더와 모든 하위 폴더의page.tsx
또는 다른layout.tsx
를 감싸게 됩니다. 이때, 상위 폴더의 레이아웃이 하위 폴더의 레이아웃을 감싸는 형태로 레이아웃이 중첩됩니다.
이러한 중첩 구조는 다음과 같은 이점을 제공합니다.
- 코드 재사용성: 공통 UI 요소를 레이아웃에 정의하여 여러 페이지에서 재사용할 수 있습니다.
- 일관된 UI/UX: 웹사이트 전체 또는 특정 섹션의 디자인과 레이아웃을 일관되게 유지할 수 있습니다.
- 성능 최적화: 레이아웃 컴포넌트는 라우트 이동 시 상태를 유지하므로, 전체 페이지를 다시 렌더링할 필요 없이 변경된 부분만 효율적으로 업데이트할 수 있습니다.
중첩 라우트 구현 실습: 대시보드 구조 만들기
이전 장에서 만들었던 대시보드 예시를 확장하여, 중첩 라우트와 레이아웃의 관계를 명확히 이해해 봅시다.
목표 구조
/dashboard (대시보드 홈)
/dashboard/overview (대시보드 개요)
/dashboard/analytics (대시보드 분석)
/dashboard/settings (대시보드 설정)
이 모든 페이지들은 공통적으로 대시보드 레이아웃(사이드바 등)을 공유하고, 이 대시보드 레이아웃은 다시 최상위 루트 레이아웃 안에 중첩될 것입니다.
-
루트 레이아웃 (
src/app/layout.tsx
) 확인/수정: 모든 페이지에 적용되는 최상위 레이아웃입니다. 이미 3장 3절에서 다음과 같이 구성했을 것입니다.src/app/layout.tsx // src/app/layout.tsx import './globals.css'; import Link from 'next/link'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ko"> <body> <header style={{ backgroundColor: '#f0f0f0', padding: '10px', borderBottom: '1px solid #ddd' }}> <nav> <Link href="/"><a>홈</a></Link> | {' '} <Link href="/about"><a>소개</a></Link> | {' '} <Link href="/dashboard"><a>대시보드</a></Link> </nav> <h1>나 혼자 Next.js</h1> </header> <main style={{ padding: '20px' }}> {children} {/* 여기에 중첩된 레이아웃 또는 페이지 콘텐츠가 들어옵니다 */} </main> <footer style={{ backgroundColor: '#e0e0e0', padding: '10px', marginTop: '20px', borderTop: '1px solid #ddd' }}> <p>© 2024 나 혼자 Next.js. All rights reserved.</p> </footer> </body> </html> ); }
-
대시보드 레이아웃 (
src/app/dashboard/layout.tsx
) 생성/수정:/dashboard
경로와 그 하위 모든 경로에 적용될 레이아웃입니다.src/app/dashboard/layout.tsx // src/app/dashboard/layout.tsx import Link from 'next/link'; export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { return ( <div style={{ display: 'flex', minHeight: 'calc(100vh - 180px)', border: '1px solid #ccc', borderRadius: '8px', overflow: 'hidden' }}> <aside style={{ width: '200px', padding: '20px', backgroundColor: '#f9f9f9', borderRight: '1px solid #eee' }}> <h2 style={{ marginBottom: '15px', color: '#333' }}>대시보드 메뉴</h2> <ul style={{ listStyle: 'none', padding: 0 }}> <li style={{ marginBottom: '10px' }}> <Link href="/dashboard"><a>대시보드 홈</a></Link> </li> <li style={{ marginBottom: '10px' }}> <Link href="/dashboard/overview"><a>개요</a></Link> </li> <li style={{ marginBottom: '10px' }}> <Link href="/dashboard/analytics"><a>분석</a></Link> </li> <li style={{ marginBottom: '10px' }}> <Link href="/dashboard/settings"><a>설정</a></Link> </li> </ul> </aside> <section style={{ flexGrow: 1, padding: '20px', backgroundColor: '#fff' }}> {children} {/* 대시보드 하위 페이지 콘텐츠가 여기에 렌더링됩니다 */} </section> </div> ); }
설명
- 이 레이아웃은
RootLayout
의children
으로 렌더링됩니다. - 자체적으로 사이드바와 메인 콘텐츠 영역을 가집니다.
children
prop은/dashboard
폴더 내의page.tsx
또는 그 하위 폴더의layout.tsx
또는page.tsx
콘텐츠를 받습니다.
- 이 레이아웃은
-
대시보드 하위 페이지 생성: 이제
dashboard
폴더 안에 각각의 페이지를 생성합니다.-
src/app/dashboard/page.tsx
(대시보드 홈)src/app/dashboard/page.tsx // src/app/dashboard/page.tsx export default function DashboardHomePage() { return ( <div> <h3>환영합니다, 대시보드 홈입니다!</h3> <p>여기에 대시보드의 주요 정보를 요약하여 표시합니다.</p> </div> ); }
-
src/app/dashboard/overview/page.tsx
:src/app/dashboard
안에overview
폴더를 만들고 그 안에page.tsx
를 생성합니다.src/app/dashboard/overview/page.tsx // src/app/dashboard/overview/page.tsx export default function DashboardOverviewPage() { return ( <div> <h3>대시보드 개요</h3> <p>이 페이지는 전체 시스템의 개요를 보여줍니다.</p> </div> ); }
-
src/app/dashboard/analytics/page.tsx
:src/app/dashboard
안에analytics
폴더를 만들고 그 안에page.tsx
를 생성합니다.src/app/dashboard/analytics/page.tsx // src/app/dashboard/analytics/page.tsx export default function DashboardAnalyticsPage() { return ( <div> <h3>대시보드 분석</h3> <p>사용자 데이터 및 트래픽 분석 정보를 제공합니다.</p> </div> ); }
-
src/app/dashboard/settings/page.tsx
:src/app/dashboard
안에settings
폴더를 만들고 그 안에page.tsx
를 생성합니다.src/app/dashboard/settings/page.tsx // src/app/dashboard/settings/page.tsx export default function DashboardSettingsPage() { return ( <div> <h3>대시보드 설정</h3> <p>계정 및 대시보드 관련 설정을 변경할 수 있습니다.</p> </div> ); }
-
실습 확인:
개발 서버(npm run dev
)를 실행한 후, 다음 URL들을 방문하며 중첩된 레이아웃이 어떻게 적용되는지 확인해 보세요.
http://localhost:3000/
(루트 레이아웃만)http://localhost:3000/about
(루트 레이아웃만)http://localhost:3000/dashboard
(루트 레이아웃 + 대시보드 레이아웃 + 대시보드 홈 페이지)http://localhost:3000/dashboard/overview
(루트 레이아웃 + 대시보드 레이아웃 + 개요 페이지)http://localhost:3000/dashboard/analytics
(루트 레이아웃 + 대시보드 레이아웃 + 분석 페이지)http://localhost:3000/dashboard/settings
(루트 레이아웃 + 대시보드 레이아웃 + 설정 페이지)
각 대시보드 관련 페이지에서 상단의 헤더와 하단의 푸터(루트 레이아웃)가 유지되면서, 왼쪽에 대시보드 메뉴(대시보드 레이아웃)가 나타나고, 오른쪽에 해당 페이지의 콘텐츠가 변경되는 것을 볼 수 있습니다.
레이아웃의 장점과 고려사항
장점
- UI 일관성: 웹사이트의 특정 섹션에 일관된 디자인과 기능을 적용하기 용이합니다.
- 성능: 레이아웃은 페이지 이동 시 리렌더링되지 않으므로, 레이아웃 내의 클라이언트 컴포넌트 상태가 유지되고, 데이터 페칭도 한 번만 발생하여 성능 이점을 얻을 수 있습니다.
- 코드 관리: 공통 UI 로직을 한 곳에 모아 관리함으로써 코드 중복을 줄이고 유지보수성을 높입니다.
고려사항
- 데이터 페칭: 레이아웃에서 데이터를 페칭할 경우, 해당 레이아웃이 적용되는 모든 하위 페이지에서 그 데이터가 필요해야 합니다. 특정 페이지에서만 필요한 데이터는 해당 페이지에서 페칭하는 것이 더 효율적일 수 있습니다.
- 클라이언트 컴포넌트 사용: 레이아웃은 기본적으로 서버 컴포넌트이므로, 상호작용이 필요한 UI(예: 클릭 이벤트, 상태 관리)는 레이아웃 내부에 클라이언트 컴포넌트를 정의하고 이를 가져와 사용해야 합니다.
- 리렌더링 범위: 레이아웃은 라우트 변경 시에도 상태를 유지하지만,
children
prop으로 전달되는 페이지 콘텐츠는 변경될 때마다 새로 렌더링됩니다. 이 점을 이해하고 컴포넌트를 설계해야 합니다.
중첩 라우트와 레이아웃은 Next.js App Router의 핵심적인 디자인 패턴입니다. 이를 통해 복잡한 웹 애플리케이션의 UI를 체계적이고 효율적으로 구성할 수 있습니다. 각 레이아웃의 역할과 중첩 방식을 명확히 이해한다면, 확장 가능하고 유지보수하기 쉬운 프로젝트를 만들 수 있을 것입니다.