{"componentChunkName":"component---src-templates-blog-template-js","path":"/2026-07-19-weekly-knowledge","result":{"data":{"site":{"siteMetadata":{"title":"KenRp Blog"}},"markdownRemark":{"html":"<p><code class=\"language-text\">CraftMargin</code> は、<code class=\"language-text\">handmade-profit-calculator</code> リポジトリで運用している、ハンドメイド販売者向けの利益計算アプリである。この週の変更で一番再利用しやすかった知見は、Cloudflare Pages に載せた静的アプリを「計算できるだけのツール」から、「検索流入を受け取り、監視し、収益化の安全確認までできる運用可能なアプリ」へ段階的に寄せた実装だった。</p>\n<p>静的アプリは公開自体は簡単でも、公開後に困る点は別にある。どの検索導線が効いたか分からない、広告やアフィリエイト設定を安全に扱いづらい、配信後に最低限何を確認すべきかが属人的になりやすい、といった問題である。この記事では、Cloudflare Pages Functions と D1、静的 SEO ページ生成、production check を組み合わせて、その隙間をどう埋めたかを整理する。</p>\n<h2>この記事で解決すること</h2>\n<ul>\n<li>Cloudflare Pages に置いた静的アプリへ、重いバックエンドを増やさずに最低限の監視基盤を足したい。</li>\n<li>SEO ページ、計算フォーム、収益化導線を別々に増やすのではなく、1 つの流れとしてつなぎたい。</li>\n<li>実装だけでなく、テスト、build、deployment、release 前確認まで含めて変更境界を整理したい。</li>\n</ul>\n<h2>実装の要点</h2>\n<p>この週に扱った変更は、<code class=\"language-text\">CraftMargin</code> の公開運用を支える 3 つの層に整理できる。1 つ目は、<code class=\"language-text\">.env.example</code>、<code class=\"language-text\">affiliateLinks.ts</code>、<code class=\"language-text\">check-production-seo.mjs</code> を使った収益化設定の安全化である。広告 ID やカテゴリ別アフィリエイト URL を「入れれば出る」だけの状態にせず、未設定や危険な URL を弾きながら、本番確認を <code class=\"language-text\">pnpm verify:production</code> に寄せた。</p>\n<p>2 つ目は、<code class=\"language-text\">seo-pages.json</code> と <code class=\"language-text\">generate-seo-pages.mjs</code> を中心にした検索流入の拡張である。ロングテール検索向けの静的ページを build 時に生成し、<code class=\"language-text\">/?marketplace=...&amp;sample=...</code> の query を使ってトップの計算体験へ最短で着地させた。検索入口とアプリ本体を別物にせず、流入意図を計算開始まで引き継ぐ構造に変えた。</p>\n<p>3 つ目は、Cloudflare Pages Functions と D1 を使った監視基盤の追加である。<code class=\"language-text\">/api/events</code> で匿名イベントを受け、<code class=\"language-text\">/api/metrics/summary</code> で集計し、<code class=\"language-text\">/monitor/</code> の静的ダッシュボードで比較表示とランキングを見られるようにした。公開アプリ本体へ管理 UI を混ぜず、運用画面だけを分離したのがポイントである。</p>\n<h2>得られた知見</h2>\n<p>成果は、集客、計測、収益化確認がばらばらの施策ではなく、同じリポジトリ内で接続されたことにある。検索流入ユーザーは SEO ページから適切な marketplace と sample が入った計算フォームへ到達し、その後の <code class=\"language-text\">calculation_completed</code> や <code class=\"language-text\">paid_interest_clicked</code> のような匿名イベントは D1 に残せるようになった。これで「流入は増えたが使われたか分からない」という状態から一歩進める。</p>\n<p>実装面でも、責務の分離が進んだ。収益化リンク構築は <code class=\"language-text\">affiliateLinks.ts</code>、検索可視化は <code class=\"language-text\">searchConsole.ts</code>、監視 API は <code class=\"language-text\">functions/api/metrics/summary.ts</code>、監視 UI は <code class=\"language-text\">public/monitor/index.html</code> と分かれており、UI、集計、配信確認を別々に変更しやすい。単に機能を足したのではなく、あとから直せる形に寄せた点が大きい。</p>\n<p>検証の根拠として確認できたのは、ユニットテスト、E2E、build、production check の導線がこの週の変更に合わせて更新されていることだった。<code class=\"language-text\">pnpm check</code>、<code class=\"language-text\">pnpm test:e2e</code>、<code class=\"language-text\">pnpm build</code>、<code class=\"language-text\">pnpm verify:production</code>、<code class=\"language-text\">pnpm exec wrangler d1 migrations apply ...</code>、<code class=\"language-text\">pnpm exec wrangler pages deploy ...</code> まで手順がコードやドキュメントに落ちているため、少なくとも「どう検証して反映するか」は再現可能な状態に近づいている。</p>\n<p>一方で、source material から本番 deployment や release の成功ログをすべて確認できたわけではない。そのため、ここでの成果は「本番運用へ寄せる実装と確認導線が揃ったこと」であり、「すべての本番反映が完了したこと」ではないと切り分けておく必要がある。</p>\n<h2>実装プロセス</h2>\n<p>最初に手を入れたのは、収益化設定の安全化だった。広告やアフィリエイトは、画面が動いていても設定ミスに気づきにくい。そこで <code class=\"language-text\">.env.example</code> のダミー値を減らし、カテゴリ別 URL を <code class=\"language-text\">buildAffiliateLinks</code> で正規化し、危険な外部 URL をテスト付きで弾く構成に変えた。さらに <code class=\"language-text\">check-production-seo.mjs</code> を用意し、本番 URL の HTML、<code class=\"language-text\">robots.txt</code>、<code class=\"language-text\">sitemap.xml</code>、<code class=\"language-text\">ads.txt</code> を HTTP レベルで見られるようにした。</p>\n<p>次に、検索流入を受ける入口を増やした。ただし、記事ページだけ増やして終わると、流入とアプリ体験が分断される。そこで <code class=\"language-text\">seo-pages.json</code> のページ定義を build で静的 HTML へ変換しつつ、各ページの CTA が query param 付きで calculator に飛ぶよう <code class=\"language-text\">generate-seo-pages.mjs</code> を拡張した。これにより、<code class=\"language-text\">minne</code>、アクセサリー、梱包費のような検索意図を、トップページ上の入力初期値へそのまま受け渡せる。</p>\n<p>最後に、施策結果を観測するための監視基盤を足した。Search Console だけではアプリ内行動が見えないため、Pages Functions 経由で匿名イベントを D1 へ保存し、<code class=\"language-text\">/monitor/</code> で期間比較や販売先別ランキングを描画する構成を追加した。SEO 導線とアフィリエイトクリック計測を先につないでおいたことで、監視画面は単なるアクセス集計ではなく、改善施策を評価する場所として機能する。</p>\n<p>つまり、この週の流れは「安全に公開できる設定を作る」「検索入口を増やす」「その結果を監視できるようにする」の順だった。機能追加を日付順に積んだというより、公開運用で詰まりやすいボトルネックを外側から順に潰していった形で理解すると分かりやすい。</p>\n<h2>利用した技術</h2>\n<ul>\n<li>対象アプリは <code class=\"language-text\">CraftMargin</code>、対象リポジトリは <code class=\"language-text\">handmade-profit-calculator</code>。フロントエンドは Vite + React + TypeScript、配信先は Cloudflare Pages である。</li>\n<li>アプリ変更では <code class=\"language-text\">src/App.tsx</code>、<code class=\"language-text\">src/components/Monetization.tsx</code>、<code class=\"language-text\">src/components/affiliateLinks.ts</code>、<code class=\"language-text\">src/lib/searchConsole.ts</code>、<code class=\"language-text\">src/lib/analytics.ts</code>、<code class=\"language-text\">src/styles.css</code> を更新し、計算導線、収益化導線、可視化ロジックを分離した。</li>\n<li>実装では <code class=\"language-text\">src/content/seo-pages.json</code> と <code class=\"language-text\">scripts/generate-seo-pages.mjs</code> を使って静的 SEO ページを build 時に生成し、<code class=\"language-text\">index.html</code>、<code class=\"language-text\">public/llms.txt</code>、<code class=\"language-text\">public/llms-full.txt</code> の入口も拡張した。</li>\n<li>監視基盤では <code class=\"language-text\">functions/api/events.ts</code>、<code class=\"language-text\">functions/api/metrics/summary.ts</code>、<code class=\"language-text\">migrations/0001_events.sql</code>、<code class=\"language-text\">wrangler.jsonc</code> を使い、Cloudflare Pages Functions + D1 の最小構成で匿名イベント収集と集計を実装した。</li>\n<li>テストは Vitest の <code class=\"language-text\">affiliateLinks.test.ts</code>、<code class=\"language-text\">searchConsole.test.ts</code>、<code class=\"language-text\">seoPages.test.ts</code>、<code class=\"language-text\">App.test.tsx</code>、<code class=\"language-text\">Monetization.test.tsx</code> と、Playwright の <code class=\"language-text\">e2e/calculator.spec.ts</code> を使っている。</li>\n<li>build 導線は <code class=\"language-text\">pnpm build</code> と <code class=\"language-text\">pnpm check</code>、配信確認は <code class=\"language-text\">pnpm verify:production</code>、D1 migration と Cloudflare Pages 反映は <code class=\"language-text\">pnpm exec wrangler d1 migrations apply ...</code> と <code class=\"language-text\">pnpm exec wrangler pages deploy ...</code> が前提になっている。</li>\n<li>release 運用としては、README と監視設計文書に migration、binding、再デプロイ、監視 URL 確認まで残しており、手順だけ口頭に残さない形にしている。</li>\n</ul>\n<h2>アーキテクチャ</h2>\n<p>アーキテクチャは、流入層、体験層、監視層の 3 段に分けると理解しやすい。流入層は静的 SEO ページ群で、検索意図ごとに作った HTML が入口になる。体験層は <code class=\"language-text\">src/App.tsx</code> の calculator 本体で、query param から marketplace や sample を受け取り、到着直後から計算できる状態を作る。監視層は Pages Functions と D1 で、利用中に発生した匿名イベントを保存し、運用者だけが <code class=\"language-text\">/monitor/</code> から見られる。</p>\n<div class=\"gatsby-highlight\" data-language=\"mermaid\"><pre class=\"language-mermaid\"><code class=\"language-mermaid\">flowchart LR\n  A[&quot;静的 SEO ページ&lt;br/&gt;seo-pages.json + generate-seo-pages.mjs&quot;] --&gt; B[&quot;トップの calculator&lt;br/&gt;App.tsx&quot;]\n  B --&gt; C[&quot;匿名イベント送信&lt;br/&gt;/api/events&quot;]\n  C --&gt; D[&quot;Cloudflare D1&lt;br/&gt;events テーブル&quot;]\n  D --&gt; E[&quot;集計 API&lt;br/&gt;/api/metrics/summary&quot;]\n  E --&gt; F[&quot;監視 UI&lt;br/&gt;/monitor/&quot;]</code></pre></div>\n<p>この構成で重要なのは、公開ユーザー向け UI と運用者向け UI を混ぜなかった点である。監視画面は <code class=\"language-text\">public/monitor/index.html</code> の静的 HTML と DOM 操作で完結しており、React バンドルへ管理機能を持ち込まない。そのため、利用者向けの初回表示を重くせずに監視を足せる。</p>\n<p>また、配信確認の責務も別にした。アプリ内部に「本番確認ロジック」を埋め込むのではなく、<code class=\"language-text\">check-production-seo.mjs</code> が外側から公開 URL を検査する。アプリ本体、静的ページ生成、運用確認スクリプトが分かれているので、変更箇所と失敗箇所を切り分けやすい。</p>\n<h2>選定理由</h2>\n<p>Cloudflare Pages Functions と D1 を選んだ理由は、既存の静的配信構成から大きく外れずに、最小限のサーバー機能だけを足せるからである。別のバックエンドや認証基盤を増やすより、既存ホスティングの近くで完結させた方が、初期運用の複雑さを抑えやすい。</p>\n<p>監視イベントを allow-list と短い文字列へ制限したのは、個人情報や無制限な payload を持ち込まないためである。行動分析を始める段階で、何でも集める構成にすると後で消す方が難しい。<code class=\"language-text\">page_view</code>、<code class=\"language-text\">calculation_completed</code>、<code class=\"language-text\">paid_interest_clicked</code> のような公開運用に必要な最低限から始めた判断は妥当だった。</p>\n<p>SEO ページ生成を CMS 追加ではなく <code class=\"language-text\">seo-pages.json</code> と build script 拡張で進めたのは、運用対象を増やしすぎないためである。静的アプリのままページを増やし、同じ build で再生成できる形なら、deployment と release の単位も崩れにくい。</p>\n<p>トップページへの導線を query param ベースにした理由も同じで、新しい状態管理や専用ルーターを追加せず、既存の calculator 体験を再利用できるからである。入口だけ増えて着地点が別物になると、テストと保守の境界が急に難しくなる。</p>\n<p>production check を script 化したのは、SEO や収益化の確認を「担当者のブラウザで見る」から「同じコマンドで再実行する」へ変えるためである。実装後の release 品質は、コードだけでなく確認手順を固定できるかでかなり変わる。</p>\n<h2>導入手順</h2>\n<ol>\n<li>まず静的アプリの build / deployment / release 導線を確認し、どのコマンドで build し、どこへ配信し、公開 URL を何で確認するかを先に固定する。</li>\n<li>収益化設定を安全にするため、<code class=\"language-text\">.env.example</code> のダミー値を見直し、カテゴリ別リンク生成や外部 URL 検証を独立関数へ切り出してユニットテストを付ける。</li>\n<li>公開 URL を外側から検査する script を追加し、<code class=\"language-text\">robots.txt</code>、<code class=\"language-text\">sitemap.xml</code>、<code class=\"language-text\">ads.txt</code>、主要 HTML の存在確認を package script として実行できるようにする。</li>\n<li>検索流入を増やしたいテーマを <code class=\"language-text\">seo-pages.json</code> のようなデータ層へ切り出し、build 時に静的ページを生成する。各ページの CTA は query param 付きで同じ calculator へ着地させる。</li>\n<li><code class=\"language-text\">App.tsx</code> 側で marketplace や sample を読み取り、検索入口ごとに初期入力を変えられるようにする。入口ページだけ増えても、到着後にすぐ試せなければ効果が薄い。</li>\n<li>次に Pages Functions と D1 を追加し、匿名イベント受信 API、集計 API、migration、binding 設定を最小構成で用意する。個人情報を避けるため、イベント名と detail の schema は最初から厳しく絞る。</li>\n<li><code class=\"language-text\">public/monitor/index.html</code> のような別 URL で監視画面を作り、比較表示、日別集計、ランキングなど、次の施策判断に使う最小指標だけを表示する。</li>\n<li>実装後は Vitest、Playwright、<code class=\"language-text\">pnpm build</code>、<code class=\"language-text\">pnpm verify:production</code> を回し、必要なら <code class=\"language-text\">wrangler d1 migrations apply</code> と <code class=\"language-text\">wrangler pages deploy</code> を release 手順へつなぐ。source material で成否未確認の項目は、未確認のまま明記する。</li>\n</ol>\n<h2>導入時の課題</h2>\n<p>最初の課題は、SEO、監視、収益化がそれぞれ別の話に見えやすいことだった。実際には、検索流入が calculator に着地し、その結果を監視し、必要なら収益化導線も改善するという 1 本の流れで考えないと、施策同士がつながらない。</p>\n<p>次の課題は、静的アプリに監視を足すときに、すぐ管理画面を React 本体へ入れたくなることだった。しかし、それをやると利用者向け体験と運用者向け体験が結合し、バンドルも責務も重くなる。今回は <code class=\"language-text\">/monitor/</code> を別にしたことで、この問題を避けている。</p>\n<p>また、イベント計測は増やしすぎるとノイズになる。特に <code class=\"language-text\">detail_json</code> のような自由度の高いフィールドは、後で何でも入れられる一方で、制約が弱いと運用しづらい。allow-list、文字列長制限、カテゴリ単位の最小メタデータに留めた点は、導入初期の制約として重要だった。</p>\n<p>最後に、deployment や release の成否が source material で全部確認できない場面がある。こういうときに実施済みと書かず、build 済み、手順整備済み、反映成否未確認を分けて書くことが、週次記事でも技術記録でも精度を保つ。</p>\n<h2>次に検討した方が良いこと</h2>\n<ul>\n<li><code class=\"language-text\">source</code> query param ごとの計算完了率や有料導線クリック率を <code class=\"language-text\">/api/metrics/summary</code> へ載せ、どの SEO 入口が実際に強いかを数値で見られるようにする。</li>\n<li><code class=\"language-text\">verify:production</code> に構造化データや静的 SEO ページ群の存在確認を足し、公開後確認をさらに自動化する。</li>\n<li><code class=\"language-text\">detail_json</code> 依存の集計が増えるなら、カテゴリ別の派生集計や古いデータの圧縮方針を早めに決める。</li>\n<li>SEO ページは増やし続けるより、Search Console と監視データを見て、残すページと削るページを選別する運用へ進めた方がよい。</li>\n<li>D1 migration、binding 設定、Pages 再デプロイを CI/CD にどこまで組み込むかを決め、release 手順の手動部分を減らしていく価値がある。</li>\n</ul>","excerpt":"は、 リポジトリで運用している、ハンドメイド販売者向けの利益計算アプリである。この週の変更で一番再利用しやすかった知見は、Cloudflare Pages…","frontmatter":{"date":"2026年07月19日","path":"/2026-07-19-weekly-knowledge","title":"Cloudflare Pages の静的アプリに SEO 導線と D1 監視を足して公開運用へ寄せた実装","thumbnail":"/kenRp-blog/assets/codex-worklog-notebook.jpg","metaDescription":"CraftMargin で、SEO ランディングページ、収益化設定の安全化、Cloudflare D1 監視基盤を段階的に追加し、公開運用へ寄せた実装と判断を整理する。","category":"Knowledge","tags":["cloudflare","d1","seo","weekly-knowledge"]}}},"pageContext":{"slug":"/2026-07-19-weekly-knowledge"}}}