🥷
Next.jsで忍者AdMax広告が一つしか表示されない問題の解決方法
問題の概要
Next.jsのApp Routerを使用したSPA(Single Page Application)で忍者AdMaxの広告を複数ページに配置した際、以下の問題が発生しました。
現象
-
ページ遷移時に広告が表示されない
- トップページで広告が表示されている状態で別のページに遷移すると、遷移先の広告が表示されない
-
ページをリロードすると正常に表示される
- F5キーやブラウザの更新ボタンでページをリロードすると、広告が正常に表示される
-
URL直接変更では正常に動作する
- ブラウザのアドレスバーでURLを手動で変更してページ遷移すると、広告が正常に表示される
-
同一ページ内で複数の広告のうち一つしか表示されない
- 同じページに複数の広告コンポーネントを配置しても、どれか一つしか表示されない
原因の分析
t.jsとは?
t.jsは忍者AdMaxの広告配信用コアスクリプトです。以下の役割を持っています:
主な機能
-
広告の初期化と配信
- ページ上の
window.admaxads配列を読み取る -
data-admax-id属性を持つDOM要素を検索 - 広告IDに基づいて適切な広告コンテンツを取得
- DOM要素内に広告を挿入・表示
- ページ上の
-
実行フロー
// ① ページ内で広告設定を配列に追加
(window.admaxads = window.admaxads || []).push({
admax_id: "広告ID",
type: "banner"
});
// ② t.jsスクリプトを読み込む
<script src="https://adm.shinobi.jp/st/t.js"></script>
// ③ t.jsが実行されると...
// - admaxads配列の内容を処理
// - 対応するDOM要素を探す
// - 広告サーバーと通信して広告を取得
// - DOM要素に広告を挿入
根本原因
このt.jsスクリプトは、通常のページ遷移(ページ全体のリロード)を前提とした設計になっています。
具体的には:
-
スクリプトの初期化タイミング
-
t.jsは読み込まれた時点でwindow.admaxads配列を処理し、DOM上の広告要素を探して広告を挿入する - 処理後、配列を空にするか、既に処理済みのフラグを立てる
-
-
Next.jsのクライアントサイドルーティング
- Next.jsのApp Routerは、ページ遷移時にJavaScriptでコンテンツを差し替える(SPAナビゲーション)
- ブラウザの完全なページリロードは発生しない
- そのため、
t.jsスクリプトが再実行されず、新しいページの広告が初期化されない
-
グローバルスクリプトの再利用問題
- 一度読み込まれた
t.jsスクリプトはグローバルに存在し続ける - 新しいページで
window.admaxads配列に広告設定を追加しても、スクリプトは既に初期化済みのため処理されない
- 一度読み込まれた
試した方法と失敗した理由
❌ 方法1: PostScribeライブラリの使用
import PostScribe from '@marshallku/react-postscribe';
export default function NinjaAdMax() {
const adHtml = `
<div class="admax-ads" data-admax-id="xxx"></div>
<script>(admaxads = window.admaxads || []).push({admax_id: "xxx", type: "banner"});</script>
<script src="https://adm.shinobi.jp/st/t.js" async></script>
`;
return <PostScribe html={adHtml} />;
}
失敗理由: PostScribeはスクリプトを順次実行できるが、グローバルに一度読み込まれたt.jsは再実行されない
❌ 方法2: useEffectでスクリプトを動的に読み込み
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://adm.shinobi.jp/st/t.js';
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
失敗理由: スクリプトタグの追加・削除はできるが、t.js自体が再初期化のメカニズムを持っていない
❌ 方法3: キャッシュバスティング
const script = document.createElement('script');
script.src = `https://adm.shinobi.jp/st/t.js?_=${Date.now()}`;
失敗理由: URLは変わるがスクリプト内部の処理ロジックは変わらないため、同じ問題が発生
解決方法:iframe方式
iframeとは?
iframe(inline frame)は、HTMLページ内に別のHTMLページを埋め込むための要素です。
iframeの特徴
-
独立した環境: iframeは独自の
windowオブジェクトとdocumentオブジェクトを持つ - 完全な分離: 親ページのJavaScript、CSS、グローバル変数と干渉しない
- サンドボックス化: セキュリティ上も分離された環境で実行される
<!-- 基本的な使い方 -->
<iframe src="https://example.com"></iframe>
<!-- インラインHTMLの埋め込み(今回使用する方法) -->
<iframe srcdoc="<!DOCTYPE html><html>...</html>"></iframe>
✅ 最終的な解決策
各広告を独立したiframe内に配置することで、完全に分離された環境で広告スクリプトを実行します。
実装コード
'use client';
import { Box } from '@mantine/core';
import { useEffect, useRef } from 'react';
export default function NinjaAdMaxSidebar() {
const adMaxId = '対象のadMaxId';
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
// 広告用のiframeを作成
const iframe = document.createElement('iframe');
iframe.style.cssText = 'width:160px;height:600px;border:none;display:block;margin:0 auto;';
iframe.srcdoc = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<div class="admax-ads" data-admax-id="${adMaxId}" style="display:inline-block;width:160px;height:600px;"></div>
<script type="text/javascript">
(window.admaxads = window.admaxads || []).push({admax_id: "${adMaxId}", type: "banner"});
</script>
<script type="text/javascript" charset="utf-8" src="https://adm.shinobi.jp/st/t.js" async></script>
</body>
</html>
`;
container.appendChild(iframe);
return () => {
container.innerHTML = '';
};
}, [adMaxId]);
return (
<Box
ref={containerRef}
style={{
width: 160,
height: 600,
margin: '16px auto',
textAlign: 'center',
}}
/>
);
}
なぜこの方法で解決するのか
1. 完全な環境分離
- 各iframeは独立した
windowオブジェクトを持つ - 親ページのグローバル変数やスクリプトと干渉しない
- 各iframeで
t.jsが独立して実行される
2. 確実な初期化順序
- iframe内でHTMLが読み込まれる際、以下の順序が保証される:
- 広告div要素の作成
-
window.admaxads配列への追加 -
t.jsスクリプトの読み込みと実行
3. クリーンアップの確実性
- コンポーネントがアンマウントされる際、iframe全体が削除される
- 次回マウント時は完全に新しいiframeが作成され、広告が再初期化される
4. SPAルーティングへの対応
- ページ遷移時、古いページのコンポーネントがアンマウントされiframeが削除される
- 新しいページのコンポーネントがマウントされ、新しいiframeが作成される
- 各ページの広告が独立して動作する
まとめ
問題の本質
- 忍者AdMaxは従来のMPA(Multi Page Application)を前提とした設計
- Next.jsのようなSPAでは、ページ遷移時にスクリプトが再初期化されない
解決のポイント
- iframe方式で各広告を完全に分離
- 各iframeが独立したwindowコンテキストを持つ
- ページ遷移時にiframeごと作り直すことで、確実に広告を再初期化
メリット
- ✅ ページ遷移時も確実に広告が表示される
- ✅ 同一ページ内に複数の広告を配置できる
- ✅ クリーンアップが確実に行われる
- ✅ 他のJavaScriptとの干渉がない
注意点
- iframe内の広告はiframeのサイズに制約される
- 広告のクリックイベントなどは親ページと分離される(通常は問題なし)
- SEOの観点ではiframe内のコンテンツはインデックスされない(広告なので問題なし)
この方法は忍者AdMaxだけでなく、従来のページ遷移を前提とした他の広告サービスやスクリプトでも応用可能です。
Discussion