레이아웃 및 템플릿
특별한 파일인 layout.js와 template.js를 사용하면 라우트 간에 공유되는 UI를 만들 수 있습니다. 이 페이지에서는 이러한 특별한 파일을 사용하는 방법과 시기를 안내합니다.
레이아웃
레이아웃은 여러 라우트 간에 공유되는 UI입니다. 네비게이션 시 레이아웃은 상태를 유지하고, 대화형 상태를 유지하며, 다시 렌더링되지 않습니다. 레이아웃은 중첩될 수도 있습니다.
layout.js
파일에서 React 컴포넌트를 기본 내보내기하여 레이아웃을 정의할 수 있습니다. 이 컴포넌트는 렌더링 중에 하위 레이아웃(있는 경우) 또는 페이지로 채워질 children
prop을 받아야 합니다.
예를 들어, 이 레이아웃은 /dashboard
와 /dashboard/settings
페이지에서 공유됩니다:

export default function DashboardLayout({
children, // 페이지 또는 중첩된 레이아웃이 될 것입니다
}: {
children: React.ReactNode;
}) {
return (
<section>
{/* 여기에 공유 UI를 포함하세요. 예: 헤더 또는 사이드바 */}
<nav></nav>
{children}
</section>
);
}
export default function DashboardLayout({
children, // 페이지 또는 중첩된 레이아웃이 될 것입니다
}) {
return (
<section>
{/* 여기에 공유 UI를 포함하세요. 예: 헤더 또는 사이드바 */}
<nav></nav>
{children}
</section>
);
}
루트 레이아웃 (필수)
루트 레이아웃은 app
디렉토리의 최상위 레벨에 정의되며 모든 라우트에 적용됩니다. 이 레이아웃은 필수이며 html
및 body
태그를 포함해야 하며, 서버에서 반환되는 초기 HTML을 수정할 수 있습니다.
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{/* 레이아웃 UI */}
<main>{children}</main>
</body>
</html>
);
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{/* 레이아웃 UI */}
<main>{children}</main>
</body>
</html>
);
}
레이아웃 중첩
기본적으로 폴더 계층 구조의 레이아웃은 중첩되어 있어 children
prop을 통해 하위 레이아웃을 감싸게 됩니다. 특정 라우트 세그먼트(폴더) 내에 layout.js
를 추가하여 레이아웃을 중첩할 수 있습니다.
예를 들어, /dashboard
라우트에 대한 레이아웃을 만들려면 dashboard
폴더 안에 새로운 layout.js
파일을 추가하세요:

