PHP でページ数の多いサイトを運営していると、記事本文より先に困るのがhead周辺の管理です。
最初は各テンプレートへ直接titleやdescriptionを書いていても、カテゴリ、記事、検索結果、問い合わせページなどが増えるにつれて、次のような問題が起きやすくなります。
- ページタイトルが空になる
- すべてのページで同じdescriptionが出力される
- canonicalがクエリ文字列付きのURLになる
- 画面上のH1とtitleの内容が一致しない
- 値がない場合の代替表示がテンプレートごとに異なる
- HTMLエスケープを忘れ、属性値が壊れる
この記事では、PHP のテンプレートでメタ情報を安全に出し分けるための考え方を、実装例とともに整理します。
- 「値があるか」だけでなく、優先順位を決める
メタ情報の出力で重要なのは、条件分岐を増やすことではありません。まず、どの値を優先するかを決めます。
たとえばtitleは、次の順序にすると管理しやすくなります。
- ページ専用のSEOタイトル
- 記事タイトルまたはカテゴリ名
- サイト共通タイトル
descriptionも同様です。
- SEO用description
- 記事の要約文
- カテゴリ共通説明
- サイト共通説明
この優先順位を全テンプレートで統一しておけば、「このページだけ何も表示されない」という事故を減らせます。
- titleの基本的な出し分け
まずは、SEOタイトルが存在する場合はそれを使い、なければサイトタイトルを表示する最小構成です。
html
{if isset($SEO['title']) && !empty($SEO['title'])}{$SEO['title']}{else}{$SEO['site_title']}{/if}ただし、記事ページではサイト名を付けたい場合があります。その場合は、区切り文字も含めて条件分岐します。
html
{if isset($SEO['title']) && !empty($SEO['title'])} {$SEO['title']} - {$SEO['site_title']} {else} {$SEO['site_title']} {/if}テンプレート内の改行が、そのままtitleの余分な空白として出力される環境もあります。最終的には一行へまとめるか、生成後のHTMLを確認しておくと安全です。
html
{if isset($SEO['title']) && !empty($SEO['title'])}{$SEO['title']} - {$SEO['site_title']}{else}{$SEO['site_title']}{/if}- descriptionは「空なら代替値」にする
descriptionでは、SEO用の値がないときに本文の説明文へ切り替える設計が実用的です。
html
ここで注意したいのは、単に変数が存在するかだけでなく、空文字ではないかも確認することです。
html
{if isset($description) && !empty($description)}
isset()だけでは、変数が存在していても中身が空というケースを防げません。
また、descriptionを長くしすぎても検索結果にすべて表示されるとは限りません。テンプレートでは極端に長い文章をそのまま入れず、ページの要点が先に来るように元データを整えるほうが重要です。
- 出力値のエスケープを忘れない
記事タイトルや説明文にダブルクォート、<、>などが含まれると、HTML属性が壊れる場合があります。
使用中のPHP環境でエスケープ用フィルタを利用できる場合は、HTML属性へ出力する直前に処理します。環境によって利用できる修飾子や独自関数が異なるため、実際の構成に合わせる必要があります。
PHP側で値を用意できるなら、次のように属性用の値を作成してからテンプレートへ渡す方法が確実です。
php
$meta_description = htmlspecialchars(
$meta_description,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
テンプレートでは加工済みの値を出力します。
html
表示用文字列とHTML属性用文字列を同じ変数で使い回さないようにすると、後から処理を追いやすくなります。
- canonicalから不要なクエリ文字列を除外する
同じ内容へ複数のURLからアクセスできるサイトでは、canonicalの設計も必要です。
たとえば広告や計測用パラメータ、ページ内移動用のフラグメントまでcanonicalへ含めると、意図しないURLが正規URLとして出力されることがあります。
理想は、PHP側でページごとの正規URLを明示的に作り、テンプレートへ渡す方法です。
php
$canonical_url = 'e.com/article/' . intval($id) . '.html';
html
{if isset($canonical_url) && !empty($canonical_url)}
現在のリクエストURLをそのままcanonicalに使う実装は簡単ですが、不要なパラメータやHTTP/HTTPSの違い、末尾スラッシュの揺れまで引き継ぐ可能性があります。
正規URLは「アクセスされたURL」から作るのではなく、「そのコンテンツに割り当てたURL」から作るほうが安定します。
- H1はページの主題を表示する
H1をSEOのためだけに非表示で追加するより、利用者がページを開いたときに内容を理解できる見出しとして表示するほうが自然です。
html
{if isset($SEO['title']) && !empty($SEO['title'])}{$SEO['title']}{else}{$SEO['site_title']}{/if}
ただし、ロゴ部分がH1になっている共通ヘッダーを使っている場合、記事本文にもH1を追加すると構造が分かりにくくなることがあります。
おすすめは次の役割分担です。
- 共通ヘッダーのサイト名:
divまたはp - 記事ページの記事タイトル:
h1 - 記事内の大見出し:
h2 - 大見出しの下位項目:
h3
見出しレベルはデザインではなく文書構造で決め、文字サイズはCSSで調整します。
- 共通部品化してテンプレート間の差を減らす
title、description、canonical、OGPを複数のテンプレートへコピーすると、修正漏れが起こります。
可能であれば、メタ情報を一つの共通テンプレートへまとめます。
html
{template "content", "meta"}
共通ファイルの例です。
html
{if !empty($SEO['title'])}{$SEO['title']} - {$SEO['site_title']}{else}{$SEO['site_title']}{/if} {if !empty($canonical_url)}{/if}この方式なら、メタ情報の仕様を変更するときも一か所の修正で済みます。
一方で、共通テンプレートの変更は多くのページへ影響します。更新前にバックアップを取り、少なくとも次のページを確認します。
- 通常の記事ページ
- カテゴリ一覧
- トップページ
- descriptionが空の記事
- タイトルに記号を含む記事
- ページ送りがある一覧
- HTML生成後の確認までを作業に含める
テンプレートを直しただけでは作業完了ではありません。PHPで静的HTMLを再生成したあと、実際の出力を確認します。
確認項目は次のとおりです。
text
□ titleが空になっていないか
□ titleにサイト名が二重に入っていないか
□ descriptionのcontent属性が途中で壊れていないか
□ canonicalが絶対URLになっているか
□ canonicalに不要なパラメータが付いていないか
□ ページの内容とH1が一致しているか
□ ソース上に同じmetaタグが二つないか
ブラウザの画面だけでなく、「ページのソースを表示」で生成後のHTMLを見ることが大切です。JavaScriptで後から変更されたDOMだけを見ていると、検索エンジンへ最初に渡されるHTMLとの差を見落とす場合があります。
まとめ
PHP でメタ情報を安定させるには、ページごとに条件式を継ぎ足すより、最初に値の優先順位を決め、共通テンプレートへ集約するほうが保守しやすくなります。
要点は次の5つです。
- 専用値、代替値、共通値の優先順位を統一する
-
isset()だけでなく空文字も確認する - HTML属性へ出力する値は適切にエスケープする
- canonicalはリクエストURLではなく正規のコンテンツURLから作る
- 静的HTMLを再生成したあと、最終的なソースを確認する
大量のページを持つサイトほど、小さなテンプレートの不整合がサイト全体へ広がります。逆にいえば、共通化された一つのテンプレートを丁寧に整えるだけで、多数のページを同時に改善できます。
実際のコンテンツページでtitle、description、canonical、見出し構造を確認するときは、診断型ページの一例として恋みくじのようなページ構成も比較材料になります。文章中心の記事ページとは異なり、操作要素を含むページでメタ情報と画面上の主題をどう一致させるかを考える参考になります。