はじめに
Reactでシングルページアプリケーション(SPA)を作っていると、必ずぶつかる壁があります。それが「<head>タグの管理」です。
ページごとにdocument.titleを変えたい、OGP画像を出し分けたい、SEO用のdescriptionメタタグをルートごとに切り替えたい——。こうした要求は、素のReactだとuseEffectの中でdocument.title = '〜'と直接DOMを書き換えるような、あまり気持ちのよくないコードになりがちです。しかもコンポーネントがアンマウントされたときに元の値に戻す処理まで自分で書く必要があり、地味に面倒です。
この問題を、宣言的なコンポーネントとして解決してくれるのがReact-Helmetです。<Helmet>というコンポーネントの中にただの<title>や<meta>を書くだけで、Reactのライフサイクルに合わせて<head>の中身を管理してくれます。
とはいえ、読むより触った方が早いと思います。ページ遷移でタイトルとメタタグがどう切り替わるか、実際に動かして確認できるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
React-Helmetとは
React-Helmetは、NFL(アメリカンフットボールリーグ)のエンジニアチームが開発した、Reactコンポーネントとして<head>タグを宣言的に管理できるライブラリです。「Helmet」=ヘルメットの名の通り、ページの頭(head)を守る、という意味合いのネーミングになっています。
主な特徴
- 宣言的なAPI -
<Helmet><title>ページタイトル</title></Helmet>のように、JSXの延長線上で<head>の中身を書ける - ネスト時の自動マージ - 親子でHelmetを重ねて書くと、後(子)に書いた値が親の値を上書きする。ページ共通のレイアウトで基本のtitleを設定しつつ、個別ページで上書きするといった使い方ができる
- サーバーサイドレンダリング対応 -
Helmet.renderStatic()で、SSR時にも同じAPIでheadの内容を取得できる - html/body属性やstyleタグも扱える -
<html lang="ja" />や<body className="dark" />のような属性指定、<style>タグの挿入までカバーしている
インストール
npm、yarn、pnpmのいずれかでインストールします。
npm install react-helmet
yarn add react-helmet
pnpm add react-helmet
TypeScriptで使う場合は、型定義パッケージも合わせて入れておくと補完が効いて便利です。
npm install --save-dev @types/react-helmet
React-Helmetのサンプルを動かす
Helmetコンポーネントの中に<title>と<meta name="description">を書くだけで、コンポーネントの表示に連動してdocument.titleとメタタグが書き換わります。下のサンプルは、ボタンで「ホーム」「会社概要」「お問い合わせ」の3ページを切り替え、そのたびにHelmetが<title>とmetaタグを差し替える様子を、document.titleの実際の値をパネルに表示することで確認できるようにしています。
要点だけを抜き出すと、次のようになります。
import { Helmet } from 'react-helmet'
function Page({ title, description }) {
return (
<div>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
</Helmet>
<h1>{title}</h1>
</div>
)
}
実際に動かせるのが下のサンプルです。「次のページへ」ボタンを押すたびに、Helmet内の<title>と<meta name="description">が切り替わり、実際のdocument.titleの値がパネルに反映されます。
ボタンを押すたびに、ブラウザのタブに表示されるタイトルと、パネル内のdocument.title表示が同時に切り替わるのが分かるはずです。これはHelmetが裏側でdocument.titleと<meta>要素を直接書き換えているためで、useEffectで自分で書き換える処理を一切書かずに済んでいます。pages配列に4つ目のページを足せば、そのまま切り替え対象が増えます。
基本的な使い方
最もシンプルな実装は、コンポーネントの中にHelmetを置き、<title>と<meta>を子要素として並べるだけです。
import React from 'react'
import { Helmet } from 'react-helmet'
function Application() {
return (
<div className="application">
<Helmet>
<meta charSet="utf-8" />
<title>マイページタイトル</title>
<link rel="canonical" href="https://example.com/page" />
</Helmet>
<h1>ようこそ</h1>
</div>
)
}
export default Application
Helmetはどこに配置しても構いません。ページ全体を管理する親コンポーネントに置いても、個別のページコンポーネントに置いても、Reactのコンポーネントツリーの中でマウントされているかぎり効果を発揮します。
実践的なユースケース
ネストしたHelmetでタイトルを上書きする
React-Helmetは、複数のHelmetがネストして存在する場合、後に描画された(子の)値が親の値を上書きするという挙動を持っています。共通レイアウトで基本のタイトルを設定しつつ、個別ページだけタイトルを差し替えたいときに便利です。
function Layout({ children }) {
return (
<>
<Helmet>
<title>サンプルサイト</title>
</Helmet>
{children}
</>
)
}
function ChildPage() {
return (
<Helmet>
<title>個別ページ | サンプルサイト</title>
</Helmet>
)
}
実際に動かせるのが下のサンプルです。チェックボックスで子コンポーネントの表示・非表示を切り替えると、Helmetが親のタイトルと子のタイトルをどう上書きするかがそのままdocument.titleに反映されます。
チェックを入れると子のHelmetが親のタイトルを上書きし、チェックを外すと親の「サンプルサイト」に戻ります。これはChildPageがアンマウントされた時点でReact-Helmetが子の指定を破棄し、直近にマウントされているHelmet(この場合は親)の値を再適用するためです。ページ共通のタイトルをLayout側に、記事固有のタイトルを各ページ側に書き分けるという設計がそのまま実現できます。
OGPメタタグをまとめて管理する
SNSでシェアされたときのプレビュー(OGP)は、og:titleやog:description、og:imageなどの複数の<meta>タグをページごとに出し分ける必要があります。React-Helmetなら、これらをデータ駆動でまとめて記述できます。
function ArticleHead({ article }) {
return (
<Helmet>
<title>{article.title}</title>
<meta property="og:title" content={article.title} />
<meta property="og:description" content={article.description} />
<meta property="og:image" content={article.image} />
</Helmet>
)
}
実際に動かせるのが下のサンプルです。記事を切り替えるたびにHelmetがog:title・og:description・og:imageを書き換え、実際に<head>に反映された値をパネルに表示します。
パネルに表示されているog:titleは、実際にdocument.headからquerySelectorで取得した値です。ボタンを押すたびにReact-Helmetが該当の<meta property="og:title">を書き換えているのが確認できます。SNSのクローラーはこの<head>内のメタタグを読み取るので、記事データを配列で持っておき、ルーティングに応じてArticleHeadのようなコンポーネントに渡すだけでOGP対応が完結します。
html/body属性とstyleタグを動的に切り替える
React-Helmetは<title>や<meta>だけでなく、<html>タグのlang属性や<body>タグのclass、さらに<style>タグの挿入まで扱えます。この特性を使うと、ダークモード切り替えのような「ページ全体の見た目を変える」処理もHelmet一箇所にまとめられます。
<Helmet>
<html lang={lang} />
<body className={mode} />
<style>{`
body.dark { background: #1e1e2e; color: #f5f5f5; }
body.light { background: #ffffff; color: #111111; }
`}</style>
</Helmet>
実際に動かせるのが下のサンプルです。「ダーク/ライト切替」ボタンで<body>のclassと<html>のlang属性が変わり、それに連動してページ全体の配色が切り替わります。
「ダーク/ライト切替」ボタンを押すと、Helmetが<body>のclass属性を書き換え、同時に挿入した<style>のCSSルールが適用されて背景色が反転します。「lang切替」ボタンを押すと<html lang>がjaとenの間で切り替わります。bodyAttributesとhtmlAttributesを直接指定する書き方(<Helmet bodyAttributes={{ class: mode }}>)でも同じ結果になるので、好みに応じて書き分けられます。
まとめ
React-Helmetを使うと、SPAで避けて通れない「<head>の動的な書き換え」を、useEffectでdocumentを直接操作するコードを書かずに、Reactらしい宣言的なコンポーネントとして扱えます。
- ページ遷移に合わせた
title・meta descriptionの切り替え - ネストした
Helmetによる、レイアウト共通設定とページ個別設定の使い分け - OGP用の複数
metaタグをデータ駆動でまとめて管理 html/body属性やstyleタグまで含めた、ページ全体の見た目の切り替え
このように、SEO対策からダークモード切り替えまで、<head>まわりの管理を一手に引き受けてくれるのがReact-Helmetの強みです。ルーティングと組み合わせれば、ページごとのSEO設定を各ページコンポーネントの中に閉じ込められるので、コードの見通しもよくなります。まずは既存のReactプロジェクトの1ページにだけ導入して、document.titleが切り替わる様子を試してみてください。
