본문으로 건너뛰기

안동민 개발노트

본문 시작

템플릿 컴포넌트 활용

페이지 이동에도 상태를 유지하는 레이아웃과 매번 새로 마운트되는 template.tsx를 비교해 적용 시점을 판단합니다.

Next.js App Router에서 UI를 공유하고 재사용하는 방법으로는 layout.tsx 파일로 정의하는 레이아웃 컴포넌트가 가장 보편적입니다.

하지만 때로는 레이아웃과 유사하게 공통 UI를 제공하면서도, 페이지 이동 시 컴포넌트 인스턴스를 새로 생성하고 상태를 초기화해야 하는 경우가 있습니다.

이때 사용되는 것이 바로 템플릿 컴포넌트(template.tsx)입니다.

이 절에서는 템플릿 컴포넌트가 레이아웃 컴포넌트와 어떻게 다른지, 그리고 어떤 상황에서 템플릿 컴포넌트를 활용해야 하는지 구체적인 예시와 함께 알아보겠습니다.


템플릿 컴포넌트란 무엇인가요?

템플릿 컴포넌트app 디렉터리 내의 특정 라우트 세그먼트 폴더 안에 위치한 template.tsx 파일입니다.

레이아웃과 마찬가지로 children prop을 받아 해당 라우트의 콘텐츠를 감싸는 역할을 합니다.

레이아웃과의 주요 차이점
특징레이아웃 (layout.tsx)템플릿 (template.tsx)
인스턴스 유지라우트 변경 시 상태를 유지하며, 한 번 마운트되면 해당 라우트 세그먼트 내에서 인스턴스가 유지됩니다.라우트 변경 시 항상 새로운 인스턴스가 마운트됩니다. 기존 상태가 파괴되고 새로 시작합니다.
용도헤더, 푸터, 사이드바 등 항상 고정되어야 할 UI, 데이터 캐싱이 필요한 공통 영역페이지 전환 애니메이션, 상태 초기화, 로거 초기화 등 페이지 변경마다 초기화되어야 할 동작
적용범위자신과 모든 하위 라우트 세그먼트에 적용자신과 모든 하위 라우트 세그먼트에 적용
기본동작기본적으로 서버 컴포넌트기본적으로 서버 컴포넌트

템플릿 컴포넌트는 레이아웃과 페이지 사이에 위치하여, 레이아웃이 자식들을 감싸고, 템플릿이 다시 그 레이아웃의 자식(즉, 페이지)을 감싸는 형태로 작동합니다.

렌더링 순서: layout.tsx (루트) -> template.tsx (루트) -> layout.tsx (세그먼트) -> template.tsx (세그먼트) -> page.tsx


템플릿 컴포넌트 활용 시나리오

먼저 layout.tsxtemplate.tsx가 라우트 이동 때 어떻게 다르게 살아남는지 한 장으로 비교해 봅니다.

핵심은 공통 UI 자체가 아니라, 상태를 유지할지 새로 시작할지의 차이입니다.

layout은 남고 template은 이동마다 다시 시작한다

같은 route 전환에서도 UI를 보존할지 초기화할지에 따라 두 파일의 생명주기가 갈린다.

  1. layout
    공통 UI 보존

    sidebar와 탭처럼 경로 사이에 남을 구조

  2. layout
    상태 유지

    하위 page가 바뀌어도 같은 인스턴스를 재사용

  3. template
    경계 재마운트

    페이지 이동마다 내부 상태와 effect를 새로 시작

  4. template
    전환 효과

    진입 animation·logger·form 초기화에 적합

템플릿 컴포넌트는 다음과 같은 경우에 유용합니다.

페이지 전환 애니메이션: 페이지가 바뀔 때마다 컴포넌트를 다시 마운트해야 하는 애니메이션 라이브러리(예: Framer Motion)를 사용할 때 유용합니다.

