ブログのトップを「スクロールで飛ぶ夜の街」にした — AIが4/100と断じた案を選んだ理由
AI が最下位と断じた案を、実トラフィックを根拠に選んだ話
はじめに
ちらりんブログのトップページを、スクロールすると夜の街を飛び回る一本の映像に丸ごと差し替えました。実物は chillarin39.com がそのまま動くデモです。
この記事で書くのは、主に次の 3 つです。
- AI に性能・SEO・UX の 3 レンズでレビューさせたら、この案は全レンズ一致で最下位(合計 4/100)でした。推奨されたのは折衷案(25/100)。それを退けて全振りを選んだ判断
- 7 シーンぶんの実装が性能ゲートも多角レビューも通った直後に、2 シーンへ切ったこと
- 踏んだ罠 4 件と、かかった実額(714 クレジット・8〜12 人日)
全振りはしましたが、品質ゲートと公開後の手直しは律儀にやりました。判断の話と、その後どうなったかの実測を、両方書きます。
scroll-world とは — スクロールで動画を「シーク」する仕組み
scroll-world とは、スクロール量に合わせて事前レンダリング済みの動画をコマ送り(スクラブ)再生し、場面から場面へ継ぎ目なく飛ぶランディングページを作る仕組みです。実体は MIT ライセンスの外部スキル oso95/scroll-world で、静止画から動画を起こす部分は別の生成サービスに任せます。本記事では、これを個人ブログの本番トップページに載せるまでの判断・実装・罠・実額を扱います。
技術的な核はひとつだけです。動画を「再生」するのではなく、video.currentTime をスクロール位置に紐づけて「シーク」します。ページ自体は止まったまま、絵だけが指の動きに追従して進みます。
この一点が、後半で出てくるモバイルのもたつき(GOP の罠)に直結します。普通の動画再生なら気にしなくていいエンコード設定が、シーク主体になった途端に体感を支配するようになる、という話です。
作ったもの — 夜のアイソメ・ジオラマを 7 シーン
トップページ案は 3 パターン作って見比べました。
| 案 | 方式 | 送客の形 |
|---|---|---|
| A | scroll-world(スクロールで街を巡る映画型) | 各シーンに導線ボタン |
| B | 俯瞰マップ 1 枚 + ホットスポット | マップ上のピン |
| C | 広場の絵を全画面ハブ化 | 一画面にピン集約 |
採用したのは A です。B と C は「一目で全部見える」強さがある代わりに、うちのブログでやりたかった「世界観を歩かせる」体験にはなりませんでした。
素材の作り方はこうです。
- 静止画 7 シーン — Codex 経由の GPT Image 2 で生成。動画側のクレジットを消費しないので、ここは追加費用ゼロ
- 動画 13 クリップ — Higgsfield(PLUS プラン・Seedance 2.0。lab シーンのみ Kling 3.0)で、シーンに潜る dive 7 本 + シーン間をつなぐ connector 6 本
- 動画の実体は計 80MB。git 履歴には入れず
.gitignoreで除外し、再生成はプロンプトから可能な状態にしています
アート方向は、scroll-world 既定の暖色クレイ調ではなく「ダーク × ミント発光の夜のアイソメ・ジオラマ」にしました。既定に乗ったほうが破綻しにくいのは分かっていましたが、トップページはブログの顔なので、ブランドの色に寄せる方を選んでいます。
なお、動画生成エンジンを Higgsfield 以外へ乗り換える検討は Higgsfield から Veo 3.1 API への乗り換えを実測で決めた話 で書いています。
AI は「4/100」と言った。それでもトップを丸ごと差し替えた
ここがこの記事の山場です。
3 レンズの評価と、AI が出した反対材料
トップページへの組み込み方は 2 通り考えていました。①トップを丸ごと差し替える ②トップは現状のまま、/town として独立ページに置く(折衷)。
これを性能 / SEO / UX の 3 レンズで AI に分析させたところ、全レンズ一致で①が最下位(合計 4/100)、推奨は折衷案(合計 25/100)でした。
反対材料は根拠を尽くして出てきました。
- トップページの内部リンク資産(記事数付きのカテゴリリンク・最新記事一覧)を壊すべきではありません
- ドメイン移行の監視期間中に、もう一つ大きな変数を重ねると原因の切り分けが効かなくなります
- 作った 80MB の動画は折衷案でも 100% 活きるので、①を選ぶ理由にはなりません
数字だけ見れば、この評価は正しいと思います。実際、私はここに反論を持っていませんでした。
前提に埋まっていた仮定
引っかかったのは、反対材料そのものではなく、その足元でした。
「SEO 資産の毀損が痛い」「入口での送客即時性が要る」という前提は、どちらも『トップに十分な検索流入がある』ことを暗黙に仮定しています。 うちの場合、そこが成り立っていませんでした。同時期に取った情報設計の監査では、Googlebot の訪問が 30 日で 3 回、訪問者数も極小という実測が出ていました。
つまり、差し替えで失うとされたものを、そもそも今のところ持っていません。
AI のスコアは「劣後がどれくらい大きいか」を一般的な重みで測ってくれます。でも「その劣後が、自分の実環境で今いくら痛いか」は、自分のトラフィックを知っている本人しか持っていない変数でした。それを掛けると、順位が入れ替わりました。
私が出した結論はこうです。
どうせ誰も見に来てないんだから無条件①GO。止まる理由は品質的にトップページとして使いものにならないと判断した時だけだ。
劣後がほぼ無コストな今のうちにブランド体験へ全振りして顔を作るほうが、流入が増えてから実験するより安く済む、という賭けです。
決めたこと、決めなかったこと
全振りだからといって、なんでも通したわけではありません。決裁のときに 3 つ線を引きました。
- 停止条件は「品質」ひとつだけにしました。判定器は性能の実測ゲートと、スクリーンショットを並べた視認レビューです。ここを満たせなければ公開せず折衷案へ退避します
- ドメイン移行を巻き戻す事故だけは、品質とは別問題として死守しました。不可逆なので、「誰も見ていない」は言い訳になりません
- サンクコストは根拠にしませんでした。80MB の動画も 714 クレジットも、折衷案なら 100% 活きます。「作ってしまったから」を理由にすると、判断そのものが濁ります
そして、以下は「受け入れる」と先に決めた劣後です。
| 受容した劣後 | なぜ工数で消えないか | どう扱ったか |
|---|---|---|
| 送客の即時性 | ファーストビューがスクロール前提になる | 実害が小さいと判断して受容(後述の 2 シーン化でほぼ解消) |
| 最新記事の鮮度 | 街は固定テーマなので新記事が反映されない | 最新記事ブロックをサーバー側描画で併存させて緩和 |
| INP / CLS のリスク | スクロールジャック由来。測らないと分からない | 性能ゲートで公開判定 |
| エンジンの保守債務 | 本番用に fork した以上、追従を恒久的に抱える | 本番の安全のため必要と判断して許容 |
この判断で残ったもの
ここで自分に残った教訓は、「AI の反対を聞くな」ではありません。むしろ逆で、根拠を尽くして反対してもらえたから、前提に埋まっている仮定が見えたというのが実感です。
残ったのは「正しい一般論 × 自分の実数」という掛け算のほうです。一般論としての重み付けは AI が出せます。実数は自分しか持っていません。両方を突き合わせて初めて、全振りが安く済む瞬間かどうかが分かる、という話でした。
本番に載せるためにやったこと
試作を本番トップにするには、実験用の作りのままでは通りません。やったことを要点だけ書きます。
サーバー側描画(SSR)を取り戻す
試作版は LP のコピーを copy.json から fetch して JS で流し込んでいました。これでは初期 HTML が空同然になります。
- LP のコピーを Hugo の data(YAML)に移し、初期 HTML へ焼き込みました
- カテゴリのハブカード 6 枚と最新記事 7 件を、scroll-world の world とは別の実セクションとして
<main>直下に併存。JS が動かなくても実 DOM として存在します - 構造化データ(JSON-LD)に ItemList を追加し、ハブ 6 + 最新 7 の 13 件を列挙
「街は固定テーマなので新記事が反映されない」という劣後は、この併存でかなり緩和できました。トップの layouts/index.html は丸ごと差し替えましたが、data と partial を初期 HTML に焼くという作法自体は Hugo のテーマ・テンプレートの基本 に書いた頃のままです。
エンジンを fork する
配布されているエンジンは、全クリップを fetch().blob() で読み込んで常駐させます。試作なら問題ありませんが、本番のモバイルには載せられません。本番用に fork して、次を入れました。
- 接触中のセグメントだけ保持(実質 LRU・上限 2)し、退出時に
revokeObjectURLで解放 navigator.connection.saveDataとeffectiveTypeが2g/slow-2gの場合は、動画を一切ロードせず静止画 + Ken Burns に確定フォールバック(Network Information API は対応ブラウザが限られるので、値が取れたときだけ効くベストエフォートの判定です)preloadをautoからmetadataへ
ブランドの顔であるチンチラのリンちゃんは、当初「動画の最前景に重ねて案内役にする」救済レイアウトを試しました。夜の街(寒色)と絵画調のリンちゃん(暖色)で色温度が断絶しうることは実装前から分かっていたので、合うかどうかは視認レビューで人間が決める、と先に線を引いてあります。実際に載せてみると、モバイルでは動き回るリンちゃんが CTA に被ってタップを邪魔する実害も出ました。最終的には、街の下に続くサーバー側描画の導入セクション(h1 の帯)へ移しています。
動画は同一ドメインの R2 から配る
配信は Cloudflare R2 にしました。egress が無料で、Cloudflare Tunnel で公開しているブログ本体 の帯域を汚さずに済みます。
配り方には 2 案ありました。R2 に専用サブドメインを当てる案と、既存ドメイン配下(chillarin39.com/media/town/*)に Worker ルートを置いて R2 バケットへ橋渡しする案です。選んだのは後者です。理由は容量でも速度でもなく、CORS と CSP が「静かに壊れる」経路を構造的に消せるからでした。別ドメインにすると、動くか壊れるかがヘッダ設定に依存します。同一ドメインなら、その分岐自体が存在しません。
実際、このあと CSP の罠を 1 件踏みました。同一ドメインでもヘッダを 1 行足す必要があったので、別ドメインにしていたら CORS 側も同時に疑うことになっていたはずです。
アップロードは 2 シーン化後の構成で 8 本(7 シーン + connector 1 本)× 2 変種 = 16 ファイル・約 63MB。md5 で往復照合して、フル取得 200 / Range 取得 206 / 未存在 404 / ブログ本体が無傷であることまで確認しています。この時点の数字で、後述する公開当日の作り直しで動画がもう 1 本(10.5MB)増えました。
15 観点の敵対的レビューと性能ゲート
公開前に、AI エージェント 15 体で観点を分けた敵対的レビューを回しました。結果は confirmed 6 / refuted 0。指摘は全件直しました。
- シーンの継ぎ目でクリップが間に合わず、静止画に落ちてしまう欠品(starvation)
- 視覚的に隠しているリンクが Tab キーで露出してしまう問題
- iOS で URL バーが伸縮したときのシーン境界のずれ
性能ゲートの実測値は次の通りです。
| 指標 | 実測 | 判定 |
|---|---|---|
| CLS | 0.015 | OK |
| INP(近似) | 基準内 | OK |
| データセーバー / reduce-motion 時の動画ロード | ゼロ(静止画のみ) | OK |
| 初期ダウンロード量 | 約 1.1MB | OK |
| LCP | 3.2 秒 | 保留 |
LCP だけ、基準の前提そのものが崩れました(詳細は後述の罠 2)。3.2 秒は Slow 4G + CPU 4 倍のエミュレーション、かつローカル配信での値です。本番配信では改善が見込めますが、公開後の実地再計測は宿題として残っています。
検証を全部通した 7 シーンを、2 シーンに切った
第二の山場です。上に書いた実装と検証が全部終わり、あとは公開判定というタイミングで、構成そのものを疑いました。
「そもそも 7 シーンは長い」。
7 シーンは、スクロール量にして約 15.5 画面分あります。初見の人にとっては映画かもしれませんが、毎日来てくれる人にとっては通行料です。読者が記事に着くまでに 15.5 画面分をスクロールさせる設計を、私は自分では使いたくないと思いました。
そこで、こう組み替えました。
- トップは最初の 2 シーンだけ(広場 → 記事通り)。約 5.1 画面分(このあとの継ぎ目詰めで 4.4 画面分になります)。着地点は実在する最新記事のセクション
- 残り 5 シーンは各カテゴリのトップページへ移植し、「1 回再生 → 静止」のヒーローに変更。スクロールジャックはしません
- lab →
/categories/homelab// school →/study// market →/trading// workshop →/apps// summit →/about/
- lab →
- 個別配信しなくなった connector 5 本は、削除せず保管のみ
物語は「広場で迎える → 記事通りを飛ぶ → 実際の最新記事に着地する」の一直線になりました。おまけに、3 レンズ分析が最大の劣後として挙げていた送客の即時性が、ここでほぼ消えました。反対材料を潰したのは工数ではなく、体験を短くする判断のほうだった、ということになります。
散らした先は、その日のうちに撤回しました
ここまでが公開前の話です。ところが、公開当日に実機で各ページを見て回ったところ、カテゴリの個別ページに単発のシーンだけが載っている状態が「なんか変」でした。街の一区画だけを切り出して貼っても、そのページの顔にはならない、という見え方です。
そこで同じ日に再決裁しました。/study/ /trading/ /apps/ /categories/homelab/ のシーンヒーローは撤回し、もともとヒーロー画像が無かった /about/ にだけ、7 シーンと connector 6 本を全部つないだ「街の全行程フライト」(86.5 秒・720p・10.5MB)を 1 本置く形にしました。見せ方は同じ「1 回再生 → 静止」です。個別配信をやめた connector も、この 1 本の中では全部流れています。
検証まで通した実装でも「体験の長さ」は別の審査項目でした。そのうえで、切ったときの狙いは 資産を削るのではなく散らせることにありました。ただ、散らし方までは当たっていません。1 ページに詰めた演出をサイト全体へ再配分する方向自体は残りましたが、生き残ったのは「1 シーンずつバラして各ページへ配る」ではなく「全部つないで、顔が無かった 1 ページに置き直す」ほうでした。
踏んだ罠 4 件
踏んだ順に書きます。どれも「エラーが出ないので気づきにくい」種類でした。
1. 生成が「NSFW」で 3 回連続で弾かれる
動画生成中、サーバーラボのシーン(dive_lab)だけがステータス nsfw で 3 回連続失敗しました。プロンプトから server / machine / equipment といった語を全部抜いて無害化しても、通りません。
原因はプロンプトではなく、入力側の start-image でした。コンテンツフィルタは開始画像も見ていて、サーバー室のラック群の絵そのものが引っかかっていたわけです。プロンプトをいくらいじっても効かないはずでした。
手順としては、①単純にリロールする(非決定的なので通ることがある)②トリガーになりそうな語を除く ③それでも駄目なら別ベンダーのモデルへ切り替える、の順です。今回は Kling 3.0 に切り替えて突破しました。1 クリップだけ別モデルでも、質感差はわずかで、継ぎ目のフレームロックにも支障はありませんでした。なお、失敗した生成には課金されません。
2. 全画面の画像は LCP の候補から外れる
性能ゲートの設計時、「全画面の poster 画像を LCP 要素に固定して LCP を守る」という前提を置いていました。これが技術的に成立しませんでした。
preload しても poster の LCP エントリは一切記録されず、LCP は小さな見出しテキストの描画時刻に支配されます。display:none で消しても、エントリごと消えるだけでした。Chrome(Chromium 系)がビューポート全面を覆う画像を「背景」と見なして LCP 候補から外すヒューリスティックを持っているためです。仕様として全ブラウザ・全画像が常に除外されるという話ではなく、あくまで Chromium 側の判定です。
理解を修正しました。全画面ヒーローのページの LCP は、実質 FCP(レンダリングをブロックする CSS とフォント)で決まります。poster 自体は preload と fetchpriority=high で早く出し、体感のほうは実描画で測る、という方針に切り替えました。
3. 本番だけ動画が無言で死ぬ
公開したら、本番だけスクラブ動画が動かず、静止画フォールバックのままになりました。厄介だったのは症状の出方です。動画ファイルの fetch は 200 で成功し、JS のエラーも出ません。 ローカルでは再現しません。
原因は CSP でした。media-src ディレクティブが無く default-src 'self' にフォールバックしており、blob: は 'self' に含まれないため、video.src = blob:... がブラウザにブロックされていました。CSP 違反はページのエラーにならず、console にしか出ません。そしてローカル配信には CSP ヘッダ自体が無いので、ローカルでは絶対に気づけません。
media-src 'self' blob: を追加して解決しました。教訓は、本番同等のセキュリティヘッダでの検証を性能ゲートに含めることです。加えて、直接 <video src=URL> は 'self' で通るので、blob 方式のコンポーネントだけが死ぬ非対称な壊れ方をします。他が動いているぶん、余計に気づきにくい形でした。
4. 720p にしてもモバイルがもっさりする
公開後、スマホの実機でスクロールがもたつきました。モバイル向けに 720p の軽量版(80MB → 11MB)を作って配信済みなのに、改善しません。
原因はビットレートではなく GOP(キーフレーム間隔) でした。冒頭に書いた通り、この仕組みは動画を再生ではなくシークしています。currentTime シークのコストは、直前のキーフレームからのデコード距離で決まります。再エンコード時に -g を指定しなかったため libx264 の既定(およそ 250)のままで、毎回のシークが遠いキーフレームからのデコードになっていました。
モバイル版は -g 4 -keyint_min 4 -sc_threshold 0 で 4 フレームごとに I フレームを打つよう変更しました。サイズは約 3 倍になるので、crf を 28 から 30 に落として相殺しています。差し替え時は immutable キャッシュが効いているので、URL に ?v= を付けてキャッシュを飛ばす必要もありました。
公開してから直したこと
罠 4 に加えて、実機で見て直したものが 4 件あります。どれも実測ではなく、目で見て気持ち悪かった類のものです。
- シーン内のタイトルが折り返していたので 1 行に収めました
- 街の終わりと、その下に続くサーバー側描画セクションの継ぎ目に暗い空白が出ていたので詰めました(スクロール量は 5.1 画面分から 4.4 画面分へ)
- ハブカード 3 枚(Homelab / Study / Trading)の構図が Tech & AI と同じで単調だったので、画風とキャラはそのままに構図だけ分散させました
- 各カテゴリへ散らしたシーンヒーローを撤回し、
/about/の全行程フライト 1 本へ集約しました(前の節に書いた通りです)
全振りの決裁をしても、公開後の手直しが免除されるわけではない、というだけの話ではあります。ただ、停止条件を「品質」ひとつに絞ったぶん、公開後に品質へ手を入れるのは当然の続きとして扱いました。
かかった実額
| 項目 | 使ったもの | 実額 |
|---|---|---|
| 静止画 7 シーン | GPT Image 2(Codex 経由) | 追加費用ゼロ |
| 動画 13 クリップ | Higgsfield PLUS(Seedance 2.0 / Kling 3.0) | 714 クレジット(月額 $59 の月次付与分でほぼ 1 本) |
| モバイル軽量版 | ffmpeg で 720p 再エンコード | 無料(80MB → 11MB) |
| 動画配信 | Cloudflare R2 + Worker(同一ドメイン) | egress 無料(16 ファイル・約 63MB + 全行程フライト 10.5MB) |
| 工数 | 判断・実装・実測・手戻り込み | 8〜12 人日(着手前の見積り。実測は取っていません) |
R2 で無料なのは egress であって、ストレージ量・操作回数・Worker の実行回数はそれぞれ別の課金軸です。今回の量ならいずれも無料枠に収まっています。
金額よりも工数のほうが重い構成です。素材の生成そのものより、本番に載せるための作業(SSR 併存・エンジンの fork・配信の用意・性能実測・手戻り)が工数の大半を占めました。試作が動いた時点は、まだ半分にも届いていなかったことになります。
まとめ
やったことをもう一度並べると、こうなります。
- AI の 3 レンズ評価が最下位(4/100)と断じた「トップ丸ごと差し替え」を、実トラフィックを根拠に選びました
- 全振りはしましたが、停止条件を品質ひとつに絞り、性能ゲートと視認レビューを判定器として通しました
- 検証を全部通した 7 シーン構成を、公開直前に 2 シーンへ切りました。残りをカテゴリごとの顔として散らす案は公開当日に撤回し、全行程を 1 本につないで
/about/へ置き直しています - 罠は 4 件とも「エラーが出ない」タイプでした
自分にとって一番効いたのは、AI の反対を根拠つきで全部出させたことでした。出させたからこそ「その反対は、トップに検索流入がある前提で成立している」と気づけたわけで、最初から聞かずに突っ走っていたら、たまたま当たっただけの判断になっていたはずです。
一般的な重み付けは AI が出せます。自分の実数は自分しか持っていません。掛け算をするのは人間側の仕事、というのが今回の結論です。
トップページは chillarin39.com で動いています。データセーバーをオンにすると動画を一切読まない静止版に切り替わるので、見比べるとフォールバックの効き方まで分かります。
AI と一緒にブラウザで動くものを作る話は、AI と一緒にブラウザゲームを 3 本作った開発記 にも書いています。このブログが載っているサーバーそのものの話は NUC×3 + Proxmox の物理・仮想基盤 を参照してください。基盤の完全再現手順(設定ファイル全量つき)は有料コンテンツの 自宅サーバー構築シリーズ にまとめています。
コメント