Next.js、Gatsby、静的サイト向けCookie同意管理:開発者のための実装ガイド
静的サイトにおける同意管理の課題
Next.js、Gatsby、Nuxt.js のようなモダンなJavaScriptフレームワークは、Webページの構築と配信方法にパラダイムシフトをもたらしました。ページはビルド時またはサーバー側でプリレンダリングされ、その後クライアント側でハイドレーションされます。これによりCookie同意には特有の課題が生じます。すなわち、同意バナーはあらゆるトラッキングスクリプトが実行される前に準備されていなければならない一方で、ページ自体はすでにレンダリングされ、エッジでキャッシュされている可能性があるという点です。
従来のCMPは、サーバーレンダリングされたPHPや、上から下へ直線的に読み込まれるシンプルなHTMLページ向けに設計されていました。コード分割、遅延読み���み、ストリーミングSSRといった仕組みを持つフレームワークの世界では、こうした前提が崩れます。これらの環境で正しく同意を扱うには、レンダリングパイプラインを理解する必要があります。
タイミングが想像以上に重要な理由
通常のHTMLページでは、<head>内で他のスクリプトより前にCMPスクリプトを配置するのは単純です。しかし Next.js App Router や Gatsby では状況がより複雑になります。
- プリレンダリングされたHTMLが先に届く: ブラウザはCDNやサーバーから完全なHTMLを受け取ります。そのHTML内にインラインスクリプトやサードパーティタグが埋め込まれていると、同意ロジックが読み込まれる前にそれらが実行される可能性があります。
- ハイドレーションギャップ: ReactのハイドレーションはHTMLが描画された後に行われます。もし同意バナーがReactコンポーネントとして実装されている場合、ハイドレーションが完了するまでそのコンポーネントは機能しません。このギャップの間に、Googleタグやアナリティクススクリプトが同意なしで発火してしまうことがあります。
- エッジキャッシュの複雑さ: ISR(Incremental Static Regeneration)やエッジ関数を利用している場合、HTMLはキャッシ���されます。クライアント側の仕組みなしに、同意に依存するロジックをキャッシュ済みHTMLへ動的に注入することはできません。
ここでの核心原則は次の通りです。同意はコンポーネントレベルではなく、スクリプトレベルで確立されていなければならないということです。ハイドレーション後に初めてインタラクティブになるReactコンポーネントとして同意バナーをレンダリングしても、それでは遅すぎます。
Next.js App Router での統合
App Router を備えた Next.js 13+ では、スクリプトの扱い方が新しくなりました。ここでは同意統合の推奨アプローチを説明します。
ステップ1: ルートレイアウトでCMPスクリプトを読み込む
ルートの layout.tsx で、Next.js の Script コンポーネントに beforeInteractive ストラテジーを指定して使用します。これにより、ハイドレーションが始まる前に初期HTMLドキュメントへスクリプトが挿入されます。
beforeInteractive ストラテジーは極めて重要です。デフォルトの afterInteractive ストラテジーでは、ス��リプトはハイドレーション後に読み込まれるため、同意には遅すぎます。beforeInteractive を使うことで、CMPスクリプトはサーバーレンダリングされたHTMLに含まれ、ページの読み込みと同時に実行されます。
ステップ2: Googleタグの前にデフォルト同意を設定する
Google Tag Manager や gtag.js のスニペットより前に、デフォルトの同意状態を設定するインラインスクリプトを挿入します。これにより、たとえCMPバナーが表示される前にGTMが読み込まれたとしても、拒否をデフォルトとする設定が尊重されます。
このインラインスクリプトは、ルートレイアウトの <head> 内で、CMPおよびGTMスクリプトより前に配置する必要があります。Next.js では、この目的のために <head> 要素内に通常の <script> タグを記述できます。
ステップ3: ルート変更への対応
シングルページアプリケーションとしてのナビゲーションでは、CMPスクリプトは一度だけ読み込まれ、ルート変更時にページ全体のリロードは発生しません。CMPはクライアントサイドのナビゲーション��またいで持続する必要があります。FlexyConsent はこれを自動的に処理します。一度読み込まれれば、再初期化なしであらゆるルート変更にわたって有効なままです。
Next.js Pages Router での統合
Pages Router をまだ利用しているプロジェクトでは、アプローチはほぼ同じですが、ルートレイアウトの代わりに _document.tsx を使用します。カスタムDocumentクラスの <Head> コンポーネント内にCMPスクリプトを配置します。beforeInteractive ストラテジーは Pages Router でも同様に機能します。
重要な違いは、_document.tsx はサーバーでのみレンダリングされるため、ここに記述した同意ロジックは必ず初期HTMLペイロードに含まれるという点です。
Gatsby 静的サイトでの統合
Gatsby はビルド時に完全な静的HTMLを生成します。リクエスト時のサーバーサイドレンダリングは行われません。これは一部を単純化する一方で、別の点を複雑にします。
gatsby-ssr.tsxを使用する: CMPスクリプトを全ページの<head>に注入します。onRenderBodyAPI を使うことで、すべての静的HTMLファイルに含まれるhead用スクリプトを追加できます。- 同意を遅延読み込みするGatsbyプラグインを避ける: 一部のコミュニティ製プラグインは、同意をReactコンポーネントでラップし、ハイドレーション後にのみマウントします。これは前述のタイミングギャップを生み出します。
- デフォルト同意はインラインで配置する:
gatsby-ssr.tsxのsetHeadComponentsを使って、デフォルトの同意状態を設定するインラインスクリプトを追加します。このスクリプトは静的HTML内に含まれ、即座に実行されます。
Gatsby のビルド時生成というアプローチにより、CDN上のすべてのHTMLファイルに同意スクリプトが含まれます。これは実際には理想的であり、失敗したりキャッシュを誤るサーバーロジックが存在しないという利点があります。
Nuxt.js における考慮点
Nuxt.js(Vueベース)は独自のパターンを持っています。Nuxt 3 では、useHead コンポーザブルや nuxt.config.ts の app head 設定を使って、CMPスクリプト��グローバルに追加します。Nuxt は body: false オプション(スクリプトをheadに配置)や、ノンブロッキング読み込みのための async 属性をサポートしています。
Nuxt のサーバーサイドレンダリングモードでも、同じ原則が適用されます。CMPスクリプトは、マウント後にVueコンポーネントから動的に注入されるのではなく、初期HTMLレスポンスに含まれていなければなりません。
レイアウトシフトを避ける
同意バナーは、SEOランキングに影響するCore Web Vitalsの一つであるCumulative Layout Shift(CLS)の原因として悪名高い存在です。ページがレンダリングされた後にバナーがポップインすると、コンテンツを押し下げたり、予期せずオーバーレイしたりします。
同意バナーによるCLSを最小限に抑えるための戦略:
- 画面下部に配置するバナーを使う: ビューポート下部のバナーはページコンテンツをシフトさせません。CLSに最も優しいアプローチです。
- スペースを予約する: 上部バナーを使わざるを得ない場合は、CSSで縦方向のスペースを���前に確保し、バナーが描画される前からページレイアウトがそれを見込むようにします。
- 読み込み時のモーダルオーバーレイを避ける: ページがレンダリングされた後に全画面の同意ウォールが表示されると、レイアウトが不安定に感じられます。ウォールが必要な場合は、初期ページ状態の一部としてレンダリングします。
- CMPをhead内で同期的に読み込む: CMPをhead内のレンダーブロッキングスクリプトとして読み込むと、バナーは後からポップインするのではなく、初回ペイントの一部として表示できます。
FlexyConsent のフレームワーク非依存アプローチ
FlexyConsent はコンポーネントレベルではなくスクリプトレベルで動作することで、どのフレームワークにも、あるいはフレームワークがなくても動作するように設計されています。これが重要である理由は次の通りです。
- 単一の非同期スクリプトタグ: 必要なのは
<head>内の1つの<script>タグだけです。npmパッケージのインストールも、フレームワーク固有のラッパーも、ビルド設定の変更も不要です。 - 同意デフォルトが即時に発火: スクリプトは、DOM操作やコールバックよりも先に、最初のアクションとして Consent Mode V2 のデフォルトを設定します。これにより、Googleタグは最初の1ミリ秒から同意を尊重します。
- DOMへの依存なし: 同意ロジックはReact、Vue、Svelteのハイドレーションを待ちません。フレームワークのライフサイクルとは独立して動作します。
- SSG、SSR、ISR、CSR すべてに対応: プレーンなスクリプトであるため、ページが静的生成、サーバーレンダリング、インクリメンタル再生成、クライアントサイドレンダリングのいずれであっても、同じように機能します。
開発者向けヒント: CMPが正しく統合されているかを確認する最も簡単なテストは、ブラウザのNetworkタブを開き、ドメインをGoogleでフィルタしてからページをリロードすることです。同意デフォルトコマンドがコンソールに表示される前に、Googleへのリクエストが発生してはなりません。もし発生している場合、CMPの���み込みが遅すぎます。
FlexyConsent の無料プランはページビュー数無制限で、Next.js、Gatsby、Nuxt、Astro、SvelteKit、Remix、プレーンHTMLで動作します。どの環境でも統合方法は同じで、適切に配置された1つのスクリプトタグだけです。