템플릿을 사용하면 각 페이지 전환 시 애니메이션이 자연스럽게 재실행됩니다.

클라이언트 컴포넌트 상태 초기화: 특정 페이지 그룹 내에서 페이지가 변경될 때마다 특정 클라이언트 컴포넌트의 로컬 상태를 초기화하고 싶을 때 사용합니다.

예를 들어, 페이지를 이동할 때마다 폼의 입력값을 초기화하고 싶을 수 있습니다.

성능 측정 또는 로거 초기화: 각 페이지 로드 시점을 정확히 측정하거나, 페이지 뷰마다 로거를 초기화하여 새로운 로그 세션을 시작해야 할 때 유용합니다.


템플릿 컴포넌트 구현 실습

간단한 페이지 전환 효과를 통해 템플릿 컴포넌트의 작동 방식을 이해해 봅시다.

이 예제에서는 Framer Motion 라이브러리를 사용하여 페이지 전환 애니메이션을 구현합니다.

Framer Motion 설치: 먼저 프로젝트에 Framer Motion 라이브러리를 설치합니다.

npm install framer-motion
# 또는
yarn add framer-motion

src/app/dashboard/template.tsx 파일 생성: src/app/dashboard 폴더 안에 template.tsx 파일을 생성합니다.

layout.tsx
template.tsx
page.tsx
...
...

src/app/dashboard/template.tsx 내용 작성: 템플릿 컴포넌트 내에서 Framer Motion의 motion 컴포넌트를 사용하여 애니메이션을 적용합니다.

이 컴포넌트는 클라이언트 컴포넌트여야 하므로 "use client" 지시어를 추가합니다.

src/app/dashboard/template.tsx
"use client"; // 클라이언트 컴포넌트임을 명시

import { motion } from 'framer-motion';

export default function DashboardTemplate({ children }: { children: React.ReactNode }) {
  return (
    // motion.div는 Framer Motion의 애니메이션 가능한 div 컴포넌트입니다.
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5, ease: "easeOut" }}
    >
      {children} {/* 여기에 대시보드 페이지 콘텐츠가 렌더링됩니다 */}
    </motion.div>
  );
}
설명
  • "use client": Framer Motion은 클라이언트 사이드에서 작동하므로, 템플릿 컴포넌트를 클라이언트 컴포넌트로 선언해야 합니다.
  • motion.div: Framer Motion에서 제공하는 컴포넌트입니다. initialanimate prop을 사용하여 애니메이션 시작점과 끝점을 정의합니다.
  • template.tsx는 라우트가 변경될 때마다 Next.js가 새 인스턴스를 만들기 때문에, 템플릿 내부의 초기 애니메이션이 매번 다시 실행됩니다. 이 동작을 이용하면 layout.tsx처럼 유지되어야 할 UI와, 이동마다 다시 시작해야 할 효과를 분리할 수 있습니다.

src/app/dashboard/layout.tsx에 약간의 스타일 추가 (선택 사항): 애니메이션 효과를 더 잘 시각화하기 위해 DashboardLayout에 최소 높이를 지정하는 스타일을 추가할 수 있습니다.

src/app/dashboard/layout.tsx (일부)
// ...
export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const userInfo = await getUserInfo(); // 기존 예시 코드 유지
  return (
    <div style={{ display: 'flex', minHeight: 'calc(100vh - 180px)', border: '1px solid #ccc', borderRadius: '8px', overflow: 'hidden' }}>
    {/* ... */}
    </div>
  );
}

실습 확인: 개발 서버(npm run dev)를 실행한 후, http://localhost:3000/dashboard로 접속합니다.

그 다음 대시보드 메뉴의 다른 링크들(예: 개요, 분석, 설정)을 클릭해 보세요.

각 페이지로 이동할 때마다 대시보드 콘텐츠 부분이 아래에서 위로 부드럽게 나타나는 애니메이션 효과를 볼 수 있습니다.