export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return <section>{children}</section>;
}
export default function DashboardLayout({ children }) {
return <section>{children}</section>;
}
위의 두 레이아웃을 결합하면, 루트 레이아웃(app/layout.js
)이 대시보드 레이아웃(app/dashboard/layout.js
)을 감싸고, 이는 다시 app/dashboard/*
내의 라우트 세그먼트를 감싸게 됩니다.
두 레이아웃은 다음과 같이 중첩됩니다:

알아두면 좋은 점:
- 레이아웃에는
.js
,.jsx
, 또는.tsx
파일 확장자를 사용할 수 있습니다.- 루트 레이아웃만
<html>
및<body>
태그를 포함할 수 있습니다.- 같은 폴더에
layout.js
와page.js
파일이 정의되어 있으면 레이아웃이 페이지를 감싸게 됩니다.- 레이아웃은 기본적으로 서버 컴포넌트이지만 클라이언트 컴포넌트로 설정할 수 있습니다.
- 레이아웃에서 데이터를 가져올 수 있습니다. 자세한 정보는 데이터 가져오기 섹션을 참조하세요.
- 부모 레이아웃과 그 자식 간에 데이터를 전달하는 것은 불가능합니다. 하지만 같은 데이터를 라우트에서 두 번 이상 가져올 수 있으며, React는 성능에 영향을 주지 않고 자동으로 요청을 중복 제거합니다.
- 레이아웃은
pathname
에 액세스할 수 없습니다 (자세히 알아보기). 그러나 가져온 클라이언트 컴포넌트는usePathname
훅을 사용하여 pathname에 접근할 수 있습니다.- 레이아웃은 그 아래의 라우트 세그먼트에 접근할 수 없습니다. 모든 라우트 세그먼트에 접근하려면 클라이언트 컴포넌트에서
useSelectedLayoutSegment
또는useSelectedLayoutSegments
를 사용할 수 있습니다.- 라우트 그룹을 사용하여 특정 라우트 세그먼트를 공유 레이아웃에 포함하거나 제외할 수 있습니다.
- 라우트 그룹을 사용하여 여러 루트 레이아웃을 만들 수 있습니다. 여기에서 예제를 확인하세요.
pages
디렉토리에서 마이그레이션: 루트 레이아웃은_app.js
와_document.js
파일을 대체합니다. 마이그레이션 가이드를 확인하세요.
템플릿
템플릿은 자식 레이아웃이나 페이지를 감싼다는 점에서 레이아웃과 유사합니다. 하지만 레이아웃과 달리 템플릿은 라우트 간에 유지되지 않고 네비게이션 시 각 자식에 대해 새 인스턴스를 생성합니다. 이는 사용자가 템플릿을 공유하는 라우트 간을 이동할 때 자식의 새로운 인스턴스가 마운트되고, DOM 요소가 다시 생성되며, 클라이언트 컴포넌트의 상태가 유지되지 않고, 효과가 다시 동기화된다는 것을 의미합니다.
이러한 특정 동작이 필요한 경우가 있을 수 있으며, 이때 템플릿이 레이아웃보다 더 적합한 옵션이 될 수 있습니다. 예를 들어:
- 네비게이션 시
useEffect
를 다시 동기화해야 할 때. - 네비게이션 시 하위 클라이언트 컴포넌트의 상태를 재설정해야 할 때.
템플릿은 template.js
파일에서 기본 React 컴포넌트를 내보내어 정의할 수 있습니다. 이 컴포넌트는 children
prop을 받아야 합니다.

export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>;
}
export default function Template({ children }) {
return <div>{children}</div>;
}
중첩 측면에서, template.js
는 레이아웃과 그 자식 사이에 렌더링됩니다. 다음은 간단한 출력 예시입니다:
<Layout>
{/* 템플릿에 고유한 키가 주어집니다. */}
<Template key={routeParam}>{children}</Template>
</Layout>
예시
메타데이터
메타데이터 API를 사용하여 title
과 meta
와 같은 <head>
HTML 요소를 수정할 수 있습니다.
메타데이터는 layout.js
또는 page.js
파일에서 metadata
객체 또는 generateMetadata
함수를 내보내어 정의할 수 있습니다.
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Next.js",
};
export default function Page() {
return "...";
}
export const metadata = {
title: "Next.js",
};
export default function Page() {
return "...";
}
알아두면 좋은 점: 루트 레이아웃에
<title>
이나<meta>
와 같은<head>
태그를 수동으로 추가해서는 안 됩니다. 대신 스트리밍과<head>
요소의 중복 제거와 같은 고급 요구 사항을 자동으로 처리하는 메타데이터 API를 사용하세요.
사용 가능한 메타데이터 옵션에 대해 자세히 알아보려면 API 참조를 확인하세요.
활성 내비게이션 링크
usePathname() 훅을 사용하여 내비게이션 링크가 활성 상태인지 확인할 수 있습니다.
usePathname()
은 클라이언트 훅이므로, 내비게이션 링크를 클라이언트 컴포넌트로 추출하여 레이아웃이나 템플릿으로 가져와야 합니다:
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
export function NavLinks() {
const pathname = usePathname();
return (
<nav>
<Link className={`link ${pathname === "/" ? "active" : ""}`} href="/">
홈
</Link>
<Link
className={`link ${pathname === "/about" ? "active" : ""}`}
href="/about"
>
소개
</Link>
</nav>
);
}
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
export function Links() {
const pathname = usePathname();
return (
<nav>
<Link className={`link ${pathname === "/" ? "active" : ""}`} href="/">
홈
</Link>
<Link
className={`link ${pathname === "/about" ? "active" : ""}`}
href="/about"
>
소개
</Link>
</nav>
);
}
import { NavLinks } from "@/app/ui/nav-links";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
);
}
import { NavLinks } from "@/app/ui/nav-links";
export default function Layout({ children }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
);
}