Lab

AstroでPC/SP別の広告タグを切り替える――発行タグを崩さず扱う実装

Astro製サイトで、広告ネットワークから発行されたPC/SP別のタグをそのまま保持し、必要な端末向けだけ実行する構成を整理します。template、端末判定、開発用プレースホルダーを使った実装例です。

  • Astro
  • JavaScript
  • 広告
  • フロントエンド

3分英語物語では、PC向けとスマートフォン向けで別々に発行された広告タグを使っています。

最初は、発行タグに含まれる設定値を取り出し、JavaScript側で広告リクエストを組み立てる実装にしていました。

ただ、この方法では、広告サービスから発行されたコードと実際にサイト上で動かすコードの形が離れていきます。そこで現在は、発行されたタグ自体をPC/SP別に保持し、必要な方だけを選んで実行する構成にしています。

この記事では、Astroでその切り替えをどのようにまとめているかを整理します。

PC/SPでタグが別なら、CSSだけでは切り替えられない

レスポンシブ対応というと、まずCSSのメディアクエリが思い浮かびます。

ただ、広告サービス側からPC用とSP用で別々のタグが発行されている場合、単に同じ広告をCSSで縮めればよいわけではありません。

必要なのは、表示幅に合わせて見た目だけを変えることではなく、その端末で使うべき発行タグだけを実行することです。

3分英語物語では、広告を置く場所ごとにPC用・SP用のタグをペアで管理しています。

概念的には次のような形です。

type IssuedTags = {
  pc: string | null;
  sp: string | null;
};

const issuedTags = {
  top: {
    pc: `<div>PC向けの発行タグ</div>`,
    sp: `<div>SP向けの発行タグ</div>`,
  },
};

実際の広告IDや発行コードを別の形式へ変換せず、受け取ったタグをそのまま文字列として保持する考え方です。

PCにしか用意していない枠なら、SP側は null にします。

const issuedTags = {
  side: {
    pc: `<div>PC向けの発行タグ</div>`,
    sp: null,
  },
};

これなら、「この場所にはSP用タグが存在しない」という状態もコード上で明示できます。

発行タグを自前で組み直さない

以前の実装では、発行タグに含まれる識別子などをJavaScriptの設定値として持ち、必要なDOMやスクリプト呼び出しをこちらで組み立てていました。

この方法でも表示はできますが、保守する側から見ると確認箇所が増えます。

たとえば広告が表示されないとき、

  • 発行されたタグ自体に問題があるのか
  • 設定値を転記するときに間違えたのか
  • 自前で組み立てたDOMが違うのか
  • スクリプトを呼び出す順序が違うのか

を切り分ける必要があります。

発行タグをそのまま保持しておけば、少なくとも「管理画面で発行されたコード」と「サイト側で保存しているコード」の差を小さくできます。

外部サービスの埋め込みコードは、こちらで自由に設計できる通常のUIコンポーネントとは少し性質が違います。

必要以上に分解せず、外部サービスが想定している形を残したまま、サイト側では切り替えだけを担当する方が扱いやすい場合があります。

両方のタグをそのままDOMへ置かない

PC用とSP用の発行タグを両方HTMLへ直接出力すると、不要な方のスクリプトまで実行される可能性があります。

そこで現在の実装では、Astro側で発行タグを <template> の中へ入れています。

<div data-ad-root>
  <template data-ad-tag="pc" set:html={tags.pc} />
  <template data-ad-tag="sp" set:html={tags.sp} />
</div>

<template> の中身は、そのままでは通常のページ内容として動作しません。

まずPC/SP両方の候補を保持しておき、ブラウザ側で端末を判定してから、必要なテンプレートだけを取り出します。

この構成にすると、Astro側では「どのタグを候補として持つか」、ブラウザ側では「どちらを実行するか」と役割を分けられます。

選んだタグだけを実行可能なDOMへ変える

テンプレートから中身を複製しただけでは、含まれている <script> が期待どおり実行されないため、現在の実装ではスクリプト要素を作り直してからDOMへ入れています。

処理を簡略化すると、次のような形です。