이 애니메이션은 template.tsx가 라우트 변경마다 새로 마운트되면서 initial 상태에서 animate 상태로 전환되기 때문에 발생합니다.

만약 이 애니메이션을 layout.tsx에 직접 적용했다면, 레이아웃은 페이지 이동 시 상태를 유지하므로 애니메이션이 재실행되지 않았을 것입니다.


템플릿 컴포넌트 사용의 장단점

장점
  • 페이지 전환 애니메이션 구현 용이: 페이지 이동마다 UI가 새로 마운트되므로 애니메이션 라이브러리와 연동하기 좋습니다.
  • 클라이언트 컴포넌트 상태 초기화: 특정 라우트 그룹 내의 페이지 이동 시 클라이언트 컴포넌트의 상태를 강제로 초기화할 수 있습니다.
  • 특정 로직의 재실행 보장: 페이지 뷰마다 특정 로거를 초기화하거나 성능 측정을 재설정하는 등, 매번 실행되어야 하는 로직에 유용합니다.
단점/고려사항
  • 성능 오버헤드: 페이지 이동 시 컴포넌트 트리의 일부가 새로 마운트되므로, 레이아웃을 사용하는 것보다는 약간의 성능 오버헤드가 발생할 수 있습니다. 꼭 필요한 경우에만 사용해야 합니다.
  • 서버 컴포넌트 상태 유지 불가: 템플릿 자체는 서버 컴포넌트일 수 있지만, 그 안에 클라이언트 컴포넌트가 있다면 해당 클라이언트 컴포넌트의 상태는 페이지 이동 시 초기화됩니다. 이는 장점이자 단점이 될 수 있습니다.
  • 남용 금지: 모든 공통 UI에 템플릿을 사용하는 것은 비효율적입니다. 대부분의 공통 UI는 레이아웃 컴포넌트로 충분합니다.

아래 다이어그램처럼 유지해야 할 공통 UI는 layout.tsx에, 이동마다 다시 시작해야 할 동작은 template.tsx에 두면 선택 기준이 명확해집니다.

유지할 공통 UI와 다시 시작할 동작을 먼저 나눈다

template은 layout의 대체재가 아니라, layout 안에서 새 인스턴스가 필요한 부분만 감싸는 도구다.

판단 질문layout.tsxtemplate.tsx틀리면 생기는 문제
페이지 이동 중 계속 보여야 하나예 사이드바, 헤더, 탭아니오공통 UI가 깜박이거나 상태가 사라짐
이동마다 효과를 다시 시작해야 하나아니오예 전환 애니메이션, 페이지뷰 로거애니메이션과 측정이 한 번만 실행됨
사용자 입력을 유지해야 하나예 검색 조건, 접힌 메뉴아니오탐색 중 입력값이 예기치 않게 초기화됨
페이지마다 초기값이 달라야 하나공통 데이터만예 폼 초깃값, 임시 편집 상태이전 페이지의 임시 상태가 남음

템플릿은 “공통 UI”라는 이름만 보고 추가하기보다, 탐색마다 정말 새 인스턴스가 필요한지 확인한 뒤 적용하는 편이 안전합니다.

다음 기준처럼 유지할 상태와 초기화할 상태를 나누면 layout.tsxtemplate.tsx의 역할이 충돌하지 않습니다.

상태는 오래 살아야 하는 범위만큼만 위로 올린다

layout과 template을 함께 쓸 때는 상태의 수명을 기준으로 파일 위치를 정한다.

  1. RootLayout

    전역 테마, 폰트, 최상위 provider처럼 앱 전체에서 유지되는 구조

  2. DashboardLayout

    대시보드 사이드바, 계정 요약처럼 하위 페이지 이동 중 유지할 UI

  3. DashTemplate

    페이지 전환 애니메이션, 페이지뷰 측정처럼 이동마다 재시작할 동작

  4. Page

    현재 URL의 실제 콘텐츠와 page별 데이터 표시

