중첩 라우트와 레이아웃 구성
대시보드의 계층적 URL을 폴더로 구성하고 중첩 레이아웃으로 공통 헤더와 사이드바를 유지합니다.
이전 절에서는 App Router의 파일 기반 라우팅과
page.tsx가 UI를 렌더링하는 구조를 확인했습니다.
또 동적 라우트로 가변 URL을 처리하는 방법도 살펴봤습니다.
이번 절에서는 이를 확장해 중첩 라우트(Nested Routes)를 구현하는 방법을 다룹니다.
중첩 라우트는 UI의 특정 부분이 다른 UI 안에 포함되는 계층적인 구조를 만들 때 사용됩니다.
이는 웹 애플리케이션에서 매우 흔한 패턴으로, 예를 들어 대시보드 내의 여러 서브 페이지들이 공통된 사이드바나 헤더를 공유하는 경우가 대표적입니다.
중첩 라우트의 기본 원리
App Router에서 중첩 라우트는 폴더 구조와 layout.tsx 파일의 조합으로 구현됩니다.
- 폴더의 중첩:
app디렉터리의 일반 라우트 폴더를 중첩하면 URL 세그먼트도 중첩됩니다. 라우트 그룹(group)이나 슬롯@slot은 URL에 포함되지 않습니다. 예를 들어,app/dashboard/settings는/dashboard/settings경로를 나타냅니다. - 레이아웃의 중첩: 각 폴더에
layout.tsx파일을 정의하면, 해당 레이아웃은 그 폴더와 모든 하위 폴더의page.tsx또는 다른layout.tsx를 감싸게 됩니다. 이때, 상위 폴더의 레이아웃이 하위 폴더의 레이아웃을 감싸는 형태로 레이아웃이 중첩됩니다.
/dashboard/settings에서는 공통 레이아웃 안에 설정 페이지가 들어갑니다. dashboard/page.tsx는 이 하위 페이지의 조상 컴포넌트가 아닙니다.
대시보드 설정 페이지의 포함 관계
이러한 중첩 구조는 다음과 같은 이점을 제공합니다.
- 코드 재사용성: 공통 UI 요소를 레이아웃에 정의하여 여러 페이지에서 재사용할 수 있습니다.
- 일관된 UI/UX: 웹사이트 전체 또는 특정 섹션의 디자인과 레이아웃을 일관되게 유지할 수 있습니다.
- 성능 최적화: 공유 레이아웃 아래에서 클라이언트 이동을 하면 해당 레이아웃을 재사용하고 바뀐 라우트 부분을 업데이트할 수 있습니다.
중첩 라우트 구현 실습: 대시보드 구조 만들기
이전 장에서 만들었던 대시보드 예시를 확장하여, 중첩 라우트와 레이아웃의 관계를 명확히 이해해 봅시다.
목표 구조이 모든 페이지들은 공통적으로 대시보드 레이아웃(사이드바 등)을 공유하고, 이 대시보드 레이아웃은 다시 최상위 루트 레이아웃 안에 중첩될 것입니다.
루트 레이아웃 (src/app/layout.tsx) 확인/수정:
모든 페이지에 적용되는 최상위 레이아웃입니다.
이미 3장 3절에서 다음과 같이 구성했을 것입니다.
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="/">홈</Link> | {' '}
<Link href="/about">소개</Link> | {' '}
<Link href="/dashboard">대시보드</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 경로와 그 하위 모든 경로에 적용될 레이아웃입니다.
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">대시보드 홈</Link>
</li>
<li style={{ marginBottom: '10px' }}>
<Link href="/dashboard/overview">개요</Link>
</li>
<li style={{ marginBottom: '10px' }}>
<Link href="/dashboard/analytics">분석</Link>
</li>
<li style={{ marginBottom: '10px' }}>
<Link href="/dashboard/settings">설정</Link>
</li>
</ul>
</aside>
<section style={{ flexGrow: 1, padding: '20px', backgroundColor: '#fff' }}>
{children} {/* 대시보드 하위 페이지 콘텐츠가 여기에 렌더링됩니다 */}
</section>
</div>
);
}- 이 레이아웃은
RootLayout의children으로 렌더링됩니다. - 자체적으로 사이드바와 메인 콘텐츠 영역을 가집니다.
childrenprop은/dashboard폴더 내의page.tsx또는 그 하위 폴더의layout.tsx또는page.tsx콘텐츠를 받습니다.
대시보드 하위 페이지 생성:
이제 dashboard 폴더 안에 각각의 페이지를 생성합니다.
-
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 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 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 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으로 들어오는 라우트 콘텐츠는 이동에 따라 바뀝니다. 유지되는 Client Component도 자신의 상태나 context 변경으로 리렌더될 수 있습니다.