📎

Next.js App Router의 Layout vs Template

notion image
Next.js App Router의 Layout과 Template은 여러 페이지에서 공통으로 사용되는 UI 구조를 정의합니다.
Layout은 페이지 간 이동 시에도 상태가 유지되고 리렌더링되지 않아 네비게이션 바, 헤더, 푸터 같은 지속적인 UI 요소에 적합합니다.
반면 Template은 페이지 전환마다 새로운 인스턴스가 생성되어 상태가 초기화되고 라우트 파라미터에 접근할 수 있어 페이지 전환 애니메이션이나 페이지별 고유한 동작이 필요한 경우에 사용됩니다.
자세히 알아보겠습니다.

1. layout.tsx의 특징

  • 모든 자식 컴포넌트를 감싸는 UI 레이아웃을 정의합니다
  • 페이지 간 이동 시 상태가 유지(preserved)됩니다
  • 페이지를 이동해도 리렌더링되지 않아 성능이 최적화됩니다
  • 모든 라우트에 필요한 공통 UI 요소를 관리하는 데 적합합니다

코드 예시

// app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="ko"> <body> <header>내비게이션 바</header> <main>{children}</main> <footer>푸터</footer> </body> </html> ); }

layout.tsx의 한계

  • 페이지마다 다른 동작이 필요할 때 한계가 있습니다
  • 페이지 전환 애니메이션을 구현하기 어렵습니다
layout.tsx에서 UI를 변경해야 하는 경우 추가적인 처리가 필요합니다. 이러한 상황에서 template.tsx가 유용할 수 있습니다.

2. template.tsx의 특징

  • 페이지 전환 시 새로운 인스턴스가 생성됩니다
  • 상태가 유지되지 않고 매번 새롭게 초기화됩니다
  • 페이지 전환 애니메이션을 구현하기에 적합합니다

코드 예시

// app/dashboard/template.tsx import { motion } from 'framer-motion'; export default function DashboardTemplate({ children }: { children: React.ReactNode }) { // 이 컴포넌트는 페이지 전환마다 새로 생성됩니다 return ( <motion.div initial={{ opacity: 0, y: 20 }} // 초기 상태 animate={{ opacity: 1, y: 0 }} // 애니메이션 상태 exit={{ opacity: 0, y: -20 }} // 종료 상태 transition={{ duration: 0.3 }} // 전환 시간 > {children} </motion.div> ); }

3. Layout과 Template 중 어떤 것을 선택해야 할까?

Layout을 사용해야 하는 경우

  • 여러 페이지에서 공통으로 사용되는 네비게이션, 사이드바, 헤더, 푸터 등의 UI 요소가 필요한 경우
  • 페이지 전환 시 상태를 유지해야 하는 경우 (예: 다크모드 설정, 로그인 상태, 사용자 세션)
  • 성능 최적화가 중요한 경우 (불필요한 리렌더링 방지)
  • 주요 로직이나 데이터 페칭이 모든 하위 페이지에서 필요한 경우

Template을 사용해야 하는 경우

  • 페이지 전환 애니메이션이 필요한 경우
  • 페이지별로 다른 라우트 파라미터에 접근해야 하는 경우
  • 페이지 방문마다 새로운 상태나 효과가 필요한 경우
  • 페이지 전환 시 항상 초기화되어야 하는 폼이나 상태가 있는 경우
  • 랜덤 데이터나 새로 추출된 데이터를 하위 페이지마다 보여주어야 하는 경우
 

app/template.ts에 구현한 경우

/page1 ↔ /page2 페이지 전환시
notion image

app/layout.ts에 구현한 경우

/page1 ↔ /page2 페이지 전환시
notion image

app/[id]/layout.ts에 구현한 경우

/page1 ↔ /page2 페이지 전환시
notion image

4. Layout과 Template 함께 사용하기

