「3分間クッキング」をもじった「3分間フッキング」、今回は外部リンクへの target="_blank" と rel="noopener" の付与を、functions.phpのフック処理で自動化する方法をご紹介します!
このシリーズでは、WordPressの真髄とも言える「フック」を使った実用的なテクニックを、functions.phpに数行書くだけで実現できる形でお伝えしていきます。
なぜ外部リンクに target=”_blank” が必要なのか
Webサイトで外部のページへリンクを張る際、リンク先を新しいタブで開くかどうかは、ユーザー体験に直結する重要な設計判断です。
外部リンクを同じタブで開いてしまうと、ユーザーがリンク先へ移動した時点で自分のサイトから離脱してしまいます。特にブログ記事の途中に参考リンクを貼っている場合、読者がそのまま戻ってこないケースも少なくありません。
target="_blank" を指定することで、リンク先を新しいタブで開き、元のページを残したままにできます。読者は参考情報を確認しながら、引き続きあなたのサイトのコンテンツを読み続けることができます。
rel=”noopener” を忘れると何が起きるか
target="_blank" を設定するだけでは、実はセキュリティ上のリスクが残ります。
新しいタブで開かれたページは、JavaScriptの window.opener というプロパティを通じて、元のページ(あなたのサイト)を参照・操作できてしまうことがあります。これを悪用した攻撃手法を「タブナビゲーション攻撃(リバーステーブナビング)」と呼びます。
悪意のあるリンク先サイトが、元のページを偽のログイン画面などにすり替えてフィッシング詐欺に誘導する、という攻撃が実際に報告されています。
rel="noopener" と rel="noreferrer" はそれぞれ独立した役割を持つ属性です。noopener は新しいタブから window.opener を通じた元ページへの操作を防ぎます。noreferrer はリンク先への参照元URL(Refererヘッダ)の送信を防ぎます。多くのブラウザでは noreferrer を指定すると window.opener も無効化されますが、HTML仕様としては別の機能です。互換性と明示性の観点から、rel="noopener noreferrer" の両方を指定するのが一般的な実装です。
なお、現在のChrome・Edge・Firefox・Safariなどの主要ブラウザでは、target="_blank" を指定すると暗黙的に noopener として扱われるようになっています。しかし、古いブラウザへの互換性と明示的なセキュリティ対策の観点から、rel="noopener noreferrer" を明記する実装は現在でも推奨されています。
target="_blank" には rel="noopener noreferrer" をセットで指定する、というのが現在のWebセキュリティのベストプラクティスです。
手動設定の限界 ── 過去記事の抜け漏れ問題
「知っているから、毎回ちゃんと設定している」という方も多いと思います。しかし、手動設定には避けられない問題があります。
記事を書くたびに、すべての外部リンクに対して手動で属性を付与するのは、作業が煩雑なだけでなく、抜け漏れが必ず発生します。
さらに厄介なのが、過去に書いた記事の問題です。「そういえば昔の記事はちゃんと設定していたっけ?」と気になっても、投稿数が多ければ1件ずつ確認・修正するのは現実的ではありません。
なお、WordPressのブロックエディタ(Gutenberg)では「新しいタブで開く」を指定すると、自動的に rel="noopener noreferrer" が付与されます。ただし、手書きHTMLや Classic Editor で作成した記事、過去にインポートした記事には適用されません。
ここでフックの出番です。functions.phpにフック処理を書けば、過去の記事も含めて、すべての外部リンクに自動で属性が付与されます。記事を書き直す必要は一切ありません。
コード解説
基本コード
まずはシンプルな基本形です。記事本文内のすべての外部リンクに target="_blank" と rel="noopener noreferrer" を自動付与します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/** * 外部リンクに target="_blank" と rel="noopener noreferrer" を自動付与する */ add_filter('the_content', 'wp3min_add_external_link_attrs'); function wp3min_add_external_link_attrs($content) { // 自サイトのドメインを取得 $home_url = parse_url(home_url(), PHP_URL_HOST); // aタグを検索して処理 $content = preg_replace_callback( '/<a\s([^>]*)href=["\']([^"\']*)["\']([^>]*)>/i', function($matches) use ($home_url) { $attrs_before = $matches[1]; $href = $matches[2]; $attrs_after = $matches[3]; // 相対パス・自サイト・アンカーリンクは除外 $parsed = parse_url($href); if ( empty($parsed['host']) || $parsed['host'] === $home_url || str_starts_with($href, '#') ) { return $matches[0]; // 変更せずそのまま返す } // すでに target が設定されている場合は上書きしない if (stripos($attrs_before . $attrs_after, 'target=') !== false) { return $matches[0]; } // target と rel を追加して返す return '<a ' . $attrs_before . 'href="' . $href . '"' . $attrs_after . ' target="_blank" rel="noopener noreferrer">'; }, $content ); return $content; } |
コードのポイント解説
the_contentフィルターフックは、投稿の本文が画面に出力される直前に実行されます。このフックを使うことで、データベースに保存された記事の内容を変更することなく、表示時だけ属性を付与することができます。
このコードで行っている処理の流れは以下の通りです:
parse_url(home_url(), PHP_URL_HOST)で自サイトのドメインを取得しますpreg_replace_callback()で本文内の<a>タグをすべて検索し、1つずつ処理します- 相対パス(内部リンク)・自サイトのドメイン・アンカーリンク(
#始まり)は対象外とします - すでに
target属性が設定されているリンクは上書きしません - 条件をすべてクリアした外部リンクにだけ
target="_blank" rel="noopener noreferrer"を追加します
※このコードは一般的な投稿HTMLを想定しています。複雑なHTML構造(属性値にスペースが含まれる場合など)を扱う場合は、DOMDocument や WordPress 6.2以降で使える WP_HTML_Tag_Processor を使う方法も検討できます。
応用コード:特定ドメインを除外する
自社グループサイトや、意図的に同じタブで開きたいドメインがある場合は、除外リストを設けることができます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
/** * 外部リンクに target="_blank" と rel="noopener noreferrer" を自動付与する * (特定ドメインを除外するバージョン) */ add_filter('the_content', 'wp3min_add_external_link_attrs_with_exclude'); function wp3min_add_external_link_attrs_with_exclude($content) { $home_url = parse_url(home_url(), PHP_URL_HOST); // 除外したいドメインを配列で指定 $exclude_domains = [ 'example-group.com', 'affiliate-trusted.jp', ]; $content = preg_replace_callback( '/<a\s([^>]*)href=["\']([^"\']*)["\']([^>]*)>/i', function($matches) use ($home_url, $exclude_domains) { $attrs_before = $matches[1]; $href = $matches[2]; $attrs_after = $matches[3]; $parsed = parse_url($href); $host = $parsed['host'] ?? ''; // 自サイト・相対パス・アンカー・除外ドメインはスキップ if ( empty($host) || $host === $home_url || str_starts_with($href, '#') || in_array($host, $exclude_domains) ) { return $matches[0]; } if (stripos($attrs_before . $attrs_after, 'target=') !== false) { return $matches[0]; } return '<a ' . $attrs_before . 'href="' . $href . '"' . $attrs_after . ' target="_blank" rel="noopener noreferrer">'; }, $content ); return $content; } |
$exclude_domains の配列に除外したいドメインを追加するだけで、そのドメインへのリンクは target="_blank" が付与されず、同じタブで開くリンクとして残ります。
実装手順
- WordPressの管理画面から「外観」→「テーマエディター」へ移動します
- 右側のファイル一覧から「functions.php」を選択します
- ファイルの適切な位置に、上記いずれかのコードを追加します
- 「ファイルを更新」ボタンをクリックして保存します
- 記事ページを開き、外部リンクを右クリック →「検証」でHTMLを確認し、
target="_blank"が付与されていれば完了です
ブラウザの開発者ツールで <a> タグを確認するのが、最もかんたんな動作確認方法です。
Mac は Cmd + Option + I、Windows は F12 または Ctrl + Shift + I で開けます。どちらもリンク上で右クリック →「検証」からも開けます。
実装時の注意点
以下の点に注意して実装してください。
- the_contentフック以外には適用されません:ウィジェットやショートコードで出力したリンクは対象外です。必要であれば
widget_textなどのフックにも同様の処理を追加してください - パフォーマンスへの影響はほぼゼロ:正規表現はコンテンツ出力時に一瞬で処理されます。キャッシュプラグインを使っている環境では実質的な負荷はありません
- PHPバージョンに注意:コード中の
str_starts_with()は PHP 8.0 以上で使用できる関数です。WordPressの動作要件はPHP 7.4以上となっているため、古い環境でも動作させたい場合はstrpos($href, '#') === 0に書き換えてください
まとめ
いかがでしたか?the_content フィルターフックを活用するだけで、過去記事も含めたすべての外部リンクに、セキュリティ対策を自動で適用できることがお分かりいただけたと思います。
手動設定の抜け漏れをなくし、過去記事もまとめて対応できる。これがフックならではの強みです。
今回のポイントをおさらいしましょう:
target="_blank"には必ずrel="noopener noreferrer"をセットで設定するthe_contentフィルターフックで、記事本文の外部リンクを出力時に自動処理できる- 自サイト・相対パス・アンカーリンクは自動的に除外されるので、内部リンクには影響しない
- 除外ドメインリストを使えば、特定のサイトだけ同一タブで開く設定も可能
埼玉県川越市を拠点とする私たちは、このようなWordPressの機能拡張や最適化などのテクニカルサポートも提供しています。WordPressのセキュリティ強化やカスタマイズでお悩みの場合は、ぜひご相談ください。
次回の「WordPress 3分間フッキング」もお楽しみに!別のフックを使った便利技をご紹介します。
よくある質問
Q: 過去に手動で target=”_blank” を設定したリンクはどうなりますか?
A: コードの中で「すでに target 属性が設定されているリンクは上書きしない」という処理を入れています。手動設定済みのリンクはそのままで、重複して属性が付くことはありません。
Q: ウィジェットやフッターのリンクにも適用できますか?
A: 今回のコードは the_content フック(記事本文)のみが対象です。ウィジェット内のリンクにも適用したい場合は、add_filter('widget_text', ...) のように、対象のフックを追加してください。
Q: rel=”noopener” と rel=”noreferrer” の違いは何ですか?
A: それぞれ独立した役割を持つ属性です。noopener は新しいタブから window.opener を通じた元ページへのJavaScript操作を防ぎます。noreferrer はリンク先への参照元情報(Refererヘッダ)の送信を防ぎます。多くのブラウザでは noreferrer を指定すると window.opener も無効化されますが、HTML仕様としては独立した機能です。互換性と明示性の観点から、両方を明記する rel="noopener noreferrer" が一般的なベストプラクティスです。
サイトのセキュリティ強化やWordPressのパフォーマンス最適化、カスタマイズなどでお悩みの場合は、WordPressカスタマイズ・プラグイン開発サービスをご検討ください。また、技術的なスキルアップを目指す方にはWordPress顧問エンジニアサービスもおすすめです。