상태 종류권장 위치근거
접힌 사이드바layout 내부 client island다른 대시보드 페이지에서도 유지되어야 함
폼 입력 초깃값template 또는 page다른 page로 이동하면 새로 시작하는 편이 자연스러움
페이지뷰 측정template탐색마다 한 번씩 다시 실행되어야 함
서버 데이터가장 가까운 layout/page사용 범위를 넘기면 캐시와 책임이 흐려짐

템플릿 컴포넌트는 Next.js App Router에서 새 마운트가 필요한 페이지 구조에 사용합니다.

레이아웃과의 차이를 먼저 확인한 뒤, 아래 기준처럼 재마운트가 실제로 필요한 경우에만 적용해야 합니다.

template.tsx를 추가하기 전에는 새로 마운트되어야 하는 이유가 분명한지, 반대로 유지되어야 할 상태를 끊고 있지는 않은지 확인해야 합니다.

template은 새 인스턴스가 필요한 이유가 있을 때만 고른다

공통 UI라는 이유만으로 template을 추가하지 말고, 재마운트가 실제 문제를 해결하는지 확인한다.

  1. keep
    layout이 맞는 신호

    keep 탐색 중 UI와 상태를 계속 유지한다. 공통 데이터나 provider가 route 전체에 필요하다. page만 바뀌고 껍데기는 안정적으로 남는다.

  2. restart
    template이 맞는 신호

    restart route 변경마다 애니메이션을 처음부터 실행한다. 폼, 로거, 측정 세션을 page마다 새로 시작한다. 이전 page의 임시 상태가 남으면 문제가 된다.

결정 질문아니오
재마운트가 핵심 요구인가template 검토layout 우선
유지해야 할 사용자 상태가 있는가layout 또는 client islandtemplate 가능
서버 데이터 공유가 목적인가layout/page fetchtemplate만으로 해결하지 않음

이 다이어그램은 템플릿 컴포넌트를 Next.js 프로젝트에 넣을 때 결정해야 할 파일 위치와 런타임 경계를 정리합니다.

위치는 layout.tsx와 비슷하지만 의미는 “공통 UI”보다 “재마운트 경계”에 가깝습니다.

template.tsx는 layout과 page 사이에서 재마운트 경계를 만든다

파일 위치는 layout과 같지만, 실행 의미는 page 전환마다 새로 만들어지는 중간 껍데기에 가깝다.

  1. 서버 기본값

    template도 기본은 서버 컴포넌트다.

  2. 애니메이션

    브라우저 상태가 필요하면 template을 client component로 만든다.

  3. children

    layout처럼 children을 받아 하위 page를 감싼다.

  4. key

    Next.js가 탐색마다 template 인스턴스를 새로 만든다.

app/
  dashboard/

layout.tsx
    유지되는 대시보드 껍데기

template.tsx
  이동마다 새로 마운트

page.tsx
      현재 URL 본문
    settings/

page.tsx

마지막으로 template.tsx가 layout.tsx와 달리 언제 재마운트를 의도하는지 비교합니다.

template의 핵심 계약은 공통 UI가 아니라 재마운트다

layout은 공유 범위를 만들고, template은 그 안에서 page 전환마다 다시 실행할 작업을 맡는다.

비교 축layout.tsxtemplate.tsx선택 기준
라우트 이동인스턴스 유지새 인스턴스 생성상태를 남길지 버릴지
대표 용도공통 UI, provider, 서버 데이터 경계전환 효과, 로거, 폼 초기화유지보다 초기화가 중요한가
children하위 layout/template/page 삽입하위 page를 새 경계로 감쌈둘 다 children을 렌더해야 함
상호작용작은 client island 권장필요하면 client component 가능브라우저 API가 필요한 부분만 client
남용 신호너무 넓은 데이터 fetch공통 UI만 감싸는 template파일을 추가한 이유가 설명되는가