Layout과 Template은 함께 사용할 수 있으며, 실제로는 그런 경우가 많습니다. 다음은 일반적인 파일 구조 예시입니다.
app/ ├── layout.tsx (전체 앱 레이아웃) ├── dashboard/ │ ├── layout.tsx (대시보드 섹션 레이아웃) │ ├── template.tsx (페이지 전환 애니메이션 적용) │ ├── page.tsx (대시보드 메인 페이지) │ └── [id]/ │ └── page.tsx (상세 페이지) └── settings/ ├── layout.tsx (설정 섹션 레이아웃) └── page.tsx (설정 페이지)
  • 전체 앱에 가장 높은 수준의 공통 레이아웃이 적용됩니다 (app/layout.tsx)
  • 각 섹션별로 특화된 레이아웃이 있습니다 (dashboard/layout.tsx, settings/layout.tsx)
  • 대시보드 섹션에서는 페이지 전환 애니메이션을 위해 template.tsx를 사용합니다
이 구조에서 layout.tsx는 전체 사이트와 포함된 섹션의 공통 UI 요소를 처리하며, 대시보드 섹션에서만 template.tsx를 사용하여 페이지 전환 애니메이션 특정 동작을 구현합니다. 이렇게 함으로써 전체 사이트의 성능은 최적화하면서도 필요한 부분에만 애니메이션을 적용할 수 있습니다.
 

6. Pages Router에서 Template처럼 구현하기

App Router가 아닌 Pages Router를 사용하는 프로젝트에서도 Template과 비슷한 효과를 구현할 수 있습니다. 핵심은 컴포넌트에 key 속성을 활용하는 것입니다.
React에서 key가 변경되면 해당 컴포넌트는 재생성됩니다. 이를 활용하여 URL 파라미터가 변경될 때마다 컴포넌트를 새로 생성하도록 할 수 있습니다.

코드 예시

// pages/[id]/index.tsx import { useRouter } from 'next/router'; export default function HomePage() { const router = useRouter(); const { id } = router.query as { id: string }; return ( <div key={id}> {/* 페이지 콘텐츠 */} </div> );
  • URL 파라미터(id)가 변경될 때마다 PageWrapper 컴포넌트는 새로운 인스턴스로 재생성됩니다
  • 각 페이지마다 애니메이션이 다시 실행됩니다
  • 상태가 초기화되므로 Template과 유사한 효과를 얻을 수 있습니다
이러한 접근 방식은 Pages Router를 사용하는 기존 프로젝트에서도 App Router의 template.tsx와 유사한 기능을 구현할 수 있게 해줍니다.

7. Next.js에서 부분적 레이아웃 구현하기

이는 앱의 특정 부분(예: 인증 페이지)에 완전히 다른 레이아웃이 필요할 때 유용합니다.

1. App Router에서 부분적 레이아웃 구현하기

App Router에서는 라우트 그룹(Route Groups)을 사용하여 부분적 레이아웃을 쉽게 구현할 수 있습니다.

디렉토리 구조 예시

app/ ├── layout.tsx # 루트 레이아웃 ├── page.tsx # 홈페이지 ├── about/ # /about 경로 │ └── page.tsx └── (auth)/ # 라우트 그룹 - URL에 포함되지 않음 ├── layout.tsx # 새로운 레이아웃 - 상위 레이아웃을 상속받지 않음 ├── login/ # /login 경로 │ └── page.tsx └── register/ # /register 경로 └── page.tsx

핵심 구현 방법

  1. 괄호(())로 감싼 폴더명으로 라우트 그룹 생성
  1. 라우트 그룹 내부에 새로운 layout.tsx 파일 작성
  1. 이 layout.tsx는 상위 레이아웃을 상속받지 않고 독립적으로 작동

주요 특징

  • URL 영향 없음: 괄호로 감싼 폴더명((auth))은 실제 URL 경로에 영향을 주지 않습니다.
  • 독립적인 레이아웃: (auth) 폴더 내의 layout.tsx는 루트 레이아웃을 상속받지 않고 완전히 새로운 레이아웃을 정의합니다.
  • html/body 태그 중복: 각 레이아웃에서 html 및 body 태그를 정의하면 Next.js가 자동으로 처리합니다.

