このブログに目次を追加しました。
Drupalのモジュールは使わず、テーマ側のJavaScriptで記事本文から見出しを取得して自動生成しています。
目次だけを見ると、単なるユーザビリティ改善に見えるかもしれません。
しかし実際に作ってみると、目次は「見た目」の問題ではなく、ページの構造をどう設計するかという話だと分かります。
この記事では、目次の実装方法だけでなく、見出しをどう設計したか、SEOやAEOについてどう考えているかも含めて記録しておきます。
なお、AEOについてはGoogleなどが「この方法でAIに引用されやすくなる」と公式に定めたものではありません。 そのため、AIに関する部分には私自身の考察も含まれます。
DrupalのモジュールではなくJavaScriptで目次を作った理由
Drupalには、記事の見出しから目次を生成するためのモジュールがあります。
要件が標準的であれば、モジュールを利用する方が早いでしょう。
今回それを使わなかった理由は単純です。
今回必要だった処理は、
- 本文からh2を取得する
- 見出しにIDを付ける
- 目次を生成する
- 見出しへ移動できるようにする
これだけだったからです。
この程度の処理であれば、JavaScriptだけで十分対応できます。
Drupalのモジュールを1つ追加すると、便利になる一方で、Drupal本体やPHPのバージョンアップ時に確認する対象も増えます。
小規模なサイトでは、必要以上に依存関係を増やさないことにも意味があります。
もちろん、目次の表示条件や階層、管理画面からの細かな設定などが必要なら、モジュールを使う方が適しています。
「モジュールを使わない方が正しい」という話ではなく、今回の要件では自前実装の方がシンプルだった、という判断です。
目次の基本的な仕組み
今回は記事本文のh2を取得して、本文の先頭に目次を挿入しています。
基本的な処理は非常に単純です。
var headings = body.querySelectorAll('h2');if (headings.length < 6) { return;}
h2が6個未満の記事では、目次を表示しません。
短い記事の場合、目次を見るよりそのまま読み進めた方が早いからです。
目次を表示するために、記事を書くたびに何か設定する必要もありません。
既存の記事にも自動的に適用されます。
記事側の運用負担を増やさないことも、この方式を選んだ理由の一つです。
日本語の見出しにIDを付ける
目次から見出しへ移動するためには、見出しに識別子であるIDを付ける必要があります。
そこで、見出しには連番を基本としたIDを付けています。
sec-1sec-2sec-3-composer
記事を書く側が毎回IDを設定する必要はありません。
これも自動生成することで、記事作成時の作業を減らしています。
固定ヘッダーによる「見出しが隠れる問題」
実装してみて実際に問題になったのが、固定ヘッダーです。
ヘッダーを画面上部に固定している場合、目次から見出しへ移動すると、見出しがヘッダーの裏側に隠れてしまいます。
そのため、移動先をヘッダーの高さ分だけずらしています。
var top = target.getBoundingClientRect().top + window.pageYOffset - 100;
CSS側にも同じ対策を入れています。
.blog-article__body h2 { scroll-margin-top: 100px;}
これは、JavaScriptを経由せず、URLのハッシュを使って直接ページを開いた場合にも必要になるためです。
スムーズスクロールとの競合を避ける
このサイトではスムーズスクロールの仕組みを利用しています。
そのため、目次側でも通常のスクロール処理を実行すると、スクロール処理同士が競合します。
ライブラリが利用できる場合はそちらを使い、利用できない場合はブラウザ標準のスクロールを使うようにしています。
if (window.__lenis) { window.__lenis.scrollTo(top, { duration: 0.8 }); return;}window.scrollTo({ top: top, behavior: 'smooth' });
スマートフォンの開閉にはdetailsを使う
スマートフォンでは、目次を最初から展開すると画面を占有します。
そこで、スマートフォンでは折りたたんだ状態にしています。
開閉処理をJavaScriptで自作することもできますが、今回はHTMLの標準要素であるdetailsを利用しました。
ブラウザ標準の機能に任せられる部分は、できるだけ自分で作らない。
こうした考え方は、長期間運用するサイトでは意外と重要です。
公開後に「番号が二重になる」問題が発生した
実装して公開したあと、1つ問題が見つかりました。
目次を順序付きリストのolで作ったため、ブラウザが自動的に番号を付けます。
一方、記事本文の見出しにも「1.」「2.」という番号を手動で入れていました。
その結果、番号が二重になりました。
さらに、記事の導入部分には番号が付いていないため、目次と本文の番号も一致しません。
対処として、目次側では自動採番をやめました。
記事の見出しに番号が書かれていれば、その番号をそのまま利用します。
番号がなければ、番号なしで表示します。
記事側の書き方を完全に統一する方法もあります。
しかし、私は運用ルールを増やすより、多少書き方が違っても動く仕組みにする方を選びました。
Webサイトは、作った瞬間よりも、その後何年も運用する時間の方が長いからです。
その後、方針を変えました(2026年8月追記)
記事が30本を超えたころ、この方式の問題が見えてきました。
見出しに手で番号を書く運用だと、記事の途中に見出しを追加したときに、それ以降をすべて振り直す必要があります。実際、番号を振り直し忘れて階層がずれている記事がいくつも見つかりました。
そこで、番号は本文に書かず、CSSのカウンター機能で自動採番する方式に変えました。目次側も同じ順序で番号を付けるため、本文と目次がずれることはありません。
当初は「多少書き方が違っても動く仕組み」を選びましたが、記事数が増えると書き方の揺れそのものが問題になると分かったためです。
見出しはデザインではなく、文書構造として考える
ここからは、目次を作って改めて感じたことです。
HTMLの見出しは、文字を大きくするためのものではありません。
h1、h2、h3という階層によって、文書の構造を表します。
そのため、文字を大きくしたいからh2にする、といった使い方は避けます。
見た目はCSSで変更できます。
HTMLでは意味を表し、CSSでは見た目を表す。
この役割分担を崩さないことが重要です。
サイト全体の構造をどう設計するかは、サイト設計とSEO・AEO|検索とAIの両方に見つけてもらう構造の作り方でまとめています。
h1は、このサイトでは1つにする
「1ページにh1は必ず1つ」という説明を見かけることがありますが、HTMLの仕様上、複数のh1が存在すること自体が直ちにエラーになるわけではありません。
ただし、このブログではページの主題を明確にするため、h1は1ページに1つという設計にしています。
Drupalでは、ページタイトルを出力するブロックと、記事テンプレート側のタイトルが重複して、意図せずh1が2つになることがあります。
このブログでは、記事のカテゴリや日付をタイトルの上に表示したかったため、h1をテンプレート側で管理する構成に変更しました。
重要なのは、CSSで片方を隠すことではありません。
不要なHTMLそのものを出力しない。
この方が、構造として分かりやすくなります。
h2・h3の階層を意識する
見出しの階層も重要です。
たとえば、h2の次にいきなりh4を置くような構成は避けています。
h2が大きな章、h3がその中の項目、というように、文書の階層として考えます。
これはSEOだけの話ではありません。
スクリーンリーダーを利用している人は、見出しを使ってページ内を移動することがあります。
見出しの階層が分かりやすければ、ページ全体の構造も把握しやすくなります。
もちろん、見出しの階層を整えたからといって、それだけで検索順位が上がるわけではありません。
重要なのは、検索エンジンのためだけではなく、人間にも機械にも理解しやすいHTMLを作ることです。
目次そのものにSEO効果を期待しない
ここは誤解されやすいところです。
「目次を設置したから検索順位が上がる」というものではありません。
Googleの検索結果にページ内の見出しへのリンクが表示されることがありますが、目次を設置すれば必ず表示されるわけでもありません。
目次をSEO施策として考えるより、まずユーザーが記事を読みやすくするための機能として考えた方がよいでしょう。
特に長い記事では、
- この記事には何が書かれているのか
- 自分が知りたい情報はどこにあるのか
- 最後まで読む必要があるのか
を短時間で判断できます。
目次の価値は、検索順位を直接上げることではなく、ページの内容を理解しやすくすることにあります。
実際に公開後の検索結果がどう動いたかは、AI検索では1番目、Google検索では3位|ホームページ公開から2週間の観察記録に記録しています。
目次を作ると、見出しの質が見える
今回、目次を実装して一番面白かったのはここです。
見出しを目次として並べてみると、記事そのものの構成が可視化されます。
| 分かりにくい見出し | 内容が分かる見出し |
|---|---|
| はじめに | DrupalのモジュールではなくJavaScriptで目次を作った理由 |
| 実装について | 日本語の見出しにIDを付ける |
| 注意点 | 固定ヘッダーによる「見出しが隠れる問題」 |
判断基準はシンプルです。
見出しだけを拾い読みして、記事の内容が想像できるか。
これができていれば、目次だけでも記事の全体像が分かります。
逆に、目次を読んでも何の記事なのか分からないのであれば、目次の問題ではありません。
記事そのものの構成が整理されていない可能性があります。
目次を作って最も役に立ったのは、実はこの部分でした。
AEOを考えるなら、見出しは「意味が分かる言葉」にする
AEOという言葉が使われるようになり、「AIに拾われるためには何をすればいいのか」と考える人も増えています。
ただし、AIに引用されるための単純なチェックリストが公式に公開されているわけではありません。
そのため、ここからは私自身の考えです。
1つの見出しに、1つの話題を置く
長い記事では、1つの見出しの下に複数の話題を詰め込まないようにしています。
たとえば「注意点」という見出しの下に、メール、セキュリティ、料金、バックアップについて書くより、
- メール設定で確認すること
- セキュリティ上の注意点
- 料金を確認するときのポイント
- 移行前にバックアップする理由
と分けた方が、読者にも機械にも内容が分かりやすくなります。
AIがどのような処理をしているかを外部から完全に確認することはできません。
だからこそ、どこを切り出されても意味が通る文章を作っておく方が合理的だと考えています。
見出しは「問い」に近づけることもできる
「目次のSEO効果」という見出しより、
「目次にSEO効果はあるのか?」
とした方が、読者が知りたいことが明確になります。
ただし、すべての見出しを疑問形にする必要はありません。
重要なのは疑問形にすることではなく、見出しを読んだだけで、その章で何を説明するのか分かることです。
結論を見出しの直後に置く
見出しの直後に結論を書き、その後に理由や具体例を続ける構成も有効です。
例えば、
「目次にSEO効果はあるのか?」
目次を設置しただけで検索順位が上がるわけではありません。
その理由は、目次の主な役割が検索順位を操作することではなく、ユーザーがページの構造を理解しやすくすることだからです。
このようにすると、最初の数行だけ読んでも内容が分かります。
いわゆるPREP型に近い考え方です。
- P(Point):結論
- R(Reason):理由
- E(Example):具体例
- P(Point):結論の再確認
これはAIのためだけではありません。
人間にとっても読みやすい構成です。
ページ内リンクを用意する
目次から各見出しへリンクすると、ページ内の特定位置をURLで指定できるようになります。
https://example.com/article#sec-3
この仕組みは、読者が特定の項目を共有したい場合にも便利です。
AIがどのようにこのURLを利用するかは分かりませんが、情報の位置を明確に指定できる状態にしておくこと自体には意味があります。
構造化データも合わせて考える
このブログでは、記事情報とパンくず情報を構造化データとして出力しています。
見出し構造が「記事の中身の構造」を表すのに対して、構造化データは「記事そのものの属性」を表します。
例えば、
- 誰が書いたのか
- いつ公開されたのか
- いつ更新されたのか
- どのカテゴリに属するのか
- どの組織が発行したのか
といった情報です。
見出しと構造化データは役割が違います。
だからこそ、両方を適切に設計することで、ページの内容と属性をそれぞれ明確にできます。
構造化データの実装方法は、構造化データでサイト全体をつなぐ|DrupalでJSON-LDを実装した方法と考え方で詳しく書いています。
あえて実装しなかったもの
今回は、すべての機能を盛り込むことはしませんでした。
h3は目次に含めない
目次にはh2だけを含めています。
h3まで含めると階層が深くなり、目次そのものが長くなります。
今回は、h2だけで記事の骨格が分かる構成を基本としています。
追記(2026年8月):その後、h3も目次に含めるように変更しました。記事が増えて1本あたりが長くなり、h2だけでは中身が分かりにくくなったためです。ただし番号は付けず、字下げと文字サイズの差だけで階層を示しています。h3には番号のない見出しも多く、一律に採番すると不自然になるからです。
現在位置の強調表示は付けない
スクロールに合わせて現在読んでいる見出しを目次側で強調する実装もあります。
ただ、現状ではコードを増やすほどの必要性を感じませんでした。
必要になれば後から追加できます。
目次を2段組にしない
目次を2段組にすれば縦の長さは短くできます。
しかし、上から下へ読むという自然な動きが途切れます。
項目数が少ない記事では、かえって不自然です。
そのため、現在は1列で表示しています。
目次を作って分かったこと
目次の実装自体は、それほど難しいものではありません。
JavaScriptで記事本文のh2を取得し、IDを付け、リンクを生成する。
それだけなら、比較的少ないコードで実装できます。
しかし、実際に作ってみて分かったのは、目次を作ることより、目次に並べる見出しを設計することの方が重要だということです。
見出しを並べたときに記事の内容が見えないのであれば、目次を改善するのではなく、記事構成そのものを改善した方がいい。
そして、これはSEOやAEO以前の問題でもあります。
人間が読んで分かりやすい。
HTMLとして構造が明確になっている。
そのうえで、検索エンジンやAIにも内容を理解しやすい。
私はこの順番で考えています。
まとめ
Drupalで目次を自動生成すること自体は、難しい処理ではありません。
今回の記事で重要なのは、むしろ目次そのものではありません。
目次を作ることで、記事の構造を客観的に確認できるようになったことです。
見出しだけを並べても内容が分からないなら、記事の構成を見直す。
h2とh3の関係が不自然なら、階層を見直す。
見出しの直後だけ読んでも意味が分からないなら、文章の順番を見直す。
こうした基本を積み重ねた方が、SEOやAEOという言葉だけを追いかけるより、長期的には意味があります。
目次は、その状態を確認するための小さなツールです。
検索エンジンやAIにどう見せるかを考える前に、人間が読んで理解できる構造になっているか。
まずそこから設計する。
私はWebサイトを作るときも、同じ考え方をしています。
自社サイトの記事構成やHTML、SEO・AEOを含めて「今の作り方でいいのか分からない」という場合は、ご相談ください。
デザインだけではなく、情報設計・HTML構造・CMS・検索を含めて、現在のサイト構成を確認するところからお手伝いします。
必要がなければ、無理に作り替えることはおすすめしません。