Next.jsのmetadata.titleがlayoutのtitle.templateと二重に表示される
※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。
結論
親layoutでtitle.templateを設定済みの配下では、子ページのmetadata.titleに同じ接尾辞を含む文字列を書くと、テンプレートの%sにその文字列全体が代入され、タイトルタグの接尾辞が二重に表示される。
結論
親layoutでtitle.templateを設定している配下では、子ページのmetadata.titleに同じ接尾辞を含む文字列をそのまま書くと、タイトルタグにその接尾辞が二重に表示される。 テンプレートは子ページが返した文字列を検査せず、%sへそのまま代入するだけだからだ。直し方は子ページのtitleから接尾辞を取り除き、付与そのものをlayout側のテンプレートに委ねること。
症状
Next.jsのApp Routerでは、親のlayout.tsxにmetadata.titleをオブジェクト形式で設定し、templateを使うと、配下の各ページが返したtitleの末尾に共通の接尾辞を自動で付けられる。
// app/layout.tsx
export const metadata: Metadata = {
title: {
default: "〇〇教室 東京",
template: "%s | サイト名",
},
// ...
};
この状態で、採用ページ側のmetadata.titleに次のように書いていた。
// app/recruit/page.tsx(修正前)
export const metadata: Metadata = {
title: "アルバイト募集(講師) | サイト名",
description: "...",
};
このページだけブラウザのタブやGoogleの検索結果に表示されるタイトルが「アルバイト募集(講師) | サイト名 | サイト名」のように、同じ接尾辞が2回続いて表示される。
原因
template: "%s | サイト名"は、子ページが返したtitleの文字列をそのまま%sへ代入するだけの単純な置換で、代入する文字列の中身は一切見ていない。子ページのtitleに接尾辞を含めていなければ%sには接尾辞を含まない文字列だけが入り、結果として「本文 | サイト名」という1回だけの形になる。
しかし採用ページのtitleには、すでに| サイト名という接尾辞そのものの文字列が含まれていた。このため%sにはこの文字列全体がそのまま代入され、テンプレートが持つ| サイト名と合わせて、タイトル全体では接尾辞に相当する部分が2回現れる形になった。
テンプレートの仕組み自体は設計どおりに動いている。壊れていたのは「子ページ側は接尾辞を書かない」という前提が、このページ1つだけ守られていなかった点にある。
直す
子ページのtitleから接尾辞の部分を取り除き、接尾辞の付与をlayout側のtemplateだけに任せる。
// app/recruit/page.tsx(修正後)
export const metadata: Metadata = {
title: "アルバイト募集(講師)",
description: "...",
};
layout.tsx側のtemplate: "%s | サイト名"はそのままで、子ページが返す文字列から接尾辞を抜いただけで直る。templateを外したり書き換えたりする必要はない。テンプレートは正しく機能していたので、触るべきは常に「代入される側」である子ページのtitleのほうだった。
再発しない形にする
title.templateを使う設計は、「子ページは接尾辞を書かない」という前提を、コードの型やコメントでは強制できない。metadata.titleは単なる文字列なので、どんな内容を入れてもTypeScriptの型チェックは通ってしまう。
この種の事故を構造的に防ぐ方法は、子ページのtitleを書く段階でテンプレートの接尾辞を意識させないことに尽きる。レビューや執筆の際に「このページのタイトルは最終的にどう表示されるか」をプレビューで確認する、あるいは接尾辞を含む文字列が子ページ側に紛れ込んでいないかを目視で見る、という当たり前の確認が、テンプレートを設定した時点でこそ一度必要になる。
よくある質問
Q1なぜtitle.templateを設定しているのに接尾辞が二重に付くのですか?
親layoutのtitle.templateは%sへ子ページのmetadata.titleをそのまま代入するだけで、子ページ側の文字列から重複する部分を取り除く処理はしていません。子ページのtitleに接尾辞を含む文字列を書いてしまうと、その文字列ごと%sへ入り、テンプレートが元々持っている接尾辞と合わせて二重に表示されます。
Q2直し方はtitle.templateを外すことですか?
いいえ。テンプレート自体は正しく機能しているので外す必要はありません。直すのは子ページのmetadata.titleのほうで、接尾辞を含めずに書き、接尾辞の付与をlayout側のtemplateに委ねるだけで解消します。
Q3この問題は他のページにも起きますか?
親layoutでtitle.templateを設定しているサイト全体に共通する注意点です。子ページ側でtitleを個別に書くときに、テンプレートが付ける接尾辞と同じ文字列を含めてしまえば、どのページでも同じ形で二重に表示されます。
確認した環境
- Next.js 15.4 / App Router
- 2026-07-08 に修正
この記事の根拠
- TypeScriptファイル 9〜9行目コミット 403e01a
- TypeScriptファイル 9〜9行目コミット f3ed55e
- TypeScriptファイル 16〜19行目コミット f3ed55e
本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。