Next.js の App Router には、共通のUIを子ページに被せるためのファイルとして layout.tsx がよく知られています。ですが、それとよく似た template.tsx というファイルがあることをご存知でしょうか。見た目のコードはほとんど同じなのに、ページを移動したときの挙動が決定的に違います。layout.tsx がページ遷移をまたいで状態を保ち続けるのに対して、template.tsx は遷移のたびに作り直されます。この記事では、template.tsx とは何か、基本の書き方、layout.tsx との違い、そしてどんなときに使うべきかを、具体的なコードとあわせて解説します。
目次
template.tsx はページ遷移ごとに作り直される共通UI
template.tsx は、layout.tsx と同じく「子ページを包む共通のUI」を定義するための特別なファイルです。children として渡されてくるページの中身を、自分のマークアップで囲んで表示します。ここまでは layout.tsx と変わりません。
決定的に違うのは、ページを移動したときの振る舞いです。layout.tsx はページ遷移をまたいでも同じインスタンスが維持され、再描画されても状態はそのまま保たれます。一方 template.tsx は、同じレイアウトの中を移動するたびに新しいインスタンスが作り直されます。その結果、コンポーネント内で保持していた useState の値はリセットされ、useEffect は遷移のたびに再実行され、DOM要素も作り直されます。「遷移ごとに毎回リセットして動かしたい処理がある」ときに使うファイル、と捉えると分かりやすいでしょう。
基本の書き方
template.tsx は、layout.tsx と同じディレクトリ(多くは app/ 直下や各セグメントのフォルダ)に置きます。children を受け取り、それをそのまま返す default エクスポートの関数を書くのが基本形です。見た目は layout.tsx とほぼ同じです。
// children にはページの中身が渡ってくる
export default function Template({
children,
}: {
children: React.ReactNode;
}) {
return <div className="template-wrapper">{children}</div>;
}
このファイルを置くと、同じ階層以下のページはすべて Template で包まれて表示されます。layout.tsx の書き方を知っていれば、そのまま同じ感覚で書けます。違いはあくまで「遷移のたびに作り直される」という内部の挙動だけで、コードの見た目には表れません。
layout.tsx との違いを整理する
両者はコードが似ているぶん、挙動の違いを正しく押さえておくことが大切です。ページ遷移をまたいだときにどう振る舞うかを表で整理します。
| 項目 | layout.tsx | template.tsx |
|---|---|---|
| 遷移時の再マウント | されない(維持される) | 毎回される(作り直し) |
| state の保持 | 遷移後も保たれる | 遷移のたびにリセット |
| useEffect の再実行 | 再実行されない | 遷移のたびに再実行 |
| DOM要素 | 使い回される | 作り直される |
| 主な用途 | ヘッダー・フッターなど恒久的な共通UI | 遷移ごとに動かしたい処理・演出 |
ざっくり言えば、layout.tsx は「ずっと画面に居続けてほしいもの」、template.tsx は「ページが変わるたびにリフレッシュしたいもの」を担当します。たとえば同じレイアウト内でページを移動しても、layout.tsx に置いた開閉状態を持つメニューは開いたままですが、template.tsx に同じものを置けば遷移のたびに閉じた初期状態に戻ります。
どんなときに template.tsx を使うか
「遷移のたびに作り直される」という性質は、裏を返せば「遷移ごとに必ず何かを実行したい」場面でちょうど役立ちます。代表的なユースケースを見ていきます。
ページ遷移ごとのアニメーション(enter animation)
ページを開くたびにフェードインなどの登場アニメーションを付けたいとき、template.tsx が向いています。layout.tsx だと遷移をまたいで維持されるため、初回しかアニメーションが走りません。template.tsx なら遷移のたびに要素が新しくマウントされるので、毎回アニメーションの起点に戻せます。
'use client';
import { useEffect, useState } from 'react';
export default function Template({
children,
}: {
children: React.ReactNode;
}) {
// 遷移のたびに template が作り直されるので、
// この state は毎回 false から始まる
const [shown, setShown] = useState(false);
useEffect(() => {
// マウント直後にフェードインを開始する
setShown(true);
}, []);
return (
<div
style={{
opacity: shown ? 1 : 0,
transition: 'opacity 0.3s ease',
}}
>
{children}
</div>
);
}
ページを移動するたびに Template が新しくマウントされ、shown は false から始まって useEffect で true に変わります。この差分によって、遷移のたびにフェードインが再生されます。
遷移ごとのログ送信や計測
ページビューの計測のように「新しいページを開いたら毎回1回だけ実行したい」処理も、template.tsx の useEffect に置くと自然に書けます。layout.tsx の useEffect は遷移をまたいで再実行されないため、同じレイアウト内の移動では発火しません。template.tsx なら遷移のたびに useEffect が走るので、ページごとの計測に向いています。
このように、「遷移のたびに state を初期化したい」「遷移のたびに useEffect を動かしたい」という要件があるときが template.tsx の出番です。逆に言えば、そうした要件がなければ layout.tsx で十分です。
layout.tsx と template.tsx を併用したときのネスト
同じディレクトリに layout.tsx と template.tsx の両方を置くこともできます。その場合、Next.js は layout の内側に template を入れ、さらにその内側にページを描画します。つまり layout > template > page という入れ子になります。
<Layout>
{/* Layout は遷移をまたいで維持される */}
<Template>
{/* Template は遷移のたびに作り直される */}
<Page />
</Template>
</Layout>
この構造のおかげで、恒久的に表示したいヘッダーやサイドバーは layout.tsx に置いて遷移をまたいで維持しつつ、そのすぐ内側の template.tsx で遷移ごとの演出や初期化を担当する、という役割分担ができます。ページを移動すると Layout はそのまま、Template から内側だけが作り直される、とイメージすると理解しやすいでしょう。
迷ったらまず layout.tsx で足りるか考える
template.tsx は便利ですが、多用するとパフォーマンス面で不利になりやすい点に注意が必要です。遷移のたびにコンポーネントが破棄・再生成されるため、DOMの作り直しや useEffect の再実行が毎回発生します。中身が重いコンポーネントを template.tsx に置くと、遷移のたびにそのコストを支払うことになります。
そのため、共通UIを作りたいときの基本はあくまで layout.tsx です。template.tsx を選ぶのは、「遷移のたびに state をリセットしたい」「遷移のたびにアニメーションや計測を走らせたい」といった、再マウントの挙動そのものが必要な場合に限るのがよいでしょう。共通の見た目を用意したいだけなら layout.tsx を使い、それでは要件を満たせないときに初めて template.tsx を検討する、という順番で考えると迷いにくくなります。
まとめ
template.tsx は、layout.tsx と同じく子ページを包む共通UIを定義するファイルですが、ページ遷移のたびに新しいインスタンスが作り直される点が決定的に違います。その結果、useState はリセットされ、useEffect は再実行され、DOM要素も作り直されます。基本の書き方は children を受け取って返す default 関数で、layout.tsx とほぼ同じ見た目です。遷移ごとの登場アニメーションやページ計測など「遷移のたびに毎回動かしたい処理」があるときに向いており、両方を置くと layout > template > page の順にネストされます。ただし多用するとパフォーマンスに影響するため、基本は layout.tsx で足り、再マウントの挙動が本当に必要なときだけ template.tsx を使う、と覚えておきましょう。