const activateIssuedTag = (
  root: HTMLElement,
  template: HTMLTemplateElement,
) => {
  const fragment = template.content.cloneNode(true) as DocumentFragment;

  fragment.querySelectorAll('script').forEach((originalScript) => {
    const executableScript = document.createElement('script');

    for (const attribute of originalScript.attributes) {
      executableScript.setAttribute(attribute.name, attribute.value);
    }

    executableScript.textContent = originalScript.textContent;
    originalScript.replaceWith(executableScript);
  });

  root.replaceChildren(fragment);
};

ここで行っているのは、広告タグそのものの内容を書き換えることではありません。

src など元のスクリプト属性と本文を新しい <script> 要素へ移し、選択した発行タグをブラウザが実行できる形でDOMへ入れています。

端末判定は広告コンポーネント内に閉じ込める

PC/SPの判定をページごとに書くと、広告を追加するたびに同じ処理が増えていきます。

3分英語物語では、端末判定も広告コンポーネント側にまとめています。

現在はUser-Agentを見ながら、iPadのデスクトップ表示も補足してPC/SPを判定しています。

const isSmartphoneOrTablet = () => {
  const ua = navigator.userAgent;
  const mobileUa =
    /iPhone|iPod|iPad|Android|Windows Phone|Mobile/i.test(ua);

  const iPadDesktopMode =
    navigator.platform === 'MacIntel' &&
    navigator.maxTouchPoints > 1;

  return mobileUa || iPadDesktopMode;
};

そのうえで、選ばれた端末用の <template> が存在すれば実行し、存在しなければ広告枠自体を取り除きます。

const platform = isSmartphoneOrTablet() ? 'sp' : 'pc';

const template = root.querySelector<HTMLTemplateElement>(
  `template[data-ad-tag="${platform}"]`,
);

if (!template) {
  root.remove();
  return;
}

端末判定の方法は、利用する広告サービスの仕様や対象端末に合わせて見直す必要があります。

重要なのは、ページ側がPC/SPの条件分岐を直接持たず、広告コンポーネントへ placement を渡せば済む状態にしておくことです。

開発環境では実広告を読み込まない

広告を組み込んだページをローカルで調整するとき、毎回実広告を読み込む必要はありません。

現在はAstroの import.meta.env.DEV を使い、開発環境では実広告の代わりにプレースホルダーを表示しています。

{
  import.meta.env.DEV ? (
    <AdPlaceholder />
  ) : (
    <div data-ad-root>...</div>
  )
}

プレースホルダーには広告枠と同程度の領域を持たせているため、実際の広告を読み込まなくても、本文との間隔やレイアウトを確認できます。

これには二つ利点があります。

一つは、開発中のリロードやCSS調整のたびに外部広告スクリプトへ依存しなくて済むことです。

もう一つは、「広告がまだ配信されていないため空白なのか」「そもそもレイアウト上に広告枠がないのか」を分けて確認しやすいことです。

ページ側は配置名だけを指定する

PC/SPのタグ、端末判定、スクリプト実行、開発環境の切り替えをコンポーネント内へまとめると、ページ側の記述は単純になります。

<Ad placement="top" />
<Ad placement="afterContent" />
<Ad placement="side" />

ページを作る側は「ここにどの広告枠を置くか」だけを指定し、内部でどの発行タグを使うかまでは知る必要がありません。

タグを差し替えるときも、各ページを探して書き換えるのではなく、広告コンポーネント側の対応表を更新できます。

広告のようにページ内の複数箇所で使う外部コードほど、配置と実装を分離しておく方が変更範囲を限定しやすくなります。

外部の発行タグは「加工する」より「包む」

今回の実装で一番大きく変えたのは、広告タグを自前の仕組みに合わせて分解するのではなく、発行された形を残したままサイト側のコンポーネントで包むようにしたことです。

Astro側が担当するのは、

  1. 配置ごとにPC/SPの発行タグを保持する
  2. 開発環境ではプレースホルダーへ切り替える
  3. 本番では端末に対応するタグだけを選ぶ
  4. 選んだタグ内のスクリプトを実行可能な形でDOMへ入れる
  5. 対応するタグがない端末では空の広告枠を残さない

という部分です。

広告サービス固有の発行コードはできるだけそのまま残し、サイト固有の都合は外側のコンポーネントで処理します。

外部サービスの埋め込みコードを複数ページ・複数端末で使うときは、この境界を分けておくと、差し替えや問題の切り分けをしやすくできます。