개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Next.js Layout — 공통 UI를 감싸는 레이아웃 컴포넌트

2025년 5월 10일
Next.jsLayoutRootLayoutNested Layouttemplate.tsx

Next.js App Router에서 Layout은 여러 페이지가 공유하는 UI를 한 곳에 정의하는 컴포넌트다. 페이지 이동 시 Layout은 언마운트되지 않고 상태를 유지하기 때문에, 내비게이션이나 사이드바 같은 공통 요소를 배치하기에 적합하다. layout.tsx 파일을 폴더에 두는 것만으로 해당 경로 하위 전체에 자동으로 적용된다.

Root Layout 필수 구조

app/layout.tsx는 애플리케이션 전체를 감싸는 Root Layout으로, 반드시 존재해야 한다. <html>과 <body> 태그를 이 파일에서 정의한다.

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ko">
      <body>
        <header>공통 헤더</header>
        {children}
        <footer>공통 푸터</footer>
      </body>
    </html>
  )
}

Root Layout: app/ 디렉터리 최상단의 layout.tsx. 삭제하거나 <html>·<body> 태그를 생략하면 빌드 오류가 발생한다.

Nested Layout 조합

폴더마다 layout.tsx를 추가해 레이아웃을 중첩할 수 있다. 하위 Layout은 상위 Layout 안에 자동으로 중첩된다.

app/
├── layout.tsx          ← Root Layout (헤더, 푸터)
├── dashboard/
│   ├── layout.tsx      ← Dashboard Layout (사이드바)
│   ├── page.tsx
│   └── settings/
│       └── page.tsx    ← 두 Layout 모두 적용
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <nav>사이드바</nav>
      <main>{children}</main>
    </div>
  )
}

/dashboard/settings에 접근하면 Root Layout → Dashboard Layout → 페이지 순으로 렌더링된다.

template.tsx와의 차이

**template.tsx**는 layout.tsx와 동일한 위치에 둘 수 있지만, 동작 방식이 다르다.

항목layout.tsxtemplate.tsx
페이지 이동 시언마운트 안 됨 (상태 유지)매번 새 인스턴스 생성
사용 사례내비게이션, 전역 상태페이지 진입 애니메이션, 매번 초기화 필요한 UI
useEffect 실행첫 마운트 한 번이동마다 재실행

template.tsx: 경로 이동마다 새 컴포넌트 인스턴스를 생성한다. 진입 애니메이션처럼 매 방문마다 트리거가 필요한 경우 사용한다.

레이아웃 데이터 패칭

Layout은 Server Component이므로 async/await로 직접 데이터를 가져올 수 있다.

// app/dashboard/layout.tsx
async function getUser() {
  const res = await fetch('https://api.example.com/me', {
    cache: 'no-store',
  })
  return res.json()
}

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const user = await getUser()

  return (
    <div className="flex">
      <nav>
        <span>{user.name}</span>
      </nav>
      <main>{children}</main>
    </div>
  )
}

Layout에서 패칭한 데이터는 하위 페이지로 props를 통해 전달할 수 없다. 공유가 필요하면 React Context나 서버 캐시(cache)를 활용한다.

공통 내비게이션 배치

내비게이션처럼 현재 경로에 반응해야 하는 컴포넌트는 Client Component로 분리한 뒤 Layout에서 사용한다.

// components/Nav.tsx
'use client'
import { usePathname } from 'next/navigation'

export function Nav() {
  const pathname = usePathname()
  return (
    <nav>
      <a href="/" style={{ fontWeight: pathname === '/' ? 'bold' : 'normal' }}>홈</a>
      <a href="/dashboard" style={{ fontWeight: pathname.startsWith('/dashboard') ? 'bold' : 'normal' }}>대시보드</a>
    </nav>
  )
}
// app/layout.tsx
import { Nav } from '@/components/Nav'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <Nav />
        {children}
      </body>
    </html>
  )
}

usePathname은 Client Component에서만 동작하므로, Layout 자체를 Client Component로 만들지 않고 내비게이션 부분만 분리하는 것이 올바른 패턴이다.

← 이전 글Next.js Routing — 파일 경로가 URL이 되는 방식
다음 글 →Next.js Server Components — 서버에서만 실행되는 컴포넌트