2. Pages Router에서 getLayout 패턴 사용하기

Pages Router에서는 getLayout 함수를 사용하여 페이지별로 다른 레이아웃을 적용할 수 있습니다.

구현 단계

  1. 페이지 컴포넌트에 getLayout 속성 추가
  1. _app.tsx에서 페이지의 getLayout 함수 사용
  1. 페이지별로 다른 레이아웃 함수 구현

코드 예시

커스텀 App 컴포넌트 (_app.tsx)

import type { ReactElement, ReactNode } from 'react' import type { NextPage } from 'next' import type { AppProps } from 'next/app' // 레이아웃을 포함한 페이지 타입 정의 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & { getLayout?: (page: ReactElement) => ReactNode } type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout } export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { // 페이지에 getLayout이 있으면 사용, 없으면 페이지를 그대로 반환 const getLayout = Component.getLayout ?? ((page) => page) return getLayout(<Component {...pageProps} />) }

기본 레이아웃 컴포넌트 (components/layouts/MainLayout.tsx)

import React, { ReactElement } from 'react' import Navbar from '../Navbar' import Footer from '../Footer' type MainLayoutProps = { children: ReactElement } export default function MainLayout({ children }: MainLayoutProps) { return ( <div className="main-layout"> <Navbar /> <main>{children}</main> <Footer /> </div> ) }

인증 레이아웃 컴포넌트 (components/layouts/AuthLayout.tsx)

import React, { ReactElement } from 'react' type AuthLayoutProps = { children: ReactElement } export default function AuthLayout({ children }: AuthLayoutProps) { return ( <div className="auth-layout"> <div className="auth-container"> <h1>인증 페이지</h1> {children} </div> </div> ) }

홈페이지 (pages/index.tsx) - 기본 레이아웃 사용

import MainLayout from '../components/layouts/MainLayout' import { ReactElement } from 'react' import { NextPageWithLayout } from './_app' const Home: NextPageWithLayout = () => { return ( <div> <h1>홈페이지</h1> <p>메인 레이아웃을 사용합니다.</p> </div> ) } // getLayout 함수 정의 Home.getLayout = function getLayout(page: ReactElement) { return <MainLayout>{page}</MainLayout> } export default Home

로그인 페이지 (pages/login.tsx) - 인증 레이아웃 사용

import AuthLayout from '../components/layouts/AuthLayout' import { ReactElement } from 'react' import { NextPageWithLayout } from './_app' const Login: NextPageWithLayout = () => { return ( <div> <h2>로그인</h2> {/* 로그인 폼 */} </div> ) } // 다른 레이아웃 사용 Login.getLayout = function getLayout(page: ReactElement) { return <AuthLayout>{page}</AuthLayout> } export default Login

주요 특징

  • 유연성: 각 페이지마다 완전히 다른 레이아웃을 사용할 수 있습니다.
  • 중첩 레이아웃: 레이아웃을 중첩해서 사용할 수도 있습니다.
  • 타입 안전성: TypeScript를 사용하면 타입 안전성을 보장할 수 있습니다.
  • 레이아웃 상태 유지: Next.js 페이지 전환 시에도 레이아웃 컴포넌트의 상태가 유지됩니다.
 

결론

Layout: React의 일반적인 트리 구조를 따르는 컴포넌트로, 자신에게 직접적인 변화가 없으면 자식 컴포넌트만 교체되고 상태가 유지
Template: URL이 변경될 때마다 완전히 새로운 인스턴스로 교체되어 모든 상태가 초기화되고 애니메이션이 재실행되므로, 지속성이 필요한 UI 요소는 레이아웃을, 매번 새롭게 시작해야 하는 요소는 템플릿을 사용하는 것이 효과적