템플릿 컴포넌트 활용
공유 레이아웃의 상태 유지와 template.tsx의 세그먼트별 재마운트 범위를 비교해 적용 시점을 판단합니다.
Next.js App Router에서 UI를 공유하고 재사용하는 방법으로는 layout.tsx 파일로 정의하는 레이아웃 컴포넌트가 가장 보편적입니다.
하지만 때로는 레이아웃과 유사하게 공통 UI를 제공하면서도, 페이지 이동 시 컴포넌트 인스턴스를 새로 생성하고 상태를 초기화해야 하는 경우가 있습니다.
이때 사용되는 것이 바로 템플릿 컴포넌트(template.tsx)입니다.
이 절에서는 템플릿 컴포넌트가 레이아웃 컴포넌트와 어떻게 다른지, 그리고 어떤 상황에서 템플릿 컴포넌트를 활용해야 하는지 구체적인 예시와 함께 알아보겠습니다.
템플릿 컴포넌트란 무엇인가요?
템플릿 컴포넌트는 app 디렉터리 내의 특정 라우트 세그먼트 폴더 안에 위치한 template.tsx 파일입니다.
레이아웃과 마찬가지로 children prop을 받아 해당 라우트의 콘텐츠를 감싸는 역할을 합니다.
- 공유
layout.tsx는 클라이언트 탐색에서 유지할 헤더·사이드바와 하위 UI를 감쌉니다. 레이아웃의 재사용 자체가 임의의 데이터 함수에 캐시를 만드는 것은 아닙니다. template.tsx는 자신의 세그먼트 키가 바뀔 때 새 인스턴스와 DOM을 만들고 내부 클라이언트 상태를 초기화합니다. 이 경계에서 애니메이션이나 로거를 다시 시작할 수 있습니다.
두 파일 모두 기본적으로 서버 컴포넌트이며 children으로 받은 하위 UI를 감쌉니다.
템플릿 컴포넌트는 레이아웃과 페이지 사이에 위치하여, 레이아웃이 자식들을 감싸고, 템플릿이 다시 그 레이아웃의 자식(즉, 페이지)을 감싸는 형태로 작동합니다.
다른 경계를 생략한 포함 관계:
layout.tsx (루트) -> template.tsx (루트) -> layout.tsx (세그먼트) -> template.tsx (세그먼트) -> page.tsx
템플릿 컴포넌트 활용 시나리오
app/dashboard/template.tsx를 기준으로 이동 범위를 비교하면 다음과 같습니다.
대시보드 template이 다시 마운트되는 범위
| 이동 종류 | dashboard/template.tsx의 동작 |
|---|---|
| 형제 화면 | overview → analytics처럼 이 레벨의 키가 바뀌면 재마운트합니다. 내부 Client Component의 로컬 상태와 effect가 새로 시작합니다. |
| 하위 화면 | profile/edit → profile/security는 더 깊은 이동입니다. 대시보드 template은 유지되고, 바뀐 하위 페이지나 더 낮은 template은 별도로 처리됩니다. |
| 검색값 변경 | overview?tab=a → overview?tab=b처럼 search params만 바뀌면 template 재마운트를 일으키지 않습니다. |
- 이동 종류: 형제 화면
dashboard/template.tsx의 동작:overview → analytics처럼 이 레벨의 키가 바뀌면 재마운트합니다. 내부 Client Component의 로컬 상태와 effect가 새로 시작합니다.- 이동 종류: 하위 화면
dashboard/template.tsx의 동작:profile/edit → profile/security는 더 깊은 이동입니다. 대시보드 template은 유지되고, 바뀐 하위 페이지나 더 낮은 template은 별도로 처리됩니다.- 이동 종류: 검색값 변경
dashboard/template.tsx의 동작:overview?tab=a → overview?tab=b처럼 search params만 바뀌면 template 재마운트를 일으키지 않습니다.
템플릿 컴포넌트는 다음과 같은 경우에 유용합니다.
페이지 전환 애니메이션: 새 마운트를 이용해 초기 애니메이션을 실행하는 라이브러리(예: Framer Motion)와 연결할 수 있습니다. 템플릿이 재마운트되는 이동에서 해당 효과가 다시 시작됩니다.
클라이언트 컴포넌트 상태 초기화: 템플릿 키가 바뀌는 이동에서 내부 클라이언트 컴포넌트의 로컬 상태를 초기화할 때 사용합니다. 예를 들어 이 경계를 떠나 다른 화면으로 이동할 때 폼 입력값을 새로 시작할 수 있습니다.
성능 측정 또는 로거 초기화: 재마운트 경계마다 로거의 상태나 측정 구간을 다시 시작할 때 사용할 수 있습니다. 모든 페이지 뷰를 세는 기능과는 범위가 다르며, 개발 중 Strict Mode의 Effect 재실행도 실제 탐색 횟수와 구별해야 합니다.
템플릿 컴포넌트 구현 실습
간단한 페이지 전환 효과를 통해 템플릿 컴포넌트의 작동 방식을 이해해 봅시다.
이 예제에서는 Framer Motion 라이브러리를 사용하여 페이지 전환 애니메이션을 구현합니다.
Framer Motion 설치: 먼저 프로젝트에 Framer Motion 라이브러리를 설치합니다.
npm install framer-motion
# 또는
yarn add framer-motionsrc/app/dashboard/template.tsx 파일 생성:
src/app/dashboard 폴더 안에 template.tsx 파일을 생성합니다.
src/app/dashboard/template.tsx 내용 작성:
템플릿 컴포넌트 내에서 Framer Motion의 motion 컴포넌트를 사용하여 애니메이션을 적용합니다.
이 예제는 motion을 직접 사용하는 컴포넌트에 "use client" 지시어를 추가합니다. 템플릿 자체를 서버 컴포넌트로 두고 별도의 클라이언트 애니메이션 컴포넌트를 감싸는 구성도 가능합니다.
"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": 이 예제의 애니메이션 컴포넌트를 클라이언트 경계로 선언합니다.motion.div: Framer Motion에서 제공하는 컴포넌트입니다.initial과animateprop을 사용하여 애니메이션 시작점과 끝점을 정의합니다.- 이 템플릿의 키가 바뀌면
motion.div도 새로 마운트되어 초기 애니메이션을 다시 시작합니다. 유지할 공통 UI는 바깥 레이아웃에 둡니다.
src/app/dashboard/layout.tsx에 약간의 스타일 추가 (선택 사항):
애니메이션 효과를 더 잘 시각화하기 위해 DashboardLayout에 최소 높이를 지정할 수 있습니다. 다음은 스타일만 보여 주는 부분 코드입니다. 생략한 영역에는 기존 사이드바와 {children} 렌더링을 그대로 유지합니다.
// ...
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로 접속합니다.
그 다음 대시보드 메뉴의 다른 링크들(예: 개요, 분석, 설정)을 클릭해 보세요.
/dashboard/overview, /dashboard/analytics, /dashboard/settings처럼 이 템플릿의 키를 바꾸는 형제 화면으로 이동하면 콘텐츠의 초기 애니메이션이 다시 시작됩니다. 이 동작은 새 마운트 시 initial에서 animate로 전환하도록 작성한 코드에 따른 것입니다.
같은 초기 마운트 애니메이션을 공유 레이아웃에 두면 그 레이아웃을 재사용하는 탐색만으로는 다시 시작하지 않습니다. 상태나 props로 별도 애니메이션을 제어하는 경우와는 구별합니다.
템플릿 컴포넌트 사용의 장단점
장점- 애니메이션과 초기화 경계: 재마운트가 필요한 화면 범위를 파일 위치로 지정하고, 그 안의 초기 애니메이션·클라이언트 상태·측정 구간을 함께 새로 시작할 수 있습니다.
- 재마운트 비용: DOM 생성과 클라이언트 Effect 설정이 반복됩니다. 비용의 크기는 실제 컴포넌트에서 측정해야 합니다.
- 내부 클라이언트 상태 소실: 유지하려던 폼 입력값이나 선택 상태도 같은 경계 안에 있으면 초기화됩니다.
- 필요한 범위에만 적용: UI 공유만 필요하면 레이아웃으로 충분합니다. 새 인스턴스가 필요한 구간에 템플릿을 둡니다.