Claude Code で 3サイト114記事のアフィリンク監査・再配置を半日で完走させた実装ログ (2026-05-21)
lab 10記事 + prompts 50記事 = 60記事 完全機会ロス状態から、本番デプロイまでの工程記録

個人運営しているメディアサイト3つ (ai-pick.tech / lab.ai-pick.tech / prompts.ai-pick.tech) のアフィリンク配置を、2026年5月21日に半日 (約4時間) で全面見直ししました。lab と prompts は60記事すべてアフィリンクがゼロだった状態 (完全機会ロス) で、ai-pick は配置はあるが文脈ミスマッチ。Claude Code を使った監査→設計→実装→ビルド→本番デプロイの全工程を、実コードと実コミット量つきで記録します。サムネを「とりあえず作る」だけのAI記事ではなく、本物の作業ログとして残す試みです。
Before の状態 — grep でわかった完全機会ロス
監査の最初に走らせたのが、3サイト全記事の HTML を px.a8.net | moshimo | af.moshimo | admax | shinobi.jp でカウントするだけの単純な grep でした。結果は予想以上に深刻でした。
| サイト | 記事数 | アフィ配置記事数 | 機会ロス率 |
|---|---|---|---|
| ai-pick.tech | 54本 (日本語) | 54本 (全配置あり) | 0% (ただし文脈ミスマッチ多) |
| lab.ai-pick.tech | 10本 | 0本 | 100% |
| prompts.ai-pick.tech | 50本 | 0本 | 100% |
lab と prompts のビルダーには、そもそもアフィブロックを描画する関数自体が存在していませんでした。lib/site-html-builder.mjs (808行) と prompts-site/scripts/build.mjs (815行) を grep で検索しても、moshimo も px.a8.net も affiliate も1件もマッチしませんでした。一方 ai-pick は html-builder.mjs の1130行付近に moshimoRakutenBlock(lang) と a8HostingBlock(lang) が定義されていて、全記事一律で「楽天市場TOP」と「エックスサーバー/さくら/XServerドメイン」のリンクを描画する設計でした。
設計方針 — 4つの非自明な判断
監査結果を踏まえて、実装前に4つの設計判断をしました。
判断1: saas-diary.com は完全除外
姉妹サイトの一つ saas-diary.com は AdSense 主審査用として運営しており、申請日まではアフィリンクで「汚さない」運用方針です。同じ site-html-builder.mjs を lab.ai-pick.tech と共有しているため、ビルダー側に「サイトごとの affiliate ON/OFF」スイッチを入れる必要がありました。site-config.json の affiliate.enabled フラグで分岐させる設計に決めました。
判断2: カテゴリ別マッピングを site-config 側に持たせる
「楽天本検索」をカテゴリごとに違うキーワードで貼るなら、マッピング情報をどこに置くかが分かれ目になります。コードに埋め込むと将来カテゴリ追加するたびにビルダー改修が必要になるため、site-config.json の affiliate.categoryMap に持たせる設計にしました。これで lab に新カテゴリを追加するときも JSON 1行追加で済みます。
判断3: 既存稼働中のアフィタグのみで完走させる
新規 ASP (Canva Pro / Notion / Udemy 等) の提携申請は本人作業として並行で進める前提にし、Claude Code 単独完走の範囲は「既に契約済みの もしも楽天 + A8 ホスティング3種」だけに絞りました。これで本人の作業を完全に並行化でき、私側の作業がブロックされません。
判断4: ステマ法対応の [PR] 表記を最初から入れる
アフィブロックを後から [PR] 化するのは漏れが出やすいので、最初から .ad-zone-label 内に <span class="ad-disclosure">[PR]</span> を組み込んだ HTML テンプレに。
実装 — 3ビルダーへの追加コード
site-html-builder.mjs (lab/saas-diary 共通)
新規追加した affiliateBlock(article, siteConfig) 関数は、siteConfig 駆動でカテゴリ別アフィをまとめて描画します。
export function affiliateBlock(article, siteConfig) {
if (!siteConfig.affiliate?.enabled) return '';
const catKey = article.category;
const mapping = siteConfig.affiliate?.categoryMap?.[catKey];
if (!mapping) return '';
const blocks = [];
if (mapping.rakutenKeyword) {
blocks.push(rakutenBlockHtml(mapping.rakutenKeyword, mapping.rakutenLabel));
}
if (mapping.a8Hosting?.length > 0) {
blocks.push(a8HostingBlockHtml(mapping.a8Hosting));
}
return blocks.length
? `<aside class="affiliate-wrap">${blocks.join('\n')}</aside>`
: '';
}
buildArticleHtml の中で本文 (article.body_html) と author-box の間に affiliateBlock(article, siteConfig) を1行差し込むだけで、全サイト・全記事に反映されます。
site-config.json への categoryMap 追記
lab/site-config.json には次の5カテゴリ分のマッピングを追加しました。
"affiliate": {
"enabled": true,
"categoryMap": {
"NOCODE": {
"rakutenKeyword": "ノーコード 自動化",
"a8Hosting": ["xserver"]
},
"WORKFLOW": { ... },
"RPA": { ... },
"AI_TOOLS": { ... },
"RECIPES": { ... }
}
}
saas-diary/site-config.json には対比的に "enabled": false を明示し、コメントで除外理由 (AdSense主審査用クリーンキープ) を残しました。
prompts-site の build.mjs は別実装
prompts.ai-pick.tech は別ビルダーで動いているため、別途 AFFILIATE_BY_CATEGORY マップと affiliateBlockForCategory(category) 関数を追加しました。coding/writing/analysis/translation/image の5カテゴリ × カテゴリ別 楽天検索キーワード です。
ai-pick.tech の既存関数リファクタ
html-builder.mjs 内の moshimoRakutenBlock(lang) を moshimoRakutenBlock(lang, category) に拡張し、引数 category で楽天検索キーワードを分岐させました。a8HostingBlock は AUTOMATION / TOOL_COMPARE のカテゴリのみ表示するように制限。呼び出し側 (article render) で article.category を伝播するだけの変更です。
踏み抜いた4つの落とし穴
落とし穴1: Edit ツールは事前 Read 必須
新規 site-config.json の編集を試みたとき、File has not been read yet. Read it first before writing to it. エラーで止まりました。Claude Code の Edit ツールは安全のため、編集前に同セッション内で対象ファイルを Read していないと書き込みを拒否します。新規ファイルの Edit は Write 経由、既存ファイル編集前は必ず Read を一発入れるのがフロー上の鉄則になりました。
落とし穴2: Node.js のWindowsパス解決
node コマンドで require('/c/AI_WORK/ai-pick/sites/lab/articles.json') を試したら Cannot find module '/c/AI_WORK/...' エラー。Git Bash 内では /c/ パスが使えるが Node は理解できません。cd してから相対パスで require('./articles.json') に書き直して解決。シェルでは /c/、Node では ./ または C:/ を使う、と分けて覚える必要があります。
落とし穴3: 楽天検索URLの二重 URI エンコード
もしも経由の楽天検索リンクは、外側の ?url= パラメータと内側の検索キーワードで 2段階の URI エンコードが必要でした。最終的に生成される URL はこのような構造になります。
//af.moshimo.com/af/c/click?a_id=5565673&p_id=54&pc_id=54&pl_id=616
&url=https%3A%2F%2Fsearch.rakuten.co.jp%2Fsearch%2Fmall%2F
%25E3%2583%258E%25E3%2583%25BC%25E3%2582%25B3%25E3%2583%25BC%25E3%2583%2589
%2520%25E8%2587%25AA%25E5%258B%2595%25E5%258C%2596%2F
内側で encodeURIComponent('ノーコード 自動化') して %E3%83%8E... になり、それを含む URL 全体を外側でさらに encodeURIComponent() すると %25 接頭辞が増えます。これは正しい挙動で、もしも → 楽天 の2段デコードを経て元のキーワード検索 URL に復元されます。
落とし穴4: サムネ画像の slug 連動を忘れる
新規記事の articles.json エントリで image フィールドに https://lab.ai-pick.tech/assets/thumbs/<slug>.png を指定したものの、サムネ PNG を生成し忘れて公開してしまい、本番で画像 alt 表示のみになりました。lab には _drafts/_gen-article-thumbs.mjs という sharp ベースのサムネ生成スクリプトが既にあり、これを実行するだけで欠落分を埋められます。サムネ生成は記事追加と同じターンで実行する手順に組み込みました。
本番デプロイ実績 — FTPS 1セッションで完了
3サイト分のデプロイは別々のスクリプトに分かれていますが、いずれも WING の同一 FTP アカウント (apick01@ai-pick.tech) で /public_html/<ドメイン> 配下に書き込む構造でした。
- lab.ai-pick.tech: 33 ファイル (記事10本+カテゴリ index+legal+assets)
- prompts.ai-pick.tech: 118 ops (記事50本+カテゴリ5+その他)
- ai-pick.tech: 611 ops (記事54本×4言語 = 216記事相当+カテゴリ index+legal+assets)
合計 762 ops の本番デプロイが、Claude Code から FTPS スクリプトを順番に走らせるだけで完了。途中、25 ファイルごとに FTPS セッションが再接続される実装が組まれていて、これは長時間接続でタイムアウトする WING の特性に合わせた既存の chunked deploy 機構でした。
検証 — saas-diary の汚染ゼロを grep で確認
最後に「saas-diary.com の dist 配下に1つもアフィリンクが混入していないこと」を grep で検証しました。
find sites/saas-diary/dist -name "*.html" \
-exec grep -l "px.a8.net\|moshimo\|admax\|shinobi.jp" {} \;
# → ヒット0件、完全クリーン維持を確認
これで AdSense 審査クリーンキープ要件が破られていないこと、ビルダー側のフラグ分岐が正しく動いていることが確認できました。
結論 — 半日で何ができて、何ができなかったか
4時間で完走できたのは「既存稼働中のアフィプログラムの再配置」までです。新規 ASP (Canva Pro / Notion / Udemy / テックキャンプ / DMM WEBCAMP / Impact Zapier等) の提携申請は本人作業として並行が必要で、Claude Code が代行できない領域です。一方、コード変更・ビルド・FTPS デプロイ・grep 検証はすべて自動化されており、本人の介入なしで完走しました。
本実装で組み込んだ affiliate.categoryMap 設計は、新規 ASP の提携承認後に JSON への数行追記だけで反映できるため、後続 Phase での記事改修コストを抑える構造になっています。Claude Code を「単発のタスク実行ツール」ではなく「資産化される運用基盤の構築者」として使う方が、長期的なROIは高いという実感を持ちました。
関連の実装解説は AIワークフローカテゴリ と 自動化レシピカテゴリ にも蓄積しています。同じく Claude を活用した記事生成パイプラインの工程は Claude+Gemini 記事量産パイプライン をご参照ください。