[{"data":1,"prerenderedAt":422},["ShallowReactive",2],{"content-/nuxt-worker-bundle-reduction-miller-viewer":3,"all-pages-for-dir":400,"related-/nuxt-worker-bundle-reduction-miller-viewer":401,"og-image-/nuxt-worker-bundle-reduction-miller-viewer":421},{"id":4,"title":5,"body":6,"category":381,"concepts":382,"description":383,"extension":384,"meta":385,"navigation":349,"ogImage":382,"path":386,"project_name":387,"published":388,"publishedAt":389,"seo":390,"stem":391,"tags":392,"todo":382,"unpublished":388,"updatedAt":382,"__hash__":399},"pages/2026-07/2026-07-30/nuxt-worker-bundle-reduction-miller-viewer.md","Cloudflare Worker のバンドル削減2日目、外部HTML化で287KiB空けて miller-viewer 島は予測の1割だった",{"type":7,"value":8,"toc":368},"minimark",[9,13,16,19,24,32,35,38,57,68,74,77,80,83,86,89,92,100,103,110,117,123,126,133,136,139,142,145,148,159,166,172,175,178,181,184,187,240,246,249,253,256,263,270,273,276,281,284,287,290,297,300,303,306,335,338,364],[10,11,12],"p",{},"朝いちで聞いたのは、昨日の積み残しは何か、だった。数字が合っていない気がしていた。",[10,14,15],{},"返ってきた答えは想像より悪い。教科書の講座は13章あって、変換が終わっているのは ch01 だけ。残り11章は手つかず。しかもその ch01 のコミットはローカルブランチに残ったままで、push もマージもデプロイもしていない。前日に走らせた切り分け調査（DD）が「このファミリで −269.8 KiB」と見積もっていた本命は、まだ1バイトも取れていなかった。",[10,17,18],{},"そのまま11章の変換に入った。",[20,21,23],"h2",{"id":22},"_3章だけ図の見た目が変わった","3章だけ、図の見た目が変わった",[10,25,26,27,31],{},"dry-run は11章すべて通過した。島78個、焼き込む図81個。変換11件も成功して、",[28,29,30],"code",{},".vue"," を消してホスト経由に切り替えても SSR は保てている。",[10,33,34],{},"比較で止まった。11章のうち8章はピクセル一致だった。ch04、ch05、ch06 の3章だけ、図の md5 が変換前と違う。",[10,36,37],{},"最初はアニメーションのタイミング差を疑った。図の transition は状態変化のためのもので、採取のタイミングでずれる種類ではない。そう分かって、その線は消えた。次に CSS の衝突を疑った。1ページに複数の図の CSS をまとめて焼き込むので、セレクタの接頭辞が共有されていれば、後から定義した図のスタイルが前の図に効いてしまう。",[10,39,40,41,44,45,48,49,52,53,56],{},"当たった。不一致の図はすべて ",[28,42,43],{},".pl-figure-wrap"," という接頭辞を共有していて、一致した2つだけが ",[28,46,47],{},".mm-"," と ",[28,50,51],{},".tax-"," という固有の接頭辞を持っていた。",[28,54,55],{},"\u003Cstyle scoped>"," を素の CSS に落とした結果、同名のセレクタが1枚のページに同居して、最後の定義が全部の図に効いていた。",[10,58,59,60,63,64,67],{},"他の章でも規則性を確かめさせた。ch05 は ",[28,61,62],{},".cf-*"," を共有する4図のうち、最後に定義される CfFcf だけが一致。ch06 は ",[28,65,66],{},".an-*"," を共有する3図のうち、最後の AnDupont だけが一致。「最後の定義が勝つ」で全部説明がつく。",[10,69,70,71,73],{},"図ごとに CSS を名前空間化する形にコンバータを直させた。ch01 だけ古い生成規則で作られているのは気持ちが悪いので、",[28,72,30],{}," を git から復元して12章まとめて作り直した。",[10,75,76],{},"HTML は名前空間化のぶん1.2〜1.3倍に膨らんだ。それでも静的アセットに出てしまえばバンドルには載らない。",[10,78,79],{},"結果は12章すべてピクセル一致で、NG はゼロだった。項目でいうと84/84 の満点で、ch04 も Vue 版と md5 完全一致に戻った。テストも3,120件パス。",[20,81,82],{"id":82},"ビルドが3分で黙る",[10,84,85],{},"margin を測るためにビルドさせた。6〜7分で終わる想定だったのに、Nitro の段階で止まった。",[10,87,88],{},"前日と同じ症状だった。ビルド用に一時退避された画像159枚が、元の場所に戻らないまま残っている。復元させて、パイプを外してログへ直接書く形で再実行した。",[10,90,91],{},"また約3分で止まった。2回連続。バックグラウンド実行で出力が途切れるのが原因と見て、159枚をもう一度復元してから foreground（上限10分）で回した。",[10,93,94,95,99],{},"通った。",[96,97,98],"strong",{},"margin 656.3 → 943.3 KiB、+287.0 KiB。"," DD の予測 −269.8 KiB を上回った。",[20,101,102],{"id":102},"学習ゲートが空回りしている",[10,104,105,106,109],{},"コミットで学習ゲートに止められた。",[28,107,108],{},"/learn"," を実行させたら、クイズを出すツールがセッションに見つからない。調べさせると、クイズサーバーが MCP に登録されていなかった。",[10,111,112,113,116],{},"ついでに履歴も見せてもらった。前日7/29のコミットは全部 ",[28,114,115],{},"LEARN_SKIP"," で通っていて、最後に受領証が出たのは7/28。学習ゲートは2日間、実質的に動いていない。",[10,118,119,120,122],{},"今日も ",[28,121,115],{}," で通してマージし、デプロイまで進めた。デプロイ後の margin は944 KiB。",[20,124,125],{"id":125},"本番で12章を見る",[10,127,128,129,132],{},"本番の SSR を確認させたら、ch02、ch03、ch04 で島も ",[28,130,131],{},"data-fig"," も0だった。手が止まる。ただエッジキャッシュの疑いがあったので、キャッシュバスターを付けて叩き直させたら正常に返ってきた。一時的な現象だった。",[10,134,135],{},"改めて12章を確認させた。島84個、SVG は全章 SSR、CSS の名前空間も焼き込み済みだった。",[10,137,138],{},"ch04 は重くて Chrome のツールがバックグラウンドに落ちたので、Playwright で本番の描画を直接撮らせた。dev と md5 完全一致、つまり移行前の Vue 版と同じ絵が出ている。自分の目でも1枚見た。マゼンタの段階利益ボックスも配色もフォントも崩れていない。クイズの島もクリックが通る。",[10,140,141],{},"昨日の積み残しは、ここで閉じた。",[20,143,144],{"id":144},"次のファミリは島化しやすいはずだった",[10,146,147],{},"miller-viewer 島に移る。着手前に1つだけ確かめさせた。その部品は今 SSR されているか。",[10,149,150,151,154,155,158],{},"代表3ファイルすべてで ",[28,152,153],{},"MillerViewerLoader"," は ",[28,156,157],{},"\u003CClientOnly>"," の内側にあった。もともとクライアントでしか描画していないのだから、島に切り出しても SSR は後退しない。教科書でやった「本文を焼き込む」方式は要らない。",[10,160,161,162,165],{},"セッションが検証ログとスクリーンショットで重くなっていたので、ここで切った。引き継ぎには注記を1つ付けた。",[28,163,164],{},"01-summary.vue"," だけは Loader に子要素を渡していて、単純な島化では済まないかもしれない。",[10,167,168,169,171],{},"引き継いだ先の第一報が、想定より大きい話だった。着手前に見ていた規模は63ページ、118箇所。実際に調べさせると、",[28,170,153],{}," を使っているのは lessons/bookkeeping の34ファイルだけではなく、8コース119ファイルにまたがっていた。今回の対象は33ページ。",[10,173,174],{},"33ページのベースラインを採取して、パイロットを1本通した。18/18で合格し、スクリーンショットはデスクトップもモバイルもピクセル一致だった。残り32ページの変換に進んだ。",[20,176,177],{"id":177},"作ったばかりのホストファイルが消えた",[10,179,180],{},"変換後のスナップショットを採らせたら、全ページが同一ハッシュで、エラーが1件出た。",[10,182,183],{},"dev サーバーが大量削除の直後に壊れていた。再起動して見に行くと、作ったばかりのホストファイルが無い。PowerShell で実在を確認させても無い。",[10,185,186],{},"原因は自分たちで書いた削除ループだった。",[188,189,194],"pre",{"className":190,"code":191,"language":192,"meta":193,"style":193},"language-sh shiki shiki-themes vitesse-light vitesse-light","# 変換済みの .vue を消すための一覧。ホスト自身のコメントにも同じ文字列が入っていた\ngrep -rl 'MillerViewerLoader' \u003C対象ディレクトリ>\n","sh","",[28,195,196,205],{"__ignoreMap":193},[197,198,201],"span",{"class":199,"line":200},"line",1,[197,202,204],{"class":203},"sxvE3","# 変換済みの .vue を消すための一覧。ホスト自身のコメントにも同じ文字列が入っていた\n",[197,206,208,212,216,220,223,226,230,233,237],{"class":199,"line":207},2,[197,209,211],{"class":210},"senZ8","grep",[197,213,215],{"class":214},"snbK4"," -rl",[197,217,219],{"class":218},"sMJiu"," '",[197,221,153],{"class":222},"sdGka",[197,224,225],{"class":218},"'",[197,227,229],{"class":228},"stQ0i"," \u003C",[197,231,232],{"class":222},"対象ディレクト",[197,234,236],{"class":235},"sG7-3","リ",[197,238,239],{"class":228},">\n",[10,241,242,243,245],{},"ホストファイルのコメントに ",[28,244,153],{}," と書いてある。だから抽出の一覧に自分自身が入り、自分を消した。他に巻き込まれたファイルは無い。件数を照合させて確かめた。ホストを作り直して、採取をやり直した。",[10,247,248],{},"33/33、全594項目が一致した。",[20,250,252],{"id":251},"ページを移動したときの2件の-ng","ページを移動したときの2件の NG",[10,254,255],{},"同じホストの中でページを移動したときの挙動も見た。島がきちんと破棄されて再マウントされるか、操作が効くか。2件 NG が出た。",[10,257,258,259,262],{},"移行前の Vue ページ（",[28,260,261],{},"intro-to-accounting/part01","）で同じ操作をさせたら、そちらでも同じ挙動だった。MillerViewer にもとからあるバグで、この移行で持ち込んだものではない。テスト側の期待値を直したら、挙動は18項目すべて OK になった。",[10,264,265,266,269],{},"歯止めも足した。",[28,267,268],{},"check:islands"," を miller-viewer に対応させて、単体テストに sharedProps のケースを追加。どちらもネガティブテストにかけて、わざと壊したときに落ちることを確認させた。通ったことしか見ていない検査は歯止めにならない、というのは前日の学びのままだ。",[20,271,272],{"id":272},"数字が出ない",[10,274,275],{},"ビルドして margin を測った。",[10,277,278],{},[96,279,280],{},"削減量は DD 予測の10.4%だった。",[10,282,283],{},"33ページの外部HTML化は完了して、検証も全部パスしている。壊れてはいない。ただ、目的だったバンドルの削減がほとんど取れていない。中断条件に引っかかったので、そこで手を止めて報告してもらった。",[10,285,286],{},"教科書のときは予測 −269.8 KiB に対して、margin が287.0 KiB 広がった。同じ DD が出した予測なのに、こちらは1割しか出ない。予測の精度はファミリごとにばらつく。上から順に潰していけばいいという前提が崩れた。",[20,288,289],{"id":289},"次は先に内訳を確定させる",[10,291,292,293,296],{},"次の候補は ",[28,294,295],{},"financial-statements-quiz"," の78ページ、予測 −277.7 KiB。同じ失敗を繰り返さないよう、着手する前に実際のチャンクの内訳を確定させた。",[10,298,299],{},"クイズのデータは前日すでに外部 JSON へ出してあった。残って重いのは図のコンポーネントらしい。図の動的な部分がルート要素のクラス1つだけなら、静的に焼き込んでも動きは保てる。89図すべてが同じパターンだった。",[10,301,302],{},"今度は数字の裏が取れている。その方式で着手して、途中まで進めたところで WIP コミットにして保全した。",[20,304,305],{"id":305},"学び",[307,308,309,313,318,326,329,332],"ul",{},[310,311,312],"li",{},"一括削除のスクリプトは、自分自身がその条件にマッチしないか先に確かめる。ホストのコメントに部品名を書いただけで削除対象になった",[310,314,315,317],{},[28,316,55],{}," を素の CSS に落とすと、接頭辞を共有する図が同じページで混ざる。図ごとに名前空間を切る",[310,319,320,321,48,323,325],{},"見た目の差分が出たら、一致した個体との違いを探す。",[28,322,47],{},[28,324,51],{}," だけが固有の接頭辞だと分かった時点で原因は決まった",[310,327,328],{},"移行で出た NG は、移行前のページで同じ操作をして切り分ける。既存バグを自分の回帰と誤認すると、直さなくていいものを直すことになる",[310,330,331],{},"ビルドをバックグラウンドで回すと出力が途切れて3分で止まる。foreground で回す",[310,333,334],{},"削減の予測はファミリ単位でばらつく。着手する前にチャンクの実体を見る",[20,336,337],{"id":337},"明日やること",[307,339,342,355],{"className":340},[341],"contains-task-list",[310,343,346,351,352,354],{"className":344},[345],"task-list-item",[347,348],"input",{"disabled":349,"type":350},true,"checkbox"," ",[28,353,295],{}," の変換を仕上げる（WIP コミットで止めてある）",[310,356,358,360,361,363],{"className":357},[345],[347,359],{"disabled":349,"type":350}," 学習ゲートのクイズサーバーを MCP に登録し直す。2日間 ",[28,362,115],{}," で通り続けている",[365,366,367],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":193,"searchDepth":207,"depth":207,"links":369},[370,371,372,373,374,375,376,377,378,379,380],{"id":22,"depth":207,"text":23},{"id":82,"depth":207,"text":82},{"id":102,"depth":207,"text":102},{"id":125,"depth":207,"text":125},{"id":144,"depth":207,"text":144},{"id":177,"depth":207,"text":177},{"id":251,"depth":207,"text":252},{"id":272,"depth":207,"text":272},{"id":289,"depth":207,"text":289},{"id":305,"depth":207,"text":305},{"id":337,"depth":207,"text":337},"dev",null,"Cloudflare Workers のバンドル上限に向けた削減の続き。教科書12章を外部HTMLへ出して margin を656.3から943.3 KiB に広げ、次に miller-viewer 島33ページを切り出したが削減は予測の10.4%で止まった。CSSの接頭辞衝突と、grep が自分自身を消した事故の記録。","md",{},"/nuxt-worker-bundle-reduction-miller-viewer","eurekapu-nuxt4",false,"2026-07-30T00:00:00.000Z",{"title":5,"description":383},"2026-07/2026-07-30/nuxt-worker-bundle-reduction-miller-viewer",[393,394,395,396,397,398],"Cloudflare Workers","Nuxt","バンドルサイズ","SSR","静的HTML","アイランドアーキテクチャ","Fc6vSG-CNfv1xbuSdz7nAahTEXRKvIFcQGacfUWLlbI",[],[402,406,410,413,417],{"title":403,"path":404,"publishedAt":405},"Vueページは必ずWorkerバンドルに載る — 静的HTML化と「島マウント」でCloudflare Workersの上限から逃げる","/worker-bundle-reduction-content-islands","2026-07-29T00:00:00.000Z",{"title":407,"path":408,"publishedAt":409},"Cloudflare Pages の 1MB Worker bundle 制限に当たって構成図A〜Dを書きながら設計判断を整理した","/cloudflare-pages-workers-bundle-architecture","2026-06-16T00:00:00.000Z",{"title":411,"path":412,"publishedAt":389},"2026年7月30日の開発日記 - 3つのリポジトリに同じ問いを投げた朝","/2026-07-30-diary",{"title":414,"path":415,"publishedAt":416},"Cloudflare Pages はもう Workers の中に住んでいる ― Static Assets 統合後の地図","/cloudflare-pages-to-workers-static-assets","2026-06-29T00:00:00.000Z",{"title":418,"path":419,"publishedAt":420},"Cloudflare Pages SSG の OOM を Excel 教材データ分離で根本対応した話 — Codex 再帰レビューと SSR fetch 404 の突破","/excel-data-separation-cloudflare-ssg-oom","2026-05-10T00:00:00.000Z","https://log.eurekapu.com/og/blog/nuxt-worker-bundle-reduction-miller-viewer.png?v=2026-07-30T00%3A00%3A00.000Z&title=Cloudflare%20Worker%20%E3%81%AE%E3%83%90%E3%83%B3%E3%83%89%E3%83%AB%E5%89%8A%E6%B8%9B2%E6%97%A5%E7%9B%AE%E3%80%81%E5%A4%96%E9%83%A8HTML%E5%8C%96%E3%81%A7287KiB%E7%A9%BA%E3%81%91%E3%81%A6%20miller-viewer%20%E5%B3%B6%E3%81%AF%E4%BA%88%E6%B8%AC%E3%81%AE1%E5%89%B2%E3%81%A0%E3%81%A3%E3%81%9F&author=Kei%20Komatsu&sig=5dcba7c55271e7e4",1785464018831]