레이아웃 컴포넌트 사용하기
Next.js App Router의 강력한 기능 중 하나가 레이아웃(Layouts)입니다.
레이아웃은 여러 페이지가 공유하는 UI를 한 곳에서 정의하게 해
코드 중복을 줄이고 일관된 사용자 경험을 유지하도록 돕습니다.
이번 절에서는 layout.tsx 개념을 더 깊이 이해하고
실제 프로젝트 적용 방법까지 살펴보겠습니다.
레이아웃이란 무엇인가요?
레이아웃은 특정 라우트 세그먼트와 그 하위 라우트에서 공유되는 UI를 래핑하는(감싸는) React 컴포넌트입니다. 이는 웹사이트의 헤더, 푸터, 사이드바, 내비게이션 바 등 여러 페이지에 걸쳐 동일하게 나타나는 부분들을 한 곳에서 관리할 수 있게 해줍니다.
레이아웃의 주요 특징- 공유 UI: 레이아웃 내부에 정의된 UI는 해당 레이아웃이 적용되는 모든 하위 페이지에 자동으로 포함됩니다.
- 중첩 가능: App Router에서는 레이아웃을 중첩하여 사용할 수 있습니다. 최상위 레이아웃(Root Layout)부터 특정 라우트 세그먼트에만 적용되는 하위 레이아웃까지 계층적으로 구성할 수 있습니다.
- 상태 유지: 레이아웃 컴포넌트는 라우트 이동 시에도 상태(State)를 유지합니다. 즉, 레이아웃 내부의 클라이언트 컴포넌트 상태는 페이지가 변경되어도 그대로 유지됩니다. 이는
template.tsx와 가장 큰 차이점입니다. - 기본적으로 서버 컴포넌트:
layout.tsx파일은 기본적으로 서버 컴포넌트로 동작합니다.
최상위 레이아웃 (RootLayout) 이해하기
Next.js 프로젝트를 생성하면 src/app/layout.tsx 파일이 자동으로 생성됩니다. 이 파일은 애플리케이션의 최상위 레이아웃(Root Layout) 으로, 모든 페이지에 적용되는 가장 기본적인 UI 구조를 정의합니다.
// src/app/layout.tsx
import './globals.css'; // 전역 스타일 임포트
export default function RootLayout({
children, // 필수 prop: 모든 하위 페이지 및 중첩 레이아웃이 여기에 렌더링됩니다.
}: {
children: React.ReactNode;
}) {
return (
<html lang="ko">
<body>
<header style={{ backgroundColor: '#f0f0f0', padding: '10px' }}>
<nav>
<a href="/">홈</a> | <a href="/about">소개</a> | <a href="/dashboard">대시보드</a>
</nav>
<h1>나 혼자 Next.js</h1>
</header>
{children} {/* 여기에 페이지 또는 하위 레이아웃 콘텐츠가 들어옵니다 */}
<footer style={{ backgroundColor: '#e0e0e0', padding: '10px', marginTop: '20px' }}>
<p>© 2024 나 혼자 Next.js. All rights reserved.</p>
</footer>
</body>
</html>
);
}childrenprop: 이 레이아웃 내부에 렌더링될 하위 콘텐츠(다른 레이아웃 또는 최종page.tsx파일)를 나타냅니다. 모든 레이아웃 컴포넌트는 이childrenprop을 받아야 합니다.<html>,<body>태그: 최상위 레이아웃은 반드시<html>과<body>태그를 포함해야 합니다. 이는 웹 페이지의 기본 구조를 형성합니다.- 공통 UI 추가: 예시에서는 간단한
<header>와<footer>를 추가했습니다. 이 부분은 어떤 페이지로 이동하든 항상 동일하게 표시됩니다.
실습
위 코드를 src/app/layout.tsx 파일에 붙여넣고 개발 서버(npm run dev)를 확인해 보세요. 어떤 페이지로 이동하든 상단에 헤더와 하단에 푸터가 항상 표시되는 것을 확인할 수 있습니다.
중첩 레이아웃 (Nested Layouts) 사용하기
특정 라우트 세그먼트에만 적용되는 별도의 레이아웃을 정의할 수도 있습니다. 이것이 바로 중첩 레이아웃입니다. 예를 들어, 대시보드 페이지와 그 하위 페이지들(dashboard/settings, dashboard/profile 등)에는 공통된 사이드바가 필요할 수 있습니다.
dashboard 폴더에 레이아웃 파일 추가:
src/app/dashboard 폴더 안에 layout.tsx 파일을 생성합니다.
src/app/dashboard/layout.tsx 파일 내용 작성export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section style={{ display: 'flex', border: '1px solid #ccc', padding: '10px', marginTop: '10px' }}>
<aside style={{ width: '200px', padding: '10px', backgroundColor: '#f9f9f9', borderRight: '1px solid #eee' }}>
<h2>대시보드 메뉴</h2>
<ul>
<li><a href="/dashboard">대시보드 홈</a></li>
<li><a href="/dashboard/settings">설정</a></li>
{/* 추가 메뉴 아이템 */}
</ul>
</aside>
<main style={{ flexGrow: 1, padding: '10px' }}>
{children} {/* 대시보드 하위 페이지 콘텐츠가 여기에 렌더링됩니다 */}
</main>
</section>
);
}DashboardLayout은section태그로 전체를 감싸고, 왼쪽에 사이드바(aside)와 오른쪽에 메인 콘텐츠 영역(main)을 가집니다.- 이 레이아웃 역시
childrenprop을 받아,/dashboard경로 또는 그 하위 경로의page.tsx콘텐츠가main태그 내부에 렌더링되도록 합니다.
실습
src/app/dashboard/layout.tsx 파일을 생성하고 위 코드를 붙여넣은 후, 개발 서버를 확인해 보세요.
http://localhost:3000/: 루트 레이아웃만 적용됩니다.http://localhost:3000/about: 루트 레이아웃만 적용됩니다.http://localhost:3000/dashboard: 루트 레이아웃 안에DashboardLayout이 중첩되어 적용되고, 그 안에/dashboard의page.tsx내용이 렌더링됩니다. 즉, 헤더-사이드바-대시보드 콘텐츠-푸터 순서로 보일 것입니다.http://localhost:3000/dashboard/settings: 마찬가지로 루트 레이아웃 안에DashboardLayout이 중첩되어 적용되고, 그 안에/dashboard/settings의page.tsx내용이 렌더링됩니다.
레이아웃의 데이터 페칭
레이아웃은 기본적으로 서버 컴포넌트이므로, 서버에서 데이터를 미리 가져와 UI를 구성하는 것이 가능합니다. 이는 해당 레이아웃이 적용되는 모든 페이지에서 공통적으로 필요한 데이터를 효율적으로 로드하는 데 유용합니다.
아래 데이터 페칭 예시는 http://localhost:4000 로컬 Mock API가 실행 중인 상황을 전제로 합니다.
import { Suspense } from 'react'; // 로딩 상태 관리를 위한 Suspense 임포트
// 서버 컴포넌트에서 데이터 페칭
async function getSharedData() {
// 실제 API 호출 로직
const response = await fetch('http://localhost:4000/shared-dashboard-info', {
cache: 'no-store' // 캐시 사용 여부 설정 (필요에 따라)
});
if (!response.ok) {
// 에러 처리
throw new Error('데이터를 가져오지 못했습니다.');
}
return response.json();
}
export default async function DashboardLayout({ // async 키워드 추가
children,
}: {
children: React.ReactNode;
}) {
const sharedData = await getSharedData(); // 데이터 호출 (서버에서 실행)
return (
<section style={{ display: 'flex', border: '1px solid #ccc', padding: '10px', marginTop: '10px' }}>
<aside style={{ width: '200px', padding: '10px', backgroundColor: '#f9f9f9', borderRight: '1px solid #eee' }}>
<h2>대시보드 메뉴</h2>
<p>공유 데이터: {sharedData.message}</p> {/* 가져온 데이터 사용 */}
<ul>
<li><a href="/dashboard">대시보드 홈</a></li>
<li><a href="/dashboard/settings">설정</a></li>
</ul>
</aside>
<main style={{ flexGrow: 1, padding: '10px' }}>
{/* Suspense를 사용하여 children 로딩 중 대체 UI 표시 가능 */}
<Suspense fallback={<div>페이지 로딩 중...</div>}>
{children}
</Suspense>
</main>
</section>
);
}참고: Next.js 16에서는 fetch 함수가 자동으로 요청을 캐싱하는 기능이 포함되어 있습니다. 동일한 데이터가 여러 레이아웃이나 페이지에서 요청될 때, 한 번만 네트워크 요청을 보내도록 최적화됩니다.
레이아웃 컴포넌트를 사용하면 애플리케이션의 UI 구조를 체계적으로 관리하고, 코드의 재사용성을 높이며, 일관된 사용자 경험을 제공하는 데 큰 도움이 됩니다. 중첩 레이아웃을 통해 복잡한 UI도 효율적으로 구성할 수 있습니다.
이제 레이아웃 컴포넌트의 중요성과 활용법에 대해 충분히 이해하셨으리라 생각합니다.