# 実例に学ぶサイトスピード改善(7) head内の同期スクリプトが描画を止める - 公開日: Wed Jul 22 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる 画像は次世代フォーマットに移行する テキストはGZIP/Brotliで圧縮する 重い日本語Webフォントが表示を遅らせる 外部CDNは速いとは限らない 画像の寸法指定でガクッと動くページを防ぐ head内の同期スクリプトが描画を止める(本記事) ブラウザはscriptタグの前で立ち止まる ブラウザはHTMLを上から順に読みながら、次に取得すべきリソースを判断していく。その途中で async / defer 属性のない <script> タグに出会うと、処理が止まる。スクリプトのダウンロードと実行が終わるまで、HTMLの解析は先へ進まない。これが同期スクリプトによるレンダリングブロックの正体だ。 外部ドメインからスクリプトを読み込んでいる場合は、DNSの解決とTLS接続の確立にかかる時間が上乗せされる。その間もHTMLのパースは止まったままになる。 弊社の表示速度ボトルネック実例研究(vigilante)では、この問題を国内サイトで繰り返し観測している。影響の形はさまざまで、FCP(First Contentful Paint)への直接的な遅延から、HTML本体の肥大化、ページ全体の安定性低下まで幅がある。4つの事例とともに確認していく。 ブラウザはscriptタグの前で立ち止まるブラウザがscriptに出会うと何が起きるか実例で確認する同期スクリプトの影響じゃらんニュース:外部CDN同期jQueryがFCPを1.3秒押し下げた(シミュレーション)朝日新聞デジタル:head内3件の同期スクリプトがレンダリングをブロックHamee本店:1MBの巨大インラインスクリプトでHTMLが肥大化高島屋オンラインストア:未依存スクリプトのasync化がページ安定性を改善(シミュレーション)同期スクリプトを非同期化する対処法defer属性:パース完了後に順序を守って実行async属性:ダウンロード完了次第実行head内スクリプトのbody末尾移動インラインスクリプトの肥大化を防ぐ確認方法まとめ ブラウザがscriptに出会うと何が起きるか ブラウザはHTMLのパース中に <script src="..."> タグを見つけると、次の順で処理する。 HTMLのパースを停止する スクリプトをダウンロードする(外部ファイルの場合) スクリプトを実行する 実行が完了したらHTMLのパースを再開する <head> 内に置かれた同期スクリプトの場合、CSSの読み込みよりも早い段階でこれが起きる。つまりスタイルシートのダウンロードとレンダリングツリーの構築が、スクリプトの完了を待つことになる。 async 属性を付ければ、HTMLのパースと並行してスクリプトをダウンロードできる(実行はダウンロード完了次第で順序は不定)。defer 属性を付ければ、パースを妨げずにダウンロードし、パース完了後に記述順で実行される。どちらの属性もない状態が「同期」で、これがボトルネックになりやすい。 外部ドメインからの読み込みでは、この問題がより深刻になる。DNS解決とTLS接続の確立が済むまで、スクリプトのダウンロードそのものが始まらないからだ。接続コストが丸ごとHTMLパースのブロック時間に変わる。 実例で確認する同期スクリプトの影響 じゃらんニュース:外部CDN同期jQueryがFCPを1.3秒押し下げた(シミュレーション) リクルートが運営する旅行・おでかけ情報メディアじゃらんニュースでは、<head> 内の最初の <script> タグで ajax.googleapis.com からjQuery 1.8.3が同期読み込みされていた。外部ドメインへのリクエストではDNS解決とTLS接続の確立が必要で、この待機は約300〜500msにのぼる。その間、HTMLのパースは止まったままだ。同じく maxcdn.bootstrapcdn.com からFont Awesome CSSもレンダリングブロックリソースとして読み込まれていた。 解消シミュレーションでは、これらの外部CDNリソースを同一ドメイン(www.jalan.net)からの配信に切り替えた。外部ドメインへの接続コストが排除され、既存のHTTP接続が再利用される形になる。 指標 解消前 解消後(シミュレーション) 変化量 FCP 2.0秒 0.7秒 -1.3秒 LCP 2.2秒 0.9秒 -1.3秒 SI 2.8秒 2.1秒 -0.7秒 総合スコア 97 100 +3 シミュレーションでは FCP が2.0秒から0.7秒へ1.3秒短縮された。LCP も同じく1.3秒短縮されている。head内の最初のスクリプトが外部ドメインへの同期リクエストを発していたことで、ページ全体の描画開始が押し下げられていた構図が読み取れる。 同サイトにはさらにhead内に15個の同期スクリプトが残っていた。これらをbody末尾に移動したシミュレーションでは、FCP がさらに0.1秒短縮されている。複数の同期スクリプトが積み重なるほど遅延が累積するパターンだ。 詳細はじゃらんニュースのボトルネック研究で確認できる。 朝日新聞デジタル:head内3件の同期スクリプトがレンダリングをブロック 朝日新聞デジタルは観測時点で 総合スコア 96、LCP 0.3秒と、基礎的なパフォーマンスが高水準にある。それでもhead内に async / defer 属性のない同期スクリプトが3件存在していた(jquery-1.9.0.min.js、sp_top.js、ad/js/sp/top.js)。CSSのパース完了後にこれらが直列で実行され、合計約23msのレンダリングブロックが発生していた。 解消シミュレーションでは3件のスクリプトをbody末尾に移動した。 指標 解消前 解消後(シミュレーション) 変化量 FCP 0.3秒 0.2秒 -0.1秒 SI 1.5秒 1.4秒 -0.1秒 シミュレーションでは FCP が0.3秒から0.2秒へ短縮された。元の値がすでに小さいため変化幅は0.1秒にとどまるが、レンダリングブロックの排除によってHTML解析が先行できるようになった効果が確認できる。 この結果が示すのは、フロントエンドの実装品質が高い状態でも、同期スクリプトのレンダリングブロックは残りうるという点だ。特別なサイトの話ではなく、広く見られるパターンだろう。詳細は朝日新聞デジタルのボトルネック研究を参照してほしい。 Hamee本店:1MBの巨大インラインスクリプトでHTMLが肥大化 同期スクリプトの問題は外部ファイルだけではない。HTMLに直接書き込まれたインラインスクリプトも、パース負荷の観点でボトルネックになりうる。 スマートフォンケース・アクセサリーを扱うHamee本店(strapya.com)では、インラインスクリプトとして、サイト内検索用の全商品データJSON(約703KB)とフォームビルダー定義データJSON(約303KB)がHTMLに埋め込まれていた。合計約1MBのデータがHTMLの一部として配信される構成で、HTMLサイズは約1.94MBに達していた。 いずれも初期表示には不要なデータだ。ページを表示する前にHTMLをまるごとダウンロードしパースするコストが、そのまま表示速度に乗る。 解消シミュレーションでは初期表示に不要な2つの巨大インラインスクリプトを削除した。HTMLサイズは1.94MBから1.00MBへ約50%削減された。 指標 解消前 解消後(シミュレーション) 変化量 LCP 1.2秒 1.0秒 -0.2秒 シミュレーションでは LCP が0.2秒短縮された。HTMLの転送量が削減されたことで、ドキュメントのダウンロード完了が早まり、後続リソースの取得開始が前倒しになった効果だと考えられる。FCP自体への影響はこのシミュレーションでは観測されなかったが、HTMLの肥大化がLCPに対してボトルネックになっていたことは数値から確認できる。 詳細はHamee本店のボトルネック研究を参照してほしい。 高島屋オンラインストア:未依存スクリプトのasync化がページ安定性を改善(シミュレーション) 高島屋オンラインストアでは、body末尾に配置された3つのスクリプト(footerFixed.js、Chart.min.js、smartphoto.js)が async 属性なしで同期的に読み込まれていた。いずれも他のスクリプトから依存されていないことを静的解析で確かめ、async 属性を付与するシミュレーションを実施した。 html<!-- 変更後 --> <script src="/sto/common/js/footerFixed.js" async></script> <script src="/cdn/cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js" async></script> <script src="/sto/common/js/lib/smartphoto.js" async></script> 指標 解消前 解消後(シミュレーション) 変化量 CLS 0.383 0.209 -0.174 総合スコア 82 89 +7 シミュレーションではFCP・LCPへの直接的な影響は観測されなかったが、CLS(Cumulative Layout Shift = レイアウトのずれ)が0.383から0.209へ大きく変化し、総合スコア が7ポイント向上した。async 属性によってスクリプトの実行順序が変わり、レイアウトシフトのタイミングに影響を与えたと考えられる。 同期スクリプトの非同期化がFCPだけでなく、ページの安定性にも波及しうることを示す例だ。詳細は高島屋オンラインストアのボトルネック研究にまとめている。 同期スクリプトを非同期化する対処法 defer属性:パース完了後に順序を守って実行 html<!-- 同期(問題のある状態) --> <script src="app.js"></script> <!-- deferに変更 --> <script src="app.js" defer></script> defer を付けると、スクリプトのダウンロードをHTMLパースと並行して行い、HTMLのパース完了後に記述順で実行する。他のスクリプトに依存しているスクリプト(実行順序が重要)には defer が適している。 async属性:ダウンロード完了次第実行 html<script src="analytics.js" async></script> async を付けると、ダウンロード完了次第に実行される。実行順序は保証されない。解析ツールや広告タグなど、他のスクリプトとの依存関係がないスクリプトに適している。 head内スクリプトのbody末尾移動 defer / async の付与が難しい場合でも、<head> 内のスクリプトを </body> 直前に移動するだけでレンダリングブロックを回避できる。 html<!-- 変更前: headに同期スクリプト --> <head> <script src="heavy.js"></script> </head> <body> <!-- コンテンツ --> </body> <!-- 変更後: body末尾に移動 --> <head> <!-- スクリプトなし --> </head> <body> <!-- コンテンツ --> <script src="heavy.js"></script> </body> HTMLのパースを先行させることができ、FCPの改善につながりやすい。 インラインスクリプトの肥大化を防ぐ Hamee本店の例のように、全商品データや設定データをHTMLに丸ごと埋め込む構成は、初期表示後に非同期で取得する形に切り替えることで、HTMLの転送サイズを大幅に削減できる。 html<!-- 問題: 初期表示に不要な大量データがinlineで埋め込まれている --> <script> window.ALL_PRODUCTS = [/* 703KB分のJSON */]; window.FORM_CONFIG = [/* 303KB分のJSON */]; </script> <!-- 解決: 必要なタイミングでAPIから非同期取得する --> <script> // 検索が必要になったときに初めてデータを取得する async function initSearch() { const res = await fetch('/api/products.json') window.ALL_PRODUCTS = await res.json() } </script> 確認方法 手元のサイトで同期スクリプトの問題を確認するには、以下の方法が手軽だ。 Chrome DevToolsのPerformanceタブを開いてページをリロードすると、タイムラインが表示される。Parse HTML の処理中に Evaluate Script が割り込んでいる箇所が、同期スクリプトによるブロックにあたる。 Lighthouseレポートでは、「Eliminate render-blocking resources」の指摘に <script> タグが含まれていれば、それが対象だ。 まとめ head内の同期スクリプトはHTMLパースを止める。 async / defer 属性のない <script> タグは、ダウンロードと実行が終わるまでパースを中断させる。この遅延はFCPに直接影響する。 外部CDNからの同期読み込みは遅延が複合する。 じゃらんニュースの例では、外部CDN(ajax.googleapis.com)から同期読み込みされたjQueryがDNS解決とTLS接続の待機をもたらしており、同一ドメイン化でFCPが2.0秒から0.7秒へ1.3秒改善するというシミュレーション結果になった。 インラインスクリプトの肥大化もHTMLパース負荷を高める。 Hamee本店では初期表示に不要な約1MBのJSONがHTML本体に埋め込まれ、HTMLサイズが1.94MBに達していた。削除だけでLCPが0.2秒改善するというシミュレーション結果が得られた。 async化の効果はFCPだけではない。 高島屋オンラインストアの例では、未依存スクリプトへのasync付与がCLSの大幅な改善(0.383→0.209)につながり、総合スコアが7ポイント向上するというシミュレーション結果になった。 対処は属性の付与か位置の変更が基本。 defer / async の付与、またはhead内からbody末尾への移動で対応できる。インラインスクリプトは不要なデータを取り除くか、非同期取得に切り替える。 実装品質が高いサイトでも同じ問題は起きる。 朝日新聞デジタルのように総合スコア96の状態でも、head内に同期スクリプトが残っていた。特別なサイトの話ではなく、広く見られるパターンだろう。 自社サイトの同期スクリプトや他のボトルネックを体系的に調べたい場合は、弊社の表示速度ボトルネック研究 vigilante の事例が参考になる。より詳しい調査が必要な場合はアイデアマンズまでご相談いただきたい。 --- # CrUXはどのくらいあてになるか - Chrome限定の実測値を全ブラウザRUMと突き合わせてみた - 公開日: Mon Jul 20 2026 00:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, research, technology CrUX(Chrome User Experience Report)は、ChromeユーザーがWebサイトを閲覧したときのスピード指標を収集したビッグデータだ。このデータの素晴らしいところは、 事前準備なしに、思いついたときにサイトスピードの状態を確認できる 他のサイトも同様に確認し、競合サイトと比較できる こういった点にある。 一方で弱点もある。Chromeユーザーの、さらにオプトインしたユーザーだけが対象であることだ。 スマホで言うと日本はiPhone利用者が多く、デスクトップもEdgeがシェアを伸ばしている。このように偏ったデータであることは否めない。 弊社では、より詳細なスピード体験を観測するため、RUM(Real User Monitoring)収集サービス「Speed is Money」を運営している。Speed is Moneyでは、iPhone SafariやEdgeといったChrome以外の有力ブラウザからも、可能な限りスピードを収集している。 この記事では、この二つのデータにどんな違いがあるかを比較してみた。 CrUXはGoogleのおかげで手軽にアクセスできる貴重なデータだが、実際のユーザー体験とどんな誤差があるのか? CrUXを見てサイトスピードに関する判断をするとき、何が正しくて、何が間違っているのか? このような疑問に、一応の回答を示したい。 ただし、Speed is Moneyもより多くのユーザーをカバーした標本ではあるが、真実(母数)ではない。また、今回は14の通販サイトを対象に調査を行ったが、これもネット全体からすると微小なサンプルだ。 そうした限界は承知のうえで、「CrUXって、サイトスピードに関する意思決定をする上で信用できるよね?」を、実データで再確認してみたい。 そもそもCrUXは誰を見ているのかデスクトップ:CrUXが見ているのは3分の2だけモバイル:iPhoneが過半なのに丸ごと不可視CrUXのデータはどんな数字でできているかデバイスは分けて見るデータの取り出し方——CrUX APIとBigQuery用途の線引き:参考になるケース/注意が必要なケース検証(1)順位はよく当たる検証(2)モバイルは一致しデスクトップはずれる検証(3)INPはデスクトップでも一致する検証(4)月次アーカイブでもモバイルのLCPは誤差が小さい検証(5)割合で見てもモバイルは一致する検証(6)月次トレンドは方向を追えるが乖離も残るまとめ:CrUXが使える範囲と注意点全ブラウザで実測するには そもそもCrUXは誰を見ているのか 「Chromeユーザーだけ」と言うと軽く聞こえるが、実際にどれだけの人が抜け落ちるのか。まず、そこを日本のブラウザシェアで具体的に押さえておきたい。ここが、この記事のすべての土台になる。 CrUXに体験が集計されるのは、Googleの公式メソドロジーによれば、次を満たすChromeユーザーだけだ。 対象 デスクトップ版Chrome(Windows / macOS / ChromeOS / Linux) Android版Chrome 対象外(公式が明記して除外) iOS版のChrome Android アプリ内の WebView その他のChromium系ブラウザ(Microsoft Edge を明示) そのうえで、利用統計レポートを有効化し、閲覧履歴を同期している(オプトインした)人だけがカウントされる。 「モバイルのChromeは全部CrUXに入る」は誤り iPhoneではAppleの制約で、Chromeを含むすべてのブラウザがSafariと同じWebKitエンジンで動く。CrUXの計測機構はChromiumのBlinkエンジン上でしか働かないため、iOS版のChromeはまるごと対象外だ。CrUX対象のモバイルChromeは、実質「Android版Chrome」だけと考えてよい(web.dev: CrUXとRUMの違い)。 デスクトップ:CrUXが見ているのは3分の2だけ 日本のデスクトップ・ブラウザシェアに、CrUXが拾う範囲を重ねてみる。上段が各ブラウザの内訳、下段はCrUX対応(緑)と対象外(グレー)に分けたもので、横軸はいずれもシェア(%)だ。赤い破線がCrUX対応の境界を示す。 CrUXが拾うのは Chromeの65.4%だけ(StatCounter, 2026年6月)。次点の Edge(22.2%)はChromium系でも対象外で、SafariやFirefoxも当然入らない。つまりデスクトップ来訪のおよそ3分の1を、CrUXは一切見ていない。後で「デスクトップのLCPが実測と大きくズレる」ことを確かめるが、その根っこは、この見えていない3分の1にある。 モバイル:iPhoneが過半なのに丸ごと不可視 モバイルは非対称がさらに極端だ。同じく上段が各ブラウザの内訳、下段がCrUX対応の上限(Android・緑)と対象外(iOS等・グレー)で、横軸はシェア(%)である。 まず日本のモバイルOSは iOS 58.7% / Android 41.2%。iPhoneのほうが多い。そして前述のとおり iOSはSafari・Chromeともに同じWebKit=丸ごとCrUX対象外だ。グラフのChrome 55%はAndroid版とiOS版の合算で、そのうちCrUXに入るのはAndroid版だけ。Safari 40%も当然入らない。 結局、CrUXのモバイル値は「Android Chromeの体験」であって、iPhoneが過半を占める日本のモバイル実体験の代表ではない。この前提を頭の隅に置いて、以降のデータを見てほしい。 出典 ブラウザ・OSシェアはいずれも StatCounter GlobalStats(日本・2026年6月)。内訳はデスクトップ・ブラウザ/モバイル・ブラウザ/モバイルOSを参照した。CrUXの対象範囲はChrome公式のメソドロジーによる。 CrUXのデータはどんな数字でできているか 対象ブラウザを理解したところで、CrUXが保持している数字の意味も確認しておこう。 まず、ユーザーの体験スピードは、同じページであってもかなりのばらつきが生じる。端末や回線が人それぞれだからだ(このばらつきは「ページスピードは一定ではない?」で詳しく書いた)。したがって、単純に「何秒」とひとつの数字では表現できない。そこでCrUXは、体験のばらつきを次の2つの形にまとめて持つ。 p75(75パーセンタイル) — 大多数(訪問者の75%)の体験が、何秒以内に収まっているか。たとえばLCPのp75が2.5秒なら「4人に3人は2.5秒以内に表示できていた」を意味する。 Good(良い)/ Needs improvement(要改善)/ Poor(悪い)の比率 — 指標ごとに良い・悪いの基準線を設け、全体験が3グループに分かれる。その構成比だ。PageSpeed Insightsの緑・黄・赤の帯がこれで、LCPなら2.5秒以下ならGood(良い)、4秒超ならPoor(悪い)。 この記事で特に見るのは、次の3つの指標だ。あわせて、どちらの向きがよいかも押さえておきたい。 p75 — 小さいほどよい Good(良い) — 大きいほどよい Poor(悪い) — 小さいほどよい デバイスは分けて見る CrUXは、端末を phone(スマホ)/ desktop(PC)/ tablet(タブレット) の3種類に分けて集計している。 PCはスマホより端末性能が高い。だから全体的によい数値が出やすい。両者を混在させると互いにノイズになり、データの意味が曖昧になる。したがって、デバイス別に集計するのが基本となる。 なお、タブレットはごく少数で、分析に足る母数がない。そこでSpeed is Moneyでは、タブレットをPCに含めている。本記事でも、タブレットは分析対象から外している。 データの取り出し方——CrUX APIとBigQuery CrUXには、データの取り出し方がふたつある。 CrUX API:直近28日のスナップショット。現在のスピードを速報として知りたいときに使う。ドメイン単位・ページ単位それぞれのデータを得られ、数値の精度も高い(1ミリ秒精度)。 BigQuery:過去のアーカイブで、毎月中旬くらいに前月分までの集計が出揃う。ドメイン単位に集約される。 イメージとしては、売上の速報値と、月次決算で締めた確定値といったところだろうか。普段おなじみのPageSpeed InsightsやSearch Consoleに表示される数値は、CrUX APIのほうだ。 BigQueryには過去のデータが何年分も蓄積されている。特別なトラッキングなしでも、サイトスピードの長期的な推移を分析できるわけだ。BigQueryにはいくつかデータセットがある。弊社では、デバイスごとに集計された device_summary をよく使う。 BigQueryの難点は、反映の遅さと、p75の値が丸められることだ(LCPは100ミリ秒、INPは25ミリ秒刻み)。 そこで今回は、一致の精度をまず1ミリ秒精度のAPIで確かめ、そのうえで実運用で頼る月次アーカイブ(device_summary)が13か月×14サイト=182点でどれだけ実測と合うかも検証した。 用途の線引き:参考になるケース/注意が必要なケース 先に結論を、用途別に整理する。「正しい使い方」と「間違えやすい使い方」の切り分けだ。 ただし、この「正しい/間違い」という物言いには留保がいる。正確には、今回の14サイト横断調査で「その読み方を否定する材料が見つかったか、そうでなかったか」を述べているにすぎない。14サイトはネット全体からすればごく小さな標本で、真偽を証明したわけではない。それでも、数字を扱って意思決定をする以上、どこかで何らかの態度を決める必要はある。弊社としては、今回支持された仮説をひとまず採用したうえで、今後のサイトスピード改善提案に活かしていく所存だ。以下の「正しい/間違い」は、その立場からの表現だと受け取ってほしい。 参考にしてよい(CrUXが当てになる) ✅ 競合と自社の速度の順位付け——「A社とB社、どちらが速いか」 ✅ モバイルの速度水準——スマホのLCPなら、CrUXの数字はほぼ実測どおり ✅ モバイルの「良し悪しの割合」——Good(良い)率が何割か、はモバイルなら信頼できる ✅ INP(操作の反応性)——これはモバイル・デスクトップを問わず当てになる ✅ 月々の大きな変化——例えばLCPの数百ミリ秒単位の変動。ただしp75で追うよりGood(良い)率のほうが正確 注意が必要(CrUXを鵜呑みにしない) ⚠️ デスクトップの絶対値、とくにLCP——実測よりかなり楽観的に出る ⚠️ 「デスクトップがどれだけ悪いか」——遅い層を捉えきれず、Poor(悪い)を過小に見せる ⚠️ 月々の小さな変動——誤差やp75の丸めに埋もれる ⚠️ アクセス数の少ないサイト——そもそもデータが生成されず欠測しやすい 以下、なぜこう言えるのかを、突き合わせの実データで一つずつ示す。 検証(1)順位はよく当たる ここから検証(3)までのグラフは、精度の高いCrUX API(直近28日のスナップショット・1ミリ秒精度)の数字を使い、Speed is Moneyの同じ28日と突き合わせている。 まず、CrUXとSpeed is MoneyのLCPを14サイト分プロットする。横軸・縦軸ともにLCPのp75(ミリ秒・小さいほど速い)で、横軸がCrUX、縦軸がSpeed is Moneyだ。破線は両者が完全一致する位置(y=x)を示す。 点は右肩上がりに並ぶ。速いサイトはCrUXと実測のどちらでも速く、遅いサイトも同様に遅い。順位の一致度を数値で見ると高く、LCPで順位相関0.96、INPで0.87、TTFBで0.93、FCPで0.96。「競合と比べてどちらが速いか」の相対評価に、CrUXは十分使える。 INP(こちらも横軸・縦軸ともにp75・ミリ秒)でも同じで、順位はよく当たる。 ただし、どちらの散布図も点は完全一致の線(破線)から外れている。LCPは線の上(実測のほうが遅い=中央値で27%遅い)、INPは線の下(CrUXのほうが大きめに出る)に寄る。順位は当たるのに、絶対値はズレる。 しかも指標で外れる向きが逆——これは単なる測定誤差ではなく、構造的な要因があることを示唆する。 検証(2)モバイルは一致しデスクトップはずれる その答えはデバイスにある。軸は変わらずLCPのp75(ミリ秒)で、同じ点をモバイル(青丸)とデスクトップ(橙の三角)に塗り分けてプロットすると、二つの層がくっきり分かれる。 デスクトップ(橙)は完全一致の線から上に外れる。 実測のほうが中央値で46%遅く、相関は r=0.640。前に見たとおり、CrUXはデスクトップでもChromeしか拾わず、Edge・Safariを含む約3分の1を計測していない。対象がブラウザの一部に限られる以上、ここにズレが出るのは想定の範囲だ。 モバイル(青)は完全一致の線に乗る。 CrUXのモバイル値と実測の差は中央値で+1.6%、相関 r=0.985。14サイトすべてがほぼ線上に並ぶ。検証(1)で見た「全体で27%ズレる」のは、このデスクトップ側のズレが全体値に混ざったためだ。 ここで思い出したいのが、CrUXのモバイルはAndroid Chromeだけの数値であることだ。日本ではモバイルの過半数がiPhoneで、Android Chromeのサンプルは半数に満たない。それでもCrUXとSpeed is Moneyの実測でp75がここまで一致するなら、iPhoneとAndroidも近いスピード分布を持つ、と推測できる。 この仮説の検証は今後の研究に譲る。現時点でも、CrUXモバイルのp75は全体像に近く、実測の参考として使える可能性が高い。 検証(3)INPはデスクトップでも一致する 次に、INP(操作してから画面が反応するまでの速さ)も同じ軸で見る。散布図はINPのp75(ミリ秒)で、モバイル(青丸)とデスクトップ(橙三角)に分けている。 LCPでは外れたデスクトップ(橙)が、INPでは線の近くに収まる(r=0.925)。モバイル(r=0.964)と合わせ、INPはデバイスを問わず実測とそろう。モバイルで反応が遅めの領域(右上)ではCrUXがやや大きめに出るが、その差は小さい。 つまり——INPに関しては、CrUXはLCP以上に「全体の実態に近い」データを見せていると言ってよさそうだ。読み込みの速さ(LCP)はデスクトップの見えない層に引っ張られたが、操作の反応(INP)はそうした層の影響を受けにくく、Android Chromeだけを見ても全体の姿とほとんど変わらない。INPで「このサイトは反応がよい/悪い」を読むなら、デバイスを問わずCrUXを信じてよいだろう。 検証(4)月次アーカイブでもモバイルのLCPは誤差が小さい ここまでの検証(1)〜(3)は、1ミリ秒精度のCrUX APIでの話だった。 だが実務で過去をさかのぼるとき見るのは、丸められた月次アーカイブ(BigQueryのdevice_summary)のほうだ。ここから検証(6)までは、このアーカイブの数字を使う。そこで、13か月×14サイト=182点をすべて打って、アーカイブが実測とどれだけ合うかを確かめた。まずは水準——LCPのp75(ミリ秒)——の散布図から。 APIのときと同じ構図だ。モバイルは完全一致の線に近い分布を描き、デスクトップにはやや大きなばらつきが見られる。過去の月をさかのぼっても、モバイルの速い/遅いは正しく読める。p75の系統差はSpeed is Moneyが約1割遅め(全ブラウザぶんの上振れ+丸めで速めに出るぶん)で、これは目安として織り込めばよい。 同じp75でも、INPはアーカイブだと緩くなる。 デスクトップ(橙)はINPが小さく(25〜75ms)低い位置に固まる。モバイル(青)は縦縞状に散らばっている。アーカイブのINP p75は25ミリ秒刻みに丸められるため、連続値の実測との間に階段状のズレが出る。相関もモバイルで r=0.83と、LCPの0.96より緩い。アーカイブのINP p75は水準の目安どまりと見ておきたい。「p75は丸めに弱い」というこの性質が、月々の変動をGood(良い)率で追うべき理由(検証6)でもある。 検証(5)割合で見てもモバイルは一致する p75だけでなく、Good(良い)/Poor(悪い)の割合でも突き合わせる。 具体的な数値が得られるp75は意味を解釈しやすい反面、丸めのぶん若干精度に劣る。イメージしやすければ、Good(良い)率——その指標の「良い」基準を満たした割合——を追うほうが、より正確だ。 まず、182点それぞれ(CrUX=横軸、実測=縦軸)を散布図にする。点が完全一致の線に寄るほど、CrUXと実測は近い。LCPから見る。左にGood(良い)率、右にPoor(悪い)率を並べた(各軸=判定に入った割合%)。 LCP Good(良い)率 LCP Poor(悪い)率 モバイル(青)はどちらも完全一致の線に寄る(Good(良い)率の相関0.98、Poor(悪い)率0.95)。一方デスクトップ(橙)は線から外れる。CrUXがGood(良い)を約5ポイント多く、Poor(悪い)は約3ポイント少なく見せるためだ。デスクトップのLCPは「良く」見えやすい——水準の話と同じ癖だ。実務でひとつ覚えておくなら、これくらいでよい。 INPも同じ2枚で見る(左=Good(良い)率、右=Poor(悪い)率)。 INP Good(良い)率 INP Poor(悪い)率 Good(良い)率を見ると、デスクトップ(橙)は95%超に固まって線に近い。モバイル(青)は、とくにGood率が低めの左下で散らばりも見られるものの、おおむね線に沿う。相関はモバイル・デスクトップとも0.86前後だ。Poor(悪い)率は、どのサイトもほぼ0%に張り付く。点が集中するため相関係数は小さく出る。とはいえ「揃ってほぼ0%」という一致だ。CrUXで「INPが悪いサイトは少ない」と判断してよい。 最後に、指標ごとに数値でまとめる。device_summary と実測の Good(良い)率・Poor(悪い)率について、相関係数と両者の差(実測−CrUX・pt)を並べた。 指標判定モバイルデスクトップ 相関 r差(pt)相関 r差(pt) LCPGood(良い)0.98-1.50.89-4.9 Poor(悪い)0.95+0.60.67+3.3 FCPGood(良い)0.99-1.20.97-5.2 Poor(悪い)0.99+0.20.93+2.8 TTFBGood(良い)0.95+4.10.88-2.0 Poor(悪い)0.97-0.90.91+0.7 INPGood(良い)0.86-1.20.86-1.4 Poor(悪い)0.56+0.80.59+0.6 モバイルはどの指標もGood(良い)率の相関が高く、差も数pt以内におさまる。一方デスクトップはGood(良い)の差が開きやすい(LCPで-4.9pt、FCPで-5.2pt)。Poor(悪い)率も傾向は同じだ。INPのPoor(悪い)は相関こそ低い。ただし値がほぼ0%に固まるためで、不一致ではない。 検証(6)月次トレンドは方向を追えるが乖離も残る 引き続き月次アーカイブ(device_summary)で「経緯」を見る。ここで確かめたいのは、月ごとに上がった/下がったという変動が、どこまで実際のスピード変化を映すかだ。CrUXの履歴を見て「今月このサイトは速くなった(遅くなった)」と判断してよいものか。 実際に動きのあった4サイトについて、モバイルのLCP p75の月次推移を、実測(実線)とCrUX(破線)で重ねた。縦軸はLCPのp75(ミリ秒)、横軸は月だ。 Site C:夏に約1.5秒→3.4秒へ急悪化。実測とCrUXが連動 Site K:同じく急悪化。CrUXも追随 Site J:ほぼ横ばい Site N:下降方向は一致するが、実測とCrUXに一段の乖離 方向はおおむね合っている。Site C・Kは2025年夏にLCPが1.5〜1.8秒台から3.4秒前後へ悪化した。実線と破線はそろって跳ね上がる。Site Jはほぼ横ばい。Site Nは改善だ。大きめの変化なら、CrUXの履歴でも向きは読める。 14サイトの月次連動は、サイト別相関の中央値0.74(14サイト中13が正)だった。 ただし、水準が一段ずれるサイトもある。改善したSite N(最後の図)がその例だ。実測とCrUXは同じ下降トレンドを描くものの、実測が一段高い位置にある。p75は100ミリ秒刻みに丸められるため、こうしたずれが出やすい。 同じ4サイトを、今度はGood(良い)率(Good(良い)判定の割合・%)で見る。 Site C:急落も密に一致 Site K Site J Site N:改善も一致 実線と破線が、p75よりもさらに密に重なる。Site C・Kの急落や、Site Nの改善を、実測とCrUXがほぼ同じ軌跡でなぞる。Good(良い)率は割合で出るぶん丸めに強く、月々の変化を素直に映す。 最後にPoor(悪い)率(%)を見る。 Site C:CrUXが小さめ Site K:CrUXが小さめ Site J Site N こちらは実測とCrUXの開きが目立つ。CrUXはPoor(悪い)を実測より小さめに見せ(Site C・Kで顕著)、値も小さいぶん揺れやすい。悪化の向きこそ捉えるが、水準を月次で細かく追うのは心もとない。 整理すると、月次で追うときの信頼性は Good(良い)率 > p75 > Poor(悪い)率 の順になる。月々の変動を追うなら、まず見るべきはGood(良い)率だ。 p75は直感的だが、丸めで一段ずれる。Poor(悪い)率は数字が小さく、揺れやすい。 それでも、追えるのは方向までだ。本物の変化かどうかの確証が要るなら、CrUXで見当をつけ、最後はRUMで実測したい。 まとめ:CrUXが使える範囲と注意点 最初の問いに戻ろう。「CrUXって、サイトスピードの意思決定に信用できるよね?」——答えは、用途を選べばイエスだ。 CrUXの守備範囲は狭い。日本で見ているのはデスクトップChrome(65%)とAndroid Chromeだけで、デスクトップのEdge・Safari・Firefox(約3分の1)も、iPhoneのSafari(モバイルの過半)も含まない。それでも、モバイルのLCPは14サイトすべてで実測とそろい、INPはデスクトップまで一致した。対象が限られていても、モバイルの水準はおおむね言い当てられていた。 だから、競合との順位、モバイルの水準と割合、INP、大きな変化の履歴は、全ブラウザのRUMと突き合わせても十分に信頼できる。裏を返せば、CrUXが実態からはっきりズレるのは、対象が構造的に狭いことに起因する一点——デスクトップの絶対値、とくにLCPの「どれだけ悪いか」——にほぼ絞られる。そこだけ割り引いて読めば、CrUXは「地図」として相当に正確だ。 CrUXを疑う必要はないし、盲信する必要もない。地図と実測の縮尺の違いを知って使い分ける。それが、フィールドデータと正しく付き合うということだと思う。 なお冒頭で断ったとおり、突き合わせに使ったSpeed is Money自身も全数ではなく標本で、対象は14サイトという小さなサンプルだ。ここで引いた線は「決定版」ではなく、あくまで一つの実測に基づく目安として受け取ってほしい。 全ブラウザで実測するには 冒頭で触れた「Speed is Money」は、サイトスピードに特化したシンプルな無料アクセス解析サービスだ。Safari・Edgeも含めた全ブラウザ・全訪問者の速度を取りこぼさず記録するので、CrUXでは見えないデスクトップの実態や、iPhoneユーザーの体験、実来訪の本当のデバイス構成まで把握できる。 この記事のデータも、Speed is Moneyを利用中の通販サイト14サイトに協力をいただいた。CrUXの数字だけで物足りなくなった方は、ぜひ実測を試してみてほしい。 なお、速度がCVRにどう効くのかについては、「通販サイトが遅いと損失はどのくらい?」でも機会損失の見える化として詳しく扱っている。あわせて読んでいただけると幸いだ。 --- # 実例に学ぶサイトスピード改善(6) 画像の寸法指定でガクッと動くページを防ぐ - 公開日: Sun Jul 19 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる 画像は次世代フォーマットに移行する テキストはGZIP/Brotliで圧縮する 重い日本語Webフォントが表示を遅らせる 外部CDNは速いとは限らない 画像の寸法指定でガクッと動くページを防ぐ(本記事) head内の同期スクリプトが描画を止める ページがガクッと動く不快感の正体 ページが途中でガクッと動く経験は珍しくない。記事を読んでいたら文章が突然下に飛ぶ。ボタンを押しそうになったら別のリンクに差し変わって誤タップする。あの不快感を数値化したのが CLS(Cumulative Layout Shift) だ。 LCPやFCPが「コンテンツが表示されるまでの時間」を問うのに対して、CLSは「読み込み中にレイアウトがどれだけずれたか」を問う指標だ。表示が速くてもページがガタガタ動けばCLSは高い。CLSが低くてもLCPが遅いケースもある。両者は独立した軸で評価される。 もっとも、CLSは比較的対処しやすい指標でもある。Core Web VitalsがSEOの文脈で注目され始めた2021〜2022年ごろには、すでにCLS対策を終えていたサイトも多かった。HTMLとCSSを丁寧に書けば防げる確率が高いからだ。とはいえ丁寧に書いたつもりでも抜け落ちる箇所はある。 弊社の表示速度ボトルネック実例研究(vigilante)では、CLSを大きく押し上げる原因として2つのパターンを繰り返し観測している。ひとつは画像に width/height 属性が指定されていないこと。もうひとつはカルーセル(スライダー)の高さがJavaScript初期化前に確保されていないことだ。どちらもHTML/CSSの数行の変更で対処できる。 ページがガクッと動く不快感の正体CLSとは何かなぜ画像でCLSが起きるのかカルーセルでCLSが起きる構造実例で確認するCLSの改善タマチャンショップ:CLS 0.447 → 0.047(シミュレーション)富澤商店(TOMIZ):CLS 0.323 → 0.061(シミュレーション)Billboard Japan:CLS 0.721 → 0.002(シミュレーション)ABAHOUSE ONLINE STORE:CLS 0.527 → 0.037(シミュレーション)自分のサイトで対処するにはステップ1:CLSを計測してシフト発生源を特定するステップ2:画像にwidth/heightを指定するステップ3:カルーセルの高さをCSSで事前確保するまとめ CLSとは何か CLS(Cumulative Layout Shift)は、ページの読み込み中に発生したレイアウトのずれを累積的に計測したCore Web Vitalsの指標だ。Googleは0.1以下を「良好」、0.25以上を「改善が必要」と定義している。 レイアウトシフトとは、表示中のコンテンツが予期せず位置を変える現象だ。読んでいたテキストが突然スクロールして消える、タップしようとしたボタンが別の要素に差し変わって誤操作するといった体験がこれにあたる。ユーザーの意図しないところでページが動くため、可読性の妨害や誤タップにつながりうる。 この記事で扱うのは「ページを速く表示する」話ではなく「ページが安定して表示される」話だ。CLSを改善してもLCPが速くなるわけではないし、LCPを改善してもCLSが下がるわけでもない。 なぜ画像でCLSが起きるのか <img> 要素に width と height を指定していない場合、ブラウザは画像の表示サイズを事前に知ることができない。HTMLを解析した時点では高さがゼロとして扱われ、画像のダウンロードが完了した瞬間に実際のサイズで描画される。この切り替わりが、周囲のテキストやボタンを一気に押しのける。 html<!-- 高さを事前計算できないので画像読み込み完了時にずれが発生する --> <img src="banner.jpg" alt="キャンペーンバナー"> <!-- width/heightを指定するとアスペクト比から空間が事前確保される --> <img src="banner.jpg" alt="キャンペーンバナー" width="800" height="400"> width/height を指定するとブラウザがアスペクト比から表示領域を計算し、ダウンロード完了前から空間を確保しておく。これだけでレイアウトシフトは大幅に抑えられる。 CSSで aspect-ratio を指定する方法でも同じ効果が得られる。レスポンシブ対応で固定ピクセルを指定しにくい場合はこちらが有効だ。 css/* aspect-ratioでアスペクト比を事前指定する代替手段 */ .logo-image { aspect-ratio: 200 / 80; width: 100%; height: auto; } なお width/height 属性を指定しつつCSSで width: 100%; height: auto; を適用しても問題ない。ブラウザはHTML属性からアスペクト比を計算して空間を確保し、CSSで実際のレイアウトサイズへスケールさせる仕組みだ。 カルーセルでCLSが起きる構造 カルーセル(スライダー)はJavaScriptで初期化される。HTML読み込み直後は全スライドが縦に積み重なって表示されることが多い。JavaScriptで1枚表示へ切り替わった瞬間、ページの高さが急変してレイアウトシフトを起こす。 この問題の根本は、JavaScript初期化前の状態が未定義のままになっていることだ。CSSで初期状態の高さをあらかじめ制限しておけば、初期化後の高さ変化を最小限に抑えられる。 css/* JavaScript初期化前に1枚目だけ表示してシフトを抑える例(slick.jsの場合) */ .main-slider:not(.slick-initialized) .slide { display: none; } .main-slider:not(.slick-initialized) .slide:first-child { display: block; } ライブラリによって実装方法は異なるが、「JS初期化前に高さが未定義のコンテナができてしまう」という構造は共通している。スライダーコンテナに最小高さをCSSで指定する方法も有効な選択肢だ。 実例で確認するCLSの改善 弊社のボトルネック実例研究では、CLSに関連するこれらの問題を4サイトで観測した。各サイトの数値は、サードパーティタグの影響を除いたサイト固有のシミュレーション結果だ。タグを含む観測値とは若干異なる場合がある。 タマチャンショップ:CLS 0.447 → 0.047(シミュレーション) 宮崎県の自然食品ECタマチャンショップでは、lazysizesでlazyloadされる複数の画像(バナー、ナビアイコン、ロゴ等)の src 属性が空(src="")で width/height 属性も未指定の状態が観測された。Flickityスライダーについても、JavaScript初期化前にCSSで高さが確保されていなかった。これらが複合的に作用して、画像読み込み完了時に大きなレイアウトシフトが発生していた。 解消のシミュレーションは2段階で実施した。lazyload画像の src 属性にSVGプレースホルダーを設定し、正確な width/height 属性を付与してアスペクト比をブラウザに伝えた。あわせてFlickityスライダーにCSSで初期高さを確保した。 指標 解消前 解消後(シミュレーション) 変化量 CLS 0.447 0.047 -0.400(89%) 総合スコア 74 92 +18 シミュレーションではCLSが0.447から0.047へ89%変化し、「良好」の基準値0.1を大きく下回る値に達した。詳細はタマチャンショップのボトルネック研究を参照してほしい。 富澤商店(TOMIZ):CLS 0.323 → 0.061(シミュレーション) 製菓・製パン材料のEC富澤商店オンラインショップでは、ページ内131個の <img> 要素のうち92個に width/height 属性が指定されていない状態が観測された。ブラウザが画像のアスペクト比を事前計算できないため、読み込み完了のたびに周囲のコンテンツがずれていた。 解消の方法はシンプルで、92個の <img> 要素に各画像の実寸に基づいた width/height 属性を追加するシミュレーションを実施した。 指標 解消前 解消後(シミュレーション) 変化量 CLS 0.323 0.061 -0.262 総合スコア 82 98 +16 width/height 属性の追加だけでCLSが81%変化し、総合スコアも16ポイント上昇するというシミュレーション結果になった。基本的なHTML属性の有無がこれほど大きな影響を持っていたことを示す例だ。詳細は富澤商店のボトルネック研究にまとめている。 Billboard Japan:CLS 0.721 → 0.002(シミュレーション) 音楽メディアのBillboard Japanでは、HTMLの74箇所すべての <img> 要素に width/height 属性が指定されていない状態が観測された。サードパーティタグ除去後の状態でCLSが0.721という極めて高い値を示しており、width/height 未指定が主因として特定された。 解消のシミュレーションでは、全74箇所の <img> 要素に width/height 属性を追加し、CSSで img { max-width: 100%; height: auto; } を併用することでレスポンシブデザインとの互換性を維持した。あわせてGoogle Fontsに font-display: swap を追加し、フォント由来のシフトの軽減も図った。 html<!-- 変更前: ブラウザが高さを確保できない --> <img src="/common/sys/img/specialbanner/1634/image_l_1.jpg" alt=""> <!-- 変更後: アスペクト比から空間が事前確保される --> <img src="/common/sys/img/specialbanner/1634/image_l_1.jpg" alt="" width="1327" height="886"> 指標 解消前 解消後(シミュレーション) 変化量 CLS 0.721 0.002 -0.719 総合スコア 76 100 +24 LCP 2.1秒 1.4秒 -0.7秒 シミュレーションではCLSが0.721から0.002へと事実上ゼロに近い値まで変化し、総合スコアは24ポイント上昇した。なお、LCP も副次的に0.7秒短縮されている。これは画像サイズの確保によって早期にレイアウトが安定し、レンダリングが効率化された結果だと思われる。詳細はBillboard Japanのボトルネック研究を参照してほしい。 ABAHOUSE ONLINE STORE:CLS 0.527 → 0.037(シミュレーション) ファッション通販のABAHOUSE ONLINE STOREでは、CLSを押し上げる要因が2つ重なっていた。 ひとつはメインカルーセル(slick.js)のレイアウトシフトだ。JavaScript初期化前に全15スライドが縦に展開された状態で表示され、初期化後に1枚表示へ切り替わる際に大きなシフトが発生していた。CSSでカルーセルの初期状態の高さを制限し、slickが付与する .slick-initialized クラスの追加後に制限を解除することで対処した。 もうひとつは img 要素への width/height 属性の未指定だ。バナー画像やヘッダーロゴを含む8箇所への属性追加と、ブランドロゴ14画像へのCSSの aspect-ratio 指定で対処した。 ボトルネック 解消前 CLS 解消後 CLS(シミュレーション) 変化量 カルーセルの高さ未確保 0.527 0.107 -0.420 width/height属性未指定 0.107 0.037 -0.070 2段階の対処を加えたシミュレーションで、CLSは0.527から0.037へと93%変化した。カルーセルのシフトが支配的で、画像属性はその残差を取り除いた形だ。総合スコアはカルーセル対処だけで79から97へ18ポイント上昇し、画像属性の追加でさらに100に到達した。詳細はABAHOUSE ONLINE STOREのボトルネック研究にまとめている。 自分のサイトで対処するには CLSの改善は、原因の種類によって対処の方向が異なる。まず自サイトのCLSを計測してシフトの発生源を特定することが先決だ。 ステップ1:CLSを計測してシフト発生源を特定する Chrome DevToolsの「Performance」タブでページを読み込むと、レイアウトシフトの発生タイミングと原因要素が記録される。「Experience」セクションに Layout Shift のイベントとして表示されるため、どの要素がシフトを引き起こしているかを確認できる。 Google Search ConsoleのCore Web Vitalsレポートも、フィールドデータとして実ユーザーのCLS傾向を把握するのに役立つ。CLSスコアが高い場合はまず発生源の特定から始めてほしい。 ステップ2:画像にwidth/heightを指定する 特定したシフトの原因が <img> 要素であれば、その要素に width/height 属性を追加する。CMSやECプラットフォームが自動生成するHTMLの場合は、テンプレートを修正するか、プラグインやオプションで属性を出力させる設定を確認したい。 CSSで width: 100%; height: auto; を適用するレスポンシブ対応と width/height 属性は共存できる。HTML属性でアスペクト比を伝えつつ、CSSで表示サイズをコントロールすればよい。 ステップ3:カルーセルの高さをCSSで事前確保する シフトの原因がカルーセルやスライダーであれば、JavaScript初期化前の初期状態で高さをCSSで制限する方法を検討したい。ライブラリが初期化後に付与するクラスを使って制限を解除する方法と、コンテナに最小高さを指定してシフト幅を抑える方法のどちらかを取る。 どちらの場合も、ライブラリのバージョンアップや設定変更で初期化のタイミングや付与されるクラス名が変わることもある。修正後はDevToolsで実際のシフトが解消されたかを確認しておきたい。 まとめ CLSはLCP/FCPとは別軸の指標。 表示が速くてもページがガタガタ動けばCLSは高い。この記事はレイアウトの安定性の話であり、表示速度の改善とは直接関係しない。 画像の width/height 未指定はCLSの最もよくある原因のひとつ。 富澤商店では92個、Billboard Japanでは74個の <img> 要素に属性が設定されておらず、追加だけで16〜24ポイントの総合スコア改善というシミュレーション結果が得られた。 カルーセルはJS初期化前の高さ確保が要点。 ABAHOUSEではslick.jsの初期化前後のシフトがCLS 0.527の主因だった。CSSで初期状態を制御するシミュレーションで0.107まで変化した。 複合要因が重なることもある。 タマチャンショップのように、lazyload画像の空間未確保とスライダーの高さ未確保が重なってCLS 0.447という値を示していた例もある。2つの対処を合わせたシミュレーションで0.047まで変化した。 対処の難易度は低い。 丁寧なHTML/CSSコーディングで防げる確率が高く、すでに対応を終えているサイトも多い。コードの変更量は少なく、サーバー設定も不要だ。CLSが高い場合はまずDevToolsでシフトの発生源を確認し、width/height の有無とカルーセルの初期状態を確認してほしい。 自社サイトのCLSや他のボトルネックを体系的に調べたい場合は、弊社の表示速度ボトルネック研究 vigilante の事例が参考になる。より詳しい調査が必要な場合はアイデアマンズまでご相談いただきたい。 --- # 通販サイトが遅いと損失はどのくらい? 遅いサイトは見込み客の90%を取りこぼしている衝撃の損失見える化 - 公開日: Thu Jul 16 2026 20:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, research, business サイトスピードが遅い通販サイトでは、本来買ってくれるはずだったユーザーが数多く離脱し、注文を取りこぼしてしまう。頭ではわかっていても、機会損失は目に見えない。この記事ではそれを可視化してみよう。 サイトスピードによる機会損失を可視化するにはサイトスピードで重要なのは「爆速率」速いか遅いかではなく「速いか爆速か」爆速率 = 読み込み時間 ≦ 1秒の割合サイトごとの機会損失よいケース Site H 爆速率33% 機会損失率62%悪いケース Site J 爆速率9% 機会損失率89%中間的なケース Site I 爆速率16% 機会損失率80%不条理だが改善を続けるしかない機会損失を測定するには サイトスピードによる機会損失を可視化するには まず、あるサイトの30日間のユーザー(実際にはセッションだが、リピーターは少ないのでユーザーと置き換える)を、体験の速い順に20等分する。サイトスピードがおよそ似たユーザーの20グループを作る操作だ。 なお、この記事ではサイトスピードの指標として読み込み時間(OnLoad)を用いる。 この20グループのCVR(コンバージョンレートつまり注文成約率)をプロットすると、次のように右肩下がりになる。 グループのサイトスピード体験が悪いほど、CVRがどんどん低下していく様子がわかる。 また、ここではグループを20等分で作っているので、青い領域の面積はそのまま注文数を示している。 このグラフで解釈できるもう一つの指標がポテンシャルCVRだ。最も体験が速いグループのCVRが最も高く、2.0%をマークしている。 もしインターネットが摩擦のない装置で、全員が良好なサイトスピードを体験できたとしたら、このサイトは本来、2.0%のCVRを実現できたはずなのだ。 言い換えると、ポテンシャルCVRは訪れたユーザーに占める見込み客(本来買うつもりのあった人)の割合を意味する。ユーザー数にポテンシャルCVRを掛けた人数が、このサイトの見込み客の数だ。 ところが現実にはユーザーによってサイトスピードにはばらつきが生じてしまい、遅いユーザーは離脱してCVRが低くなる。そうして最後にCVRは0.76%に落ち着く。そのような構造になっている。 このポテンシャルCVRに対し、サイトスピードに応じて低下したCVRとの間に生じた差が、サイトスピードによる機会損失である。この損失がポテンシャル全体に占める割合が「機会損失率」であり、見込み客のうち何%を取りこぼしたかを表す。 このように表現することで、そのサイトがスピードによってどのくらい離脱を招き、機会損失を生んでいるか可視化できる。 ユーザーごとのサイトスピードについて ここでユーザーごとのサイトスピードとは、それぞれのユーザーのOnLoadの最小値によって代表している。平均値や中央値ではなく最小値を使う理由は「サイトスピードと収益性の定量的な関係 - 最小LCPによる新モデル」で詳しく解説した。 したがって、20グループの分け方はより正確には、「サイトスピード体験において背負わされたハンディキャップの大きい順にグループ化したものの逆順」である。 サイトスピードで重要なのは「爆速率」 この機会損失率をサイト間で比較していくが、その前にサイト自体のスピードをどう表現するか考える。 シンプルに平均値でよいかというと、収益性と比較する上ではもっとよい表現がある。それが爆速率だ。 速いか遅いかではなく「速いか爆速か」 次のグラフをみていただきたい。これは実際の14サイトにおけるサイトスピードとCVRの関係を示したものだ。それぞれのサイトのポテンシャルCVRを1に標準化している。 線が多くてみづらいが、いずれのサイトもCVRが高いのはスピードが速い領域だけで、急速に低下している様子がわかるだろう。そして、2.5秒以降はほぼ横ばいになっている。 Googleは良好な読み込み時間の基準として2.5秒を挙げているが、現実のユーザーにとって2.5秒はすでに遅いのだ。 もう少し全体の傾向を見てみよう。これらの線を点にプロットし直し、中央値のトレンドを示したのが以下のグラフだ。 読み込み時間が約0.9秒で、ポテンシャルCVRに対して半減する。そして、1.5秒でおよそ2割にまで低下している。 このように収益性を左右するサイトスピードは「速いか、遅いか」の問題ではない。「速いか、爆速であるか」が問題なのだ。 爆速率 = 読み込み時間 ≦ 1秒の割合 そこでこの記事では、サイトスピードを比較するための便宜的な指標として「爆速率」を設定したい。これは全ユーザーのうち、CVRのほぼ半減する読み込み時間1秒以下だったユーザーの割合である。 爆速率読み込み時間が秒以下のユーザー数全ユーザー数爆速率=読み込み時間が1秒以下のユーザー数全ユーザー数 平均値の問題は、「すでにユーザーにとって遅すぎる水準」である点だ。平均値を出すとおよそ2.5秒から4.5秒の範囲になるが、3秒なのか5秒なのかを比較しても、その時点でユーザーは離脱してしまっている。 爆速率の正確な定義 繰り返しになるが、実際にはユーザーの読み込み時間は最小値によって代表しており、爆速率は読み込み時間のハンディキャップが1秒以上にならないユーザーの割合、というのが正確なところである。 爆速率の代わりとしては、上位10%点の読み込み時間(p10)などを用いてもよいだろう。 サイトごとの機会損失 さて、このようにスピードによる「機会損失率」と、収益性を左右する体験のよさの指標として「爆速率」を設定できた。14サイトについてこれらをグラフに表すと、明確な関係が現れる。 爆速率が低いサイトほど機会損失率が大きく、逆に爆速率が高いサイトは機会損失が小さくなる。Site NやSite Hは機会損失率が60%ほどだが、いくつかのサイトは機会損失率が90%にも達している。 いくつか具体例を見てみよう。 よいケース Site H 爆速率33% 機会損失率62% 爆速率が高く機会損失の少ない例としてSite Hを見てみよう。 Site Hは実際、シンプルで高速な部類に入る。しかし62%のユーザーは遅いと感じて離脱してしまっている。 これはインターネットの構造上、仕方ない現象だ。サイト自体が高速でもユーザーまでのネットワーク経路や、閲覧する端末の性能は千差万別であり、大きなばらつきが生じてしまう。そして不運にも遅い体験を強いられたユーザーは離脱してしまう。 悪いケース Site J 爆速率9% 機会損失率89% 逆にサイトスピードが遅く機会損失の大きい例としてSite Jを見てみよう。 このサイトでは本来買うつもりのあったユーザーのうち、たまたま高速な体験のできた約10%がかろうじて注文に辿り着き、残りの約90%は脱落してしまっている。 もし仮にこのサイトがSite Hの水準にまでサイトスピードを改善し、機会損失率を62%まで回復できると、売り上げは約3.5倍になる見込みがある。 改善後のポテンシャル改善後のCVR=ポテンシャルCVR 1.06%×(1−0.62)≒0.40% 改善後の現在の倍改善後のCVR 0.40%÷現在のCVR 0.115%≒3.5倍 中間的なケース Site I 爆速率16% 機会損失率80% 中間的なケースとしてSite Iのグラフがこちらだ。 やはりCVRは爆速ゾーンから急速に低下をはじめ、1秒ほどで半減する。1.5秒付近ではもう横ばいになってしまう。 遅いSite JがSite Hを目指して着実に改善を進めると、このような分布を示す段階に達する場合もあるだろう。 不条理だが改善を続けるしかない このようにサイトスピードは機会損失率に直結している。遅いサイトは見込み客の90%以上を失いながら運営されている。これはまるで錆びついた自転車を必死に漕ぎ進めるような苦しみである。 一方で、速いサイトであっても運営者と無関係の要因で60%を失ってしまう。これは不条理としか言いようがない。それでも60%以上にならないように維持する他ない。 見方を変えると、遅いサイトにはスピード改善によって売り上げを伸ばせる伸び代が必ずある。今回、可視化された機会損失を見ることで希望に変えてもらえると幸いである。 機会損失を測定するには 弊社ではサイトスピードに特化したシンプルな無料アクセス解析サービス「Speed is Money」を提供している。 このサービスを利用することで、本記事と同様に機会損失を可視化できる。 この記事のデータも、Speed is Moneyを利用中の通販サイトから14サイトに協力をいただいた。 興味を持った方はぜひお試しいただきたい。 --- # 実例に学ぶサイトスピード改善(5) 外部CDNは速いとは限らない - 公開日: Tue Jul 14 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる 画像は次世代フォーマットに移行する テキストはGZIP/Brotliで圧縮する 重い日本語Webフォントが表示を遅らせる 外部CDNは速いとは限らない(本記事) 画像の寸法指定でガクッと動くページを防ぐ head内の同期スクリプトが描画を止める パブリックCDNは速いという昔の常識 外部のパブリックCDNからjQueryを読み込むべきだという説を、かつてよく目にした。世界中にエッジサーバーを持つCDNなら自社サーバーより速いはずだ、という理屈だ。今もその前提でライブラリをパブリックCDNから読み込んでいるサイトは少なくないと思われる。 ネットワークの負荷分散という目的でパブリックCDNを使うサイトも珍しくない。狙い自体は理解できる。だが弊社の経験上、日本国内でパブリックCDNが体感できるほど速く働く場面はそう多くない。むしろ別ドメインへの接続にかかるDNS解決やTLSハンドシェイクが、最も急ぎたい初期表示のタイミングでオーバーヘッドとして上乗せされる。 しかし弊社の表示速度ボトルネック実例研究(vigilante)では、外部ドメインからのリソース配信がFCPやLCPを1秒以上押し上げているケースをシミュレーションで繰り返し確認している。同一ドメインへのリクエストなら既存のHTTP接続を再利用できるが、別ドメインに初めて接続するときはDNS解決・TCP接続・TLSハンドシェイクをゼロから実施しなければならない。このオーバーヘッドが、状況によってはFCP・LCPに直結する遅延として表れる。 ただし影響の大きさはリソースによって大きく変わる。クリティカルパス上にあるCSSや同期的なJSライブラリでは接続コストがそのままFCP・LCPの遅延になりやすい。一方でクリティカルパスから外れたリソースでは、接続コストは存在しても指標への影響は小さい。この濃淡が実例の数値ではっきり見えてくる。 パブリックCDNは速いという昔の常識外部ドメインに接続するたびに発生するコストパブリックCDNのキャッシュ共有はもう効かない速い領域の0.1秒こそ効く実例で確認する外部ドメイン配信の影響ダイヤモンド・オンライン:CSSの別ドメイン配信でFCP・LCPが激変(シミュレーション)ギズモード・ジャパン:Google Fonts CSSの外部ドメイン配信がFCPを遅延弁護士ドットコム:複数のパブリックCDNがLCPを1秒押し上げ(シミュレーション)タマチャンショップ:jQuery CDNの同一ドメイン化は効果が小さかった(シミュレーション)効果が大きいケースと小さいケースクリティカルパス上のリソースは効果が大きい補助的なリソースは効果が小さい自ドメインへの集約は比較的シンプルまとめ 外部ドメインに接続するたびに発生するコスト ブラウザが同一ドメインのリソースを取得するとき、HTMLの取得ですでに確立した接続をそのまま再利用できる。HTTP/2ではさらに多重化が効き、複数リソースを1本の接続で並列ダウンロードできる。 別ドメインに接続するときは話が違う。まずDNS解決でドメイン名をIPアドレスに変換し、次にTCP接続を確立し、さらにHTTPSならTLSハンドシェイクが必要だ。これらが積み重なり、初回接続だけで数百msのオーバーヘッドが生じる。実際にダイヤモンド・オンラインの例では、別ドメインへの初回接続TTFBが939msという値が観測された。 パブリックCDNのキャッシュ共有はもう効かない かつて「人気のCDNのファイルは多くのユーザーのブラウザにキャッシュされているはずだ」という議論があった。しかし今この前提は崩れている。Chromeをはじめとするモダンブラウザはプライバシー保護のためにキャッシュパーティションを導入しており、サイトをまたいだキャッシュ共有は無効化されている。別サイトが取得してキャッシュしたパブリックCDNのファイルが自分のサイトで再利用されることはない。 結果として外部ドメインへの接続コストは避けられず、「キャッシュが効く」という利点は実質的に失われた。同じファイルを自ドメインに置いて配信すれば接続を再利用できる分だけ有利になることが多いと考えられる。 速い領域の0.1秒こそ効く HTML・CSS・JavaScriptはできるだけ早く読み込みたい最優先のリソースだ。DNS解決のような処理は一瞬で終わる。だからパフォーマンスへの影響はないと感じるかもしれない。だがその数百msが初期表示のスタートダッシュに丸ごと乗ってくる。 ここで見落とされやすいのが、改善の価値は速い領域ほど大きいという点だ。弊社の観測では、LCPを5秒から3秒に縮めてもコンバージョンや収益への寄与はほとんどない。一方で1秒を0.9秒に縮めるほうが、収益へのインパクトははるかに大きい。だからこそ、すでに速いページでの0.1秒を甘く見ないことが大切だ。外部ドメインへの接続コストは、まさにこの0.1秒を削る余地として無視できない。 実例で確認する外部ドメイン配信の影響 弊社の表示速度ボトルネック実例研究(vigilante)では、まずサードパーティタグの影響を切り離した上で、サイト固有のボトルネックを一つずつシミュレーションして数値を記録している。以下の4例はいずれも、外部ドメインからのリソース配信を同一ドメインに変更した場合の結果だ。 ダイヤモンド・オンライン:CSSの別ドメイン配信でFCP・LCPが激変(シミュレーション) ダイヤモンド・オンラインでは、トップページのレンダリングに必要なSwiperのCSS(swiper-bundle.min.css)が、HTMLの配信元 diamond.jp ではなく自社CDN dol.ismcdn.jp から読み込まれていた。別ドメインへの初回接続TTFBが939msと計測されており、この待ち時間がそのままFCPの遅延として積み上がっていた。 CSSはレンダリングブロックリソースだ。ダウンロードと適用が完了するまでブラウザはページの描画を開始できないため、外部ドメインへの接続コストがそのままFCPとLCPの遅延になる。変更は <link> タグのhref属性を dol.ismcdn.jp から diamond.jp に書き換えるだけだった。 html<!-- 変更前 --> <link rel="stylesheet" href="https://dol.ismcdn.jp/common/dol/js/lib/swiper-11.1.3/swiper-bundle.min.css"> <!-- 変更後 --> <link rel="stylesheet" href="https://diamond.jp/common/dol/js/lib/swiper-11.1.3/swiper-bundle.min.css"> 指標 変更前 変更後(シミュレーション) 変化量 FCP 1.5秒 0.1秒 -1.4秒(92%) LCP 2.4秒 0.2秒 -2.3秒(94%) SI 2.0秒 1.2秒 -0.8秒 総合スコア 97 100 +3 1行の書き換えでLighthouseスコアが97から100に到達するというシミュレーション結果が得られた。外部ドメインへの接続コストがFCP・LCPの支配的ボトルネックだったことが、この数値から明確に読み取れる。詳細はダイヤモンド・オンラインのボトルネック研究にまとめている。 ギズモード・ジャパン:Google Fonts CSSの外部ドメイン配信がFCPを遅延 ギズモード・ジャパンでは、Google Fonts CSS(3ファイル)が fonts.googleapis.com からレンダリングブロックリソースとして読み込まれていた。外部ドメインへの接続コストの合計が約517msと計測されており、この遅延がFCPに直接影響していた。 Google Fonts CSSを自ドメインから配信するシミュレーションを実施した。なお、CSS内で参照されるフォントファイル(fonts.gstatic.com)はレンダリングブロックリソースではない。フォントファイル自体が外部ドメインのままでも、FCPへの直接的な影響はないと考えてよい。 指標 変更前 変更後(シミュレーション) 変化量 FCP 0.7秒 0.1秒 -0.6秒 LCP 0.7秒 0.7秒 変化なし SI 0.9秒 0.9秒 変化なし シミュレーションではFCPが0.7秒から0.1秒へ約0.6秒短縮された。LCPへの変化が出なかったのは、この段階ではまだjQueryのパーサーブロッキングという別のボトルネックが残っていたためだ。詳細はギズモード・ジャパンのボトルネック研究を参照してほしい。 弁護士ドットコム:複数のパブリックCDNがLCPを1秒押し上げ(シミュレーション) 弁護士ドットコムでは、polyfill-fastly.io・ajax.googleapis.com・cdnjs.cloudflare.com という3つの外部ドメインからJavaScriptライブラリが読み込まれていた。polyfill.min.js のTTFBが463ms・jquery.min.js のTTFBが468msと、接続確立に要する時間が計測されていた。 これらのスクリプトは <head> 内に defer 属性なしで配置されていた。同期スクリプトはHTMLパースをブロックするため、外部ドメインへの接続コストにダウンロード待ちが重なり、FCP・LCPの双方に影響が出た。 指標 変更前 変更後(シミュレーション) 変化量 LCP 1.2秒 0.2秒 -1.0秒 FCP 1.1秒 0.2秒 -0.9秒 SI 1.6秒 1.1秒 -0.5秒 3ドメイン分のリソースをまとめて自ドメインに集約するだけで、LCPとFCPがそれぞれ約1秒短縮されるというシミュレーション結果が得られた。詳細は弁護士ドットコムのボトルネック研究にまとめている。 タマチャンショップ:jQuery CDNの同一ドメイン化は効果が小さかった(シミュレーション) タマチャンショップでも、jQueryが ajax.googleapis.com(Google Hosted Libraries)から読み込まれていた。接続コストの理屈は前の3例と同じで、外部ドメインへのDNS解決とTLS接続が発生する。 ただしこのシミュレーションは、LCP画像のlazyload解除やCLSの修正など主要なボトルネックをすべて解消した後の段階で実施したものだ。その時点でスコアはすでに100に達していた。 指標 変更前 変更後(シミュレーション) 変化量 SI 1.3秒 1.2秒 -0.1秒(119ms) 総合スコア 100 100 変化なし シミュレーションでの変化はSIが119ms短縮されるにとどまり、FCP・LCPへの影響は観測されなかった。ただし接続コスト自体は存在する。効果が小さかった理由は、この段階でjQueryがFCP・LCPのクリティカルパスから外れていたことにあると思われる。詳細はタマチャンショップのボトルネック研究を参照してほしい。 効果が大きいケースと小さいケース 4例を比較すると、外部ドメイン配信の影響の大きさに明確な差がある。どのサイトでも外部ドメインへの接続コストは発生している。それが指標に現れるのは、クリティカルパス上のリソースだけだ。 クリティカルパス上のリソースは効果が大きい CSSはレンダリングブロックリソースだ。ブラウザはCSSのダウンロードと適用が完了するまでページの描画を開始できないため、外部ドメインのCSSは接続コストがそのままFCPの遅延に転嫁されやすい。ダイヤモンド・オンライン(FCP 92%短縮)とギズモード・ジャパン(FCP 0.6秒短縮)のシミュレーション結果は、この構造を直接示している。 同様に <head> 内で defer なしに配置された同期スクリプトも描画をブロックする。弁護士ドットコムの例では外部ドメインの接続コストとパーサーブロッキングが重なり、シミュレーションでFCP・LCPともに約1秒の改善が得られた。 補助的なリソースは効果が小さい クリティカルパスから外れているリソースや、他のボトルネックに比べて影響が相対的に小さいリソースでは、接続コストは存在してもFCP・LCPへの改善はわずかにとどまることがある。タマチャンショップのjQueryはその典型だ。 補助的なリソースでも外部ドメインのコストはゼロではなく、ページ全体のロード時間には影響する。自ドメインへの集約に取り組む意義はある。ただしFCP・LCPへの即効性を期待するなら、まずクリティカルパス上のリソースを優先する判断が合理的だ。 自ドメインへの集約は比較的シンプル 外部ドメインのリソースを自ドメインに集約する実装は、難しくない。 CSSやJSライブラリ(パブリックCDNからの読み込み): 対象ファイルを自サーバーやCDNに配置し、HTMLの link タグや script タグのURLを書き換えるだけだ。 html<!-- 変更前: パブリックCDNから --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <!-- 変更後: 自ドメインから --> <script src="https://example.com/assets/jquery.min.js"></script> Google Fonts CSS: Fonts APIから取得したCSSを自サーバーで配信する構成に変えるだけでも、レンダリングブロックの接続コストは解消できる。CSS内で参照するフォントファイル(.woff2)は引き続き fonts.gstatic.com から読み込んでよい。フォントファイルごと自ドメインに配置すれば外部ドメインへの依存を完全に排除できる。 自前配信に切り替えるときはバージョン更新フローも整備しておく必要がある。パブリックCDNは設定次第で最新版を自動配信するが、自ドメイン配信では更新を手動で管理することになる。 まとめ 外部ドメインへのリソース配信はドメインごとにDNS・TCP・TLS接続コストを生む。 同一ドメインなら既存接続を再利用できるが、外部ドメインは毎回ゼロから接続する必要がある。 クリティカルパス上のリソースほど影響が大きい。 レンダリングブロックするCSSや同期的なJSが外部ドメインにあると、接続コストがそのままFCP・LCPの遅延として現れる。シミュレーションでは、ダイヤモンド・オンラインでFCP92%・LCP94%、弁護士ドットコムでもLCP・FCPそれぞれ約1秒の短縮という結果になった。 補助的なリソースでは効果が小さいこともある。 タマチャンショップのjQueryのように、クリティカルパスから外れたリソースでは接続コストは存在しても、シミュレーションで確認できたFCP・LCPへの改善効果はわずかにとどまった。外部ドメイン配信の同一ドメイン化がすべてのリソースで劇的な効果を保証するわけではない。 「パブリックCDNはキャッシュが共有される」という前提はもう通用しない。 ブラウザのキャッシュパーティション化により、サイトをまたいだキャッシュ共有は無効化されている。 HTML・CSS・JavaScriptは同一ドメインから配信したい。 初期表示のスタートダッシュを少しでも早く切るうえで、別ドメインへの接続コストは無視できない。すでに速いページでも、その0.1秒が収益を左右する局面はある。 対処は比較的シンプルで、ファイルを自ドメインに配置してURLを書き換えるだけだ。クリティカルパス上のCSSやJSライブラリから優先的に取り組む価値がある。 自社サイトのボトルネックを体系的に調べたい場合は、弊社の表示速度ボトルネック研究 vigilante の事例が参考になる。より詳しい調査が必要な場合はアイデアマンズまでご相談いただきたい。 --- # 実例に学ぶサイトスピード改善(4) 重い日本語Webフォントが表示を遅らせる - 公開日: Mon Jul 13 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる 画像は次世代フォーマットに移行する テキストはGZIP/Brotliで圧縮する 重い日本語Webフォントが表示を遅らせる(本記事) 外部CDNは速いとは限らない 画像の寸法指定でガクッと動くページを防ぐ head内の同期スクリプトが描画を止める 日本語Webフォントは英語フォントと事情が違う Webフォントはデザインの道具という認識が強く、パフォーマンス改善の議論では画像やJavaScriptに比べて後回しになりやすい。しかし日本語Webフォントは、英語フォントとは事情が大きく異なる。 日本語は文字種が多く、ひらがな・カタカナ・漢字を合わせると、フォントファイルが数MB単位になることがある。この転送コストがFCPやLCPのレンダリングをブロックし、ページの初期表示を遅らせる原因になりうる。 弊社の表示速度ボトルネック実例研究(vigilante)では、日本語Webフォントが主要なボトルネックとして観測されたサイトを複数記録している。問題のパターンは一様でない。6MB超のフォントが転送を圧迫するケース、レンダリングブロックでFCPとLCPが大きく遅れるケース、実際には使われていないフォントが読み込まれているケースなど、性質がそれぞれ違う。 この記事では4つの実例を取り上げ、日本語Webフォントがサイトスピードに与えている影響をシミュレーションの数値とともに解説する。 日本語Webフォントは英語フォントと事情が違う日本語フォントはなぜ重くなるのか実例で確認する日本語Webフォントの影響e☆イヤホン: Noto Sans Japanese 6.05MBの転送コストルタオ: 933msのレンダリングブロックとFCP・LCPへの影響ロコンド: アイコンフォントとして全文字セット3.88MBを読み込むHamee本店: 使われていないNoto Sans JPがレンダリングをブロックそもそもWebフォントを使うべきか自分のサイトで対処するにはまず未使用フォントを探すfont-display: swap でFOITを防ぐサブセット化で転送量を削減する読み込むウェイトを絞るpreconnect で接続コストを前倒しするシステムフォントへの切り替えを検討するまとめ 日本語フォントはなぜ重くなるのか 理由は単純だ。数百字で足りる英語フォントに対し、日本語は漢字まで含めて数千から数万字を抱える。その差がそのままファイルサイズに表れる。 Noto Sans JPのような一般的な日本語フォントは、フルセットなら1ウェイトでも1.5〜2MB、複数ウェイトで数MBに達する。アイコンフォントとして全文字セットを読み込めば、さらに重くなることもある。 さらにブラウザは、Webフォントの到着を待って該当部分の描画を止めることがある(FOIT:Flash of Invisible Text)。font-display 未設定だとこの挙動が起きやすく、FCPやLCPの遅延につながる。 ※woff2・フルセットの目安。日本語はサブセット化で数百KBまで削減できる。 実例で確認する日本語Webフォントの影響 弊社の表示速度ボトルネック実例研究(vigilante)では、ボトルネックを一つずつ切り分けてシミュレーションし、before/afterの数値を記録している。以下の4サイトでは、日本語Webフォントが観測されたボトルネックのひとつだった。 e☆イヤホン: Noto Sans Japanese 6.05MBの転送コスト 国内最大級のイヤホン・ヘッドホン専門店e☆イヤホンのトップページでは、Noto Sans Japaneseの3ウェイト(Regular / Semi-Bold / Bold)が読み込まれており、フォントファイルの合計サイズは6.05MBに達していた。 日本語Webフォントは文字種の多さからデータ量が非常に大きくなる。このシミュレーションでは @font-face 定義を削除し、CSSのフォント指定をシステムフォントスタック(-apple-system、BlinkMacSystemFont、Hiragino Kaku Gothic ProN など)にフォールバックする変更を加えた。サイトの見た目がシステムフォントに変わることを前提としたシミュレーションだ。 指標 解消前 解消後(シミュレーション) 変化量 SI 3.3秒 1.7秒 -1.6秒(48%) 総合スコア 99 100 +1 シミュレーションの結果、LCP 自体に変化はなかったが、SI(Speed Index = ビューが視覚的に表示される速さ)は3.3秒から1.7秒へと大幅に短縮された。ページ全体の総転送サイズは14.0MBから8.0MBへと43%削減され、フォント転送サイズだけで6.05MBから62.1KBへと99%の削減となっている。 LCP要素が画像だったため、フォント削除がLCPの数値を動かさなかったのは自然な結果だ。とはいえ転送サイズへの影響とSIの改善数値から、日本語Webフォントがページ全体の読み込み体感に与えていた影響の大きさは読み取れる。 詳細はe☆イヤホンのボトルネック研究にまとめている。 ルタオ: 933msのレンダリングブロックとFCP・LCPへの影響 北海道・小樽の洋菓子舗ルタオ(LeTAO)の公式オンラインショップでは、Google FontsからNoto Serif JPを読み込んでいた。このフォントに関連するリソースが描画プロセスの主要なボトルネックとして機能していた状況が観察されている。 観察項目 詳細 レンダリングブロック Google Fonts CSSにより933ms相当の描画遅延 未使用CSS Google Fonts CSS 150KBが未使用扱い フォントファイル 14個のwoff2ファイル(約400KB)の転送コスト 外部ドメイン接続 fonts.googleapis.com / fonts.gstatic.com へのDNS/TLS接続コスト シミュレーションではGoogle Fonts(Noto Serif JP)の読み込みを除去し、CSSの font-family をシステムフォント(serif)に置き換えた。フォントの見た目が変わることを前提としたシミュレーションだ。 指標 解消前 解消後(シミュレーション) 変化量 LCP 2.8秒 0.2秒 -2.6秒(93%) FCP 1.4秒 0.2秒 -1.2秒(86%) SI 2.2秒 1.2秒 -0.9秒 総合スコア 92 97 +5 シミュレーションではLCPが2.8秒から0.2秒へと93%短縮された。この数値は、Noto Serif JPのレンダリングブロックとフォントファイルの転送コストがLCP全体の大部分を押し上げていたことを示している。FCPも1.4秒から0.2秒へと86%改善されており、フォントの読み込みが描画開始そのものを遅らせていたことが読み取れる。 詳細はルタオのボトルネック研究にまとめている。 ロコンド: アイコンフォントとして全文字セット3.88MBを読み込む 靴・ファッションの通販サイトロコンドでは、Google FontsからMaterial Symbols Outlined(3.88MB)とLato(欧文フォント、3ウェイト、42KB)が読み込まれていた。日本語フォントではなく、アイコン用途のフォントが問題だった。 アイコンフォントは文字コードにアイコン画像を対応付ける仕組みだ。使うアイコンが数十種類でも、全文字セットのフォントファイルをそのまま配信する構成になっていると、フォントトラフィックが数MBに達する。実際に使うアイコンはほんの一部でも、ファイル全体がダウンロードされる点に問題がある。 シミュレーションではGoogle Fonts関連の link 要素をすべて削除し、フォントの読み込みを停止した状態を計測した。 指標 解消前 解消後(シミュレーション) 変化量 LCP 1.8秒 1.1秒 -0.7秒 FCP 1.6秒 1.0秒 -0.6秒 SI 1.8秒 1.4秒 -0.4秒 総合スコア 99 100 +1(満点) シミュレーションでは、フォントトラフィックが4.0MBから79KBへ3.92MB削減されたことで、LCPが0.7秒、FCPが0.6秒それぞれ短縮され、総合スコアが満点に到達した。3.88MBのMaterial Symbols Outlinedがネットワーク帯域を圧迫し、ページ全体のリソース読み込みに影響を与えていたことがこの結果から読み取れる。 アイコンフォントをそのまま使い続けたい場合でも、実際に使用するアイコンだけを含んだサブセットを自社ドメインから配信することで、本シミュレーションに近い大幅な削減効果を得られる可能性がある。 詳細はロコンドのボトルネック研究にまとめている。 Hamee本店: 使われていないNoto Sans JPがレンダリングをブロック スマートフォンケース・アクセサリーの専門店Hamee本店では、Google FontsからNoto Sans JP(ウェイト400/700)が読み込まれていた。しかしCSS内にNoto Sans JPの font-family 参照が一切存在しなかった。229KBのCSSが読み込まれ、外部ドメイン(fonts.googleapis.com)へのリクエストも発生していたにもかかわらず、実際のレンダリングには使用されていなかったと思われる状況だ。 対処はシンプルで、使われていないGoogle Fontsの <link> 要素を削除するだけだ。CSSで参照されていないフォントのため、ページの見た目に変化はない。この削除を適用したシミュレーションの結果が以下だ。 指標 解消前 解消後(シミュレーション) 変化量 FCP 0.8秒 0.4秒 -0.4秒 LCP 1.4秒 1.2秒 -0.2秒 SI 1.9秒 1.7秒 -0.2秒 この229KBのCSSはレンダリングブロックリソースとして検出されていたため、削除によってレンダリング開始を前倒しでき、シミュレーションではFCPが0.4秒短縮された。使われていないにもかかわらずレンダリングをブロックしていた点が、このケースで注目すべき特徴だ。 詳細はHamee本店のボトルネック研究にまとめている。 そもそもWebフォントを使うべきか 対処の手段を見る前に、一段大きな問いがある。そのページに本当にWebフォントが必要か、という問いだ。 整ったWebフォントでブランドの世界観を表現したい。その心情は理解できる。だが日本語はWebフォントのデータ量という点でどうしても不利な立場にある。数千から数万字を抱える以上、英語のように軽くは済まない。理不尽な面もあるが、これは致し方ないところだ。 とりわけ通販サイトやメディアサイトでは、商品や記事といったコンテンツそのものが主役だ。世界観を伝えるデザインを価値の中心に据えるサイトとは事情が違う。コンテンツが快適に読めればよいのなら、数MBのフォントを読み込んでまでWebフォントにこだわる必要は薄い。コストが見合わない場面も多いはずだ。少なくとも弊社はそう考えている。 とはいえこれは想像だけで結論を出せる話ではない。いちばん確実なのは、WebフォントとシステムフォントでABテストを行い、どちらがコンバージョンや滞在につながるかを実際に比べることだ。弊社は通販・メディア用途でのWebフォントの必要性を低いと見ている。ただし最終的な判断はサイトごとのデータに委ねるのが筋だろう。まず計測し、白黒をつけてから決めればよい。 自分のサイトで対処するには 日本語Webフォントへの対処は、問題の性質によっていくつかの方向がある。 まず未使用フォントを探す Hamee本店の例のように、実際には使われていないフォントが読み込まれているケースは意外と多い。Chrome DevToolsの「カバレッジ」機能や、LighthouseのレポートでUnused CSSの指摘がないかを確認するとよい。CSSで参照されていないフォントは削除するだけでよく、見た目に影響しない。まずここから確認したい。 font-display: swap でFOITを防ぐ font-display: swap を指定すると、フォントが読み込まれるまでの間は代替フォント(システムフォント)で一時的に描画し、ダウンロード完了後に切り替える。フォントファイルの転送量そのものは変わらないが、「フォントが届くまで何も描画しない」状態を避けられるため、FCPの改善につながることがある。 css@font-face { font-family: 'Noto Sans JP'; font-display: swap; /* 追加 */ src: url('...') format('woff2'); } Google Fontsで配信する場合はURLに &display=swap を付与する。 html<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet"> サブセット化で転送量を削減する 日本語フォントを使い続けるのであれば、サブセット化が最も効果的な転送量削減手段だ。実際に使用する文字だけを含んだフォントファイルを作成すれば、ECサイトのトップページで使う文字が500字程度であれば数MB全体を転送する代わりに数十KBで済む可能性がある。 アイコンフォントも同様で、使用するアイコンだけを含むサブセットを作成し自社ドメインから配信することで、ロコンドの例に近い大幅な削減を得られる可能性がある。 なおGoogle Fontsはもともとページで実際に使われている文字だけを含んだサブセットを動的に生成して配信する仕組みを持っている。ただし外部ドメインへの接続コストは残る。 読み込むウェイトを絞る 必要以上に多くのウェイト(太さ)を読み込んでいるサイトもときどき見かける。日本語フォントはウェイトごとに別ファイルで、それぞれが数百KB〜MB級だ。RegularとBoldとMediumを並べるだけで、転送量はあっという間に積み上がる。 どうしても日本語Webフォントを使うなら、本当に必要なウェイトだけに絞りたい。あるいはバリアブルフォントを採用する手もある。バリアブルフォントは1つのファイルで太さを連続的に変えられるため、複数のウェイトを別々に読み込む必要がなくなる。 preconnect で接続コストを前倒しする Google Fontsを使う場合、フォントCSSと実際のフォントファイルが別ドメイン(fonts.googleapis.com と fonts.gstatic.com)から配信される。preconnect を指定しておくとDNSルックアップとTLSハンドシェイクのコストを前倒しできる。 html<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 転送量は変わらないが、接続確立のオーバーヘッドをあらかじめ済ませておく効果がある。 システムフォントへの切り替えを検討する いずれの対処も採用しにくい場合は、日本語Webフォントをやめてシステムフォントにフォールバックすることも選択肢だ。macOSの「ヒラギノ角ゴ」やWindowsの「游ゴシック」など、モダンなシステムフォントは品質が高く、Webフォントとの差がそれほど目立たないケースも多い。 e☆イヤホンのシミュレーションがこのアプローチで実施された。見た目の変更を前提とする大きな判断だが、パフォーマンスへの効果は実例が示すとおりだ。 まとめ 日本語Webフォントは数MB単位になりやすい。 文字種の多さからファイルサイズが英語フォントと桁違いになるため、転送コストがページ全体の読み込みに影響することがある。e☆イヤホンでは、6.05MBのフォントがページの転送量を43%押し上げていたことがシミュレーションから推定された。 FCPとLCPへの影響が大きい。 フォントCSSはレンダリングブロックリソースになりやすく、font-display 未設定のままでは描画が大きく遅れる場合もある。ルタオの例では、フォント削除だけでLCPが2.8秒から0.2秒へと93%短縮されるというシミュレーション結果が得られた。 未使用フォントは削除するだけでよい。 Hamee本店の例のように、CSSで参照されていないフォントが読み込まれていることがある。見た目に影響せず削除できるため、まずここから確認したい。 アイコンフォントも要注意。 ロコンドの例のように、アイコン用途でも全文字セットを読み込む構成では数MBの転送が発生する。使用するアイコンのサブセットだけを配信する形に変えることで大幅な削減が期待できる。 対処の選択肢は複数ある。 font-display: swap でFOITを防ぐ、サブセット化や使うウェイトの絞り込み(バリアブルフォント)で転送量を削減する、preconnect で接続コストを前倒しする、あるいはシステムフォントに切り替えるという選択肢がそれぞれある。効果の性質が異なるため、問題の性質に合わせて選ぶのがよい。 通販・メディアサイトではそもそもの要否も検討したい。 これらのサイトはコンテンツが主役であり、世界観デザインを中心に据えるサイトとは事情が違う。数MBのコストに見合うかは、WebフォントとシステムフォントのABテストでコンバージョンを比べて判断するのが確実だ。弊社は要否を厳しく見ている。ただ最終判断は計測したデータに委ねたい。 Webフォントの最適化はサイトのデザイン方針と密接に関わる。全体を一律に削除や切り替えで対処するのが難しいとしても、未使用フォントの削除や font-display: swap の設定は見た目の変更なしに進められる。まずそこから始めるのが現実的だろう。 自社サイトのWebフォント設定や他のボトルネックを体系的に調べたい場合は、弊社の表示速度ボトルネック研究 vigilanteの事例が参考になる。より詳しい調査が必要な場合はアイデアマンズまでご相談いただきたい。 --- # サイトスピードを改善してもCVRが変わらない? 「機会損失率」なら成果が見えるかも - 公開日: Thu Jul 09 2026 23:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, research, business サイトスピードを大幅に改善し、その前後でCVR(コンバージョン率)を見たものの、期待したような成果が確認できなかった。サイトスピード改善に取り組んだことのある人なら、一度はこのすっきりしないモヤモヤを感じたことがあるのではないだろうか。 速くなったのだから当然CVRも上がるはず、と期待して前月比を見ても、誤差の範囲にとどまっているか、下手をすると下がっていることさえある。改善は失敗だったのだろうか。それともサイトスピードによる収益改善など幻だったのか? 弊社のサイトスピードに特化したアクセス解析サービス「Speed is Money」で、通販サイト14サイト・2025年6月から2026年5月までの12か月・約2,000万セッションのデータを詳しく調べたところ、サイトスピード改善の成果を計る指標として「機会損失率」がより有効だと分かった。CVRは季節性や突発的な需要で変動しやすい。そこで、サイトスピードのせいで取りこぼした売上を定量化するほうが、ビジネス成果は説明しやすいのだ。 そもそも「速くなった」とは何か速さを一つの数字で表す「最頻値」サイトスピードの改善による収益への影響を実験CVRとの関係性はやや微妙サイトスピードによる損失だけを取り出す「機会損失率」機会損失率はサイトスピードにぴったり追従複数サイトの傾向もみてみる仮にCVRが低下しても成果が示せる機会損失率も万能ではない そもそも「速くなった」とは何か サイトスピード改善の前後で、速くなったかどうかをどう比べればいいのか。単純に平均値で比べればいいのでは、と思うかもしれないが、実はそんなに単純な話ではない。まずはここから整理しておきたい。 先日の「ページスピードは一定ではない? みんなのスピード体験を想像するコツは『対数正規分布』」でも紹介したように、サイトスピードはユーザーによって一律ではない。あるサイトの読み込み時間(OnLoad)を全セッションぶん集めて分布にすると、次のような形を描く。 多くの人は似たようなサイトスピードを体験する山がある一方、遅い人はとことん遅く、右に長いロングテールを描く。これが「対数正規分布」と呼ばれる形で、サイトスピードに限らずネットワークの応答時間全般でよく現れる。 では、この分布のもとで「サイトスピードが速くなる」とはどういうことか。速い人から遅い人まで、分布全体が山ごと左(速い側)へシフトすることを意味する。 改善前(青)に対して改善後(緑)は山ごと左に動く。遅い人の裾も、全体につられて速くなる方向へ引きずられる。 図について この図は分かりやすさを優先して対数正規分布をややデフォルメしている。本来は分布が速い方へ動くと山の高さ自体も変わるのだが、ここでは山が左側へシフトする点を強調するために、形を簡略化して描いている。 速さを一つの数字で表す「最頻値」 さきほど「単純に平均値でよいとはいかない」と書いたが、対数正規分布の最頻値・中央値・平均値を描くと次のようになる。 このグラフを見ると、平均値はなんとも中途半端な位置にある。山が動いた度合いを比べるなら、最頻値(山の頂上)で比べるのが直感的だろう。 そこで本記事では、ある期間のサイトスピードの代表値に最頻値を用いることとする。つまり山の頂上がどのくらいずれたかで、サイトスピードを比較するわけだ。 補足:すでに速いユーザーほど収益を左右する 平均値より最頻値がよい理由はもうひとつある。それは、遅いユーザーの体験が変わったところで収益への影響は小さいということだ。直感に反するかもしれないが、実は収益の伸びは、すでに速い体験をできているユーザーがさらに爆速になるかどうか、にかかっている。 平均値付近の体験は遅い部類に入り、それが多少速くなってもCVRは上がらず、収益にほとんど影響しない。したがって、速い部類の体験がどうなったかを比較する方が説明しやすい。 サイトスピードの改善による収益への影響を実験 では実際にとある通販サイト(ジュエリー系EC)で、読み込み時間(OnLoad)の最頻値を月次推移で見てみよう。 このサイトでは月によって読み込み時間に比較的大きな変動(0.84〜2.33秒)が見られた。これを「毎月、サイトスピードに関する改修や悪化のイベントがあった」とみなしてみる。つまり、実際に改修があったわけではないが、事実としてサイトスピードの変動はあった。それをサイトスピード改善の実験結果として解釈してみよう(自然実験)。 CVRとの関係性はやや微妙 この最頻値のグラフにCVRを重ねたのが次のグラフだ。 読み込み時間の最頻値は大きいほどサイトが遅く、理論上、CVRは小さくなる。したがって、このふたつの系列は逆の動きをするはずである。 なんとなくそう見える気もするが、ちょっとわかりにくい。そこでCVRの軸の方向を逆にしてみる。CVRも下にあるほど良好、というようにプロットし直してみたのが以下のグラフだ。 こうして方向性を揃えると、両者はある程度、連動しているようにも見える。ただ、逆の方向に動く月や、大きく乖離する月もある。このグラフで「サイトスピードはCVRに影響するんです!」と言われても、そう…かもね、という程度ではないだろうか。相関係数も r = -0.38 で、弱い相関にとどまる。 なぜこのようにすっきりしない動きとなるのか。 サイトスピードによる損失だけを取り出す「機会損失率」 理由は単純で、CVRはサイトスピードよりも月々の需要の変化で大きく動くからだ。セールや新商品、在庫状況、広告によるブースト、季節性。CVRを左右するのはむしろこうした要因で、サイトスピードの影響は調整項目でしかない。 したがって、サイトスピード改善の成果をCVRで証明しようとしても、うまくいかないことが多いのだ。 そこで、CVRに代わる新たな収益性の指標を提案したい。それがサイトスピードによる「機会損失率」だ。本来は購入されるはずだったのに、サイトスピードが悪いせいで離脱してしまった割合を計算してみよう。手順はこうだ。 月ごとに、セッションが体験したサイトスピード(min(ol))が速い順で10グループに等分する その中で最もCVRの高いグループ(=サイトスピードに不満が小さかったグループ)のCVRを、ポテンシャルCVR(本来の需要)とみなす 遅いグループほどポテンシャルからCVRが下がっていくので、その差の面積(サイトスピードによる機会損失)を求める ポテンシャルCVRに対するその割合を「機会損失率」とする サイトスピードの不満がもっとも小さかったグループは、それを理由に離脱したとは最も考えにくい。そこで、そのCVRをサイトが本来期待できたCVR(ポテンシャルCVR)とみなす。この値と現実のCVRとの差が、サイトスピードを理由とした離脱だと推定する考え方である。 この例で言えば、そのポテンシャルCVRは 3.95% だった。しかし、サイトスピードの遅さが原因で 65.2% を取りこぼし、実現できたCVRは 1.37% にとどまっている。 サイトスピードを改善しても、ポテンシャルCVRそのものは原則として変わらない。速くなったからといって、いらないものが欲しくなるわけではないからだ。しかし、これまでサイトスピードを理由に離脱していたユーザーの一部が、購入までの操作をやり遂げてくれるようになる。それによって全体のCVRが底上げされる。これがこのモデルの論理だ。 機会損失率はサイトスピードにぴったり追従 この機会損失率と、サイトスピード(読み込み時間の最頻値)の月次推移を重ねてプロットしたのが次のグラフだ。 CVRのときに比べて、ずっと強く連動しているのが分かる。相関係数も r = 0.83 と、かなり強い相関を示す。 CVRでは月々の需要の変化に邪魔されて、サイトスピードとの関係の説明がいまいち弱かった。だが機会損失率は、サイトスピードの影響をよく説明してくれている。 複数サイトの傾向もみてみる この調査対象サイト以外の13の通販サイトも含めて、サイトスピードとCVR、サイトスピードと機会損失率の関係を見てみよう。通販サイト14サイト×12か月=168点を、サイトごとの平均で標準化して散布図にした。 まずはCVRだが、あいにくサイトスピードとの相関に方向性は見られない。r = -0.009 と、ほぼ無相関だ。 一方、機会損失率では r = 0.659 と、はっきり右肩上がりの相関がみられる。 このように、「サイトスピードを改善した → だからCVRが上がる」というのは、案外説明が難しい。しかし機会損失率であれば、サイトスピード改善によるビジネス成果をずっと説明しやすい。 仮にCVRが低下しても成果が示せる たとえば、サイトスピードの大幅な改善を実施した前後の月次データで、CVRがかえって低下してしまったとする。サイトスピード改善は、ビジネスに成果をもたらさなかったのだろうか。 そこで機会損失率に注目する。すると、機会損失率のほうは低下していた。この状況なら「CVRは需要の変化で下がったが、もしサイトスピードを改善していなければ、CVRはもっと低下していた」と説明できる。 このように実務においても、CVRだけでなく機会損失率にも注目することで、「サイトスピードはCVRに関係ない」という誤った判断を避けられる。 機会損失率も万能ではない これほどCVRより説明力の高い機会損失率だが、残念ながら万能ではない。読み込み時間との連動を見たグラフでも逆の関係が現れる月はあったし、今回挙げたのはあくまで「機会損失率での説明がうまくいったケース」である。 実際、サイトスピードと機会損失率のあいだに明確な関係が見られないサイトもある。 これは、需要の強さといった要因が、ユーザーの待ち時間に対する振る舞いそのものを変えてしまうからだ。ユーザーが「待てる・待てない」の基準も、必ずしも一定ではないということである。 とはいえ、サイトスピード改善の成果をCVRで直接測ることの危うさと、サイトスピードの違いに着目した機会損失率という指標の有効性は、感じてもらえたのではないだろうか。両者を組み合わせて見ることで、「サイトスピードは成果に関係ない」という誤判断を避けられる。 なお、この機会損失率は、弊社の、サイトスピードに特化したアクセス解析サービス「Speed is Money」を導入すれば、実測データから手軽に計測できる。 --- # 実例に学ぶサイトスピード改善(3) テキストはGZIP/Brotliで圧縮する - 公開日: Wed Jul 08 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる 画像は次世代フォーマットに移行する テキストはGZIP/Brotliで圧縮する(本記事) 重い日本語Webフォントが表示を遅らせる 外部CDNは速いとは限らない 画像の寸法指定でガクッと動くページを防ぐ head内の同期スクリプトが描画を止める コードを書かずサーバー設定だけで効く改善 今回はテキストリソースの圧縮配信を取り上げる。前回のサードパーティタグ は「見えない敵」という意外性があったが、今回はさらに地味だ。コードを一行も書かず、サーバーの設定を変えるだけで効く。しかし実在サイトを調べてみると、この設定が漏れたままのサイトは意外と多い。その多くは単なる設定忘れ、つまりケアレスミスであると思われる。 弊社では 表示速度ボトルネックの実例研究 というブログで、実際のWebページの表示速度のボトルネックを研究している。研究を通して見えてきたのは、GZIP圧縮の有効でないサイトが意外に多いという事実だ。そして圧縮を適用するだけで LCP が1秒以上改善するというシミュレーション結果も珍しくなかった。 コードを書かずサーバー設定だけで効く改善テキスト圧縮の仕組みGZIPとBrotliの違い「テキストは小さいから差は出ない」という誤解なぜ未圧縮のまま放置されるのか実在サイトでの実例京都きもの市場:35件の未圧縮でLCPが1.8秒遅れていた(シミュレーション)エトヴォス:わずか11件でLCPが1.9秒遅れていた(シミュレーション)RUNWAY channel:44件が未圧縮でFCP・LCP各0.6秒遅れ(シミュレーション)Billboard Japan:HTMLは圧縮済みでCSS・JSだけ漏れていた確認方法と対処法まず圧縮されているか確認するサーバーまたはCDNで有効化するまとめ テキスト圧縮の仕組み HTMLやCSS、JavaScriptは人間にも読みやすい形で作られたテキストだ。同じ単語やパターンが何度も繰り返されるため、データ圧縮はてきめんに効く。GZIPはこの繰り返しを検出して短く置き換える。それだけでファイルサイズは大きく縮む。 一般に、テキストリソースは GZIP圧縮で60〜80%のサイズ削減 が期待できる。仮に200KBのJavaScriptファイルが40〜80KBに縮まるとしたら、ダウンロード時間も同じ割合で短くなる。画像はすでに内部で圧縮されているためGZIPを重ねてもほとんど小さくならないが、テキストリソースには大きな効果が出やすい。 GZIPとBrotliの違い テキスト圧縮には現在 GZIP と Brotli の2方式が広く使われている。 GZIPは歴史が長く(1990年代から使われている)、ほぼすべてのサーバーとブラウザに対応したデファクトスタンダードだ。一方のBrotliはGoogleが開発した比較的新しい方式(2015年公開)で、GZIPより圧縮率が高く、同じファイルでも転送量をさらに15〜20%ほど削減できる。現在の主要ブラウザはBrotliをほぼすべてサポートしている。 どちらも「なし」よりはるかに効果が大きいため、まずGZIPを確実に有効にするのが第一歩だ。CDNやサーバーが対応しているなら、Brotliも合わせて有効にしておくとよい。サーバーはブラウザが Accept-Encoding: br ヘッダーを送ってきた場合のみBrotliで返答し、対応していないブラウザには自動的にGZIPにフォールバックするため、互換性の心配はない。 「テキストは小さいから差は出ない」という誤解 HTML・CSS・JavaScriptのデータ量は、画像や動画に比べれば小さい。だから「圧縮しても大して変わらない」と考えてしまいがちだ。 しかし、ここに落とし穴がある。これらのテキストリソースは、ページ表示のいちばん最初の段階で読み込まれる。HTMLの受信後、CSSやJavaScriptの解析を終えて初めて、ブラウザは描画を始められる。つまりテキストリソースの読み込みは、ページ表示の「スタートダッシュ」そのものだ。 このスタートダッシュが圧縮の有無で遅れると、その遅延は後続のすべての処理にのしかかる。データ量が小さくても、初期段階に乗っているぶん、体感上の遅さへの影響は想像以上に大きい。とりわけHTML・CSS・JavaScriptは、いかに早く読み込みを終えるかが勝負どころだ。これらについては、圧縮してダウンロードを少しでも短く済ませることが欠かせない。 なぜ未圧縮のまま放置されるのか 設定ひとつで効くのに、なぜ圧縮の有効でないサイトがあるのか。理由はおおむね2つに分けられる。 ひとつは、単なる設定忘れ、つまりケアレスミスだろう。多くのWebサーバーは初期状態で圧縮が有効になっている。しかし設定変更の過程で無効のまま引き継がれたり、特定のMIMEタイプが対象から漏れたりする。とくにHTMLは圧縮済みなのにCSS・JavaScriptが対象外というパターンは多い。GZIP設定では対象MIMEタイプを明示的に列挙する必要があり、その列挙漏れがそのまま穴になる。後述するBillboard Japanはまさにこれだ。しかもこの種のミスはサイトの見た目や機能に影響しないため、意識して計測しない限り気づきにくい。開発環境はネットワーク遅延がほぼなく、圧縮の有無で体感差が出ないことも見逃しを助長する。 もうひとつは、過去の名残だ。かつてはデータ圧縮によるサーバーのCPU負荷を嫌い、あえて圧縮しない方針をとるサイトもあった。だが今となっては、これはほとんど意味をなさない。計算機の処理は十分に高速になった。圧縮にかかるCPUコストは今やほぼ無視できる。むしろ大きなデータをネットワークに流すほうが、時間とエネルギーの両面ではるかにコストが高い。CPU負荷を気にして大きなデータを未圧縮で送るのは、まったく割に合わない。テキストリソースを意図的に圧縮しないという選択は、今ではナンセンスと言ってよい。 実在サイトでの実例 弊社の実例研究から、テキスト圧縮の設定漏れが観測されたサイトをいくつか紹介する。以下の数値はすべてシミュレーション結果だ。 京都きもの市場:35件の未圧縮でLCPが1.8秒遅れていた(シミュレーション) 着物専門ECサイト 京都きもの市場 のトップページでは、HTML 2件、CSS 28件、JavaScript 5件の合計 35件のテキストリソースがGZIP圧縮なしで配信されている状態が観測された。テキストリソースは一般に60〜80%の圧縮率が得られるため、未圧縮のままでは転送データ量が本来の3〜5倍となり、ダウンロード時間に直接影響する。 サードパーティタグの除去や外部CDN依存の解消を経たあとで、このGZIP圧縮を個別に計測した結果が下記だ。 指標 解消前 解消後(シミュレーション) 変化量 LCP 3.5秒 1.7秒 -1.8秒 FCP 0.8秒 0.6秒 -0.2秒 SI 2.0秒 1.7秒 -0.3秒 総合スコア 91 100 +9 GZIP圧縮を適用しただけで LCP が1.8秒短縮され、総合スコア が91から100に到達するというシミュレーション結果が得られた。CSS 28件が未圧縮という状況は、スタイルシートが細かく分割されているサイトで起きやすい。サーバー設定の漏れがどれほど大きな影響を与えていたかが、この数値から読み取れる。 詳しいシミュレーション手順は 京都きもの市場のボトルネック研究 にまとめている。 エトヴォス:わずか11件でLCPが1.9秒遅れていた(シミュレーション) コスメブランド ETVOS(エトヴォス) の公式オンラインショップでは、CSS 2件、JavaScript 6件、HTML 2件の合計 11件のテキストリソースが未圧縮だった。件数こそ少ないが、他のボトルネックを取り除いた状態のシミュレーションで計測すると、その影響は小さくなかった。 指標 解消前 解消後(シミュレーション) 変化量 LCP 5.8秒 3.9秒 -1.9秒 FCP 1.9秒 1.7秒 -0.2秒 SI 3.3秒 2.9秒 -0.4秒 総合スコア 75 86 +11 シミュレーションでは、11件という少ない数でも LCP が1.9秒短縮され、総合スコア は11ポイント上がった。件数の少なさと影響の大きさは比例しない。重要なリソースが未圧縮なだけで、そのダウンロード遅延がクリティカルパスに乗れば指標に直結する。 詳細は エトヴォスのボトルネック研究 を参照してほしい。 RUNWAY channel:44件が未圧縮でFCP・LCP各0.6秒遅れ(シミュレーション) 複数のレディースファッションブランドを扱うEC RUNWAY channel(ランウェイチャンネル) では、HTML・CSS・JavaScript・JSONを合わせた 44件のテキストリソースがGZIP未圧縮だった。JSON 13件が含まれている点が特徴的で、APIレスポンスや設定データのJSONに圧縮が適用されていないケースは珍しくない。 指標 解消前 解消後(シミュレーション) 変化量 LCP 3.4秒 2.8秒 -0.6秒 FCP 2.3秒 1.7秒 -0.6秒 SI 2.3秒 1.8秒 -0.5秒 総合スコア 89 95 +6 シミュレーションでは FCP と LCP がそれぞれ0.6秒短縮された。このサイトはサードパーティタグ除去後もいくつかのボトルネックが残っていたが、圧縮の適用はサーバー設定のみで解消できるため、改修コストとして最も低い対処法のひとつだ。 詳細は RUNWAY channelのボトルネック研究 で確認できる。 Billboard Japan:HTMLは圧縮済みでCSS・JSだけ漏れていた 音楽チャート・ニュースサイト Billboard Japan は「圧縮の設定漏れ」の典型例だ。HTMLはすでにGZIP圧縮済みだったが、CSS 12ファイル・JS 22ファイルを含む計35ファイルが未圧縮のまま配信されていた。「圧縮は設定している」という認識のまま、特定のファイルタイプが対象から抜け落ちているパターンだ。 圧縮を適用したシミュレーションでは、ファイルサイズの変化が顕著だった。CSSは合計215.2KBから35.4KBへ84%削減、JSは425.8KBから107.1KBへ75%削減という結果になった。 指標 解消前 解消後(シミュレーション) 変化量 FCP 0.7秒 0.6秒 -0.1秒 LCP 1.4秒 1.2秒 -0.2秒 SI 1.1秒 0.9秒 -0.2秒 このサイトはすでに他の最適化が進んでいた状態での計測のため、指標の変化幅は小さかった。しかしCSSとJSのサイズを7〜8割も削減できるというシミュレーション結果は大きい。ユーザーの転送量という観点でも無視できない改善だ。「HTMLは圧縮しているからOK」ではなく、CSS・JavaScript・JSONもすべて対象になっているかを確認することが重要だとわかる。 詳細は Billboard Japanのボトルネック研究 に掲載している。 確認方法と対処法 まず圧縮されているか確認する テキストリソースが圧縮配信されているかは、ブラウザの開発者ツールで確認できる。任意のページを開き、DevToolsの「ネットワーク」タブで対象のCSS・JSファイルをクリックし、レスポンスヘッダーの content-encoding を確認する。 content-encoding: gzip または content-encoding: br と表示されていれば圧縮が有効だ。content-encoding ヘッダー自体が存在しない場合は未圧縮で配信されている。 HTMLだけでなく、CSSとJavaScript、できればJSONやSVGも対象になっているかを確認する。Billboard Japanの例のように、ファイルタイプによって設定が漏れているケースは多い。 Lighthouseの監査結果でも「テキスト圧縮を有効にする(Enable text compression)」という警告が表示される場合もある。Lighthouseを実行して警告がなければ、主要なリソースに対して圧縮は適用されていると考えてよい。 サーバーまたはCDNで有効化する 圧縮の有効化方法はサーバー・CDNの種類によって異なる。代表的な設定例を示す。 Apache(.htaccess または httpd.conf) apache<IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml </IfModule> nginx(nginx.conf) nginxgzip on; gzip_types text/html text/css text/javascript application/javascript application/json image/svg+xml; CloudflareなどのCDN 多くのCDNはSpeed(パフォーマンス)設定から圧縮を有効化できる。CloudflareはデフォルトでBrotliにも対応している。サービスのダッシュボードからSpeed → Optimizationを確認するとよい。 設定変更後は必ずDevToolsで content-encoding ヘッダーを再確認すること。設定を変えたつもりでも、キャッシュやCDNの挙動で即座に反映されないケースもある。 まとめ テキストリソース(HTML・CSS・JavaScript・JSON・SVG)はGZIP圧縮で 60〜80%のサイズ削減 が見込める。転送時間が短くなる分、FCP・LCP の改善につながる。 弊社の実例研究では、圧縮を有効にするだけで LCP が 1.8〜1.9秒 短縮されるというシミュレーション結果のサイトが複数あった。件数が少なくても、重要なリソースが未圧縮なら影響は大きい。 「HTMLは圧縮している」だけでは不十分だ。CSS・JavaScript・JSONが対象から漏れているケースは多い。全ファイルタイプが対象に設定されているかを確認することが必要だ。 確認方法はシンプルで、DevToolsの「ネットワーク」タブで content-encoding ヘッダーを見るだけだ。Lighthouseの警告も参考になる。 コードの修正は一切不要で、サーバーまたはCDNの設定変更で済む。改修コストが最も低く、効果が大きい手法のひとつ。 サイトに手を入れる前に、まずこの確認から始めてほしい。設定ひとつで指標が動く可能性は十分にある。 --- # WebP/AVIFだけじゃないWeb画像最適化チェックリスト - 公開日: Mon Jul 06 2026 00:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, sitespeed ウェブ画像最適化と聞くと、いまなら多くの人が、JPEGやPNGをAVIFやWebPなどの次世代画像フォーマットに変換することを思い浮かべるだろう。もちろんそれも有効だが、それは画像最適化の一部でしかない。 本当のウェブ画像最適化とは、画像ファイル単体ではなく、画像そのものと、画像を届ける仕組みの両面を最適化することだ。どれだけ画像を軽くしても、たとえばスマホに超高解像度の画像を配信したり、帯域幅の狭いVPSなどから配信していては、体感速度は改善しない。 CDNもただ使えばいいというものではない。アクセス数が多いサイトなのにキャッシュがほぼ効いていない状態では、画像の配信効率は悪いままだ。 Web画像最適化チェックリスト 早速だが、Web画像最適化のチェックリストは以下のとおりだ。 カテゴリ ✅ 端的な解説 1. 解像度 ☐ 過剰な解像度で配信しない(DPR 3倍まで) 1. 解像度 ☐ srcset / sizes でデバイス・DPRに応じて解像度を出し分ける 2. 形式 ☐ 写真的な画像にロスレス(PNG)を使わない 2. 形式 ☐ 図・アイコン・ロゴはSVG、文字はHTML。画像にしなくて済むものは画像にしない 2. 形式 ☐ アニメーションGIFをやめ、動画(MP4/WebM)やAnimated WebPに置き換える 3. データ ☐ 画像の内容に合わせて圧縮品質・色数を選ぶ(JPEG品質・PNG色数) 3. データ ☐ 最適化エンコーダ(MozJPEG・pngquant/oxipng)を使う 3. データ ☐ 不要なメタデータを除去する(ただしOrientationとICCは要注意) 3. データ ☐ 次世代フォーマット(WebP / AVIF)をフォールバック付きで採用する 4. 配信 ☐ CDNを効果的に使う(キャッシュヒット率が低いと逆効果にもなる) 4. 配信 ☐ 無造作な同期変換(オンザフライ変換)を見直す 4. 配信 ☐ 帯域を広く取り、輻輳を避ける(CDNオフロード・総転送量の削減) 4. 配信 ☐ 正しいキャッシュ戦略をとる(長期max-age+キャッシュバスター) 5. 読み込み ☐ JavaScriptによる遅延読み込みをやめ、ネイティブの loading 属性を活用する 5. 読み込み ☐ 遅延読み込みはページ下部の画像だけに。LCP画像には付けない 5. 読み込み ☐ CSSで画像を出し分けず、srcset/picture で無駄な読み込みを避ける 5. 読み込み ☐ LCP画像は fetchpriority="high" で優先。preloadは素のimgには基本不要 1. 適切な解像度で配信する 過剰な解像度で配信しない ブラウザが実際に必要とするピクセル数は「CSS上の表示サイズ × DPR(デバイスピクセル比)」だ。Retinaディスプレイをはじめ DPR2〜3 の端末が一般的になり、私たちの目もそれに慣れたため、等倍(1:1)の画像はもう粗く感じる。表示幅の2〜3倍までは高精細化の効果があるが、それを超えると人間の目には判別できない過剰な解像度で、転送量を無駄にするだけだ。 さらにモバイルのように性能の低い端末では、大きすぎる画像はデコード処理の負荷が増え、動作が重くなる原因にもなる。 対応は、レイアウト上の最大表示幅 × 目標DPR(上限3程度)で必要なピクセル幅を割り出し、その幅で生成・配信すること。あわせて width/height 属性(または aspect-ratio)を付けてCLSも防ぐ。 srcset / sizes で出し分ける 単一解像度の画像をすべての端末に配信すると、どちらかのミスマッチが生じる。モバイルにデスクトップ用の巨大画像が届いてしまうか、逆に、固定した小さい画像では高DPR端末で解像度が足りずボケてしまうか、のどちらかだ。srcset(幅記述子つきの候補群)と sizes(レイアウト上の表示幅の宣言)を渡すと、ブラウザがビューポートとDPRを見て最適な1枚を選ぶ。 html<img srcset="photo-320.webp 320w, photo-640.webp 640w, photo-1280.webp 1280w" sizes="(max-width: 600px) 100vw, 600px" width="1280" height="853" alt="..."> sizes は「このブレークポイントではこの幅で表示する」というレイアウト情報なので、CSSと整合させるのが肝心だ。画角そのものを変えるアートディレクションが必要なら picture + source を使う。 ここで重要なのは、レスポンシブ対応の画像の出し分けを、CSSの表示/非表示(display:none やメディアクエリでの切り替え)でやらないことだ。CSSで隠しても画像自体は読み込まれてしまい、PC用とスマホ用の両方をダウンロードする、といった無駄が起きがちだ。端末ごとの出し分けは、CSSではなく srcset / sizes / picture に任せるのが正解だ(「5. 必要な画像だけ読み込む」でも触れる)。 2. 適切な画像形式を選ぶ 写真にロスレス(PNG)を使わない PNGは可逆圧縮で、写真のような連続階調・高エントロピーの画像とは相性が悪く、同じ見た目でもJPEG/WebPの数倍〜10倍のサイズになる。「劣化させたくない」という理由でスクリーンショットや写真をPNGにしているケースは多いが、たいていは非可逆で十分だ。写真は原則JPEG/WebP/AVIF。透過が必要でPNGを選びがちな場面も、WebP/AVIFなら非可逆+アルファチャンネルが使えるので、透過付きの写真もPNGより大幅に軽くできる。 画像にしなくて済むものは画像にしない ロゴ・アイコン・幾何学的な図はベクター(SVG)が適する。ラスタのロゴは高DPRでぼやけ、複数解像度を用意する手間もかかるが、SVGなら1ファイルで無限に鮮明、かつ多くの場合より軽量だ。 同じ発想で、そもそも画像にする必要がないものを画像にしないことも大切だ。文字はHTMLテキストとWebフォントで置けば、圧縮で潰れることも、SEOや多言語化で不利になることもない。単色の背景・グラデーション・角丸・影・境界線などもCSSで表現できる。たとえば「背景写真+その上のHTMLテキスト」に分ければ、背景は大胆に圧縮でき、文字はくっきり保てる。SVGも、SVGOにかけて不要なメタデータや小数桁を落とせばさらに小さくできる。 アニメーションGIFをやめる GIFは256色制限・フレーム間圧縮が貧弱で、数秒のクリップでも数MBに膨れる。実写やグラデーションを含むループはとくに非効率だ。 対応は、実質「動画」であれば <video> で MP4(H.264)や WebM(VP9/AV1)に。短いUIループなら Animated WebP でもGIFから桁違いに小さくなり、対応ブラウザも広く実用的だ。自動再生ループにするなら muted playsinline loop を忘れずに。 なお Animated AVIF も規格上は可能で、使える環境では最も軽くできる可能性があるが、ブラウザ対応にばらつきがあり、エンコードや配信まわりのツールもまだ枯れていない。現時点では「余力があれば試す発展途上の選択肢」と捉え、まずは動画化か Animated WebP を軸にするのが無難だ。 3. 画像データを最適化する 画像の内容に合わせて圧縮品質・色数を選ぶ JPEGは、描かれている内容によって「どこまで圧縮に耐えられるか」が大きく変わる。 細部やノイズの多い写真は劣化が目立ちにくく強く圧縮できるが、平坦な面やなだらかなグラデーションを含む画像は、同じ品質値でもブロックノイズやバンディング(縞)が出やすくなる。 つまり、サイト全体を一律の品質値(たとえば全部 q80)で書き出すと、画像ごとに「まだ削れるのに削っていない」「削りすぎて汚い」というムラが必ず出る。PNGも同様で、写真ではなくイラストやスクリーンショットなら、フルカラー(24/32bit)ではなく256色以下のパレットに落とせるものが多い一方、無闇に減色すると階調が破綻する。本来は画像1枚ごとに、内容を見ながら適切な品質・色数を選ぶべきなのだ。 問題は、それを人間が目視で1枚ずつ判断するのは、画像点数の多い普通のサイトでは現実的でないことだ。ここを自動化して1枚ずつ最適な設定を選ぶには、弊社が提供する LightFile Next のような、画像の特性を分析して最適な設定を自動選択する商用の画像最適化サービスを使うのが実務的な解になる。 最適化エンコーダを使う 同じフォーマット・同じ品質でも、エンコーダによってサイズは変わる。標準ライブラリの素朴なエンコードは非効率なことが多い。JPEGは MozJPEG(トレリス量子化で同画質でも小さい)で再エンコード、PNGは pngquant(減色・パレット化)や oxipng / zopflipng(可逆再圧縮)で詰める。これらは手作業ではなく、ビルドパイプラインやアップロード処理に組み込むのが現実的だ。 不要なメタデータを除去する(ただしOrientation・ICCは要注意) 写真には EXIF / IPTC / XMP、撮影機材の情報、埋め込みサムネイル、編集ソフトの作業データなどが付き、数十KB単位で無駄になることがある。これらはWeb配信には不要だが、exiftool -all= のような一括削除は事故のもとで、次の2つは要注意だ。 Orientation(回転情報):消すと、EXIFの回転を前提にした画像が横倒し・上下逆で表示されることがある。削除するなら、先に回転をピクセルへ焼き込む(auto-orient)。 ICCカラープロファイル:消すと、広色域や非sRGBの画像の色がくすんだり転んだりする。保持するか、sRGBへ変換してから外す。 要は「表示に無関係なデータだけ外し、表示に影響するものは残すか、焼き込む・変換してから外す」というのが安全な進め方だ。 次世代フォーマットをフォールバック付きで採用する Web画像の代表フォーマットであるJPEG/PNG/GIFは登場から30年以上が経ち、圧縮効率に限界がある。 WebPやAVIFは、同等の画質を従来フォーマットの半分以下のファイルサイズで配信できる。弊社の実務経験でも、ほとんどの画像で半分以下にできている。加えて透過やアニメーションにも対応する。変換には cwebp(WebP)や avifenc(AVIF)などのエンコーダを使う。 フォーマットの出し分け(フォールバック)は、HTMLのマークアップで実現する方法もある。picture + source(type="image/avif" → type="image/webp" → 旧フォーマット)と並べれば、対応ブラウザだけが新フォーマットを読み込む。 ただ、圧倒的におすすめなのはサーバー側でフォールバックする方法だ。ブラウザは Accept ヘッダーにWebP/AVIFの対応状況を載せてリクエストしてくるので、サーバーはそれを見て、非対応の相手にだけ従来フォーマットを返せばよい。HTMLを書き換えず1つのURLで自動的に出し分けられるため、実務上は圧倒的に現実的だ。AVIFはエンコードが重い点だけ、運用で見込んでおく。 4. 画像配信を最適化する ここからは「届ける仕組み」だ。画像が十分軽くても、届け方が悪いと「画像は軽いのに表示が遅い」状態になる。以下はいずれもサイト運営者が制御できる要因だ。 CDNを効果的に使う 画像を単一オリジン(例:東京のサーバー)だけに置くと、遠方のユーザーは毎回そこまで取りに行く。距離はRTTに直結し、TLSハンドシェイクも往復するため、遠いほどTTFBが積み上がる。CloudFront / Cloudflare / Fastly などで最寄りエッジから配信すれば、これを短縮できる。「海外サーバーだから遅い」と言われがちだが、本質は距離そのものではなくエッジ配信をしていないことだ。 ただし、CDNを入れれば万全というわけではない。 CDNは経路に一段はさむ仕組みなので、キャッシュヒット率が低ければ、エッジで空振りしてオリジンまで取りに行く「寄り道」が増えるだけになりかねない。効果はキャッシュが効いてこそだ。逆に、更新頻度が低く規模の小さいサイトなら、素直にオリジンから配信したほうが単純で速いこともある。要は「CDNを使うか」ではなく「CDNを効果的に使えているか(キャッシュ設計とセットになっているか)」が問われる。 無造作な同期変換(オンザフライ変換)を見直す オリジンが画像を返すのに時間がかかると、それはそのままTTFBに乗る。とくに見落としやすいのが、リクエストのたびに画像をリサイズ・フォーマット変換する「同期的なオンザフライ変換」だ。変換処理そのものがレスポンスをブロックするため、100KBの画像でもTTFBが数百msに達することがある。静的ファイルなら本来、数十msで返せるはずだ。 対応は、変換を事前生成して静的ファイル化するか、変換結果をキャッシュして2回目以降は再変換しないこと。つまり変換を、リクエスト経路の同期処理から外す。弊社の LightFile Proxy は、初回は元画像を短時間キャッシュで即座に返しつつ裏側でWebP変換を進め、変換が済んだ以降のリクエストから変換済み画像を長時間キャッシュで配信する。同期的なオンザフライ変換のTTFBペナルティを、利用者に負わせない設計だ。 帯域を広く取り、輻輳を避ける 100Mbps級のVPSにアクセスが集中すると、上限帯域で頭打ちになり、全ユーザーのダウンロードが遅くなる。大きなヒーロー画像ほど帯域を食い、輻輳を招く。クラウドやCDNでは起きにくい一方、自社サーバーやVPSからの直配信では今も現実的な問題だ。対応は、画像だけでもCDNへ逃がして配信をオフロードすること、そして1〜3で画像の総転送量そのものを減らすこと。軽い画像は帯域の問題の予防にもなる。 正しいキャッシュ戦略をとる Cache-Control: no-cache や極端に短い max-age になっていると、変わらないロゴでも毎回サーバーに問い合わせ、そのたびRTTが発生する。20KBの画像でもTTFB分は毎回かかり、逆に1MBでもブラウザのローカルキャッシュが効けば、2回目以降はネットワークに出ず実質0秒だ。「更新できなくなるのが怖くて長くできない」という理由で短くしているサイトも多い。 定石は、長期の max-age(例 max-age=31536000)とキャッシュバスターの組み合わせだ。ファイル名にコンテンツハッシュを含める(例 logo.a1b2c3.webp)か、クエリにバージョンを付ける(例 logo.webp?v=a1b2c3)ことで、中身が変われば別URLになり、長期キャッシュと確実な更新を両立できる。あわせて immutable を付ければ、有効期間中の再検証も省ける。頻繁に変わる動的画像は ETag / Last-Modified で条件付きリクエストにする。 補足:レイテンシとスループット、HTTP/2・HTTP/3 「画像が遅い」の内訳は画像サイズで変わる。小さな画像(数十〜数百KB)では接続確立やTTFBなどレイテンシが支配的になりやすく、大きなヒーロー画像では転送速度(スループット)も効く。上のCDN・オリジン性能・帯域・キャッシュは、この両方に効く施策だ。なおHTTP/2・HTTP/3も多数画像の並列取得に有効だが、いまはほとんどのCDN・Webサーバーで既定なので、モダンな構成なら特に意識しなくて構わない。 5. 必要な画像だけ読み込む JavaScriptによる遅延読み込みをやめる かつては遅延読み込みをJavaScript(data-src を IntersectionObserver で差し替える等)で実装するのが一般的だったが、いまは不要だ。JS方式は、スクリプトの読み込みと実行を待ってから画像取得が始まるため、かえって表示が遅れたり、JSが失敗すると画像が出ないといった脆さがある。標準の loading="lazy" に置き換えたい。ブラウザネイティブなので速くて確実だ。 遅延読み込みを正しく使う loading="lazy" は、ページ下部(初期表示に不要な画像)に対して使うのが基本だ。初期表示外の画像を最初から全部取得すると、初期の帯域と接続を奪い合ってしまう。 ただし逆に、ファーストビュー内、とくにLCP候補の画像に lazy を付けてはいけない。 遅延によって読み込みが後ろにずれ、LCPがかえって悪化する。lazyはページ下部限定、が鉄則だ。 CSSで画像を出し分けない 「1. 適切な解像度で配信する」でも触れたが、レスポンシブ対応でCSSのメディアクエリと display:none で画像を出し分けるのは避ける。CSSで隠しても画像自体はダウンロードされるため、PC用・スマホ用の両方を読み込む無駄が生じる。端末ごとの出し分けは srcset / sizes / picture に任せ、実際に必要な1枚だけを読み込ませるのが正解だ。 優先度を制御する ブラウザの既定の優先度では、ページ上部の主役画像が他リソースに埋もれて出遅れることがある。LCP画像には fetchpriority="high" を付けて「これは重要だ」と明示したい。ただし土台になるのは、あくまでその画像から lazy を外しておくことだ。 一方、<link rel="preload"> は、素の <img src> で書かれたLCP画像には基本的に不要だ。プリロードスキャナーがそのまま画像を発見して早期に読み込むうえ、preloadを多用するとかえって優先度のヒントが薄まり、本当に優先したいリソースが埋もれる。preloadが効くのは、CSS背景画像やJavaScriptで後から挿入される「発見されにくい画像」に限られる。詳しくは「LCP画像は最優先で読み込ませる」で掘り下げている。 まとめ Web画像最適化は「AVIF/WebPへの変換」という単一作業ではない。画像そのもの(①解像度 ②形式 ③データ)と、届ける仕組み・読み込み方(④配信 ⑤読み込み)の5本柱を横断して初めて、本当の意味での最適化になる。まずはチェックリストで自社サイトを一通りチェックしてみてほしい。 そして③で触れたように、画像1枚ごとに最適な圧縮を施す作業は、本来は手間がかかり自動化も難しい領域だ。ここを効率化したいときは、弊社の LightFile Next(画像を1枚ずつ分析して最適化)や LightFile Proxy(CloudFront向けの自動WebP変換)も検討してほしい。 --- # 実例に学ぶサイトスピード改善(2) 画像は次世代フォーマットに移行する - 公開日: Sun Jul 05 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる 画像は次世代フォーマットに移行する(本記事) テキストはGZIP/Brotliで圧縮する 重い日本語Webフォントが表示を遅らせる 外部CDNは速いとは限らない 画像の寸法指定でガクッと動くページを防ぐ head内の同期スクリプトが描画を止める 「画像を軽くすればLCPが改善する」は本当か Lighthouseのレポートを開くと、ほぼ必ずといっていいほど「次世代フォーマットの画像を使用してください」という指摘が並ぶ。そして世の中には「画像を軽くすればLCPが改善する」という解説も多い。 だが弊社の立場は少し違う。画像を次世代フォーマットにしても、LCPがそこまで改善するわけではない、というのが実情だ。LCPの時間はその前段のFCP(最初のコンテンツが表示されるまでの時間)でほぼ決まってしまう。しかも画像はダウンロードにかかる時間よりも「ダウンロードがいつ始まるか」のほうが効く。だから画像を軽くしてLCPが目に見えて良くなるのは、実はかなりのレアケースだ。 とはいえゼロではない。画像が極端に重いサイトでは、軽量化で読み込みプロセスが改善し、結果としてLCPまで縮む例も実際に存在する。この記事では、その数少ない実例を紹介しつつ、次世代フォーマット化が本来効くところ、つまり転送量の削減を、実在サイトのデータとともに整理する。 「画像を軽くすればLCPが改善する」は本当か画像の最適化はLCPより転送量削減のため次世代フォーマットAVIFとWebPブラウザ対応と注意点実在サイトで観測された次世代フォーマット化の効果ルタオ:LCPが10.6秒から2.8秒に短縮(シミュレーション)アイルミネ:101枚で8.11MBの転送量削減(シミュレーション)JINS:35枚で60%の転送量削減のシミュレーション(LCP短縮は0.2秒)自分のサイトで次世代フォーマットに移行するにはまずLighthouseで現状を確認するビルドツールやプラグインで変換を自動化する手作業を省くなら変換ツールや配信最適化を検討する変換後は品質を必ず目視確認するまとめ 画像の最適化はLCPより転送量削減のため Webページを構成するリソースには、HTML・CSS・JavaScript・画像・フォントなどがある。その中で、転送量という観点から最も大きな割合を占めやすいのが画像だ。 テキスト系のリソース(HTML/CSS/JS)はGZIPやBrotliで圧縮すれば元サイズの10〜30%程度まで転送量を抑えられることも多い。一方で画像はすでにバイナリ形式の圧縮済みデータであり、テキスト圧縮の恩恵が受けにくい。ページ上の画像の枚数や解像度が増えれば、それがほぼそのまま転送量に反映される。 出典: HTTP Archive Web Almanac 2022(Page Weight) では LCP はどうか。「画像を軽くすればLCPが改善する」とよく言われるが、そう単純な話ではない。LCP(Largest Contentful Paint)は最も大きなコンテンツが表示されるまでの時間で、その大半が前段の FCP(最初のコンテンツが表示される時間)で決まってしまう。さらに画像は、転送そのものにかかる時間よりも「ダウンロードがいつ始まるか」のほうが効く。結局、画像のファイルサイズを小さくしてLCPが目に見えて縮むのは、もともと画像が極端に重く、転送時間がボトルネックになっているような限られた状況だけだ。多くのサイトで次世代フォーマット化が効くのはLCPではなく転送量のほうである。 転送量を減らす意味は体感速度だけにとどまらない。近年はAWSをはじめ、データ転送量に応じて課金するクラウドサービスの利用が増えている。配信する画像が重いほど、その転送量はそのままクラウドの料金に跳ね返る。つまり画像の最適化は、いまやクラウド費用を抑える手段としてこそ現実的な意味を持つ。 次世代フォーマットAVIFとWebP JPEG/PNGに代わる「次世代フォーマット」の代表が、AVIFとWebPだ。どちらも同じ画質をJPEG/PNGより小さいファイルで表現でき、非可逆・可逆の両方の圧縮に対応する。AVIFのほうが圧縮率は高いが、そのぶんエンコードに時間とマシン負荷がかかる。WebPは主要ブラウザにほぼ対応済みで、エンコードが速く扱いやすい。弊社が画像最適化で主にWebPを使っているのも、この扱いやすさによる。本記事の実例もWebPへの変換で計測している。 圧縮効率について、GoogleはWebPで「同等画質のJPEGより25〜34%程度小さくなる」としている。ただし弊社の経験では、実際にはもっと縮む。画像データが半分以下になるケースも珍しくない。削減率は画像の内容や元のJPEGの圧縮設定によって変わる。とはいえJPEG/PNGを次世代フォーマットに変えるだけで、半分前後かそれ以上は軽くなると見ておくとよい。 ブラウザ対応と注意点 WebPは現在、主要ブラウザのほぼすべてに対応している。Chrome・Firefox・Edge・Safari(バージョン14以降)で表示でき、実務上の問題はほとんどない。万全を期すなら <picture> 要素でフォールバックを記述する方法もある。 html<picture> <source srcset="/image.webp" type="image/webp"> <img src="/image.jpg" alt="商品画像"> </picture> 注意すべきは過圧縮による画質劣化だ。非可逆モードで圧縮率を高めすぎると、写真系の画像でディテールが失われる。特に商品画像は元のJPEGと見比べながら品質パラメータを設定し、目視で確認してから本番に適用したい。 実在サイトで観測された次世代フォーマット化の効果 弊社の表示速度ボトルネックの実例研究(vigilante)では、実際のサイトを観測し、各ボトルネックを一つずつ解消するシミュレーションを行っている。以下では、WebP変換の効果が観測された3例を紹介する。最初の1例は、画像が極端に重く LCP まで大きく改善する数少ないケース。残りの2例は、LCP はほとんど動かないが転送量に効いたケースだ。各数値はサードパーティータグなど他のボトルネックの影響を切り離した状態で計測したシミュレーション結果だ。 ルタオ:LCPが10.6秒から2.8秒に短縮(シミュレーション) 北海道小樽発の洋菓子ブランド ルタオ(LeTAO) のトップページでは、80枚のJPEG/PNG画像が検出され、合計転送量は19.47MBに達していた。特に LCP 要素であるトップスライダーの画像(456KB)がダウンロードに時間を要し、LCP が10.6秒という観測値を示していた。 80枚をすべてWebPに変換したシミュレーションの結果が以下だ。 指標 変換前 変換後(シミュレーション) 変化量 LCP 10.6秒 2.8秒 -7.7秒 総合スコア 71 92 +21 画像転送量 19.47MB 2.84MB -16.63MB(85.4%削減) シミュレーションでは画像転送量が85%削減され、LCP が7.7秒短縮、スコアも21ポイント改善するという結果が得られた。ただしこれは典型例というより、むしろ例外に近い。元の画像転送量が19.47MBと極端に重く、LCP要素の画像のダウンロード時間がそのままボトルネックになっていたためだ。ここまで重ければ画像の軽量化がLCPに直結するが、逆に言えば、これほど極端でなければLCPはここまで動かない。 詳細な手順と観測データは ルタオのボトルネック研究 にまとめている。 アイルミネ:101枚で8.11MBの転送量削減(シミュレーション) ルミネグループが運営するファッション通販サイト アイルミネ(i LUMINE) では、トップページの画像101枚がすべてJPEG形式で配信されており、画像総サイズは10.46MBを記録していた。101枚をすべてWebPに変換したシミュレーションの結果が以下だ。 指標 変換前 変換後(シミュレーション) 変化量 LCP 0.4秒 0.2秒 -0.2秒 画像総サイズ 10.46MB 2.36MB -8.11MB(77.5%削減) このサイトでは別のボトルネック解消(カルーセル画像の遅延読み込み)のシミュレーションでLCPがすでに改善していたため、WebP変換によるLCP短縮は0.2秒にとどまった。しかし転送量の削減幅は8.11MBという規模だ。Lighthouse スコアの数値には現れにくいが、モバイル回線でのデータ通信量や、同一ユーザーが複数ページを連続閲覧した際のトータルコストに確実に影響する。 詳細は アイルミネのボトルネック研究 を参照してほしい。 JINS:35枚で60%の転送量削減のシミュレーション(LCP短縮は0.2秒) アイウエアブランド JINS のオンラインショップでは、35枚のJPEG/PNG画像がWebP未変換のまま配信されていた。とりわけ LCP 対象のトップバナー画像が337KBのJPEGであり、これが描画時間を押し上げていた。 35枚のWebP変換シミュレーションの結果が以下だ。 指標 変換前 変換後(シミュレーション) 変化量 LCP 0.8秒 0.6秒 -0.2秒 画像合計 1.81MB 736.8KB -1.09MB(60.3%削減) LCP画像単体 337KB 82.4KB -254.6KB(75.5%削減) シミュレーションでは LCP 対象のトップバナー画像が75.5%小さくなった一方、LCP 自体の短縮は0.2秒にとどまった。画像を軽くしてもLCPが大きく動くわけではない、という先ほどの話のとおりだ。一方で画像全体は60.3%(1.09MB)削減されており、転送量という観点での効果ははっきりしている。 詳細は JINSのボトルネック研究 を参照してほしい。 自分のサイトで次世代フォーマットに移行するには 実例を踏まえて、実際の対処の方向性を整理する。 まずLighthouseで現状を確認する Google Chrome DevToolsのLighthouseタブで診断すると、「次世代フォーマットの画像を使用してください(Serve images in next-gen formats)」という指摘がある場合、WebP変換の余地が存在する。指摘欄には対象の画像ファイルと推定削減量も表示されるため、優先順位を判断しやすい。 ビルドツールやプラグインで変換を自動化する 静的サイトならビルド時に一括変換するのが現実的だ。Vite・Webpack系のプラグインや画像変換CLIを組み込めば、JPEG/PNGをWebPに自動変換した上でビルドできる。変換後は <picture> 要素か <img> の src に直接WebPを指定して配信する。 WordPressなどのCMSを使っているなら、WebP変換に対応したプラグインが多数公開されている。アップロード時またはサーブ時に自動変換してくれるものを選ぶとよい。 手作業を省くなら変換ツールや配信最適化を検討する コードを変更せずに対応したいなら、画像配信を最適化する仕組みを挟む方法もある。CloudinaryやImgixといった画像CDNは、ブラウザの Accept ヘッダーに応じて自動でWebPを返してくれる。 手元での変換作業が負担であれば、弊社が提供する LightFile(画像最適化ツール)でJPEG/PNG画像のWebP一括変換を行うことができる。また、サイトのHTMLを変更しないまま既存の画像URLに対して配信時の自動WebP変換・最適化を行う LightFile Proxy もある。コードの書き換えが不要なため、CMS管理のサイトや開発リソースが限られる場合にも向いている。 変換後は品質を必ず目視確認する どの変換手段を使うにせよ、変換後の画像を元のJPEGと見比べる確認は欠かせない。圧縮パラメータが強すぎると商品画像や人物写真でディテールが失われる。品質と削減率のバランスを確認してから本番に反映したい。 まとめ 次世代フォーマット化の主な効果は「転送量の削減」。 「画像を軽くすればLCPが改善する」とよく言われるが、LCPは前段の FCP とダウンロードの開始タイミングでほぼ決まる。画像を軽くしてLCPが目に見えて縮むのは、もともと画像が極端に重いサイトに限られる。 とはいえ例外はある。ルタオのシミュレーションでは画像転送量が19.47MB→2.84MB(85.4%削減)と極端に重い状態が解消され、LCP が10.6秒→2.8秒に短縮するという結果が得られた。こうしたケースは少数だが実在する。 多くのサイトでは、次世代フォーマット化の価値は転送量に表れる。シミュレーションではアイルミネで画像10.46MB→2.36MB(77.5%削減)、JINSで35枚60.3%削減という結果になった。Lighthouse スコアに出にくくても、モバイル回線のデータ通信量や、AWSなど転送量課金のクラウド費用に確実に効く。 まずLighthouseで対象画像を確認し、ビルドツール・プラグイン・画像CDNを使って変換を自動化する。変換後は品質を必ず目視で確認する。 変換作業の自動化には LightFile、コード変更なしの配信時最適化には LightFile Proxy も選択肢のひとつとして検討してほしい。 --- # 実例に学ぶサイトスピード改善(1) LCP画像は最優先で読み込ませる - 公開日: Wed Jul 01 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 連載「実例に学ぶサイトスピード改善」 実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。 LCP画像は最優先で読み込ませる(本記事) 画像は次世代フォーマットに移行する テキストはGZIP/Brotliで圧縮する 重い日本語Webフォントが表示を遅らせる 外部CDNは速いとは限らない 画像の寸法指定でガクッと動くページを防ぐ head内の同期スクリプトが描画を止める LCPが遅いのは画像が重いからとは限らない 「LCPが遅い」と聞いて、画像の圧縮や次世代フォーマットへの変換を考える人は多い。もちろんそれは有効な場合もあるが、そもそもブラウザがその画像を「あとで読んでいい」と判断して後回しにしていたとしたら、軽量化だけでは問題を根本から解決できない。 弊社の表示速度ボトルネックの実例研究では、ファーストビューの最大画像、つまりLCP要素に遅延読み込みが適用されているケースを何度も観測している。LCP 3秒超という値が、HTML属性を数文字変えるだけで0.2秒台まで下がるというシミュレーション結果もある。 この記事では、なぜLCP画像が「あとで読む」扱いになってしまうのか、そしてどう直せばよいのかを、実在サイトのデータとともに解説する。 LCPが遅いのは画像が重いからとは限らないLCPはページの「顔」が現れるまでの時間JavaScriptによるlazyloadはもう役目を終えているLCP画像にlazyloadが残るのはたいてい見落としなぜそれでLCPが致命的に遅れるのか実例で確認するLCP画像lazyloadの影響タマチャンショップ:LCPが3.1秒から0.2秒へ(92.5%短縮のシミュレーション)FABIUS:Load Delayが全体の33%を占めていたワコールウェブストア:非LCP画像との帯域の奪い合い自分のサイトで対処するにはステップ1:LCP要素を特定するステップ2:lazyloadが適用されていないか確認するステップ3:属性を直してLCP画像を遅延させない素のimg srcならpreloadは基本不要カルーセルは1枚目だけ優先し2枚目以降はlazyReactやVue.jsでのSPAを使っている場合まとめ LCPはページの「顔」が現れるまでの時間 LCP(Largest Contentful Paint)は、ビューポート内で最も大きなコンテンツが画面に描画されるまでの時間を示す指標だ。通常はファーストビューに配置されたメイン画像やヒーローバナーがLCP要素になる。Googleは2.5秒以内を「良好」、4秒以上を「改善が必要」と定義しており、Core Web Vitalsの主要指標のひとつでもある。 ユーザーが感じる「ページが表示されたかどうか」の感覚に最も近い指標がLCPだと言っていい。ECサイトでいえば、商品のメイン画像や商品詳細ページの商品画像を思い浮かべると分かりやすい。一番見たいコンテンツがなかなか表示されないのは、ユーザーにとって離脱を招く大きなストレスになる。 JavaScriptによるlazyloadはもう役目を終えている 画像の遅延読み込み(lazyload)には、世代の違いというより歴史的な経緯がある。一昔前は、JavaScriptでスクロール位置を監視し、表示領域に近づいた画像から読み込む方式が一般的だった。画像は重いリソースなので、画面外の画像を無駄にダウンロードしない。この発想で、lazysizesやjQuery Lazyといったライブラリが広く使われた。 だが今となっては、このJavaScript方式は明確な悪手だ。そもそもJavaScriptの実行は、ブラウザや端末に負荷をかける。<img loading="lazy"> という標準属性をモダンブラウザがほぼすべてサポートしている今、わざわざ負荷の大きいJavaScriptで同じことをする意味はほとんどない。むしろページ全体の負荷を増やすだけの要因になりやすい。読み込みにメリハリをつけたいなら、JavaScriptではなく標準の loading 属性を使う。これが今の前提だ。 LCP画像にlazyloadが残るのはたいてい見落とし そのうえで本題はもっと初歩的な話だ。冷静に考えれば、できる限り早く読み込ませたいLCP画像に読み込みを遅らせる制御がわざわざ入っている。これは高度なチューニングの失敗ではなく、単なるケアレスミスだと思われる。 あるいは「lazyloadをすべきだ」という発想だけが一人歩きし、本来いちばん早く読み込ませたいLCP画像にまでうっかり付けてしまったのだろう。実例研究でも、このパターンは繰り返し見つかった。かつて全画像へ一律でJavaScript lazyloadをかけた名残が、LCP画像にも残っている。標準の loading="lazy" をファーストビュー画像にまでうっかり付けている例もある。だからこそ、数文字の修正でLCPが大きく改善する。 なぜそれでLCPが致命的に遅れるのか ブラウザはHTMLを受信すると、並行して「プリロードスキャナー」と呼ばれる先読み処理が走る。CSSやJavaScriptの解析を待たずに src 属性の画像URLを検出し、ダウンロードを前倒しで始める仕組みだ。 ところがJavaScript lazyloadでは、画像の実URLを data-src などの独自属性に格納し、src にはプレースホルダーや空文字列を置くことが多い。プリロードスキャナーは data-src を見ない。結果として、JSの実行後にIntersectionObserverが判定を終えるまで、画像のダウンロードは一切始まらない。 標準の loading="lazy" でも遅れは起きる。実URLが src にあるためプリロードスキャナーには見えるが、ブラウザは loading="lazy" の画像を意図的に低優先度として後回しにする。視覚的にファーストビューへ収まっていても、判定のタイミングしだいでリクエストは大きく遅れる。 宅配便で例えるなら、すでに玄関の前へ届いている荷物を「不在票を確認してからでないと受け取れない」と決めてしまうようなものだ。荷物はそこにあるのに、手順のせいで受け取りが遅れる。 実例で確認するLCP画像lazyloadの影響 弊社の表示速度ボトルネック実例研究(vigilante)では、サイトごとに個別のボトルネックを一つずつ解消するシミュレーションを実施し、before/afterの数値を記録している。LCP画像へのlazyload適用という問題は、ECサイト・メディアサイトを問わず繰り返し出てきたパターンだ。 タマチャンショップ:LCPが3.1秒から0.2秒へ(92.5%短縮のシミュレーション) 宮崎県の自然食品ECサイトタマチャンショップでは、ファーストビューのFlickityスライダー第1画像がlazysizesによるlazyloadの対象になっていた。src 属性にはSVGプレースホルダーが設定され、実際の画像URLは data-src 属性に格納された状態だった。 JavaScriptが実行されるまで画像ダウンロードが始まらないため、この遅延は約1,425msにのぼっていた。解消の方法はシンプルだ。src 属性に実際の画像URLを直接設定し、loading="eager" と fetchpriority="high" を付与するだけだ。 指標 解消前 解消後(シミュレーション) 変化量 LCP 3.1秒 0.2秒 -2.9秒(92.5%) SI 1.8秒 1.3秒 -0.5秒 総合スコア 94 100 +6 HTML属性の変更だけで LCP が92.5%短縮できるシミュレーション結果が得られた。詳細はタマチャンショップのボトルネック研究にまとめている。 FABIUS:Load Delayが全体の33%を占めていた 美容・健康食品ECFABIUSでも、メインビジュアルのカルーセル1枚目(kv_cellplus_sp.jpg、72.9KB)に loading="lazy" が設定されていた。 Lighthouseの LCP 内訳分析を確認すると、Load Delay(リクエスト開始までの遅延)が999msでLCP全体の33%を占めていた。画像そのものは72.9KBと決して重くない。ところがLCPの3分の1は「リクエストを開始するまでの待ち時間」に費やされていた。 loading="eager" + fetchpriority="high" に変更するだけで、シミュレーションではこのLoad Delayが解消された。 指標 解消前 解消後(シミュレーション) 変化量 LCP 3.0秒 1.2秒 -1.8秒(60%) 総合スコア 94 100 +6 詳細はFABIUSのボトルネック研究にまとめている。 ワコールウェブストア:非LCP画像との帯域の奪い合い ワコールウェブストアのケースは、「LCP画像を優先する」原則を別の角度から示す例だ。 LCP要素のバナー画像はアニメーションGIF(180フレーム、約1.4MB)で配信されていた。CloudflareのPolish機能でWebPへの自動変換が行われていたが、アニメーション情報を保持したままの変換のため、ファイルサイズはほぼ変わっていなかった。この画像を静止画WebP(30KB)に変換するだけでLCPが半分以下に縮むというシミュレーション結果が得られた。 指標 解消前 解消後(シミュレーション) 変化量 LCP 2.6秒 1.2秒 -1.4秒(54.8%) SI 1.9秒 1.4秒 -0.5秒 総合スコア 97 100 +3 さらに問題があった。カルーセルの2〜10枚目(初期表示に不要なスライド)が loading="lazy" なしでページ読み込みと同時に一斉ダウンロードされており、LCP画像と帯域を奪い合っていた。これらに loading="lazy" を付与し、LCP画像は loading="eager" + fetchpriority="high" を明示したシミュレーションでは、さらなる改善が確認できた。 指標 解消前 解消後(シミュレーション) 変化量 LCP 1.2秒 1.0秒 -0.2秒(16.0%) LCP画像を速くするためには、lazyloadを付けるべき画像と外すべき画像を正しく使い分けることが重要だ。 詳細はワコールウェブストアのボトルネック研究を参照してほしい。 自分のサイトで対処するには LCP画像の遅延を取り除くのは、一般的に難易度が低い部類の施策だ。ツールでLCP要素を確認し、HTML属性を数文字書き換えるだけで対処できる。 ステップ1:LCP要素を特定する Chrome DevTools の [Performance タブ]や[Lighthouse]を使い、LCP要素を確認する。Lighthouseのレポートには「Avoid chaining critical requests」「Preload LCP element」などの指摘が表示されることもある。ほとんどの場合、ファーストビューで最も大きい画像がLCP要素だ。 ステップ2:lazyloadが適用されていないか確認する LCP要素として特定した画像の <img> タグを確認し、以下のいずれかに該当しないかチェックする。 loading="lazy" が設定されている src がプレースホルダーで実URLが data-src などに格納されている(lazysizesなどのライブラリによるlazyload) ステップ3:属性を直してLCP画像を遅延させない 対処はシンプルで、LCP画像から loading="lazy" を外す。JavaScript lazyloadなら、src 属性に実際の画像URLを戻す。 html<!-- 問題: loading="lazy"がLCP画像に付いている --> <img src="hero.jpg" loading="lazy" alt="メインビジュアル"> <!-- 解決: lazyを外す(必要なら loading="eager" を明示) --> <img src="hero.jpg" loading="eager" alt="メインビジュアル"> html<!-- 問題: lazysizesなどによるdata-src形式 --> <img src="placeholder.svg" data-src="hero.jpg" class="lazyload" alt="メインビジュアル"> <!-- 解決: src属性に実URLを戻し、LCP画像はlazyloadの対象から外す --> <img src="hero.jpg" loading="eager" alt="メインビジュアル"> あわせて fetchpriority="high" を付けると、ブラウザに「この画像は重要だ」と明示できる。web.devも推奨する仕上げの一手だ。ただし土台になるのは、あくまでlazyを外すことにある。 素のimg srcならpreloadは基本不要 web.devは、LCP画像に <link rel="preload"> と fetchpriority="high" を付けて先読みすることを推奨している。preloadしたLCP画像はそうでない場合よりLCPが速いというデータもある。 ただし大きく効くのは、おもにLCP画像が早期に発見されにくいケースだ。CSSの背景画像やJavaScriptで後から挿入される画像は、プリロードスキャナーに発見されない。こうした画像にはpreloadで存在を知らせる価値がある。 一方で、HTMLに素の <img src="…"> として書かれた画像は、プリロードスキャナーがそのまま発見して早期に読み込む。この場合はlazyさえ外れていれば、preloadを足さなくても十分に早い。むしろpreloadを多用すると優先度のヒントが薄まり、本当に優先したいリソースを埋もれさせることもある。だから素のimg srcのLCP画像については、preloadは基本的に不要だというのが弊社の整理だ。 本質的に大事なのは、LCP画像を「不自然に最速で」読ませることではない。lazyloadなどで不必要に遅延させないことだ。余計な遅延さえ取り除けば、ブラウザは自然な読み込み順序の中でLCP画像を十分に早く扱う。タイムライン上で、LCP画像が画像群の中で早い読み込み順位を確保できているか。そこを保証するだけでよい。 カルーセルは1枚目だけ優先し2枚目以降はlazy カルーセルを使っている場合の対処法はワコールウェブストアの例で示したとおりだ。 html<!-- 1枚目のスライド:LCP画像なのでlazyを外す --> <img src="slide1.jpg" loading="eager" fetchpriority="high" alt="春のキャンペーン"> <!-- 2枚目以降:初期表示に不要なのでlazyloadでOK --> <img src="slide2.jpg" loading="lazy" alt="..."> <img src="slide3.jpg" loading="lazy" alt="..."> SlickやSplide、Swiperなどのカルーセルライブラリは、初期化処理の中で全スライドに一律でlazyload設定を施すものも多い。ライブラリ経由でlazyloadを使っている場合は、LCP要素となる1枚目だけ設定を上書きするか、ライブラリのオプションで除外する必要がある。 ReactやVue.jsでのSPAを使っている場合 SPAではJSが実行されるまでLCP画像がDOMに存在しないケースがある。この場合、サーバーサイドレンダリング(SSR)やHTMLに初期状態のLCP画像を静的に埋め込む実装を検討したい。JavaScriptが動いてから初めて画像が描画される構成では、プリロードスキャナーが機能しないだけでなく、LCPそのものが根本的に遅くなりやすい。 まとめ LCP画像へのlazyloadはたいてい見落としによるもの。 loading="lazy" やJavaScript lazyload(lazysizes等)がファーストビューの最大画像に残っていると、リクエスト開始が大幅に遅れてLCPが悪化する。冷静に見れば気づけるはずの取りこぼしだろう。 JavaScriptによるlazyloadはもう不要。 読み込みにメリハリをつけたいなら、標準の loading 属性を使う。JS方式は速やかに置き換えたい。 直し方は「遅延を取り除く」こと。 LCP画像から loading="lazy" を外し、JS lazyloadなら src に実URLを戻すだけでよい。素の <img src> ならプリロードスキャナーに発見されるため、<link rel="preload"> を足さなくても十分に早い。web.devもpreloadを推奨しているが、大きく効くのはおもに発見されにくい画像のケースだ。 実在サイトを対象にしたシミュレーションでのインパクトは大きい。タマチャンショップでは LCP が92.5%短縮(3.1秒→0.2秒)、FABIUSでは60%短縮(3.0秒→1.2秒)というシミュレーション結果が得られた。 ファーストビュー外の画像にはlazyloadを使う。 ワコールウェブストアの例のように、初期表示で不要なカルーセル画像が帯域を奪ってLCPに響くこともある。LCP画像とそれ以外で読み込み戦略を分けるのが要点だ。 lazysizesやSlick、Splide、Swiperなどのライブラリを使っている場合は、最初のスライドがlazyload対象になっていないかを必ず確認してほしい。 LCPを改善しようとしてフレームワークや配信インフラに手を入れる前に、まずLCP画像の属性を確認することをすすめる。数文字の変更が、秒単位の改善につながることがある。 自社サイトのLCP画像や他のボトルネックを体系的に調べたい場合は、弊社の表示速度ボトルネック研究 vigilante の事例が参考になる。より詳しい調査が必要な場合はアイデアマンズまでご相談いただきたい。 --- # Tailscaleという新しいネットワーク体験 - 公開日: Sun Jun 28 2026 00:18:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: infrastructure, development 今更ながらTailscaleを紹介したい。半年ほど前にオンプレVPNをTailscaleに刷新したのだが、UXが抜群にいい。そしてひとり月額$8と安い。一瞬で手放せない必須ツールとなってしまった。 社内の「自称データセンター」運用Tailscaleに乗り換えmacOSはVPNに常時接続Linuxサーバーへの導入も簡単アプリケーション向けの機能AIエージェント時代の揺り戻しまとめ 社内の「自称データセンター」運用 弊社内ではサーバーを複数台運用している。といっても本格的なものではなく、退役したPCにProxmoxを入れて仮想サーバー化した程度のものだ。クラウドやVPSでパワーのあるインスタンスを稼働させるとそれなりに費用がかかるが、退役PCを使えば同等スペックのコンピューティングをほぼ電気代のみで維持できる。それで簡易的にGitLabを動かしたり、開発サーバーや重めのバッチ処理用のワーカーマシンとして活用している。 それに加え、Claude Codeによる長時間の処理をこなすための「サブデスクトップ」のニーズも増えている。 当然、それらのサーバー群には必要に応じて社外からもアクセスする必要がある。それで以前はヤマハのRTXルーターを設置し、IPSecベースのVPNを運用していた。 大きな不便はなかったが、片手間での自前運用だからセキュリティには漠然とした不安があった。またバックアップ機は持っていたものの、機器が故障したら面倒だなとも感じていた。 Tailscaleに乗り換え そこでよく耳にしていた Tailscale を試してみた。そして当日、すっかりファンになってしまった。 これはVPNという単なるネットワークコンポーネントに非ず。ネットワークという、コンピューターを繋げるエクスペリエンスを見直すサービスと言ってよい。 macOSはVPNに常時接続 macOSやWindowsでの体験は想像以上にシンプルだった。専用アプリをインストールして、アカウントにログインするだけ。それだけでTailscaleが構成する仮想ネットワーク(tailnet)に参加できる。 Macの場合、OS標準のVPN機能と連携してほぼ常時接続の状態を保ってくれる。以前のルーター運用と比べて何が変わったかといえば、とにかく接続操作がなくなったことだ。 MacBookを開けば、すでに繋がっている。社内のGitLabを触りたいときも、開発サーバーにSSHしたいときも、VPNのことを意識する必要がない。この差は、やってみると思ったより大きかった。 Linuxサーバーへの導入も簡単 Linuxサーバー向けにはワンライナーが用意されている。管理コンソールから一時的な接続トークンを発行すると、ワンライナーが必要なソフトウェアや設定をまるごと揃えてくれる。 bashcurl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up --auth-key=xxxxxxxxxxxxxxxx サービスとして自動起動する設定まで一気に整うので、再起動後も自動的に参加した状態になる。 通常、LinuxサーバーをVPNに常時接続させようとするとかなり手間がかかる。設定ファイルの編集、systemdのユニット作成、証明書の管理など、調べながらやると数時間かかることもある。それがコマンド一発で終わるのは、純粋に気持ちがいい。 これで複数の社内サーバーをすべてtailnetに加えた。 アプリケーション向けの機能 Tailscaleは単にネットワーク接続を提供するだけにとどまらず、アプリケーション向けの機能もいくつか提供されている。私自身は今のところ用途がないので使っていないが、これらの機能を使うとあたかもオフィスに出社してイントラネットを使うような体験が得られるだろう。 App Connectors — 特定のSaaSや外部サービスへの通信を、決まった出口IPに集約するEgressゲートウェイ。GitHub EnterpriseやSalesforce、AWS ConsoleなどのIPホワイトリスト運用に使える。 Tailscale Services — tailnet内のAPI、データベース、MCPサーバー、Redisなどを論理的な名前で扱えるようにするサービスディスカバリの仕組み。裏側のホストがDocker/K8s/VMに変わっても名前は変わらず、複数ホストで冗長化や負荷分散もできる。 Tailscale SSH — SSH認証・認可をTailscaleに委任する仕組み。SSH鍵の配布・管理が不要になり、Google WorkspaceやGitHubのSSOで認証しながら、ポリシーファイルで「誰がどのサーバーにどのユーザーで入れるか」を一元管理できる。22番ポートをインターネットへ公開せず運用できるのも助かる。 AIエージェント時代の揺り戻し つい最近まで、ブラウザやインターネット経由で必要なときだけ「疎」に接続できるSaaSがトレンドを支配していた。 ところが今は「人間がUIを頻繁に操作する」という前提自体が怪しくなってきている。AIエージェントの躍進だ。便利なSaaSより、Claude CoworkのようなAIエージェントを常時起動するマシンが欲しい。最先端を行く人のニーズはそのように変化している。 もしかしたらAIエージェントとの「密」なネットワークに揺り戻しがくるかもしれない。そうなるとTailscaleは一歩先ゆくサービスを提供している。 まとめ 半年使ってみた率直な感想は「もう以前の構成には戻れない」のひと言だ。VPN接続を意識しなくなっただけで、日常の小さなストレスがかなり減った。セキュリティ面も、片手間で自前ルーターを設定するより信頼できる。 ローカルサーバーやNASを持っている人、リモートワーク中に社内機器へアクセスしたい人にはまず素直に試してみてほしい。Linuxサーバー持ちの人なら、慣れれば1時間もかからず全部乗り換えられると思う。 公式サイト: Tailscale --- # アクセスランキングは何%クリックされるのか - 公開日: Wed Jun 24 2026 20:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: research, business アクセスランキングを設置したらどのくらいクリックされるか、定量的なデータを見たことはあるだろうか。回遊性を高める施策としてランキングはよく使われるが、その効果が実際どの程度なのかを数字で語れる材料は意外と少ない。弊社はアクセスランキングウィジェットサービス Ranklet4 を運営しており、累計で約11.5億回の表示と約3,100万回のクリックという実データを持っている。今回はこの実測値をもとに、ランキングのクリック率を正面から分析してみたい。 先に要点をまとめておく。 典型的なアクセスランキングウィジェットでは、おおむね1〜10%のクリック率が期待できる。典型値は3.5%前後。 全ランキングの単純平均は2.7%。Google ディスプレイ広告のおよそ6倍にあたる。 まとめサイトのようにランキングを主要なナビゲーションとして使うと、20%超のクリック率も出る。設置場所の影響は大きい。 この「1〜10%」という幅は、3年以上の長期で見ても大きくは変わらない安定した傾向だ。 以下、累計11.5億表示というデータから見えてきた事実を順番に紹介していく。 単純平均は2.7%あなたのサイトのランキングはどうかクリック率の分布のかたちどこに置くかで大きく変わる長期的な変動は小さいまとめ 単純平均は2.7% まずは全体の数字から見てみよう。Ranklet4 のアクセスランキングはこれまでに約11.5億回ユーザーの目に触れ、そのうち約3,100万回がクリックされた。表示に対するクリックの単純平均は 2.70% になる。 一つの参照点として、WordStream が公表する Google ディスプレイ広告の全業種平均クリック率は約0.46%。アクセスランキングはその約6倍の水準にある。なぜランキングがこれほどクリックされるのかという心理的な背景は、別記事「アクセスランキングの心理学」で考察した。 ちなみに Ranklet4 では、ランキングの表示数を IntersectionObserver で計測している。ページが読み込まれたかではなく、ウィジェットが画面内に入り実際にユーザーの目に触れたかで判定しているので、ここでのクリック率は広告でいうビューアブルインプレッションに近い堅めの分母になっている。 ただし、この2.7%は全ランキングウィジェットをまとめた平均にすぎない。あなたのサイトにアクセスランキングを設置したらどれくらいクリックされるかを思い描くには、もう少し違う角度から分析する必要がある。 あなたのサイトのランキングはどうか そこで、1つ1つのランキングがどのくらいクリックされているかを見てみる。累計表示が1,000回以上のランキングは296件あり、この296件を対象とした。ランキング単位でクリック率を集計し、その分布を分位点で表したのが次のグラフだ。 分位点 クリック率(表示100回あたり) 下位10%(P10) 0.26% 下位25%(P25) 0.97% 中央値(P50) 3.47% 上位25%(P75) 10.65% 上位10%(P90) 28.82% ランキング単位で見たとき、クリック率の中央値は3.47%だった。全体平均がこれよりやや低いのは、表示数の多い大規模サイトに引っ張られるためだ。普通のアクセスランキングの手応えに近いのは中央値のほうである。 そして下位25%(P25)から上位25%(P75)までのいわゆる四分位範囲は1〜10%。つまりアクセスランキングを設置すれば、アクセス数やクリック数が極端に少ない例や逆に多すぎる例を除き、大抵は1〜10%の回遊効果が期待できるという見立てになる。 クリック率の分布のかたち もう少し、アクセスランキングによってクリック率がどう違うのかを詳しく見てみよう。クリック率を1%刻みで区切り、それぞれに何件のランキングが当てはまるかを数えたのが次のグラフだ。 右に長く裾を引く分布になった。0〜1%が77件で一番高い。右端の「20%以上」も38件と大きく伸びているが、これは20%を超えるランキングが少数ずつ広く存在し、それをまとめて1本に折り返した結果だ。1つ1つは希少でも、合算すれば無視できない山になる。 実は、この横軸の目盛りを対数に変換すると面白い形が見えてくる。分布の山が中央値(3.5%付近)へ寄り、正規分布のようなベル型になるのだ。そこに対数正規分布のフィット曲線を重ねたのが次のグラフだ。 実測のヒストグラム(青)に、フィット曲線(オレンジ)がよく重なっている。クリック率は近似的に 対数正規分布(log-normal) を描くと言えそうだ。 なぜそうなるのか。クリック率は コンテンツの魅力 × 設置位置の良さ × 見せ方の質 × サイトの業種・属性 といった、複数の独立した要因の掛け算で決まると考えられる。掛け算で積み上がる量は対数を取ると足し算になり、中心極限定理によって正規分布へ近づく。これは対数正規分布が現れる理屈だ。だからボリュームゾーンを持ちつつ、上振れしたものは桁違いに大きいというロングテールな広がりになる。 弊社では以前、サイトスピードの指標(OnLoad や LCP)も対数正規分布を描くことを実データで確かめている(「サイトスピード指標の対数正規分布を確かめる」)。まったく別の現象であるはずのクリック率に、くしくも同じ分布が顔を出したのは、個人的に面白い符合だと思う。 どこに置くかで大きく変わる 対数正規分布の背景にある「掛け算の要因」のうち、現場でとりわけ効くのが どこにどう置くか、つまり設置場所だ。 先ほどの上位10%(P90)の 28.82%=表示3〜4回に1回クリックという数字は、一見すると外れ値のように見える。だがこれらは、ランキングをサイトの主要なナビゲーションとして使っているケースだ。まとめサイトのように、ランキングそのものが回遊の幹線になっている構成では、恒常的に20%を超えるクリック率が出る。 加えて、サイトそのもののコンテンツの魅力も効いてくる。こうした複数の要因が掛け合わさるからこそ、クリック率は対数正規分布のような広い裾を持つのだろう。 長期的な変動は小さい さて、ここまでの数値が一時の偶然でないかを確かめるため、長期的なトレンドも見ておきたい。年ごとに、ランキング単位のクリック率の中央値と四分位範囲(P25〜P75)を追ってみた。 年 下位25%(P25) 中央値 上位25%(P75) 2023 1.14% 4.12% 10.83% 2024 0.82% 3.17% 10.71% 2025 0.81% 2.72% 10.03% 2026※ 0.66% 2.77% 9.88% ※2026は年途中までの集計。 中央値は4.1%から2.7%へゆるやかに下がっているが、これは利用サイトが増えて多様化したぶんならされた動きと見られる。一方で、上位25%が約10%前後、四分位範囲としても1〜10%という点は、年を追っても大きく変わらない。つまり、おおむね1〜10%のクリック率を期待できるという見立ては、一時の数字ではなく長期的にも当てはまる傾向だと言えるのではないだろうか。 まとめ 以前の調査で、ニュース系メディアサイトの約74%がアクセスランキングを採用していることが分かっている(「メディアサイトのアクセスランキング採用率は? 平均 74%」)。これだけ広く使われているのは、回遊率を高める仕組みとして十分に認められている証だろう。 累計11.5億表示の実測から見えた事実をまとめると、こうなる。 アクセスランキングを設置すると、おおむね1〜10%の回遊が得られ、典型値は3.5%前後。 全ランキングの単純平均は2.7%で、Google ディスプレイ広告の約6倍。 主要ナビゲーションとして使えば20%超も狙える。設置場所と見せ方の影響は大きい。 この「1〜10%」という幅は、3年以上の長期でも安定して見られる傾向。 「メディアサイトにアクセスランキングを置いたらどうなるか」という問いに対して、「おおむね1〜10%、典型的には3.5%前後のクリック率による回遊が期待できる」 という幅は、設置を検討するときの現実的な根拠として使えるはずだ。 弊社の Ranklet4 は、GA4と連携して最新のアクセスランキングを自動生成・配信するウィジェットサービスだ。任意のデザインで設置でき、毎週の手動更新も不要。こうした実測データに裏打ちされた回遊改善の手段として、検討してみてはいかがだろうか。 --- # サイトスピードが遅いなら、まず疑うべきはサードパーティタグ - 公開日: Mon Jun 22 2026 15:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology サイトスピードの相談を受けると、ページの作りを見直すという前提で話が進みやすい。何か実装方法が間違っているのだから、それを直せばスピードは上がるはず、と考えるのは自然な話だ。 しかし実際のタイムラインを見てみると、遅さの主な原因がページそのものではないケースは多い。HTMLやCSS、画像が十分に軽いにもかかわらず、後から差し込まれた広告や解析の サードパーティタグ が表示速度を押し下げている。サイト本体に手を入れる前に、これらのタグを見直すだけでスピードをかなり改善できるケースは決して珍しくない。 この記事では、実際のサイトを対象にした弊社のサイトスピードの弱点研究のデータをもとに、サードパーティタグがどれだけスピードに影響を与えるか、そしてそれにどう対処すべきかを紹介したい。 ブラウザは優しい世界ではない実例で確認するサードパーティタグの影響ABAHOUSE ONLINE STORE:ページ本体は軽いのに、タグでLCPが1.8秒延びるホームセンターバロー:たった3個のタグでLCPが20秒プレジデント・オンライン:半分以上がタグ。外せばスコアは99にASCII.jp:約7割がサードパーティタグ大規模改修の前にできることがある積極的に使われていない「ゾンビタグ」を消す読み込みのタイミングを後ろ倒しにするそもそもGoogle Tag Manager自体が重いGTMコンテナはひとつにGTMの読み込み自体を後ろ倒しにする「計測が乱れるのではないか?」という懸念についてまとめ ブラウザは優しい世界ではない なぜ、補助的なはずのサードパーティタグがページスピードに大きな影響を与えるのか。その答えは、ブラウザの中の仕組みにある。 JavaScriptに限らず、ブラウザの主要な処理はシングルスレッドで動く。マルチコアのCPUが当たり前になった今でも、同時に動かせる処理はひとつだけだ。1台しかないATMに、みんなで行列を作って順番に使う。そんなイメージである。 ここで私たちは、ユーザー向けのコンテンツが優先されるはず、と期待してしまう。だが現実は違う。コンテンツだろうとサードパーティタグだろうと、我先にとATMに並び、並んだ順にCPUを使う。決してユーザー中心の優しい世界ではないのだ。 理想を言えば、補助的なサードパーティタグは、ユーザーが見たいコンテンツに順番を譲るべきだ。ところが現実には、タグの処理がコンテンツの表示を妨げているケースは決して少なくない。 これが、ページ本体が軽くてもサードパーティタグによって表示スピードが低下する現象の正体だ。 実例で確認するサードパーティタグの影響 弊社では、表示速度ボトルネックの実例研究 というブログで、実際のWebページの表示速度のボトルネックを研究し、その成果を公開している。フロントエンドのどこをどう直すとどの指標が改善するかを、精度の高いシミュレーションで一つずつ検証し、結果を数値とともに公表する取り組みだ。 この研究には共通の手順がある。調査したいページ本体を観察する前に、まずサードパーティタグを取り除くのだ。理由は2つある。 ひとつはサードパーティタグがノイズとなり、サイト本体のパフォーマンスの分析が難しくなるためだ。もうひとつは、サードパーティタグ自体がどれだけスピードに影響しているかを計測するためである。タグを全部外すのは運用上できない話だが、外したときの数値は「タグを最適化すれば最大でここまで速くなる」という改善ポテンシャルを教えてくれる。 ではサードパーティタグが大きな影響を与えている具体例をいくつか見ていこう。 ABAHOUSE ONLINE STORE:ページ本体は軽いのに、タグでLCPが1.8秒延びる ファッション通販の ABAHOUSE ONLINE STORE のトップページでは、12種類のサードパーティタグが検出され、合計約2.8MBの転送とメインスレッド640ms以上の占有が観測された。全215リソースのうち、サードパーティタグ由来は88件と約4割を占めていた。 これらをすべて外したシミュレーションの数値が分かりやすい。 指標 観測時点(タグあり) タグ全除去後(シミュレーション) 変化量 LCP 1.8秒 0.1秒 -1.7秒 FCP 1.6秒 0.1秒 -1.5秒 タグを外したシミュレーションの LCP(主要コンテンツが表示されるまでの時間)はわずか0.1秒。ページ本体はこれほど高速に表示できる作りなのに、タグが乗っているだけで LCP が1.8秒まで伸びていた。「ページが重い」のではなく「タグが重い」ことがはっきり分かる一例だ。なお、このタグを含む全ボトルネックを解消したシミュレーションでは、Lighthouseの 総合スコア は86から100へ到達している。 ページ本体の速さとタグの重さを分けて検証した詳しい手順は、弱点研究の記事 ABAHOUSE ONLINE STORE のボトルネック研究 にまとめている。 ホームセンターバロー:たった3個のタグでLCPが20秒 ホームセンターバロー公式オンラインショップ はもっと極端だ。検出されたサードパーティタグはわずか3個、全93リソースのうち1割にすぎない。しかもその3個はすべてGoogle Analytics/Google Tag Manager系のトラッキングコードで、機能が重複していた。 段階 総合スコア LCP FCP 観測時点 35 20.2秒 4.5秒 GTMまで除去(シミュレーション) 64 4.5秒 1.2秒 LCP 20.2秒という観測値が、タグを外したシミュレーションでは4.5秒まで縮んだ。とりわけ最後に Google Tag Manager を外した段階で LCP が一気に落ちている。数の少なさと重さは比例しない。 タグマネージャーひとつがこれだけの遅延を生むことがある。総合スコア もタグ除去のシミュレーションだけで35から64へと29ポイント改善した。全93リソースのうちタグはたった1割で、残り9割はサイト本体だ。それでも、この1割が表示速度を支配していた。 3個のタグを段階的に外したときの記録は、ホームセンターバロー公式オンラインショップのボトルネック研究 で確認できる。 プレジデント・オンライン:半分以上がタグ。外せばスコアは99に 広告で運営されるメディアサイトはタグの量がさらに桁違いになる。プレジデント・オンライン のトップページでは、全369リソースのうち約6割にあたる216件がサードパーティタグ由来で、その種類は54種、合計10.79MBにのぼった。 タグを3つのグループに分けて段階的に外すシミュレーションを行うと、指標は次のように改善した。 除去フェーズ(シミュレーション) 総合スコア LCP 観測時点 56 15.9秒 高負荷広告・レコメンド除去 67 6.9秒 広告インフラ・Prebid除去 80 4.6秒 GTM/GA・トラッキング除去 99 1.8秒 シミュレーションでは LCP が15.9秒から1.8秒へと約14秒短縮された。スコアの動きも見逃せない。総合スコア はタグを外すだけで56から99へと一気に上がり、サイト本体の微修正まで含めた全解消シミュレーションでは100に達した。100点満点で44ポイントの変化だ。 注目すべきは、これだけタグを外しても 見た目はほぼ変わらなかった こと(差分0.11%以下)。広告枠はサーバーサイドで制御されており、ユーザーが目にするコンテンツの大半はタグなしでも成立していた。スコアを44ポイント引き上げた要素の大半が、ユーザー体験には不要なものだったわけだ。 54種のタグを3グループに分けて外した詳細は、プレジデント・オンラインのボトルネック研究 を参照してほしい。 ASCII.jp:約7割がサードパーティタグ IT情報サイトの ASCII.jp でも、全298リソースの約7割(203件)がタグ由来だった。これらを外したシミュレーションでは LCP が11.6秒から0.4秒へ、総合スコア が71から100へと変化した。リソースの7割を占めるタグを外しただけで、スコアが満点に届く計算だ。 Header Biddingを含む203件のタグの内訳は、ASCII.jp のボトルネック研究 に載せている。 念のため補足すると、これらはあくまでタグを全除去した上限値だ。実運用ではこの一部しか実現できない。それでも最適化で取り戻せる余地がこれほど大きいという事実は重い。 大規模改修の前にできることがある ここまでの例に共通するのは、ページ本体はそこまで重くないという点だ。むしろ本体は十分に速く、後から積み重なったサードパーティタグが表示を押し下げている。 ただ、商用サイトではこれがむしろ普通の状態だと言ってよい。広告、アクセス解析、ABテスト、リターゲティング、チャットボット、UGC連携と、ビジネス上の必要から少しずつタグが増えていくのはしかたがない。 だからこそサイトスピード改善を狙ってリニューアルや大規模改修を実施しても、サードパーティタグを入れたら元の木阿弥、という事態にもなり得る。フロントエンドの作り直しは時間とコストのかかる大仕事だが、タグの整理はすぐに始められる。サイト本体を疑うのはその後でもできることだ。 では具体的にどう整理していくか。大きく2つの方向がある。 積極的に使われていない「ゾンビタグ」を消す ひとつめは、もう使われていないタグを削除することだ。 長くサイトを運用していると、Google Tag Managerの中に「いつ、誰が、何のために入れたのか分からないタグ」「実はもうほとんど使われていないタグ」が溜まっていく。担当者が代わり、代理店が変わり、施策が終わり、計測ツールが乗り換わっても、外したときに思わぬ副作用が出るのを恐れて、誰も手を付けられないまま放置される。いわば ゾンビタグ だ。 長期運用のサイトでゾンビタグがまったく無いケースは見たことがない。まずはGTMのコンテナの棚卸しをして、計測や配信に使われていないタグを洗い出し、削除していく。 できれば「これだけは譲れない」とはっきり言えるタグだけを残し、他は思い切って消す決断をする。これで読み込むスクリプトが減り、メインスレッドの負荷は下がる。 読み込みのタイミングを後ろ倒しにする ふたつめは、タグを読み込むタイミングを遅らせることだ。冒頭で見たとおり、メインスレッドというATMは1台きりで、あらゆる処理がそこを奪い合っている。本来サードパーティタグは、ユーザーが見たいコンテンツに順番を譲るのが理想だ。 ところが多くのサイトはページが開かれたのと同時にタグを読み込んでいる。そのようにタグのベンダーに指示されるからだが、コンテンツ表示にとっては非常に都合が悪い。ページの初期表示にCPUをフル回転させたいまさにその瞬間に、タグが行列に割り込んでくるからだ。 ユーザーが見たいのはコンテンツであって、計測や広告ではない。にもかかわらず補助的なはずのタグが初期表示に割り込み、コンテンツの描画を遅らせる。これでは本末転倒だ。表示の初期段階というゴールデンタイムは、できる限りユーザー向けのコンテンツに振り向けたい。タグの実行は後ろに回すのが望ましい。 GTMを使っているなら、これは各タグの トリガー(発火条件) を見直すことで実現できる。GTMのページビュー系トリガーには、発火が早い順に次の3種類がある。 ページビュー:ブラウザがページの読み込みを開始した直後に発火(最も早い) DOM Ready:HTMLの解析が終わった時点で発火(DOMContentLoaded 相当) ウィンドウの読み込み:画像なども含めてページが読み込み終わった時点で発火(load 相当、最も遅い) ところが多くのタグは、最も早い「ページビュー」(All Pages)のまま設定されている。表示を優先したいタグは、このトリガーをできる限り遅い「DOM Ready」や「ウィンドウの読み込み」に変更する。これが、GTMにおける読み込みタイミング後ろ倒しの具体的な指定方法だ。 そもそもGoogle Tag Manager自体が重い タグのタイミングを考えるうえで、ひとつ認識を改めてほしいことがある。Google Tag Manager(GTM)そのものが、かなり重い ということだ。 GTMは「タグを管理しているだけ」のツールに見えるので、それ自体は軽いと思われがちだ。だが実際にはデータ量が大きく、読み込むスクリプトも相応に重い。 GTMコンテナはひとつに ひとつのページに複数のGTMコンテナを配置しているサイトも少なくない。しかしGTMコンテナはほとんどのコードが重複しており、ネットワークやCPUに無駄な負担を強いる。 管理上、致し方ない点があるのかもしれないが、GTMを利用する場合はひとつのページで読み込むコンテナをひとつに絞ることを推奨する。 GTMの読み込み自体を後ろ倒しにする ユーザー向けのコンテンツ表示を優先する大胆な方法としては、GTM自体の読み込みタイミングを遅らせるやり方もある。 考えられるのは2段階だ。ひとつは DOMContentLoaded(DCL、HTMLの解析が終わったタイミング)でGTMを展開する方法。もうひとつ、ユーザー向けコンテンツの表示をさらに優先するなら、load(onload、画像なども含めてページの読み込みが完了したタイミング)まで遅らせる方法だ。 通常、GTMのスニペットはページの読み込みと同時にタグを展開する。 html<!-- Google Tag Manager(標準のスニペット:即時実行) --> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXXX');</script> <!-- End Google Tag Manager --> これを、以下のようにDOMContentLoaded イベントまで展開を遅らせる形に書き換える。 html<!-- Google Tag Manager(onload後に展開) --> <script> function loadGTM() { (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXXX'); } // ユーザー向けコンテンツの表示を優先し、読み込み完了後にGTMを展開する if (document.readyState === 'complete') { loadGTM(); } else { window.addEventListener('DOMContentLoaded', loadGTM); } </script> <!-- End Google Tag Manager --> こうしておけば、ページ表示の初期段階というゴールデンタイムをまるごとユーザー向けコンテンツに割り当てられる。 window.addEventListener('DOMContentLoaded', ...) の DOMContentLoaded を load に変えれば、サードパーティタグはほぼ完全にユーザーコンテンツの表示にCPUを譲ることができる。 「計測が乱れるのではないか?」という懸念について タイミングを遅らせる話をすると、多くの人が「計測がズレてしまうのでは」と心配する。もっともな懸念だ。だが、ここで冒頭のATMの行列を思い出してほしい。 私たちは、サードパーティタグが決まった時刻にいっせいに計測や広告配信を始め、それぞれ正しく動いていると感じがちだ。だが実際には、タグたちは1台しかないATMを奪い合い、自分の作業をなかなか始められずにいる。順番を待つあいだにも、時間はどんどん過ぎていく。 つまり、いま現在のサードパーティタグも正確なタイミングで計測できているわけではない。ほかのスクリプトが立て込んでいれば、計測タグはその実行が空くまで待たされる。タグを後ろ倒しにすれば確かに遅延は大きくなる。だが「いまは遅延がゼロ」なのかというと、そんなことはない。もとから遅延はある。計測はすでに乱れているのだ。 だから計測のタイミングが多少ずれても深刻に捉えすぎる必要はない、というのが弊社の見解だ。サードパーティタグの計測値はもともと参考値であり、厳密な精度を期待するものではない。後ろ倒しによる多少の上乗せよりも、ユーザーが体験する表示スピードを優先するほうが、得るものは大きい。 まとめ サイトスピードが遅いとき、最初に疑うべきはサードパーティタグだ。ページ本体は十分に軽いのに、タグだけで LCP が10秒以上延び、Lighthouseの 総合スコア が40ポイント以上動くというシミュレーション結果の例も実在する。 商用サイトでタグが増えるのは避けにくい。だからこそ、リニューアルや大規模改修を始める前に、まずタグを見直したい。サイト本体を疑うのはそのあとでも遅くない。 整理の方向は2つ。ひとつは使われていない「ゾンビタグ」を削除すること。もうひとつは残すタグの読み込みを後ろ倒しにし、初期表示をユーザー向けコンテンツに譲ることだ(GTMならトリガーを「DOM Ready」や「ウィンドウの読み込み」へ)。 GTM自体が重いことも忘れずに。読み込むコンテナはひとつに絞り、必要ならGTMの読み込み自体を DOMContentLoaded や load まで遅らせる。 「後ろ倒しで計測が乱れるのでは」という懸念はもっともだが、計測はもともと早い者勝ちで乱れている。過度に気にする必要はない。 ページを作り直す前に、まずタグを疑う。それだけで取り戻せるスピードは、きっと思っているより大きい。 --- # ページスピードは一定ではない? みんなのスピード体験を想像するコツは「対数正規分布」 - 公開日: Sat Jun 20 2026 20:15:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, research, technology 「自分のPCで見るとそんなに遅くないんですけど」。サイトの表示速度を議論するとき、こういう言葉を耳にすることがある。悪意ではなく本当にそう感じているのだ。 なぜなら自分以外のユーザーがどんなスピードでサイトを体験しているかを正確に想像するのは、とても難しいからだ。速い人と遅い人がいるのは頭では分かっているつもりだ。だが、それぞれどのくらいのスピードで、どのくらいの割合でいるのかと言われると途端に説明が難しくなる。 実はサイトスピードのばらつきは、統計的にどんな形になるかがほぼ決まっている現象だ。その特徴を一度つかんでしまえば自分以外のユーザーの体験をぐっと想像しやすくなる。この記事ではその考え方を実測データと一緒に紹介したい。 「みんな自分と同じくらいだろう」という認知バイアス実測データで見るトップページのばらつきページ読み込み時間は超ロングテールを描く速いPVにも偏りがあるサイトスピードを把握するための「対数正規分布」速い側に偏った山の正体対数正規分布は変形した正規分布対数正規分布はサイトスピードの標準的な考え方LCP も INP も同じく対数正規分布とされるLCPの分布INPの分布なぜ対数正規分布になるのか自分の体感ではなく「分布」で想像する参考文献 「みんな自分と同じくらいだろう」という認知バイアス そもそも人は自分が経験していないことを正しく想像するのが苦手だ。加えてサイトスピードは時間的な体験である。長さや高さのような空間的な大きさであれば、並べて一度に見比べられる。だが時間的な体験は同時に比べることが物理的にできない。 サイトの開発者や運営者はたいてい高性能なPCなどストレスなく動く環境を基準にしている。その環境での動作確認によって「これが普通のスピードだ」と感じやすい。ところがユーザーの実態は想像以上に幅広い。古い格安スマートフォンの人もいれば、電波の不安定な場所や回線の混み合う時間帯にアクセスする人もいる。 このように「他のユーザーも自分と同じくらいのスピードで体験しているだろう」という強い思い込みが無意識のうちに生じる。サイトスピードは認知バイアスがかかりやすい問題なのだ。 では客観的に全体像を把握するにはどうすればいいのか。賢者は歴史に学ぶと言われたように、自分の経験に頼るのではなく、ユーザーが実際に体験した具体的な値の 分布 を見なければならない。 実測データで見るトップページのばらつき 弊社サービス Speed is Money を利用中のある通販(EC)サイトのトップページの実測データを見てみよう。計測対象は ページ読み込み時間(OnLoad) で、直近3か月(2026年3〜5月)に計測された 329,533件のPV(ページ表示)のデータだ。 ページ読み込み時間は超ロングテールを描く まずページ読み込み時間を1秒ごとに区切る。その秒台に何件のPVが収まったかを描いたのが次のグラフだ。横軸がページ読み込み時間、縦軸がPV数である。点線は中央値(3.37秒)と p90(7.15秒)の位置を表す。中央値 3.37秒とは速い順に並べても遅い順に並べても、ちょうど真ん中にくるPVの読み込み時間。p90の7.15秒は、90%のPVが7.15秒以内に収まっていることを意味する。 多くのPVは5秒前後までに読み込みを終えている。だが遅いPVは非常に遅く、最も遅いものは60秒に迫る。しかもこの60秒はぽつんと離れた異常値ではない。0秒台から60秒台までどの秒台にもPVが存在している。 大半のPVはある範囲に収まる。それでもときどき極端に遅い体験を引いてしまうPVが確実に存在し、その値は右へ右へと長い裾を引く。この超ロングテールがページスピードを客観的に理解するための最初の事実だ。 速いPVにも偏りがある では左側の頭の部分にズームインしてみよう。先ほどのグラフでは多くのPVが0〜5秒あたりに集中しているのは分かるが、山の形まではつかみにくい。上位95%に絞り、約0.5秒ごとの細かいヒストグラムにしたのが次の図だ。 山の頂上(最も多い秒数)は 約2.5〜3秒付近 にある。そこから右側に向かって裾を引きながらゆっくりと減衰していく形状だ。 数値で見ると 最頻値 < 中央値(3.37秒)< 平均値(4.22秒) の順に並ぶ。山を描いてはいるが、速い秒数の側に偏った形となっている。この速い側に偏った形は 「対数正規分布」 によって理論的に説明できる。 サイトスピードを把握するための「対数正規分布」 速い側に偏った山の正体 先ほどのヒストグラムに対数正規分布の理論値(確率密度関数)を重ねてみよう。 実測のヒストグラムと理論曲線がかなり近しいと見て取れるはずだ。このデータは対数正規分布でよく説明できる(μ=1.214、σ=0.647、理論最頻値 約2.21秒、理論中央値 約3.37秒、理論平均 約4.15秒)。 ズームした0〜10秒だけでなく、最初に見た0〜60秒の全体に同じ理論曲線を重ねてみる。長く伸びる裾までよく沿っている。一握りの「すごく遅い」体験までを含めて、分布全体が一本の対数正規分布で説明できるということだ。 対数正規分布は変形した正規分布 「正規分布(左右対称の釣鐘型)」 は聞いたことがある人も多いだろう。一方で「対数正規分布」は初めて聞くという方も少なくないと思う。だがそこまで難しく考える必要はない。対数正規分布とは横軸を対数目盛に書き換えると、おなじみの正規分布になる分布のことだ。 試しに先ほどの山の横軸を対数目盛に変換してみよう。 このように対数目盛にすると左右対称のおなじみの正規分布の形にかなり近づく。つまりページ読み込み時間の表れ方は、根っこは正規分布でありながら時間軸の取り方が少し特殊なケースだということだ。 対数正規分布はサイトスピードの標準的な考え方 この通販サイトのトップページがたまたま対数正規分布になったという話ではない。 実は PageSpeed Insights(Lighthouse)のパフォーマンススコアも、各指標が対数正規分布となることを前提に設計されている。HTTP Archive の実データから対数正規曲線の制御点(25パーセンタイル相当のスコアが50、8パーセンタイル相当がスコア90)を決める。そこからスコアを算出している。 "The score is determined based on the performance score's distribution curve computed from HTTP Archive data, with that distribution modeled as a log-normal distribution." — Lighthouse performance scoring | Chrome for Developers つまり対数正規分布はサイトスピードの計測・評価を行う上で事実上の業界標準となっているのだ。 LCP も INP も同じく対数正規分布とされる ここまで見てきたのはクラシックなページ読み込み時間(OnLoad)だが、対数正規でばらつくのはページ読み込み時間に限らない。同じトップページ・同じ期間で、Core Web Vitalsに含まれる LCP(主要コンテンツが描画されるまでの時間、中央値 約1.1秒)と INP(操作への反応時間、中央値 約89ミリ秒)も見てみよう。 LCPの分布 LCP もページ読み込み時間と同じく速い側に偏った山と、ロングテールを描く。対数正規分布の理論値(オレンジの線)ともおおむね重なる。 INPの分布 INP も同じく速い側へ偏った山を描く。ブラウザの制約上、INP はミリ秒単位で粗く記録される。そのため曲線には凹凸や小さないびつさが出るが、対数正規分布の特徴はかなり捉えられていると言える。 指標が変わっても同じ形が現れるのはばらつきの背後に共通の仕組みがあるからだ。 なぜ対数正規分布になるのか メカニズムの面からも軽く確認しておこう。ウェブページの表示は一見すると単純だ。だがその裏では、ネットワークの通信、サーバーの処理、たくさんのリソースの取得と合成…といった工程の連なる複雑なパイプラインが走っている。 こうした工程の遅延は足し算ではなく 掛け算 的に効く。回線が混んでいれば転送やハンドシェイク、リソース取得まで同時に遅くなり、悪条件が相乗的に膨らむ。最終的なページ読み込み時間 T は各工程の遅延比率の積として表せる。 T=T0×r1×r2×⋯×rn 両辺の対数をとると掛け算は足し算に変わる。 ln⁡T=ln⁡T0+ln⁡r1+ln⁡r2+⋯+ln⁡rn 独立した変動の足し算は正規分布に近づく(中心極限定理)。つまり ln T が正規分布に近づき、T 自身は対数正規分布に従う。時間軸が「対数的」になるのはこのためだ。 掛け算的に積み重なる量が対数正規分布になることは一般的にも広く観察されており、インターネット上の応答時間やダウンロード時間も同じ構造を持つその一例だ(Downey, 2005/Limpert, Stahel & Abbt, 2001)。つまり 実際に観測される分布としても、ページ表示の仕組みから考えたメカニズムとしても、サイトスピードを対数正規分布で捉えるのは妥当な考え方だといえる。 自分の体感ではなく「分布」で想像する 今回見てきたことを、3点にまとめておく。 同じトップページでも体験するスピードは大きく異なる。 ボリュームゾーンはあるが、遅い人はとことん遅い。 そのばらつきは対数正規分布でよく説明できる。 インターネット上の応答時間の経験則であり、Lighthouseのスコア設計もこれを前提にしている。 ばらつきの形はほぼ決まっている。 遅延要因が直列に連なり掛け算的に積み重なる以上、分布が右に長い裾を引くのは数学的に予測しやすい。 「自分が試したら3秒だった」は事実かもしれない。だがそれは中央値あたりの体験ができたに過ぎない。1%のPVでは20秒以上待たされていることは、その感覚からは見えてこない。 しかし、ここで対数正規分布の形を思い出すと他のユーザーの体験がぐっと想像しやすくなる。 弊社サービス Speed is Money はこのような RUM(Real User Monitoring)による実測データの収集と分析を提供している。p75・p90・p99などのパーセンタイルでスピードの分布を把握できる。「遅いユーザーがどれほどの体験をしているか」を客観的に可視化することで、施策の優先度づけに役立てられる。 参考文献 Downey, A. B. (2005). "Lognormal and Pareto distributions in the Internet." Computer Communications, 28(7), 790–801. Limpert, E., Stahel, W. A., & Abbt, M. (2001). "Log-normal Distributions across the Sciences: Keys and Clues." BioScience, 51(5), 341–352. Google Chrome for Developers. "Lighthouse performance scoring." https://developer.chrome.com/docs/lighthouse/performance/performance-scoring --- # アクセスランキングの心理学 - 公開日: Wed Jun 17 2026 23:18:55 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: research, business あなたはアクセスランキングをクリックしたことがあるだろうか。「人気記事ランキング」「よく読まれている記事」。こうした枠がサイドバーや記事下に並んでいると、つい目が留まり、クリックしてしまった経験が誰しもあるはずだ。 実は、アクセスランキングのクリック率はかなり高い。弊社のアクセスランキングウィジェットサービス Ranklet4 で計測したアクセスランキングの平均クリック率は 約2.7%。WordStreamが公表する業種横断のベンチマークでは、Googleディスプレイ広告の全業種平均クリック率はおよそ0.46%だから、ディスプレイ広告の約6倍 という水準になる。クリック率の高いランキングに至っては、さらにその数倍のクリック率を恒常的にたたき出している。 では、なぜアクセスランキングはこれほどクリックされるのか。あくまで私たちの主観的な見立てにすぎないが、心理学・行動経済学の知見を重ねながら、大きく2つの仮説を考えている。 社会的証明:みんなが選んだものは間違いない、という安心感 選択肢の少なさ:候補が絞られているから、気軽に選べる 以下、それぞれの仮説と、それを支持する研究を見ていきたい。 仮説1:社会的証明(Social Proof)人気の情報は、コンテンツの質以上に選択を左右する最初に付いた「人気」が、後の評価を連鎖的にゆがめる社会的証明は、選択の不確実性を下げる(UX実務での整理)仮説2:選択肢を減らす効果(認知的負荷の軽減)選択肢が多すぎると、人はかえって選べなくなる人は「情報の匂い」をたどって読むものを決める選択肢が増えるほど、決定に時間がかかるまとめ:心理学的な根拠があるという仮説 仮説1:社会的証明(Social Proof) 一つめは 社会的証明(Social Proof)ではないか、という見立てだ。「他の多くの人が良いと感じたものに価値を見出す」という、人間の根本的な心理である。 ランキングは本質的に「他の人がこれを読んだ」という跡だ。「1位」「週間ランキング」という表示は、無数の他者が選んだという事実をシグナルとして送ってくる。私たちはそれを「価値があるはずだ」と受け取り、「失敗したくない」「ハズレを引きたくない」という不安を、他者の選択を根拠に和らげているのではないか。この心理については、いくつかの研究が手がかりになる。 人気の情報は、コンテンツの質以上に選択を左右する Salganikらは、14,341人を対象にしたランダム化実験を行った。無名の楽曲を試聴・ダウンロードできる環境で、「他者のダウンロード数(=ランキング情報)が見える群」と「見えない群」を比較したところ、ランキング情報の提示が人々の選択を強力に方向づけることが示された。何が人気になるかは、曲そのものの質以上に「他人が選んだという情報」に左右されたのだ。 出典:Salganik, M. J., Dodds, P. S., & Watts, D. J. (2006). Experimental Study of Inequality and Unpredictability in an Artificial Cultural Market. Science, 311(5762). 論文(DOI) / 無料PDF 最初に付いた「人気」が、後の評価を連鎖的にゆがめる Muchnikらの実験はさらに示唆的だ。投稿コメントの最初の1票を人為的にプラス操作したところ、後続のユーザーのプラス評価率が高まり、最終的な平均スコアまで押し上げられた。最初に付いた「人気」のシグナルが、後から来た人々の判断を連鎖的にゆがめていく様子が観測されている。 出典:Muchnik, L., Aral, S., & Taylor, S. J. (2013). Social Influence Bias: A Randomized Experiment. Science, 341(6146). 論文(DOI) / 無料PDF 社会的証明は、選択の不確実性を下げる(UX実務での整理) UXの実務面でも、Nielsen Norman Groupが、人気指標やレビューといった社会的証明のUIが、ユーザーの選択における不確実性を下げる効果を整理している。学術研究とユーザー体験の現場の両方で、同じ方向の観察がなされているわけだ。 出典:Nielsen Norman Group「Social Proof in the User Experience」(2014年) アクセスランキングのクリックの裏側では、こうした「みんなが選んだなら安心だ」という心理が静かに働いているのかもしれない。 仮説2:選択肢を減らす効果(認知的負荷の軽減) もう一つは、選択肢を減らすことによるクリックへの誘い ではないか、という見立てだ。 規模の大きなメディアサイトでは、グローバルナビゲーションやカテゴリー一覧、関連記事、タグなど、ナビゲーションメニューに多数の選択肢が並ぶことはもう珍しくない。選択肢が増えるほど、その中から一つを選ぶのは骨が折れる。 その中でアクセスランキングは、たいてい5〜10件程度(弊社 Ranklet4 ではトップ5形式が最も多い)のシンプルな選択肢だけを提示する。数ある導線のなかでも、ぱっと見て選びやすいナビゲーションUIになっているわけだ。この「選びやすさ」が、ユーザーがクリックという行動を起こすときの抵抗を下げているのではないか。これが、二つめの仮説の核心だ。 選択肢が多すぎると、人はかえって選べなくなる Iyengar & Lepperの有名な「ジャム実験」は、選択肢の数と行動の関係を示した。試食台にジャムを24種類並べた場合と6種類に絞った場合を比べると、立ち止まった客のうち実際に購入に至った割合は、選択肢を絞った6種類のほうが大幅に高かった。多すぎる選択肢は、人を「選べない」状態に追い込んでしまう。 出典:Iyengar, S. S., & Lepper, M. R. (2000). When Choice Is Demotivating: Can One Desire Too Much of a Good Thing? Journal of Personality and Social Psychology, 79(6). 論文(DOI) 人は「情報の匂い」をたどって読むものを決める Pirolli & Cardの「情報採餌理論(Information Foraging)」によれば、人は情報を探すとき「情報の匂い」、つまり価値がありそうだという手がかりをたどって行動を決める。「人気1位」という表示は、記事の中身を読む前から強い「情報の匂い」を放つラベルとして機能する。 出典:Pirolli, P., & Card, S. (1999). Information Foraging. Psychological Review, 106(4). 論文(DOI) / 解説:Nielsen Norman Group「Information Scent」 選択肢が増えるほど、決定に時間がかかる 認知心理学の古典である Hick–Hymanの法則 は、選択肢が増えるほど意思決定にかかる時間が対数的に伸びることを示している。ランキングは候補を上位数件に圧縮することで、この決定コストを大きく下げてくれる。 つまりアクセスランキングは、「人気」という妥当な基準で候補を絞り、「これを見ておけば問題ない」という入口を用意してくれる。しかも数件ほどなら一目で全体を把握でき、選択にかかる負荷が自然と軽くなっている。だからこそ、つい手が伸びる。そう考えると説明がつくのではないか。 まとめ:心理学的な根拠があるという仮説 アクセスランキングが高いクリック率を生み、多くの日本のサイトで採用されていることには、やはり相応の根拠があるのだろう。本稿で見てきたように、その背景には 社会的証明 と 選択肢の少なさ という、2つの心理的メカニズムがあるのではないか。「これだけ読まれているなら間違いない」という安心感と、「少ない候補からなら気軽に選べる」という手軽さ。この2つが重なって、人はつい無意識のうちにランキングをクリックしてしまう。少なくとも弊社としては、そう感じている。 弊社の Ranklet4 は、GA4と連携して最新のアクセスランキングを自動生成・配信するウィジェットサービスだ。任意のデザインで設置でき、毎週の手動更新も不要。サイトの回遊性を高める手段として、ランキングの心理的な効果を活かしてみてはいかがだろうか。 --- # 「サイトスピード=LCPなどの指標」ではない理由 - 公開日: Wed Jun 17 2026 11:04:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology 「サイトスピードを改善しましょう」と言うとき、多くの人は指標の数値を良くすることだと受け取る。それは間違いではないし、実用上も大きな問題はない。しかしユーザーが体感するサイトスピードこそが本来の正体であり、時間的な指標はそれを数値化する便宜的な尺度に過ぎない。だから、便宜的な指標が改善されても、サイトスピードそのものが改善されたとは限らないのだ。 本記事では、よく使われる指標を整理したうえで、「サイトスピードとは何か」という本質的な問いに向き合ってみたい。 まずは指標のおさらい「サイトスピード」という言葉の本質なぜ待ち時間がコンバージョンを妨げるのか指標はサイトスピードそのものではないまとめ:指標とサイトスピードは同じではない まずは指標のおさらい 「サイトスピード」を語るときに使われる指標は、すでにご存じの方も多いはずだ。まずは軽くおさらいしておきたい。 今もっとも注目されている指標は、やはり Core Web Vitals だろう。Googleが定義したページ体験の品質指標群で、SEOの文脈で大きな注目を集めた。現在は次の3つを指す。 LCP(Largest Contentful Paint) … 最も大きなコンテンツが画面に描画されるまでの時間。ページが「見えてきた」と感じるまでの速さ。 INP(Interaction to Next Paint) … 操作してから次の画面更新までの時間。ページの「反応速度」を測る指標で、FIDから置き換わった。 CLS(Cumulative Layout Shift) … 表示中にレイアウトがずれる量。厳密には時間の指標ではないが、レイアウトが速やかに安定しないと悪化するため、スピードの一種とも言える。 「サイトスピード」という言葉の本質 これらの指標を、慣例的に「サイトスピード」と呼ぶことが多い。たとえばLCPの数値が大きい(悪い)といったときに、「サイトスピードが遅い」と表現する。 しかし、サイトスピードの本質は、こうした時間的な指標で正確に表せるものではない。そう言われると、意外に思うかもしれない。 サイトスピードの本質は、ユーザーが待ち時間を理由にウェブサイトから離脱しにくい状態を「速い」と表現することにある。仮に待たされたと感じず離脱の原因にならないのであれば、LCPの数値が悪くてもサイトスピードは速いと言える。逆に指標の数値が良くても、待ち時間を感じてユーザーが離脱してしまうなら、それはサイトスピードが遅いということだ。 なぜ待ち時間がコンバージョンを妨げるのか 直感的には、遅いとイライラして離脱する、という話だ。だが噛み砕くと、それ以上の心理的なメカニズムが働いている。 以前に当サイトで紹介したSpeedCurveの論文「サイトスピードと幸福の心理学」でも詳しく解説しているが、待たされることによって、ユーザーの集中力は少しずつ削られていく。 ウェブサイトでの買い物のように一見シンプルな行為であっても、実は相当な集中力が必要だ。お金を使う行為でもあるし、サイトの操作に慣れていなければ迷うこともある。ネット上でお金を使うことへの不安、商品が自分に合わなかったらどうしようという心配。こうした懸念を抱えながら「買おう」という意思を維持し続ける行為は、思いのほか認知的なコストがかかるものなのだ。 買い物を進める間、小さな待ち時間が生じるたびに集中力は少しずつ削られていく。削るのは待ち時間だけではない。UX上の違和感やつまずき、難しさを感じる点(いわゆる「ペインポイント」)もある。こうした体験上の摩擦に出会うたび、集中力の残量は減っていく。 結果として、本来は買い物をするつもりだったユーザーであっても、待ち時間やペインポイントが重なれば集中力をすべて使い果たし、サイトを離脱してしまう。「イライラした」という自覚がなくても、買い物を完了させる前に集中力を使い切ってしまう。それが離脱という行動として現れるのだ。 むしろ、イライラという負の感情にまで発達するのは、それだけサイトへの期待が大きかった裏返しとも言える。多くのケースでは、なんとなく自覚もないまま離脱していく。それがほとんどだろう。 サイトの応答が遅いほど、この集中力の消耗は大きくなる。 指標はサイトスピードそのものではない このように、サイトスピードとは結局、主観的な領域の話だ。待たされるストレスと集中力の枯渇。その積み重ねがサイト離脱を招く「抵抗の大きさ」こそがサイトスピードの本質である。 訪れる人によって、もともと持ち合わせる集中力は違う。「どうしても今日中に買わなければいけない」といった強いモチベーションの有無でも、発揮できる集中力は大きく変わる。その人のウェブリテラシーや経験によっても、集中力の持続性は異なる。 つまり、主観的で個人的で文脈に依存した抵抗の大きさこそが、サイトスピードの正体だ。では、そのような概念を一意の数値で表現できるだろうか。 個人や状況で枯渇の速さが変わる集中力への抵抗を、ひとつの数字に落とし込むのは難しい。実に捉えどころがない、というのが実際のところだ。 しかし、それでは議論が進まない。だからこそ一定の基準で計測できる「時間」を指標にしようというところから、LCPやINPといったCore Web Vitalsが便宜的に用いられている。 つまり指標は、測りにくいサイトスピードの本質を時間という尺度で近似する道具にすぎない。 「スピード」という言葉も、誤解を招く原因かもしれない。物理的な速度は時速何キロといった具合に、解釈の余地のない数値で表せるからだ。ところがサイトスピードの本質は人の心の中にある。これが本記事で伝えたかったことである。 まとめ:指標とサイトスピードは同じではない ユーザーが期待するコンバージョンへ到達する前に、時間的な遅延で集中力が削られ、結果としてコンバージョンが妨げられてしまう。これが、サイトスピードがなぜ重要なのかを説明するもっとも妥当な捉え方だろう。 もちろん、指標を改善することは手段として正しい。しかし指標が改善したからといって、ユーザーのストレスが必ず減るとは限らない。最後に強調しておきたい。いわゆるサイトスピードと指標は、必ずしも表裏一体の同じものではないのだ。 便宜的な指標が改善されても、サイトスピードそのものが改善されたとは限らない。 数値の改善に満足せず、ユーザーの体験から待ち時間という抵抗が本当に減ったのかを問い続けること。それこそがサイトスピード改善の真の目的である。 --- # GridgramをAIエージェントフレンドリーにする - 公開日: Thu Apr 23 2026 22:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, ai, automation 前回の記事でGridgramのコンセプトと基本的な使い方を紹介した。今回は、このCLIツールを「AIエージェントから使いやすい状態」へ整えるべく取り組んだことを記録する。 CLIツールを作ることと、AIエージェントがそのツールをうまく使えることは別の話だ。エージェントはドキュメントを読み、コマンドを試し、診断結果を解釈できなければならない。それを可能にするには、CLIの設計段階からエージェントを意識した工夫が要る。 AIエージェントへのツール提供は試行錯誤の段階にある取り組みの全体像——2系統のアプローチドキュメント系の取り組みllms.txt / llms-full.txt——Web公開のLLM向けドキュメントContext7——MCP経由のドキュメント配信gg llm——手元でリファレンスを取り出すサブコマンドSSOT(Single Source of Truth)設計——情報を1か所から自動派生するコマンド(スキル)系の取り組みSkillsの設計——エージェントが呼び出せるワークフロー配布経路——Anthropic Claude Marketplaceとgh skillの2系統gg icons——AIエージェントが実行時に叩くアイコン検索プロジェクトローカルなClaude Code環境AI時代のCLIツール設計——今回の試行から見えたこと AIエージェントへのツール提供は試行錯誤の段階にある 本題の前に、前提を共有しておく。 AIエージェントに対するツール側の能力提供は、まだまだ発展途上の領域だ。どの粒度でドキュメントを渡すのが効果的か、どの形でコマンド群を配布するのが自然か、といった知見はこれから積み上がっていく段階にある。「このやり方が正解」と言える定型は、現時点では存在しない。 Gridgramでも、現時点で有効そうに見える経路をいくつか試し、手応えを観察している段階だ。本記事では、その試行錯誤の内容を取り組みごとに紹介する。 取り組みの全体像——2系統のアプローチ Gridgramで実施したAIエージェント対応を整理すると、大きく2系統に分かれる。 ドキュメント系——.gg DSLの書き方やCLIの使い方を、AIが参照しやすい形に整えて提供するもの: llms.txt / llms-full.txt のWeb公開 Context7 への登録(MCP経由で検索可能にする) gg llm サブコマンド(手元でLLM向けリファレンスMarkdownを取り出す) コマンド(スキル)系——AIエージェントが作業の中で呼び出せるワークフローやサブコマンド: Skills(Anthropic Claude Marketplace / gh skill install の2経路で配布) gg icons サブコマンド(エージェントがアイコン検索に使う) この区別が後の説明の軸になる。ドキュメント系は「エージェントが知識を得るための経路」、コマンド系は「エージェントが作業を実行するための経路」だ。 なお、これらの出口を複数持つことは、逆に言えば「同じ情報を複数の場所に保つ」ことを意味する。手で同期すれば必ず内容がずれる。この問題への答えが、後述するSSOT(単一情報源)からの自動派生だ。 ドキュメント系の取り組み llms.txt / llms-full.txt——Web公開のLLM向けドキュメント llmstxt.orgが提唱するパブリック標準に対応した。サイト直下の/llms.txtにリンク集Markdownを、/llms-full.txtに全文連結を配置する。 bashcurl https://gridgram.ideamans.com/llms.txt curl https://gridgram.ideamans.com/llms-full.txt これはVitePressで構築したドキュメントサイトのビルド前に生成スクリプト(bun run build-llms-txt)を走らせる形で実現した。生成物はgitignoreとし、毎回のビルドで作り直す。 Context7——MCP経由のドキュメント配信 Context7はUpstashが提供するMCPサービスだ。リポジトリにcontext7.jsonを置いてAdd Docsから登録すると、Context7側がドキュメントをクロールしてMCP経由で各種エージェントに配信してくれる。 json{ "$schema": "https://context7.com/schema/context7.json", "projectTitle": "gridgram", "description": "Grid-based diagram rendering library and CLI...", "folders": ["docs/en", "examples", "src/generated"], "excludeFolders": ["docs/.vitepress", "docs/public", "docs/ja", ...], "rules": [ "Use `doc { … }` command statements. The legacy `%%{…}%%` form is removed.", "Built-in Tabler icons are referenced as `tabler/<name>` (outline) or `tabler/filled/<name>` (filled).", "Diagnostics are warnings, not exceptions. An SVG with diagnostics is still a valid render.", ... ] } foldersにsrc/generatedを含めているのは、後述のgg llmが生成するllm-reference.md(CLIリファレンス)をContext7にも読ませるためだ。人が書いたドキュメントだけでは拾えないCLIの詳細仕様が検索でヒットするようになる。 rules配列の設計にも意図がある。「コードを読めば当然わかること」ではなく、「ハマりやすい落とし穴」だけを5〜10本に絞って書く。doc { }の記法(旧来の%%{…}%%記法の廃止)や、ダイアグノスティクスがエラーではなく警告である点など、実際にエージェントが間違えやすいポイントを選んだ。 Context7のMCPツールをAIエージェント(例: Claude Code)に設定しておけば、エージェントは.ggファイルの記法について質問したとき、自動的にリポジトリのドキュメントを参照できる。 gg llm——手元でリファレンスを取り出すサブコマンド gg llmはエージェントが実行時に叩くというより、ドキュメント配布パイプラインの一部として機能するサブコマンドだ。 bashgg llm gg llm --format json 実行すると、.ggのDSL文法、CLIオプションの一覧、doc { }ブロックの設定キー、代表的な使用例をひとつのMarkdownドキュメントとして出力する。この出力物がllms.txt / llms-full.txt / Context7 / Skills内のリファレンスに使われる。 バイナリとしてコンパイルしたときも、生成済みのMarkdownが静的に埋め込まれているので同様に動作する。オフライン環境のエージェントが自習のために叩く使い方も想定しているが、主眼はパイプラインでの利用だ。 SSOT(Single Source of Truth)設計——情報を1か所から自動派生する ドキュメントの出口が llms.txt・llms-full.txt・Context7・Skills内リファレンス・gg llm出力と複数になると、人力で同期するのはすぐ破綻する。CLIのオプションを1つ変えたとき、それを5か所に反映する作業は現実離れしている。 そこで、すべての派生物をソースコードから自動生成する構造にした。具体的には、次のような派生構造になっている。 src/gg/dsl.ts(BNF文法コメント)、src/cli/args.ts(CLIオプション定義)、src/templates/llm-reference.template.md(テンプレート)、examples/*.gg(サンプル群)の4つがSSOTだ。これらを原本としてbun run ai:regen(または/regen-aiスキル)を実行すると、src/generated/llm-reference.md・src/generated/icon-index.json・docs/public/llms.txt・docs/public/llms-full.txtの4つが自動生成される。 派生ファイルは直接編集せず、原本を修正してからbun run ai:regen(または/regen-aiスキル)で再生成する。 このポリシーを強制するために、.claude/rules/ai-artifacts-policy.mdというClaude Code向けのルールファイルをリポジトリにコミットしている。Claude Codeがセッションを開始するたびにこのルールを読み込み、派生物への手動編集を防ぐ仕組みだ。 さらにregen-triggers.mdというルールファイルもある。こちらはfrontmatterのpaths:で対象ファイルを指定し、SSOTに当たるファイルを編集したセッションでだけ読み込まれる。「/regen-aiを実行してください」とリマインドする役割で、常駐ルールにするとノイズになるため、必要なときだけ表示される設計にしている。 コマンド(スキル)系の取り組み Skillsの設計——エージェントが呼び出せるワークフロー Gridgramはリポジトリ内にplugins/gridgram/ディレクトリを持ち、4つのスキルを定義している。 スキル 役割 gg-render .ggファイルをSVG/PNG/JSONにレンダリング gg-icons Tablerアイコンの検索ワークフロー gg-author 説明文から.ggファイルを作成してレンダリング gg-install gg CLIをGitHubリリースから導入・更新 各スキルはSKILL.mdというMarkdownファイルで定義する。frontmatterにはスキル名・説明・ライセンス・互換性要件・許可ツールを書き、本文にはAIエージェント向けのワークフロー手順を書く。 yaml--- name: gg-render description: Render a .gg file to SVG, PNG, or a JSON envelope using the gridgram CLI. license: MIT compatibility: Requires the gridgram `gg` CLI on PATH. allowed-tools: Bash(gg:*) Bash(bun:*) Read Write --- Agent Skills標準フィールドのみを使う ここで重要な設計判断がある。SKILL.mdにはAgent Skills標準フィールドのみを書くという方針だ。 Claude Code固有の拡張フィールド(disable-model-invocation、argument-hint、pathsなど)はmetadata.claude-code.*以下にのみ記述する。こうすることで、同一のSKILL.mdがClaude Code、GitHub Copilot、Cursor、Gemini CLI、Codexのいずれでも動作する。 このポリシーを維持するためにscripts/validate-plugin-skills.tsという検証スクリプトを自作し、CIで必ず通過させている。検証項目はnameフィールドと親ディレクトリ名の一致、descriptionの文字数、必須キーワードの有無、plugin.jsonのバージョンがpackage.jsonと一致しているかなどだ。 配布経路——Anthropic Claude Marketplaceとgh skillの2系統 Anthropic Claude Marketplace経由 Anthropic Claude Marketplace経由でClaude Codeにインストールするには2リポジトリ構成を採用した。 ideamans/gridgram(本体リポ) └── plugins/gridgram/ ← プラグイン本体 ideamans/claude-public-plugins(別リポ) └── .claude-plugin/marketplace.json ← git-subdirで本体を参照 マーケットプレイスリポがgit-subdirで本体リポのplugins/gridgramを参照するため、本体リポを更新するだけでマーケットプレイスにも自動で反映される。 /plugin marketplace add ideamans/claude-public-plugins /plugin install gridgram@ideamans-plugins gh skillコマンド経由 GitHubが追加したgh skillコマンドにも同じSKILL.mdで対応できる。 bashgh skill install ideamans/gridgram plugins/gridgram/skills/gg-render --agent claude-code gh skill install ideamans/gridgram plugins/gridgram/skills/gg-icons --agent copilot gh skill installは実行時にリポジトリ情報(repository、ref、tree SHA)をfrontmatterに注入する。この仕組みにより、gh skill updateがバージョン変更を検出できる。本体リポでSKILL.mdを更新すれば、ユーザー側はgh skill updateを実行するだけで最新版を取得できる。 Agent Skills標準フィールドのみというポリシーが、ここでも活きてくる。同じファイルをClaude CodeとCopilotで共有できるため、「エージェントホストごとにSKILL.mdを別々に管理する」という手間がない。 gg-installスキルによるggコマンドの導入 配布を意識してgg-installスキルも用意した。スキルをインストールしてもggコマンドがPATHにないと動かない。gg-installはOSとアーキテクチャを自動検出してGitHubリリースから適切なバイナリをダウンロードし、書き込み可能なディレクトリに設置する。 gg icons——AIエージェントが実行時に叩くアイコン検索 gg iconsは、エージェントが実行時に呼び出すサブコマンドだ。GridgramにはTablerアイコン6,000種類以上が組み込まれているが、その中から目的のアイコンを探すのは人間でも難しい。エージェントが総なめで検討するのは不可能だ。 bash# キーワードで意味的に検索 gg icons --search database --limit 10 --format json # タグで絞り込む gg icons --tag cloud --limit 15 # 使えるタグの一覧 gg icons --tags --limit 30 --searchはアイコン名・ラベル・タグ・カテゴリを横断するファジー検索で、スコアで結果をランキングする。エージェントはgg-iconsスキルの手順に従い、まず--tagsでドメイン関連の語彙を確認し、--searchで候補を絞り込むというフローを取れる。 この検索インデックス(src/generated/icon-index.json)はTablerアイコンのJSONダンプに加え、src/data/icon-tags.jsonという手書きのタグ補完ファイルから生成している。Tablerのメタだけでは拾えない語(cache、kubernetes、loadbalancerなど)をここで補っている。なおicon-index.jsonもSSOTから自動生成される派生物の一つだ。 プロジェクトローカルなClaude Code環境 プロジェクトローカルな.claude/ディレクトリにも仕掛けを入れている。これはリポジトリにコミットしてあるので、チームの誰がチェックアウトしても同じ環境が再現する。 .claude/ ├── rules/ │ ├── ai-artifacts-policy.md # 派生物の手編集禁止ポリシー(常駐ロード) │ └── regen-triggers.md # SSOT編集時にだけ読まれるリマインダー └── skills/ └── regen-ai/ └── SKILL.md # /regen-ai スラッシュコマンド /regen-aiスキルはbun run ai:regenの実行、型チェック、テストの3ステップをまとめたものだ。SSOTを変更したあとにこれを実行すれば、すべての派生物が整合性を保って再生成される。 AI時代のCLIツール設計——今回の試行から見えたこと 今回の取り組みを通じて、「AIエージェントフレンドリーなCLIツール」に共通して必要な要素が見えてきた。ただしこれらはあくまでも現時点での仮説であり、実際にエージェントとの協働の中で検証を続けている段階だ。 1. ドキュメントの出口を複数持つ Web公開(llms.txt)・MCP(Context7)・CLIコマンド(gg llm)・Skills内リファレンスという複数の経路を用意することで、エージェントの環境や状況に関わらずドキュメントへのアクセス手段を確保できる。 2. SSOTと自動派生で情報の整合性を保つ 出口が増えるほど同期の問題が深刻になる。ソースコードを正とし、すべての出口をビルドスクリプトで自動生成する構造が必要だ。 3. セマンティック検索のサポート 6,000件のアイコンをキーワード・タグで絞り込めるgg iconsのように、大量の選択肢に対して、エージェントが効率よく探せる仕組みは重要だ。 4. 決定論的な出力とエラー情報 Gridgramは--diagnosticsフラグで配置上の問題をJSON形式でstderrに出力する。エラーが起きても途中経過のSVGを返し、診断情報を別途提供することでエージェントが修正のループを回しやすくなる。 5. ポータブルなスキル定義 特定のエージェントホスト固有の仕様に依存しないAgent Skills標準フィールドで書くことで、Claude Code、GitHub Copilot、Cursorなど複数のエージェント環境に同一のスキルを提供できる。 Gridgramはまだ開発中のツールだが、この一連の取り組みは「CLIツールをAIエージェントと一緒に使う」という新しい開発スタイルの実験でもある。 公式ドキュメント(日本語): https://gridgram.ideamans.com/ja/?utm_source=notes.ideamans.com&utm_medium=owned_media&utm_campaign=regular&utm_content=gridgram-ai-agent GitHub: https://github.com/ideamans/gridgram コンセプト編はこちら: Mermaid風のテキストから図解を生成するGridgram --- # Mermaid風のテキストから図解を生成するGridgram - 公開日: Thu Apr 23 2026 21:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, ai, ideas 業務では、物事の関係図や概念図——いわゆるポンチ絵——を作る場面がよくある。PowerPointやKeynote上でマウス操作によりチクチク作ると、案外手間と時間がかかる。構造は数分で頭に浮かんでいても、それを絵にする作業だけで30分、1時間と溶けることは珍しくない。 AIがあらゆる文章やプログラムを生成できるようになった今でも、図の生成についてはまだ少し手間を感じる。コードやテキスト生成ではAI活用で生産性が上がっているものの、図作成についてはぴったりのソリューションがまだない、というのが現状の印象だ。 そこで、テキストDSL(.ggファイル)からSVGとPNGを生成するCLIツール「Gridgram」を開発し、オープンソースで公開した。Gridgramを使うと、次のような図をテキストベースで生成できる。 FaC(Figures as Code)への関心GraphVizからMermaid、そしてD2へ既存ツールの課題Gridgramのアプローチまずグリッドに配置するという発想.ggファイルの文法実際に生成した図インストールと使い方インストール.ggファイルを書いてレンダリングアイコンを探すLLM向けリファレンスを出力まとめと次の記事 FaC(Figures as Code)への関心 GraphVizからMermaid、そしてD2へ FaC——IaC(Infrastructure as Code)をもじった造語で、Figures as Code の略——にはかねてから関心があった。コードで図解を記述するというアプローチだ。 最初に触れたのはGraphVizだった。その後Mermaidを試し、最近はD2を本格的に使っている。D2については以前このブログにも書いた。コンテナのネストやSVGアイコンの埋め込みができ、AIエージェントとの連携にも使いやすいツールだ。 画像生成系ではNano Bananaも活用している。Gemini 3から日本語テキストの扱いが安定したため、このnotesブログのOGP画像もNano Bananaで生成している。 既存ツールの課題 一方で、既存ツールには業務資料向けに使おうとすると気になる点がある。 Mermaid・GraphViz・D2 のようなテキストDSL系は、グラフが横または縦に延々と伸びやすい。フローが長くなると画面からはみ出し、「このスペースに収める」という制御が難しい。プレゼン資料の1スライドや提案書の1枠に収まる図を作ろうとすると、試行錯誤が必要になる。また、見た目がテキスト出身で味気なく、提案資料で使うと 寂しい印象 になりがちだ。 Nano Banana のような画像生成系は逆に表現力が高く装飾も豊かだが、細かいレイアウト調整が難しく、再現性(決定論的な制御)も高くない。統一感を保つのが難しく、仕様書や提案資料に載せると ドラマチックすぎて浮いてしまう ことがある。 つまり、決定論的に生成でき、かつ寂しすぎず、いい感じの見た目を両立するツール がこれまでなかった。それがGridgramを作った背景だ。 Gridgramのアプローチ まずグリッドに配置するという発想 Gridgramのアプローチは、まずオブジェクトをグリッド(Excel風のマス目)上に配置するところから始まる。これがGridgramの出発点だ。 ユーザーは各ノード(アイコン)をA1・B2のようなセル座標で配置する。その上で、記述したいコネクタ(矢印)やラベルをテキストで定義すると、グリッドの隙間を決定論的なアルゴリズムで埋めるように、互いの衝突を回避しながら自動配置される。 このアプローチには大きな利点がある。描画空間(アスペクト比)を事前に決められることだ。たとえば「4列2行のグリッドに収める」と決めれば、2:1の画角で出力が得られる。Mermaid/D2のように「気付いたら横/縦に延びすぎていた」という事態が起こらない。 そのうえで、Tabler Iconsという6000種類以上のオープンソースアイコンライブラリを組み込んでいるため、テキストだけでなくアイコンで視覚的に装飾でき、寂しくない見た目を作れる。 .ggファイルの文法 Gridgramの記法はMermaidに似ている。.gg拡張子のテキストファイルに記述する。 doc { cols: 4, theme: { primary: '#0369a1', secondary: '#0891b2', accent: '#f59e0b' }, } region @A1:B2 "作成・編集" color=accent/20 region @C1:C2 "変換" color=secondary/20 region @D1:D2 "生成物" color=primary/20 icon :user @A1 tabler/user "ユーザー" icon :agent @B1 tabler/robot "AIエージェント" icon :editor @A2 tabler/pencil "テキストエディタ" icon :ggfile @B2 tabler/file-code ".ggファイル" icon :ggcmd @C1 tabler/terminal "ggコマンド" icon :svg @D1 tabler/vector "SVG" icon :png @D2 tabler/photo "PNG" user --> agent "依頼" agent --> ggfile "生成" editor --> ggfile "直接編集" ggfile --> ggcmd "入力" ggcmd --> svg "出力" ggcmd --> png "出力" doc { cols: N } でグリッドの列数を設定する。icon でノードをセル座標に配置し、--> でコネクタを引くと、矢印はグリッドの隙間を自動でルーティングされる。 実際に生成した図 上記の.ggファイルをggコマンドで生成すると、以下のような図が出力される。 bashgg flow.gg -o flow.svg gg flow.gg -o flow.png --width 1200 cols: 4 を指定してあるので4列のグリッドに収まる。リージョンの色は color=accent/20(透明度20%のアクセントカラー)のように設定できる。 インストールと使い方 インストール GitHubのリリースページからバイナリをダウンロードするか、Bunでインストールする。 bash# バイナリをダウンロード # https://github.com/ideamans/gridgram/releases から OS / アーキテクチャに合わせて選ぶ # または Bun でインストール(公開後) bun install -g gridgram .ggファイルを書いてレンダリング bash# SVG出力(デフォルト) gg diagram.gg -o output.svg # PNG出力(幅1200px) gg diagram.gg -o output.png --width 1200 # 標準出力へ gg diagram.gg --stdout アイコンを探す 6000種類以上のTabler Iconsの中から使いたいものを検索できる。 bash# キーワードで検索 gg icons --search database --limit 10 # タグで絞り込む gg icons --tag cloud --limit 15 LLM向けリファレンスを出力 AIエージェントに文法を教えたいときはgg llmが便利だ。 bashgg llm gg llm --format json まとめと次の記事 Gridgramは「スペースを先に決めてから図を描く」という発想をテキストDSLで実現するツールだ。グリッドに収まる決定論的な出力、6000種類以上のアイコン、MermaidライクなシンプルなDSLを組み合わせた。 公式ドキュメント(日本語): https://gridgram.ideamans.com/ja/?utm_source=notes.ideamans.com&utm_medium=owned_media&utm_campaign=regular&utm_content=gridgram-concept GitHub: https://github.com/ideamans/gridgram Gridgramでは、AIエージェントとの親和性を高めるためにいくつかの試みを行っている。Claude Codeプラグイン対応、ghコマンドのSkill Command対応、Context7へのドキュメント登録、gg llmサブコマンドやアイコン検索の設計など、コーディング系AIエージェントがGridgramを自律的に扱えるようにする工夫だ。詳細は次の記事で紹介する。 GridgramをAIエージェントフレンドリーにする --- # D2 + Freepik APIでポンチ絵を自動生成するAIエージェントを作ってシビれた - 公開日: Wed Apr 08 2026 01:47:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, claude-code, development ポンチ絵——つまり概念図やアーキテクチャ図のことだが、これを描くのはずっと面倒な作業だった。PowerPointやKeynoteで四角や矢印を並べ、位置を微調整し、画像として書き出す。たったひとつの図に30分、1時間とかかることもざらである。 最近はGeminiのような画像生成AIも日本語サポートが充実してきて、概念図のようなものも出力してくれるようになった。しかし、どうしても意図しない装飾が乗ったり、余計なビジュアル要素が混入したりする。あくまで「それっぽい雰囲気の絵」であって、ドキュメントに貼って概念をきっちり伝えたい場面では使いにくいのが正直なところだった。 そこで、以前から気になっていたD2(図版記述言語)と、長年利用してきたFreepik(ストック素材サービス)を組み合わせ、AIエージェントにポンチ絵を自動生成させるコンセプトを試作してみた。結果は想像以上で、正直シビれた。 D2とFreepik——2つの武器D2: Mermaidの強化版Freepik: アイコン検索の精度が高いエージェントの仕組み出力例outline(Neutral Grey)flat(Buttered Toast)color-outline(Mixed Berry Blue)freehand(Vanilla Nitro Cola + sketch)何がシビれたのかエージェント時代の図版表現リポジトリ D2とFreepik——2つの武器 D2: Mermaidの強化版 D2はMermaidの強化版とも言える図版記述言語である。テキストベースで図を記述でき、以下のような特徴を持つ。 コンテナのネスト: グループやサブシステムを入れ子構造で表現できる SVGアイコンの埋め込み: 外部のSVGアイコンをノードに組み込める スケッチモード: 手描き風のカジュアルなスタイルにも対応 豊富なカラーテーマ: 複数のプリセットテーマを選べる テキストベースであることが決定的に重要だ。テキストならAIエージェントが生成・編集できる。GUIツールでマウスをドラッグする必要はない。 Freepik: アイコン検索の精度が高い Freepikは写真・イラスト・アイコンなどあらゆるビジュアル素材を提供するストックサービスである。私はこれまでにも様々なプロダクトでFreepik APIを利用してきたが、特にアイコン検索の精度と網羅性には信頼を置いている。 APIを叩けばキーワードからアイコンをSVGで取得でき、そのままD2に埋め込める。しかもアイコンファミリー(統一されたデザインテイストのセット)を指定できるため、図全体のトーンを揃えることが容易である。 エージェントの仕組み このプロジェクトでは、Claude Codeのエージェントスキルとしてポンチ絵生成機能を組み込んでいる。ワークフローは以下の通りである。 ユーザーが自然言語で「こういう図を描いて」とリクエスト エージェントが図の構成要素を分析し、必要なアイコンのキーワードをリストアップ Freepik APIで各キーワードに最適なアイコンを検索・ダウンロード アイコンを埋め込んだD2ファイルを生成 D2エンジンでSVG / PNGにレンダリング ポイントは、アイコン選定からD2コード生成、レンダリングまでをすべてエージェントが一気通貫で行うことである。人間は「AWSのWebサービスのアーキテクチャ図を描いて」と言うだけでよい。 出力例 「AWSの少し複雑な構成のWebサービスのアーキテクチャ図を描いて」とお願いした結果を、4つのテーマで出力した。画像をクリックすると原寸で確認できる。 outline(Neutral Grey) 線画のアイコンセットとグレー系のカラーテーマ。最もフォーマルな印象で、技術ドキュメントとの相性がよい。 flat(Buttered Toast) 塗りのアイコンセットと暖色系テーマ。カジュアルなプレゼン資料に向いている。 color-outline(Mixed Berry Blue) カラー線画のアイコンセットとブルー系テーマ。情報量と視認性のバランスがよい。 freehand(Vanilla Nitro Cola + sketch) 手描き風アイコンとスケッチモード。ホワイトボードに描いたような親しみやすさがある。 何がシビれたのか 率直に言って、出力のクオリティに驚いた。「シビれた」としか言いようがない。 まず、アイコンの選定が的確である。「AWS」「CloudFront」「RDS」といったキーワードから、文脈に合ったアイコンをFreepik APIから引き当ててくる。しかも同じファミリーのアイコンで統一されているため、図全体のトーンが崩れない。 次に、D2による構造表現が明快である。コンテナのネストでVPCやサブネットの階層構造を自然に表現し、矢印でデータフローを示す。余計な装飾がなく、概念がストレートに伝わる。これはAI画像生成では難しいポイントだ。 そして何より、自然言語で頼むだけという手軽さ。PowerPointを開く必要もなければ、四角の位置を揃える必要もない。「こういう図を描いて」——それだけで、プロフェッショナルな品質のポンチ絵が手に入る。 エージェント時代の図版表現 このコンセプトの先には、もっと大きな可能性が見える。 今後、AIエージェントがレポートを作成し、それをメールやSlackなどの既存メディアに載せて配信するという場面はますます増えていくだろう。その際、テキストだけでなく図版も含めた出力ができれば、情報伝達の質は格段に上がる。D2のようなテキストベースの図版言語は、まさにエージェントシステムに載せやすいモジュールとして適している。 また、ソースコードの解析や理解にもAIエージェントはこれから一層活躍するはずだ。コードを読み解き、その構造を解釈し、概念として可視化する——そうした場面で、ポンチ絵エージェントは一役買うだろう。アーキテクチャの全体像を図にしてもらえば、コードリーディングの効率は劇的に上がる。 ポンチ絵を描くのは面倒な作業だった。しかし、もうその時代は終わりつつある。 リポジトリ プロジェクトの全コードはGitHubで公開している。 miyanaga/d2-end-freepik-agent-concept あくまでコンセプトの紹介と成果物の共有を目的とした実験的プロジェクトである。興味のある方はぜひ触ってみてほしい。 --- # Chart.jsのグラフをCLIで画像化するOSSツール「chartjs2img」を公開 - 公開日: Tue Apr 07 2026 20:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development AIエージェントによるレポート自動生成が当たり前になりつつある今、「見栄えの良いグラフをサクッと画像にしたい」という場面が増えている。そこで、Chart.jsの設定JSONを渡すだけでグラフ画像を生成できるCLIツール chartjs2img をOSSとして公開した。 GitHub: ideamans/chartjs2img なぜ作ったのか特徴インストール基本的な使い方JSONファイルからグラフ画像を生成標準入力からの生成サイズやフォーマットの指定出力例シンプルなグラフ複雑なオプションを駆使したグラフLLMとの連携Webサーバーモードまとめ なぜ作ったのか 私自身、AIエージェントシステムを使ったレポート作成の中で、分析結果をグラフにして画像として埋め込みたいことが何度もあった。 以前は QuickChart というOSSサービスを使っていたのだが、2つの不満があった。 表現力の限界 -- Chart.jsの最新版や豊富なプラグイン(Annotation、Datalabelsなど)に対応しきれていない Webサーバー前提 -- サーバーを別途用意して稼働させる必要がある LLMやAIエージェントによる定期レポート作成がこれからどんどん増えていくことを考えると、コマンドラインでサッと使えて、表現力も高いツールが欲しい。そう思って作ったのがchartjs2imgである。 特徴 Chart.js 4.4 + 12プラグイン内蔵 -- Datalabels、Annotation、Treemap、Sankey、Wordcloudなど、豊富なプラグインがすぐに使える CLIで即座に画像化 -- JSONファイルを渡すだけでPNG/JPEG/WebP画像を生成 LLM対応 -- chartjs2img llmコマンドでLLM向けのナレッジを出力。エージェントのコンテキストに渡せば、エージェント自身がグラフのJSONを書ける Webサーバーモードも搭載 -- QuickChartのようにHTTP APIとしても使える ゼロコンフィグ -- Chromiumが未インストールなら自動でダウンロード。面倒なセットアップは不要 日本語対応 -- Dockerイメージには Noto Sans CJK フォントを同梱 インストール macOS / Linux / WSL なら、ワンライナーでインストールできる。 bashcurl -fsSL 'https://bin.ideamans.com/install/chartjs2img.sh' | bash Windows(PowerShell)の場合はこちら。 powershellirm 'https://bin.ideamans.com/install/chartjs2img.ps1' | iex パッケージマネージャでもインストール可能だ。 bash# Ubuntu / Debian curl -fsSL https://bin.ideamans.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/ideamans-oss.gpg echo "deb [signed-by=/usr/share/keyrings/ideamans-oss.gpg] https://bin.ideamans.com/apt oss main" | sudo tee /etc/apt/sources.list.d/ideamans-oss.list sudo apt update && sudo apt install chartjs2img # Chocolatey (Windows) choco install chartjs2img 詳細は インストールページ を参照してほしい。 基本的な使い方 JSONファイルからグラフ画像を生成 Chart.jsの設定をJSONファイルとして用意し、renderサブコマンドで画像化する。 bashchartjs2img render -i chart.json -o chart.png たとえば、以下のようなシンプルなJSONで棒グラフが生成できる。 json{ "type": "bar", "data": { "labels": ["January", "February", "March", "April", "May", "June"], "datasets": [{ "label": "Revenue ($K)", "data": [12, 19, 3, 5, 2, 15], "backgroundColor": [ "rgba(255, 99, 132, 0.7)", "rgba(54, 162, 235, 0.7)", "rgba(255, 206, 86, 0.7)", "rgba(75, 192, 192, 0.7)", "rgba(153, 102, 255, 0.7)", "rgba(255, 159, 64, 0.7)" ] }] } } 出力されるグラフはこのようになる。 標準入力からの生成 パイプで渡すことも可能なので、LLMの出力をそのまま流し込むような使い方もできる。 bashecho '{"type":"bar","data":{"labels":["A","B","C"],"datasets":[{"data":[10,20,30]}]}}' | chartjs2img render -o output.png サイズやフォーマットの指定 bashchartjs2img render -i chart.json -o chart.png -w 1200 -h 400 -f png chartjs2img render -i chart.json -o chart.webp -f webp -q 90 出力例 chartjs2imgで生成できるグラフの例を紹介する。いずれもJSONを渡すだけで生成したものだ。 シンプルなグラフ まずは基本的な例から。棒グラフと折れ線グラフを重ねた複合チャートや、日本語ラベルを使ったグラフも問題なく描画できる。 複雑なオプションを駆使したグラフ chartjs2imgの真価は、Chart.jsの豊富なプラグインとオプションを組み合わせた高度な表現にある。Annotation、Datalabels、グラデーションなどを駆使すれば、以下のようなダッシュボード品質のグラフも生成できる。 年度業績ダッシュボード。積み上げ棒グラフ、利益率の折れ線、目標ライン、データラベルを1枚に集約した例だ。 Webプラットフォームの年間アナリティクス。複数指標の折れ線・棒グラフにアノテーションボックスやエリア塗りを組み合わせた例である。 これらはすべてJSON設定だけで表現されている。Chart.js 4.4と12のプラグインの組み合わせにより、円グラフ、レーダーチャート、散布図、バブルチャート、ツリーマップなど多彩なグラフタイプにも対応している。 LLMとの連携 chartjs2imgの大きな特徴の1つが、LLMとの親和性である。 bashchartjs2img llm このコマンドを実行すると、Chart.jsのコア仕様、全12プラグインのオプション、JSONの書き方を網羅した約1,400行のMarkdownドキュメントが出力される。これをLLMのコンテキストウィンドウに渡せば、LLM自身が適切なChart.js設定JSONを生成できるようになる。 つまり、AIエージェントに「このデータをグラフにして」と頼むだけで、LLMがJSONを生成し、chartjs2imgが画像化するという流れが実現できるわけだ。 Webサーバーモード QuickChartのようにHTTP APIとしても使える。 bashchartjs2img serve --port 3000 ただし、QuickChartとは設計思想が異なる。QuickChartはURLパラメータにJSONを埋め込む方式だが、Chart.jsの豊富なプラグインを活用するとJSONが大きくなるため、chartjs2imgでは POSTでJSONを送信し、キャッシュされた画像をダウンロードする 方式を採用している。 bash# POSTでグラフを生成 curl -X POST http://localhost:3000/render \ -H "Content-Type: application/json" \ -d @chart.json \ -o chart.png # キャッシュされた画像をハッシュで取得 curl http://localhost:3000/cache/{hash} -o chart.png URLを貼るだけで表示する用途ではなく、グラフを生成して画像としてローカルに保存し、レポートに埋め込むという使い方を想定している。 まとめ chartjs2imgは、Chart.js 4.4と12のプラグインの表現力をそのままに、CLIでサッと画像化できるツールである。Chromiumの自動インストールによるゼロコンフィグ、LLM向けナレッジ出力、Webサーバーモードなど、AIエージェント時代のレポート作成に必要な機能を揃えた。 JSONを書くだけで見栄えの良いグラフ画像が手に入る。ぜひ試してみてほしい。 GitHub: ideamans/chartjs2img インストール: bin.ideamans.com/oss/chartjs2img --- # LLMの原理を学ぶとAIとの付き合い方が上手くなる - 公開日: Fri Mar 06 2026 18:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development Claude Codeを本格運用するようになってから、AIのコーディング中に手持ち無沙汰となる時間が増えた。その隙間で手にしたのが「作りながら学ぶ! LLM自作入門」だった。この経験が、AIを活用したコーディングやエージェント構築において、想像以上に役立つこととなった。 今さらLLMの中身を学ぶ意味一問一答ではなくコンテキストで考える暗黙の前提を共有する重要性学習プロセスの想像が役割分担を明確にする地味だからこそ希少価値がある 今さらLLMの中身を学ぶ意味 もちろん、LLMを自作してClaude Codeの代わりにしようなどという考えは毛頭ない。目的はただひとつ、目の前で魔法のような仕事をしているLLMが、どんな原理で動いているのかを本質的に理解することだった。 SNSでは「こんなプロンプトを書けば上手くいく」「このサービスを使えばすごいことができる」といった、成果を早く得るための表面的なテクニックが多く取り上げられている。LLMの中身を学んだところで、自分たちがLLMを構築できるわけではない。遠回りに見えるし、「そんなこと学んでどうするの」と思われがちだ。 しかし実際にやってみると、LLMの現象を外側から機能的に捉えるだけでなく、原理的な部分から演繹的に理解することで、AIとの付き合い方が格段に上手くなった。以下、特に実感した3つのポイントを紹介する。 一問一答ではなくコンテキストで考える LLMを使うとき、多くの人は「人間が入力したものに対して機械が答える」という単純な一問一答のモデルを思い浮かべている。しかしLLMの仕組みを学ぶと、実際にはそうではないことがわかる。 LLMの入力は、直前のプロンプトだけではない。過去の会話内容すべて、つまりコンテキスト全体が次の出力の入力になっている。重要なのは、LLMが出力したもの自体もまた、自分自身の次の回答の入力になっているという点だ。 この構造を知らないと、AIをガチャのようなものだと捉えてしまう。良い出力が出ることもあれば、悪い出力が出ることもある。その確率を制御できないから使えない、という失望につながる。 しかしコンテキストという構造で考えれば、LLMとの関係は一回きりの勝負ではなく、長期的な対話の積み重ねだとわかる。もちろん、悪い出力がコンテキストに残れば次の出力にも悪影響を与えることもある。だからこそ途中での軌道修正が重要であり、それも含めて対話を重ねていくと、ある段階で急激に自分にとって満足のいく出力に到達することがある。一問一答の発想ではこの感覚は得られない。コンテキストの原理を理解して初めて、LLMとの長期的な付き合い方が見えてくるのだ。 暗黙の前提を共有する重要性 LLMの原理を学ぶと、結局のところLLMはトークン(言葉の最小単位)同士の関係性を、比喩的に言えば「距離」のようなものとして計算する機械であることが見えてくる。もちろんこれは大胆な簡略化であり、実際にはアテンション機構による複雑な文脈依存の重み付けが行われている。しかし、膨大なパラメータによる関係性の計算が、まるで知性を持っているかのような振る舞いを生み出しているという本質は変わらない。 この理解は、指示の出し方に直結する。 LLMは単語の表記の揺れには驚くほど強い。同じ意味の言葉であれば、多少表現が異なっていてもかなり正確に理解してくれる。指示代名詞の解決や文脈の推論も、最近のモデルでは非常に精度が高くなってきている。 しかしLLMが依然として苦手とするのは、ユーザーが頭の中で持っている前提が共有されていない状況だ。自分にとっては当たり前の前提条件でも、それが言語化されていなければLLMには伝わらない。ユーザーの意図と違う結果が返ってくるとき、その原因の多くは指示代名詞の曖昧さではなく、暗黙の前提のずれにある。 これは人間関係とまったく同じだ。優秀なスタッフであっても、こちらの前提を知らなければ的外れな成果物を出してくることがある。相手が作業する上で何が前提として必要なのか、それを効率よく共有することがLLMとの協業でも極めて重要である。前提の共有が大事だとわかるのは、LLMが言葉を理解しているのではなく関係性を計算しているに過ぎないと知ればこそだ。 学習プロセスの想像が役割分担を明確にする LLMがインターネット上の膨大な情報を学習しているという事実は広く知られている。GitHubのオープンソースコード、Stack OverflowのようなQ&Aサイト、技術ドキュメント、ブログ記事。世界中の知識を学習データとして取り込んだうえで、さらにRLHFなどの訓練を経て今の姿がある。 ここで気をつけたいのは、AIを使いながら自分で検索して方針を決めてからAIに指示を出すやり方だ。実際にこのやり方をとる人は少なくない。特定の技術を深掘りしたいときや、最新のAPI仕様を確認したいときには、もちろん自分で調べた情報を与えることが有効である。ただしこの方法には注意点がある。ユーザーから与えられた情報はLLMにとっての大前提として作用し、その後の推論方向を強く規定してしまうのだ。 LLMは基本的に世界中のインターネットを「検索済み」の存在である。探索的に広く可能性を検討したい場面では、自分の限られた検索結果で方向性を狭めるよりも、まずLLMに広く浅く知識を求めるほうが効果的なケースも多い。 もちろんLLMの言うことがすべて正解というわけではない。しかし、どういう学習を経て今ここにいるのかを想像できると、自分でやるべきこととLLMに任せていいことの線引きがより明確になる。LLMが膨大な知識から最適解を提案してくれる領域と、人間がドメイン知識や文脈で判断すべき領域。その境界を見極める感覚は、学習プロセスの理解から自然と身につくものだ。 地味だからこそ希少価値がある 新しいAIサービスやツールは今も次々と登場している。それらのほうが華やかだし、できることも派手で、世間の注目を集めやすい。みんなが飛びつくのも当然だ。 しかし、この分野の新しい情報のほとんどはすぐに陳腐化する。新しいモデルが出ればリセットされ、常に最先端を追い続けなければ自分の優位性を保てない。消耗戦である。 一方、LLMの原理的な部分はそう簡単には変わらない。現在の主流アーキテクチャであるトランスフォーマーのアテンション機構、トークンの埋め込み表現、コンテキストウィンドウの仕組み。もちろんAI研究の進展は速く、将来的にトランスフォーマーに代わるアーキテクチャが台頭する可能性もある。しかし少なくとも数年単位では通用する知識であり、数日で風化する新サービスの使い方とは時間軸がまったく異なる。そして地味で注目を集めないからこそ、この知識は市場において希少価値を持つ。 これはAIに限った話ではなく、あらゆる分野に共通する構造だ。表層の流行を追う人は多いが、原理を深く理解している人は少ない。だからこそ、原理を知る人の判断には厚みが出るし、新しいツールが登場しても本質的な特性と限界を素早く見抜ける。 AIが魔法と見える今だからこそ、華やかな最先端の動きに惑わされず、その魔法の種を学んでみることを勧めたい。手持ち無沙汰な時間で手にした一冊が、AIとの付き合い方を根本から変えてくれた。 --- # 「画像を軽くして」と言われたときの定量的な基準 - 公開日: Wed Feb 25 2026 18:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, sitespeed Web制作の現場で「画像を軽くして」と言われることはよくある。一枚一枚人間の目で品質やパラメータを調整すれば軽くすることは可能だが、現在のWeb制作でそこまで手間をかけるのはほぼ不可能だ。 もし他のサイトと比べて遜色ない、あるいは十分に軽いといった定量的な基準があれば、ひとつの判断材料になるだろう。機械化した工程の中でもパラメータの妥当性を評価しやすくなる。 先日、主要なECサイトの画像の使われ方を徹底調査した「ECサイト画像白書 2026」を公開した。この調査で用いた画像配信効率という指標と、そこから導き出した5段階の評価基準が、「画像を軽くして」への定量的な答えになると考えている。 画像配信効率という指標画像配信効率のばらつきと対数正規分布5分位による評価基準実際の画像で比較してみるJPEG品質95でそのまま保存LightFileで最適化WebPに変換AVIFに変換比較一覧画像単体よりもページ全体・サイト全体で評価する次世代フォーマットへの移行が鍵まとめ 画像配信効率という指標 コンピュータデバイスで表示する画像はピクセルに支配されている。画像の大きさ=解像度であり、一定の解像度の画像を配信するためにどれだけデータ量を費やしたか——それが画像の配信効率だ。 この調査では、1メガピクセル(1000×1000=100万画素)あたりのデータ量(KB/Mpx)でこれを数値化した。1メガピクセルという単位に深い意味はなく、100万画素・1000×1000という解像度がイメージしやすく語呂もいいので便宜的に採用しただけだ。 計算例 200KBで2メガピクセル(2000×1000px)の画像 → 100KB/Mpx 500KBで1メガピクセル(1000×1000px)の画像 → 500KB/Mpx 数値が小さいほど、少ないデータ量で多くの画素を表現できている。解像度の異なる画像でも同じ基準で比較できるのがこの指標の利点だ。 画像配信効率のばらつきと対数正規分布 調査対象1,404ページのデータを分析したところ、サイトによって画像配信効率に想像以上のばらつきがあるとわかった。 最も多く見られるのは88〜172KB/Mpxの範囲だが、ページによってはその20倍近い1,600KB/Mpxに及ぶケースもある。同じ解像度の画像を配信するのに、これだけデータ量に差があるということだ。 このばらつきは、数学的に対数正規分布で説明できそうだ。Kolmogorov-Smirnov検定でも統計的に妥当という結果が出ている。詳しくは「ECサイト画像白書 2026 - 画像配信効率の分布」を参照してほしい。 対数正規分布は、Webサイトのスピード指標(ページ読み込み時間など)にも見られる分布パターンだ。どのサイトも、画像であれスピードであれ、意図的に間違った使い方をしようとしているわけではない。それぞれが正しいと思っている使い方をしている。それにもかかわらず、画像の配信効率にもWebのスピードにも同じように良し悪しのばらつきが生じる。その両方が同じ数式で説明できるというのは興味深い。 分布のパラメータは以下のとおりだ。 μ(対数平均)= 5.283 σ(対数標準偏差)= 0.738 平均値:259KB/Mpx 中央値:197KB/Mpx 範囲:4〜1,684KB/Mpx 5分位による評価基準 理論的な分布と実測値の一致が見られたところで、画像配信効率に分かりやすい評価基準を設ける。ここでは画像配信効率を5分位(Quintile)で分類し、「とても軽い」から「とても重い」までの5段階とした。 評価 KB/Mpx 解説 とても軽い 0〜111 上位20%の効率 やや軽い 111〜166 ふつう 166〜236 中央値(197KB/Mpx)を含む やや重い 236〜372 とても重い 372〜 下位20%の効率 「画像を軽くして」と言われたとき、この5分位がひとつの定量的な指標になる。約300サイトのECサイトという母集団から、統計的な根拠をもった画像の軽さ・重さの評価基準を導き出したものだ。 たとえば「画像が重い」と言われたときに、実際に画像の配信効率を計測してこの評価基準に当てはめてみる。その結果「ふつう」や「やや軽い」という評価が得られたとすれば、画像データが大きいというのは一種の思い込みである可能性がある。デザイナー・エンジニア・クライアントなど、あらゆるステークホルダーの間で定量的な指標として共有できる。それがこの基準の使い方だ。 注意 画像データが大きくなくても、通信上のボトルネックがあれば当然「画像が重い」という体感は正しいかもしれない。この指標は画像データの大きさを評価するものであり、体感的な重さ・軽さのすべてを説明できるものではない。 実際の画像で比較してみる 実際に同じ写真素材(1000×1000px、1メガピクセル)を異なるフォーマット・設定で変換し、配信効率を比較してみた。画像をクリックすると原寸で表示される。 写真素材のクレジット: UnsplashのAngela Baileyが撮影した写真を正方形にトリミング JPEG品質95でそのまま保存 まず、オリジナル写真を無造作にリサイズして品質95のJPEGで保存したもの。これが 384KB となり、先ほどの評価基準でいうと 「とても重い」 に分類される。 LightFileで最適化 弊社が提供する画像最適化ツール LightFile で、品質設定を容量優先にして最適化すると 130KB に軽量化できた。こうなると 「やや軽い」 という評価になる。 WebPに変換 次世代フォーマットではどうか。heavy.jpg をWebP(品質75)に変換すると 116KB で 「やや軽い」 という評価になる。 AVIFに変換 AVIFに変換すると 70KB まで削減でき 「とても軽い」 という評価になる。これ以上のデータ削減は可能かもしれないが、他のサイトと比較しても十分に軽量な画像になったと評価できる。 比較一覧 heavy.jpg lightfile.jpg heavy.webp heavy.avif フォーマット JPEG (品質95) JPEG (LightFile 容量優先) WebP (品質75) AVIF (デフォルト) サイズ 384KB 130KB 116KB 70KB KB/Mpx 384 130 116 70 評価 とても重い やや軽い やや軽い とても軽い おそらく、これらの画像を素で見比べて画質の良し悪しをすぐに指摘できるユーザーはほぼいないだろう。しかしデータ量にはこれだけの差が出ている。一見するとほぼ区別がつかないのに、最も重い heavy.jpg と最も軽い heavy.avif の間には5倍以上の開きがある。 画像単体よりもページ全体・サイト全体で評価する ここまでは一例として画像1枚の配信効率を見てきたが、実際のWeb画像は描かれている内容によって圧縮のしやすさが大きく変わる。複雑な図案や細かいディテールの多い画像は圧縮しにくく、平坦な背景が多い画像は圧縮しやすい。 そのため、画像1枚1枚を厳密にこの基準で評価するよりも、1ページの中での全体の配信効率や、サイト全体での配信効率を評価する方が現実的だ。個々の画像にばらつきがあっても、ページ単位やサイト単位で見たときに「ふつう」〜「やや軽い」の範囲に収まっていれば、十分に健全な運用ができていると言える。 次世代フォーマットへの移行が鍵 次世代フォーマットを活用するサイトは少しずつ増えてきている。それに伴い、画像配信効率の全体的な水準はおそらく数年前と比べて改善されており、「軽い」「重い」の評価基準は少し厳しくなっていると想定される。 従来フォーマット(JPEG/PNG)のままでこの基準に追いつくのは難しく、次世代フォーマットへの移行が鍵となる。弊社の LightFile Proxy のように、CloudFrontのオリジンに設定するだけで画像を自動的にWebP/AVIF変換できるサービスもあるので、配信インフラの面でも導入のハードルは下がってきている。 まとめ 「画像を軽くして」という要望に定量的に向き合うための指標として、画像配信効率(KB/Mpx)と5分位による評価基準を紹介した。 画像配信効率には法則的な分布が見られそうである 5分位の基準で「やや重い」「とても重い」に該当する場合は改善の余地がある 次世代フォーマット(WebP, AVIF)の活用が、配信効率改善の最も確実な手段 この基準があれば、デザイナー・エンジニア・クライアントの間で「どこまで軽くすればいいか」の認識を揃えやすくなる。感覚的な議論を、データに基づいた議論へと移行するための出発点として活用してほしい。 ECサイト画像白書 2026 - 画像配信効率の分布 --- # サイトスピードとCVRの関係を数式で記述する - 指数減衰モデル - 公開日: Tue Feb 10 2026 16:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, research 「読み込みが1秒速くなるとCVRが○%上がる」——サイトスピードとコンバージョンレート(CVR)の関係について、これまで耳にしてきたのはこうした断片的なフレーズばかりだった。しかし、両者の関係をもっと全体的に、シンプルな数式で記述できないだろうか。 14の通販サイトからデータ提供をいただき分析を進めた結果、シンプルな数式でその関係をかなり正確に表現できることが分かった。それが以下の指数減衰モデルである。 ポテンシャルサイトスピード減衰率ロイヤルティCVR=ポテンシャルCVR×e−サイトスピード×減衰率+ロイヤルティCVR サイトスピードとCVRの関係は「曲線」を描く4つの数学モデルを比較14サイトの補正R²を比較する14サイトの指数減衰フィッティング指数減衰モデルのパラメータが意味するものc: ロイヤルティCVRa: ポテンシャルCVRb: 減衰率(CVR半減期の決定因子)LCP以外の指標への適用INP(Interaction to Next Paint)とCVROnLoad(読み込み時間)とCVRスピードの影響を排除した「サイトの魅力」が見える本調査の限界と今後 サイトスピードとCVRの関係は「曲線」を描く まず、サイトスピードの代表的な指標である LCP(Largest Contentful Paint) とCVRの関係をグラフで確認する。 14サイトの集計データから、LCPの値ごとにCVRをプロットしたものである。一目見て分かるように、両者の関係は単純な直線(線形)ではない。 特徴的なのは以下の点だ。 LCPが良好な最初のわずかな区間でのみ、CVRは非常に高い 少しでもLCPが悪化すると、CVRは急激に低下する ある程度遅くなると、CVRはほぼ底を打ち、それ以上変化しなくなる このような「急激に低下して、やがて水平に近づく」カーブを数式で表現するには、どのようなモデルが適切だろうか。 LCPとCVRの関連づけについて 本記事では、ユーザーごとの 最小LCP(そのユーザーが体験した中で最も速かったLCPの値)を横軸に用いている。なぜ平均値や中央値ではなく最小LCPを採用するのか、その理論的背景と妥当性については以下の記事で詳しく解説している。 👉 サイトスピードと収益性の定量的な関係 - 最小LCPによる新モデル 4つの数学モデルを比較 先ほどの曲線的な減衰パターンを表現できそうな候補として、以下の4つの数学モデルを検討した。 モデル 数式 パラメータ数 対数 Y=a+b×log⁡(x) 2 べき乗 Y=a×xb 2 逆数 Y=a+b/x 2 指数減衰 Y=a×e−bx+c 3 補正R²について 図中に登場する 補正R² は、モデルが実際の観測値をどれだけ正確に表現できているかを示す指標である。0から1の間の値を取り、1に近いほどモデルの精度が高い。「補正」とはパラメータ数の違いを考慮した補正で、パラメータが多いモデルほどフィッティングしやすい分のペナルティが加えられている。 実際にあるサイトのデータにそれぞれのモデルをフィッティングした結果が以下の図だ。 対数モデルはあまりフィットしていないが、べき乗・逆数・指数減衰の3つは実測値の特徴をかなりよく捉えている。特に逆数モデルは補正R²が0.998と、この1サイトにおいては最も高い正確さを示した。 では逆数モデルが最適なのだろうか。1サイトだけの結果で判断するのは早計だ。14サイトすべてについて、モデルの正確さを示す補正R²を比較してみる。 14サイトの補正R²を比較する 14サイトそれぞれについて4つのモデルの補正R²を算出し、箱ひげ図(ボックスプロット)で比較したのが以下の図である。 先ほどの1サイトの例では逆数モデルが最も高い補正R²を示していたが、14サイト全体で見比べると様相が異なる。 対数モデル: R²が低く(中央値0.6程度)、ばらつきも大きい。明らかに不適切 べき乗モデル: 全体的にR²が高いが、ややばらつきがある 逆数モデル: べき乗と同程度に高いが、下方向に外れ値がある 指数減衰モデル: 圧倒的にR²が高く、かつばらつきが極めて小さい 14サイトを横断して比較すると、指数減衰モデルが最も安定して実測値をよく表現していることが分かった。ほぼすべてのサイトで補正R²が1に近い値を示しており、精度と安定性の両面で他のモデルを圧倒している。 14サイトの指数減衰フィッティング 実際に14サイトそれぞれのデータに指数減衰モデルを当てはめた結果が以下の図である。丸い点が観測値、線が理論値を表している。縦に長い図ではあるが、ぜひざっと目を通してほしい。 ほぼすべてのサイトにおいて、観測値の点と理論値の線がよく一致していることが見て取れるはずだ。サイトによってCVRの絶対値や減衰のスピードは異なるが、指数減衰という同じ構造がすべてのサイトに共通して現れている。 この分析結果をもって、LCPとCVRの関係性には指数減衰モデルを適用することとしたい。 指数減衰モデルのパラメータが意味するもの CVRを Y、サイトスピード(LCP等)を x としたとき、指数減衰モデルは3つのパラメータを持つ以下の数式で表される。 Y=a×e−bx+c それぞれのパラメータが何を意味しているのかを見ていく。 順番は前後するが、まず分かりやすい c から説明する。 c: ロイヤルティCVR c はサイトがどれだけ遅くても購入してくれるユーザー層によるCVR——いわば ベースラインのCVR を表す。これを ロイヤルティCVR と呼ぶ。 どんなにサイトが遅くても「この商品がどうしても欲しい」「このショップでしか買えない」と思ってくれる忠誠度の高い顧客層は一定数存在する。c はそうした顧客によるコンバージョンの底値であり、商品力やブランドへのロイヤルティの強さが反映されている。 a: ポテンシャルCVR もしすべてのユーザーが待ち時間によるストレスゼロの体験ができたとしたら、ロイヤルティCVRに対してどれくらいのCVRを上乗せできるだろうか。 a はその 上乗せ分のCVR を表している。これを ポテンシャルCVR と呼ぶことにする。 つまり、スピードの摩擦がゼロのときに達成できる理想のCVRは a + c となる。商品の魅力、ブランドへの信頼、サイトの使いやすさ、顧客の期待度——これらスピード以外のサイトの総合力が a + c に集約されている。 b: 減衰率(CVR半減期の決定因子) b はCVRがどれだけ速く減衰するかを決めるパラメータであり、ユーザーの「待てなさ」 を示している。 直感的には理解しにくいが、b は CVRの半減期 を決めるパラメータだと考えれば分かりやすいだろう。 半減期CVR半減期=ln⁡2b 半減期とは文字通り、ポテンシャルCVRが半分に低下するまでのLCPの増加量(秒数)である。たとえば半減期が0.2秒であれば、LCPが0.2秒悪化するだけでCVRが半分になるということだ。 b が大きいサイトは、スピードが少しでも遅くなるとすぐにユーザーが離脱してしまう特性を持っている。逆に b が小さいサイトは、多少遅くても欲しいと思わせる商品やブランドの魅力があるか、あるいはセールや限定キャンペーンなど販促がうまく機能しているケースも考えられる。 これらを日本語で表現すると、冒頭に挙げた以下の数式になる。 ポテンシャルサイトスピード減衰率ロイヤルティCVR=ポテンシャルCVR×e−サイトスピード×減衰率+ロイヤルティCVR LCP以外の指標への適用 LCP以外のスピード指標についても同様の分析を行った。 INP(Interaction to Next Paint)とCVR INPはLCPと同じくCore Web Vitalsの一角であり、ユーザーのクリックやタップなどの入力に対する応答の速さを示す指標である。 INPに対しても指数減衰モデルは高いフィット精度を示している。ただし、べき乗モデルのほうがさらに高精度という結果になった。 これはINPとCVRの関係が、LCPとCVRの関係よりもかなりシビアな特性を持っているためだ。INPが少しでも悪化するとCVRは瞬間的に低下する。このような急激なカーブを表現するには、指数関数よりもべき乗モデルのほうが適している。 OnLoad(読み込み時間)とCVR OnLoadに対してはLCPと同様に、指数減衰モデルが圧倒的にフィット精度が高い結果となった。べき乗・逆数モデルよりも明確に優位である。 ページの読み込み完了までの時間であるOnLoadは、LCPと類似した特性を持っており、指数減衰モデルによる記述が適していると言える。 スピードの影響を排除した「サイトの魅力」が見える 改めて、日本語による数式を確認する。 ポテンシャルサイトスピード減衰率ロイヤルティCVR=ポテンシャルCVR×e−サイトスピード×減衰率+ロイヤルティCVR これまでCVRには、スピードの影響とサイト本来の魅力が混在して現れていた。この数式はサイトスピードとCVRの関係性を記述するものであるが、裏を返すと、各パラメータはスピードの影響を取り除いた上での サイト固有の魅力を表す指標 にもなっている。 ポテンシャルCVRが高い → 商品力、ブランドへの期待、サイトの使いやすさなど、サイトの総合的な魅力が大きい 減衰率が小さい → ユーザーは多少の遅さを我慢してでも利用したいと思える、販促や訴求の力強さを示す。セールや限定キャンペーンなど「今買いたい」と思わせる施策が効いているとも解釈できる ロイヤルティCVRが高い → どんなに遅くても買いたいと思わせる、ブランドや商品への忠誠が厚い つまりこの数式は、サイトスピードの改善に役立てる一方で、それぞれのパラメータの変化がスピード以外のサイトの魅力の増減を表現する指標としても活用できる可能性が大いにある。 本調査の限界と今後 今回の分析はあくまで14サイトのデータに基づくものである。これをもってして「指数減衰モデルがサイトスピードとCVRの関係を普遍的に記述できる」と断言するのは時期尚早だろう。 しかし、調査対象の範囲においては非常に高い精度でフィットし、かつパラメータの意味も直感的で理にかなった数式モデルを発見できたと思う。 次のステップとして考えているのは、サイトスピードの確率分布(対数正規分布に従うことが知られている)と、今回のスピード-CVR関係モデルを合成することだ。通販サイトのスピードと収益性の関係を統合的に記述し、より正確な予測に役立てるモデルを構築する土台が整ったと感じている。 --- # AIエージェントによる自社メディア運営の統合プラットフォーム - セカンドフェーズ完成 - 公開日: Fri Feb 06 2026 23:32:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, claude-code, automation, content-management AIエージェントによる自社メディア運営プロジェクトのセカンドフェーズが完成し、安定稼働を始めた。 放置し続けてきた顧客タッチポイント 正直に告白すると、私たちアイデアマンズ株式会社は、SNSやメール配信といった顧客タッチポイントを 長年にわたって放置し続けてきた。 様々なサービスを提供している中で、SNSやメール配信をしっかり運用して集客に活かしたいという思いはずっとあった。ブログ記事を書いたらSNSで紹介したい。定期的にニュースレターを配信したい。 しかし、あいにく自分はそんなマメではない。開発には夢中になれるものの、集客に関する作業はどうにも習慣化できなかった。たまに思い出したように使ってみるものの、どうしても継続することができない。そんな状態が何年も続いてしまった。 CMSとAIエージェントで99%の作業を自動化 この状況を一変させたのが、AIエージェント(Claude Code)の活用だ。 まず、SNS運用やメール配信を効率化するためのCMSを整備した。このCMS開発自体もAIエージェントを活用することで、驚くほど短期間で構築できた。そして、そのCMSとAIエージェントを組み合わせることで、99%の作業を自動化する体制 が整った。 人間がやるべきことは、アイデア出し、コンテンツの方針決定、AIが作成したコンテンツへのフィードバック だけ。マメでなくても、メディア運営が回るようになった。 放置し続けてきた顧客タッチポイントCMSとAIエージェントで99%の作業を自動化モノレポで全Webサイトを集約セカンドフェーズで実現したこと1. SNS運用のフルオート化2. 10人のライターアバター3. メール配信の効率化4つのフェーズで段階的に構築まとめ:CMSとAIエージェントがうまく仕事をしてくれる モノレポで全Webサイトを集約 この仕組みの土台となるのが、全Webサイトのリポジトリを1つに集約したモノレポ だ。 10以上のWebサイトと5つのWebアプリケーションは、それぞれ独立したGitリポジトリで管理されている。これらをGitサブモジュールとして親プロジェクトにぶら下げることで、AIエージェントが全サイトを横断的に参照できる環境を構築した。 この構造により、AIエージェントが複数サイトの記事を一度に参照し、情報の整合性を保ちながらコンテンツを制作できる。 セカンドフェーズで実現したこと 今回完成したセカンドフェーズでは、3つの機能を実装した。 1. SNS運用のフルオート化 毎日のSNS投稿が、ほぼ完全に自動化された。 AIエージェントが複数サイトのブログ記事を横断的に参照し、どの記事をいつ紹介するかを計画する。原稿を作成し、スケジュールをGitHubにプッシュ。別サーバーの配信エージェントがスケジュールを読み取り、X/Facebook/Threadsに自動投稿する。 人間の作業は、たまに原稿を確認してフィードバックするだけ。 2. 10人のライターアバター SNS投稿のワンパターン化を防ぐため、10人のAIコピーライターアバター を用意した。 ウェブマーケティングやコピーライティングの本をいくつか読んだ中で感銘を受けたアイデアをAIに伝え、そこから10種類のライター人格を作成してもらった。名前は「アニメのキャラクターから適当につけて」と依頼したところ、こうなった。 AIエージェントが投稿のたびにランダムで1人を選択し、そのライターの個性で原稿を作成する。同じ記事でも紹介の仕方が変わり、投稿に多様性が生まれる。 3. メール配信の効率化 HTMLメールの原稿作成から、CRM(Mautic)への下書き投入までを自動化した。 HTMLメールは通常のWebページとは異なり、テーブルレイアウトやインラインスタイルなど独特のコーディングが必要になる。そこで、Markdownで記述した原稿を適切なHTMLテンプレートに流し込み、メール配信用のソースコードを生成する簡易的なCMSを構築した。 ブログ記事を書く感覚でMarkdownメール原稿を作成すると、CMSがHTMLメールに変換。API連携でMauticのメール配信下書きに一括投入される。 あとは内容を確認して配信ボタンを押すだけ。 4つのフェーズで段階的に構築 このプロジェクトは4つのフェーズで構成されている。 フェーズ 内容 状態 ファースト ブログ執筆のAIアシスト 完了 セカンド SNS・メール配信・クロスリンク 今回完成 サード アナリティクス・インサイトの自動解析 今後 フォース 広告ブーストの自動化 今後 サードフェーズでは、GoogleアナリティクスやSNSインサイトを自動解析し、どのコンテンツが読者に響いているかをAIが評価する。10人のライターアバターのうち、どの個性が効果的かも分析対象だ。 フォースフェーズでは、引きのいいコンテンツを広告でブーストする意思決定までAIがアシストする。 まとめ:CMSとAIエージェントがうまく仕事をしてくれる セカンドフェーズの完成により、CMSとAIエージェントで自社メディア運営の99%を自動化する体制が整った。 人間がやること(1%) アイデア出し コンテンツの方針決定 AIが作成したコンテンツへのフィードバック AIエージェントがやること(99%) ブログ記事の執筆(複数サイト参照、整合性チェック) SNS投稿(計画、原稿作成、投稿実行) メール配信(HTML生成、CRM投入) サイト間リンクのUTM自動付与 AIエージェントは本当によく働いてくれる。人間は方向性を決めてフィードバックを返すだけで、メディア運営が回る。 「作る→配る→測る→改善する」 のサイクル全体をAIエージェントが支援する体制を目指し、次のフェーズに進む。 --- # アニメーションを抑制したWebスクリーンショットを撮るCLIツール static-webshot - 公開日: Wed Feb 04 2026 17:07:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology Webページのビジュアルリグレッションテストを行う際、最大の敵はアニメーションだ。カルーセルスライダー、CSSアニメーション、フェードイン効果...これらの動的要素があると、撮影のたびにスクリーンショットが異なり、まともなピクセル比較ができない。 アニメーションを徹底的に抑制した静的なスクリーンショットを撮影するCLIツール static-webshot を開発し、MITライセンスのオープンソースとして公開した。 ideamans/static-webshot ビジュアルリグレッションテストとはなぜこのツールが必要だったのか従来のスクリーンショットツールの問題基本的な使い方スクリーンショットの撮影スクリーンショットの比較アニメーション抑制の仕組みCSSによる抑制JavaScriptの決定論化動画・音声の自動再生を無効化IntersectionObserverの即時発火カルーセルライブラリへの対応setInterval/setTimeoutの追跡とクリアWeb Animations APIの無効化比較機能の詳細差分画像の構成色差の閾値インストール現時点での制限と今後まとめ ビジュアルリグレッションテストとは まずは実際の出力を見てほしい。以下は dummy-ec-site.ideamans.com というデモ用ECサイトを、異なるタイミングで2回撮影し、比較した結果だ。 static-webshotは、変更前後のスクリーンショットを撮影し、ピクセル単位で比較する。出力される差分画像は3分割されている。 左: ベースライン(1回目の撮影) 中央: 差分のハイライト(変化した箇所が赤く表示される) 右: 現在(2回目の撮影) この例では、右下のチャットアイコンの部分にわずかな差分(0.89%)が検出されている。一方、ページ上部にあるSwiperベースのカルーセルスライダーは、撮影タイミングが異なるにもかかわらず同じスライドが表示されている。static-webshotが自動的にスライダーを検出し、最初のスライドで停止させているからだ。 差分のピクセル数とパーセンテージも数値で出力されるため、機械的な判定が可能だ。 なぜこのツールが必要だったのか 弊社ではWebサイトの表示スピード改善を提案している。画像の最適化、JavaScriptの遅延読み込み、CSSの軽量化...こうした施策を適用すると、意図せず表示が崩れる場面もある。 以前は、こうした副作用による表示崩れを目視で確認していた。しかし現在、AIエージェントによる自動改善を進める中で、目視ではなく機械的なレグレッションテストが必要になった。AIによる変更が表示に副作用を与えていないか、自動で検証したいからだ。 従来のスクリーンショットツールの問題 そこで既存のスクリーンショットツールを使ってみたが、アニメーションが問題になった。 冒頭で触れたとおり、カルーセルスライダーがあると撮影のタイミングによって表示されるスライドが変わってしまう。構造的には何も変わっていないのに、ピクセル比較では「大きな差分がある」と判定され、副作用による表示崩れと区別がつかない。 CSSアニメーションも同様だ。フェードイン途中で撮影されると、透明度が50%の状態がキャプチャされることもある。ローディングインジケーターが回転している途中かもしれない。 このようなアニメーションのタイミングによるノイズがあると、実際の表示崩れを見逃したり、逆に問題ないのにアラートが出たりして、レグレッションテストの信頼性が損なわれる。 基本的な使い方 スクリーンショットの撮影 bash# 基本的な使い方(デスクトップ、1920x1080) static-webshot capture https://example.com -o screenshot.png # モバイル端末をエミュレート(iPhone相当) static-webshot capture https://example.com -o mobile.png --preset mobile # 特定の要素を非表示にする(広告やCookieバナーなど) static-webshot capture https://example.com -o clean.png \ --mask ".ad-banner" \ --mask ".cookie-notice" スクリーンショットの比較 bash# 2つの画像を比較し、差分画像を生成 static-webshot compare baseline.png current.png -o diff.png # 比較結果をJSONで出力(CI連携用) static-webshot compare baseline.png current.png -o diff.png --digest-json result.json 出力例: [Compare Result] Baseline: baseline.png Current: current.png Output: ./diff.png Diff Pixels: 100 / 2073600 Diff Percent: 0.0048% JSONダイジェスト: json{ "pixelDiffCount": 100, "pixelDiffRatio": 0.000048, "diffPercent": 0.0048, "totalPixels": 2073600, "baselinePath": "baseline.png", "currentPath": "current.png", "diffPath": "./diff.png" } 詳細なオプションについてはREADMEを参照してほしい。 アニメーション抑制の仕組み ここからが技術的な核心部分だ。static-webshotは複数のレイヤーでアニメーションを抑制している。 CSSによる抑制 まず、すべての要素に対してCSSアニメーションとトランジションを無効化する。 css*, *::before, *::after { animation: none !important; animation-duration: 0s !important; animation-delay: 0s !important; transition: none !important; transition-duration: 0s !important; transition-delay: 0s !important; caret-color: transparent !important; } html { scroll-behavior: auto !important; } !importantフラグですべてのスタイルを上書きする。caret-color: transparentでテキストカーソルの点滅も消す。scroll-behavior: autoでスムーズスクロールも無効化だ。 JavaScriptの決定論化 時間やランダム値に依存する処理も抑制する。 javascript// Date.now()を固定値に const fixedTimestamp = new Date('2025-01-15T00:00:00Z').valueOf(); window.Date = class extends OriginalDate { constructor(...args) { if (args.length === 0) { super(fixedTimestamp); } else { super(...args); } } static now() { return fixedTimestamp; } }; // Math.random()を常に0.5に Math.random = function() { return 0.5; }; // Performance.now()も固定 performance.now = function() { return 0; }; これにより、「今日の日付」や「ランダムなバナー」といった要素も毎回同じ表示になる。 動画・音声の自動再生を無効化 javascript// play()を空実装に上書き HTMLMediaElement.prototype.play = function() { this.pause(); this.currentTime = 0; return Promise.resolve(); }; // autoplay属性を常にfalseに Object.defineProperty(HTMLMediaElement.prototype, 'autoplay', { get() { return false; }, set() {}, configurable: true }); 動画が自動再生されると、再生位置によってスクリーンショットが変わってしまう。これを防ぐため、すべてのメディア要素を0秒の位置で停止させる。 IntersectionObserverの即時発火 遅延読み込み(Lazy Loading)を使っているサイトでは、画像がスクロールしないと読み込まれない。これを解決するため、IntersectionObserverを上書きして、すべての要素を「表示されている」と判定させる。 javascriptwindow.IntersectionObserver = class IntersectionObserver { constructor(callback) { this.callback = callback; } observe(element) { setTimeout(() => { this.callback([{ target: element, isIntersecting: true, intersectionRatio: 1.0, // ... }], this); }, 0); } unobserve() {} disconnect() {} }; これにより、ビューポート外の画像も即座に読み込まれる。 カルーセルライブラリへの対応 ここが最も泥臭い部分だ。主要なカルーセルライブラリごとに、個別の停止処理を実装している。 javascriptfunction freezeSliders() { // Swiper document.querySelectorAll('.swiper-container, .swiper').forEach(el => { if (el.swiper) { el.swiper.autoplay.stop(); el.swiper.slideTo(0, 0); } }); // Slick (jQuery) if (typeof jQuery !== 'undefined' && jQuery.fn.slick) { jQuery('.slick-initialized').slick('slickPause'); jQuery('.slick-initialized').slick('slickGoTo', 0, true); } // Owl Carousel if (typeof jQuery !== 'undefined' && jQuery.fn.owlCarousel) { jQuery('.owl-carousel').trigger('stop.owl.autoplay'); jQuery('.owl-carousel').trigger('to.owl.carousel', [0, 0]); } // Flickity if (typeof Flickity !== 'undefined') { document.querySelectorAll('.flickity-enabled').forEach(el => { const flkty = Flickity.data(el); if (flkty) { flkty.pausePlayer(); flkty.select(0, false, true); } }); } // Bootstrap 5 Carousel if (typeof bootstrap !== 'undefined' && bootstrap.Carousel) { document.querySelectorAll('.carousel').forEach(el => { const carousel = bootstrap.Carousel.getInstance(el); if (carousel) { carousel.pause(); carousel.to(0); } }); } } 対応しているライブラリ: Swiper (swiper.js) Slick (slick.js + jQuery) Owl Carousel (owl.carousel.js + jQuery) Flickity (flickity.js) Bootstrap 5 Carousel カスタム実装のスライダーには対応できないが、--maskオプションで該当要素を非表示にするか、--inject-cssで個別に対処できる。 setInterval/setTimeoutの追跡とクリア ページ読み込み後、すべてのタイマーをクリアする。これにより、JavaScriptで実装されたアニメーションや自動スライドも停止する。 javascriptconst intervals = new Set(); const originalSetInterval = window.setInterval; window.setInterval = function(...args) { const id = originalSetInterval.apply(this, args); intervals.add(id); return id; }; window.addEventListener('load', () => { setTimeout(() => { intervals.forEach(id => clearInterval(id)); intervals.clear(); }, 100); }); Web Animations APIの無効化 CSSアニメーションだけでなく、JavaScriptからのアニメーションAPIも潰す。 javascriptElement.prototype.animate = function() { return { cancel: () => {}, finish: () => {}, pause: () => {}, play: () => {}, playState: 'finished', // ... }; }; document.getAnimations = () => []; 比較機能の詳細 スクリーンショットの比較では、ピクセル単位で色の違いを検出する。 差分画像の構成 出力される差分画像は3パネル構成だ。 左パネル: ベースライン画像(変更前) 中央パネル: 差分の可視化(変化した箇所が赤くハイライト、ベースラインは50%の明るさで表示) 右パネル: 現在の画像(変更後) パネルのラベルはカスタマイズ可能だ。 bashstatic-webshot compare baseline.png current.png -o diff.png \ --baseline-label "リニューアル前" \ --diff-label "変更箇所" \ --current-label "リニューアル後" 色差の閾値 --color-thresholdオプションで、ピクセルごとの色差の閾値を設定できる。デフォルトは10だ。 bash# 厳密な比較(微細な違いも検出) static-webshot compare baseline.png current.png --color-threshold 0 # 緩い比較(アンチエイリアスの差異を許容) static-webshot compare baseline.png current.png --color-threshold 30 インストール GitHubのReleasesからバイナリをダウンロードできる。 また、以下のページでイージーインストールをサポートしている。 static-webshot イージーインストール Chromeがインストールされていない環境でも、Playwrightの機能により自動的にChromiumがダウンロードされる。 現時点での制限と今後 今回紹介したアニメーション抑制の手法は、現時点でいくつかのサイトに対して効果を確認できた範囲のものだ。すべてのアニメーションを完全に抑制できるわけではない。 たとえば独自実装のカルーセルや、特殊なアニメーションライブラリを使っているサイトでは、抑制が効かないケースもありうる。そうした場合は--maskオプションで該当要素を非表示にするか、--inject-cssで個別に対処することになる。 今後、うまく抑制できないアニメーションのケースが出てきたら、随時対応していく予定だ。 まとめ static-webshotは、ビジュアルリグレッションテストのための決定論的スクリーンショットを実現するツールだ。 CSSアニメーション、トランジションの無効化 時間・乱数の固定による再現性の確保 主要カルーセルライブラリへの対応 ピクセル単位の差分比較と可視化 Webサイトの表示スピード改善やリニューアルプロジェクトで、表示崩れがないことを機械的に検証したい場合に活用してほしい。 ideamans/static-webshot --- # サイトスピードとテレビの視聴率の不思議な関係 - 公開日: Sat Jan 31 2026 00:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, business サイトスピードが上がると収益が上がる。テレビの番組が面白いと視聴率が上がる。どちらも当たり前のように語られている。しかし、よく考えてみてほしい。サイトの表示が速くなったからといって、ユーザーの購買意欲が増すわけではない。テレビの面白い瞬間を視聴者が事前に知ることもできない。それなのに、なぜ「上昇方向」の因果関係が成り立つのだろうか。実はこの2つの現象は、 確率的にまったく同じ構造 を持っている。 「遅いと離脱する」は直感的に分かるでも「速いと収益が上がる」は不思議見えない損失 ― 観測できない離脱サイトスピードを改善すると何が起きるかテレビの視聴率でも同じことが起きている固定視聴者の離脱防止ザッピング層の取り込み2つの現象の対応関係確率的な共通構造サイトスピードの改善は、面白い番組を作ることと同じ努力 「遅いと離脱する」は直感的に分かる サイトスピードが遅いとユーザーが離脱する。これはとても直感的だ。ページの読み込みを待てずにブラウザを閉じたり、別のサイトに移った経験は誰にでもある。通販サイトであればコンバージョンレートが下がり、メディアサイトであれば広告収益が下がる。 テレビの視聴率も同様で、番組がつまらなければ視聴者はチャンネルを変える。これも非常に直感的だ。わざわざつまらない番組を見続ける人は少ない。 「悪い体験 → 離脱」という因果関係は明確 である。 でも「速いと収益が上がる」は不思議 では逆に、サイトスピードが速くなると収益が上がるというのはどうだろうか。 冷静に考えると、これはかなり奇妙な主張だ。サイトの表示が速くなったからといって、 もともと欲しくなかったものを急に欲しくなるだろうか? ページが0.5秒速く表示されたから購買意欲が高まる、ということは普通ない。 サイトスピードと購買意欲の間に直接的な因果関係はない のだ。 テレビの視聴率も同じ構造を持っている。番組が面白いと視聴率が上がるというが、 視聴者はこれから面白い場面が来ることを事前に知っているわけではない。 まだ放送されていない場面が面白いかどうかを事前に知ることはできない。友人から「今この番組面白いよ」とリアルタイムに紹介されるケースもゼロではないが、極めて少数だろう。 つまり「良い体験 → 指標が改善する」という因果関係は、よく考えると 直接的にはつながっていない のである。 見えない損失 ― 観測できない離脱 この謎を解くカギは、 私たちが観測できていない損失 にある。 サイトスピードが遅いことによって、ユーザーは何も言わずにサイトを離れている。問い合わせフォームに「遅かったので帰ります」と書いてくれる人はいない。アクセス解析ツールにも、 ページが表示される前に離脱したユーザーは記録されない ことが多い。 この「観測できない機会損失」が、実は日常的かつ大量に発生している。 サイト運営者から見れば、今ある売上が普通の状態に見える。しかし実際には、スピードの問題で離脱してしまった 「本来のお客様」が水面下で大量に失われている 。見えないからこそ、損失として認識されにくいのだ。 サイトスピードを改善すると何が起きるか サイトスピードを改善したとき、起きていることは次の 2段階の因果関係 だ。 もともと観測できなかった離脱が大量に存在していた — スピードが遅いことで、ページが表示される前に帰ってしまうユーザーが日常的に発生している スピード改善により、離脱するはずだった人の一部がサイトに留まる — ページが速く表示されることで、離脱の閾値を超えずに済むユーザーが増える つまり、サイトスピードの改善は購買意欲を高めているのではない。 今まで離脱していた人を引き留めることで、もともと持っていた購買の可能性を実現させている のだ。 見かけ上の因果関係は「スピード改善 → 収益アップ」だが、実態は「大量の機会損失 → スピード改善 → 離脱減少 → コンバージョン機会の回復」という ワンクッションを挟んだ間接的な因果関係 である。 テレビの視聴率でも同じことが起きている テレビの視聴率にも、まったく同じメカニズムが働いている。 固定視聴者の離脱防止 まず「この番組を見よう」と決めてチャンネルを合わせた人がいる。この固定視聴者は、番組が面白ければ最後まで見続ける。逆に つまらなければ途中で離脱する 。 これはECサイトで「この商品を買おう」と決めて訪問した人と同じだ。サイトスピードが速ければ注文を完了するし、遅ければ離脱してしまう。 ザッピング層の取り込み もう一つの重要な存在が ザッピング層 だ。チャンネルをコロコロ変えながらテレビを見ている人たちである。 彼らは どの番組を見るか決めていない 。しかし、あるチャンネルへ切り替えた瞬間にたまたま面白い場面が映っていれば、 そこで手を止めて視聴を続ける 。番組が面白ければ面白いほど、この「浮動的な視聴者」がそのチャンネルに留まる確率が上がる。 これは通販サイトにおける 衝動買い と同じ構造だ。商品を見るだけのつもりだったユーザーが、たまたま良い商品に出会い、サイトが快適に動作していたおかげでそのまま購入を完了してしまう。サイトスピードが速いことは購買意欲を生み出したわけではない。 衝動買いの芽を摘まなかった だけだ。 結果として、番組が面白いと視聴率が上がるように見える。しかし実際には、「面白さが視聴者を引き寄せた」のではなく、 「面白さがザッピング中の視聴者を引き留めた」 のだ。サイトスピードも同様に、「速さがユーザーを呼び込んだ」のではなく、 「速さがウィンドウショッピング中のユーザーの衝動買いを邪魔しなかった」 のである。 2つの現象の対応関係 この2つの現象を並べてみると、対応関係が明確になる。 サイトスピード テレビの視聴率 目的を持った層 買おうと思って訪問したユーザー 番組を見ようと決めた固定視聴者 浮動的な層 ウィンドウショッピング中のユーザー ザッピング中の視聴者 離脱の原因 ページの読み込みが遅い 番組がつまらない 留まる条件 ページが快適に表示される 面白い場面に出くわす 見かけ上の因果 スピード改善 → 収益アップ 面白い番組 → 視聴率アップ 実際の因果 離脱予定者の引き留め 浮動視聴者の引き留め どちらも、 「良い体験が新しい行動を生み出す」のではなく、「良い体験が離脱を防ぐことで、もともとあり得た行動が実現する」 という構造になっている。 確率的な共通構造 この2つの現象を確率論的に見ると、共通の構造が浮かび上がる。 ある集団の中で、各個人が 「留まる」か「離脱する」かの二者択一の選択 を行う。その選択確率は体験の質(スピード、面白さ)によって変動する。体験が悪ければ離脱確率が上がり、体験が良ければ離脱確率が下がる。 重要なのは、 体験の質が直接的に成果(購買、視聴)を生むのではなく、離脱確率を下げることで間接的に成果を高めている という点だ。 これは 「生存分析(生存時間解析)」 と呼ばれる統計的フレームワークとも親和性が高い。ユーザーや視聴者がいつ離脱するかを時間経過に沿って確率的にモデル化し、体験の質がその離脱リスクにどう影響するかを分析する枠組みで、代表的な手法にCox比例ハザードモデルがある。 Cox比例ハザードモデルとは Cox比例ハザードモデルは、ある事象(離脱、故障、死亡など)が起きるリスクに対して、複数の要因がどの程度影響しているかを推定する統計手法だ。もともとは医学分野で患者の生存期間を分析するために開発されたが、現在ではマーケティングにおける顧客離脱分析など幅広い分野で活用されている。サイトスピードの文脈では「ページ読み込み時間」を要因とし、「ユーザーが離脱するまでの時間」を事象として、スピードが離脱リスクにどれだけ影響するかを定量的に評価できる。 サイトスピードの改善は、面白い番組を作ることと同じ努力 こうして見ると、Webサイトの運営者がサイトスピードを改善するという行為は、テレビの制作者が番組を面白く作って視聴者の離脱を減らすという行為と、 実はまったく同じ種類の努力 だったことがわかる。 どちらも「良いものを作れば人が来る」という単純な話ではない。 普段から目に見えない形で発生している大量の離脱を減らし、本来あるべき成果を取り戻す という、地道だが確実に効果のある取り組みだ。 テレビ制作者は視聴者が離れないように番組の質を磨く。Webサイトの運営者はユーザーが離れないようにスピードを磨く。やっていることの本質は同じである。 見えない損失に気づくこと。それがサイトスピード改善の第一歩だ。 --- # Webページのほぼ完全なコピー環境を作る http-playback-proxy - 公開日: Mon Jan 26 2026 18:52:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology Webページの表示スピード改善は、やはり実際に変更して計測してみないと効果は見えにくいものだ。そのため、できれば本番環境には手を加えず、どこを変えたらどうなるかを何度も繰り返しシミュレーションしたい。そのための技術として「http-playback-proxy」をオープンソースで公開している。 ideamans/rust-http-playback-proxy - GitHub このツールは、Webページ表示に必要なすべての通信を記録し、それを再生することでWebページの精巧なコピー環境を作り上げる ものである。以前はTypeScriptで開発していたが、現在はRustで書き直し、より高速かつ動作が正確になるよう改善している。 http-playback-proxyとはHTTPプロキシについてhttp-playback-proxyの特徴記録と再生の仕組み記録(Recording)再生(Playback)編集しやすいデータ形式編集と再生による検証3つのメリット1. スピーディな試行錯誤2. 本番環境に影響を与えない3. 環境の再現が不要活用例:PageSpeed改善リハーサルまとめ http-playback-proxyとは HTTPプロキシについて そもそもHTTPプロキシとは、ブラウザとサーバーの間に立つ中継サーバー のことである。ブラウザやOSには標準機能としてHTTPプロキシを指定する仕組みが備わっており、設定すればすべての通信を指定したサーバー経由で行うようになる。 企業ネットワークでのアクセス制御やキャッシュ、開発時のデバッグなど、さまざまな用途で利用されている。 http-playback-proxyの特徴 http-playback-proxyは、このHTTPプロキシの一種である。特徴は 「記録」と「再生」 の2つのモードを持つこと。 記録モードでは、ブラウザがWebページを表示する際の一連のリソース取得を監視する。どんなリソースが、どのタイミングで、どれくらいの遅延で届いたか。すべての通信をファイルとして記録する。 再生モードでは、この記録に基づいてサーバーからデータが返ってきているかのような「幻覚」をブラウザに見せる。ブラウザはサーバーにアクセスしているつもりでも、実際には事前に記録されたデータが返されている。 エンジニアの方であればVCR系のライブラリをご存知かもしれない。VCRはAPIのような単独の通信を記録・再生するものだが、http-playback-proxyはそれを ブラウザのWebページ表示全体に拡張 したようなソフトウェアである。 Webページの表示に関する一連の通信プロセスをほぼ完全に再現できるため、本物のサーバーなしに Webページの表示を再現できるのである。 以下、この仕組みについて図解を交えて詳しく解説する。 記録と再生の仕組み 記録(Recording) 記録モードでは、ブラウザはhttp-playback-proxyをプロキシとして設定し、通常どおりWebページを閲覧する。 プロキシはブラウザとサーバーの間に立ち、すべての通信を監視する。このとき記録されるのは単なるレスポンスデータだけではない。 レスポンスボディ - HTML、CSS、JavaScript、画像などのコンテンツ HTTPヘッダー - Content-Type、Cache-Controlなどのメタ情報 タイミング情報 - リクエストから最初のバイトが届くまでの遅延(TTFB)、データ転送完了までの時間 現代のWebページが単一のサーバーからデータを配信されることは稀である。サードパーティのタグ、CDN、広告ネットワークなど、複数のサーバーからリソースを取得して1つのページを構成している。 http-playback-proxyはこれらすべてのサーバーとの通信を記録する。結果としてWebページ表示に必要なあらゆるリソースが漏れなくキャプチャされる。 TTFBとは TTFB(Time To First Byte)は、リクエストを送信してから最初の1バイトが返ってくるまでの時間である。サーバーの応答速度やネットワーク遅延を反映する重要な指標となる。 再生(Playback) 再生モードでは、ブラウザは引き続きhttp-playback-proxyをプロキシとして使用する。しかし今度はプロキシがサーバーに問い合わせることはない。 ブラウザからリクエストが来ると、プロキシは記録されたデータの中から該当するレスポンスを探し出す。そして記録されたタイミング情報に従って応答を返す。 たとえばあるリソースの取得に元々200msかかっていたなら、再生時も200ms待ってからレスポンスを返す。これによりWebページの表示プロセスがスピード面でも忠実に再現される。 ブラウザから見ると 実際のサーバーにアクセスしているのとまったく区別がつかない。Lighthouseのようなスピード計測ツールも通常どおりページを計測できる。 編集しやすいデータ形式 http-playback-proxyの大きな特徴は、記録されたデータが非常に編集しやすい形式であることだ。 通常、HTTPレスポンスは圧縮されていたり、ミニファイされていたりする。しかしhttp-playback-proxyは記録時にこれらを展開・整形する。 圧縮の展開 - gzip、brotliなどの圧縮を解いて保存 文字コードの統一 - Shift-JISなど旧来の文字コードもUTF-8に変換 コードの整形 - ミニファイされたHTML、CSS、JavaScriptを読みやすく整形 結果として記録されたファイル群は、まるで 手作りの静的サイトのような構造 になる。ファイルを開けば普通にHTMLやCSSとして読み書きできる。 なぜ編集しやすさが重要か スピード改善の検証では、CSSの一部を削除したり、JavaScriptの読み込み順を変更したりする作業が頻繁に発生する。そのたびに圧縮やミニファイを解除する手間がかかっては効率が悪い。最初から編集しやすい形式で保存することで、試行錯誤のサイクルを高速で回せるようになる。 編集と再生による検証 記録されたデータは自由に編集できる。そして編集した内容は次回の再生に反映される。 たとえば以下のような検証が可能である。 CSSの最適化 - 不要なスタイルを削除してファイルサイズを減らす JavaScriptの遅延読み込み - スクリプトにdefer/async属性を追加する 画像の軽量化 - 画像を圧縮して差し替える リソースの結合・分割 - 複数のファイルを1つにまとめる、または分割する 編集後、再生モードでLighthouseなどの計測ツールを実行すれば、変更がスピードにどう影響するかを即座に確認できる。 3つのメリット この仕組みがもたらすメリットは大きく3つある。 1. スピーディな試行錯誤 これまで解説したように、http-playback-proxyを使えばスピード改善のアイデアと検証をスピーディに繰り返すことができる。記録されたデータを編集し、再生して計測する。このサイクルを何度でも回せる。 従来であれば開発環境の構築、コードの修正、デプロイ、計測という長いプロセスが必要だった。http-playback-proxyならファイルを編集してすぐに計測できる。 2. 本番環境に影響を与えない 以前は、スピード改善の効果を検証するには実際にサイトを修正して本番に反映する必要があった。うまくいかなかったら戻す。その繰り返しである。 http-playback-proxyを使えば、本番サイトには一切手を加えずに検証できる。記録されたコピー環境の中でいくら実験しても本番には影響しない。安全に何度でもやり直しがきく。 3. 環境の再現が不要 スピード改善のために開発環境を用意しようとすると意外と大変である。シンプルな静的サイトならまだしも、データベースと連携した動的サイト、複雑なインフラ構成のサイトでは環境の再現自体が技術的に困難な課題になる。 さらに社外のコンサルタントや専門家に協力を仰ぐ場合、環境や情報の共有がボトルネックになりがちである。 http-playback-proxyは、Webページのフロントエンド構成だけを高い精度で複製する。バックエンドのデータベースやアプリケーションサーバーは不要である。必要なのは記録されたファイル群だけ。これならチーム外との共有も容易だ。 活用例:PageSpeed改善リハーサル この仕組みを活用して提供しているのが「PageSpeed改善リハーサル」というサービスである。 PageSpeed改善リハーサル 対象のWebページを複製して安全なコピー環境を作り、そこであらゆるスピード改善施策のシミュレーションを実施する。 対象ページの複製(安全なコピー環境を作成) 改善施策の適用(画像軽量化、CSS最適化、JS遅延読み込みなど) 効果測定(Lighthouseでスコアを計測) 繰り返し検証(複数のアイデアを比較) 事前にどの施策がどれくらい効果的かを把握した上で、本番への適用を進められる。「やってみないとわからない」から「効果を確認してから実装する」へ。スピード改善のアプローチが変わるのである。 まとめ http-playback-proxyは、Webページの表示プロセスを丸ごと記録・再生することで精巧なコピー環境を作り上げるツールである。 記録 - ブラウザがページを表示する際のすべての通信をキャプチャ 再生 - 記録に基づいてブラウザにタイミングまで再現した「夢」を見せる 編集 - 記録されたデータは静的サイトと同様に自由な編集が可能 検証 - 編集結果がスピードに与える影響を即座に確認 MITライセンスのオープンソースとして公開している。GoやTypeScriptからも利用可能である。 ideamans/rust-http-playback-proxy - GitHub スピード改善に取り組む方は、ぜひ活用いただきたい。 --- # 通販サイトのスピードを遅くする悪魔のA/Bテスト - CVRに19倍の差 - 公開日: Wed Jan 21 2026 09:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, business 実際の通販サイトにおいて一部のユーザーの体験を わざと遅くする 禁断のA/Bテストを実施した。遅い体験を強いられたユーザーの行動にどんな違いが現れたか、衝撃的な結果を報告する。 衝撃のA/Bテストを実施対象サイトについてCVR(成約率)に19倍の差再訪問率にも約4.5倍の差この結果が意味すること短期的な収益への影響長期的なLTVへの影響種明かし:実は誰も遅くしていないユーザーの体験スピードは自然にバラつくなぜこのようなばらつきが起きるのか自然なばらつきでグループ分けしたLCPとCVRの関係をより細かく見るまとめ自然なばらつきで影響を測定できる今回の実験で明らかになったこと短期的にも長期的にも重要 衝撃のA/Bテストを実施 今回はサイトスピードの指標として、Core Web Vitalsにも含まれる LCP(Largest Contentful Paint) を題材に扱う。 約30%のユーザーに対して LCPが1.5秒より早い体験ができないようにする 特別な措置を施した。 グループA(約30%): LCPが1.5秒より早くならないよう調整 グループB(約70%): それ以外のユーザー(自然なスピード体験) この2つのグループでユーザーの行動にどんな違いが現れるかを測定した。 対象サイトについて 今回の実験対象は、以下のような中堅規模の通販サイトである。 業種: 服飾系ECサイト 月間売上: 約3,000万円 十分なサンプル数 を確保できる規模 CVR(成約率)に19倍の差 まずはコンバージョンレートの比較結果だ。 グループ 条件 CVR グループA(約30%) LCPが1.5秒より早くならないよう調整 0.04% グループB(約70%) 自然なスピード体験 0.67% 約19倍の差 が生じた。 GoogleはLCP 2.5秒までを「良い」体験と規定しているが、実際には 1.5秒という境目を設けるだけで、これほどの差が出る。 LCP 2.5秒は決して「良い」とは言えない。ユーザーは本当に爆速な体験を求めており、 少しでも遅いと感じるとすぐに離脱する特性 を持っている。 再訪問率にも約4.5倍の差 次に、再訪問率の比較をご覧いただこう。 ここでいう 再訪問率 とは、ひとりのユーザーが複数の日にちをまたいでサイトを訪れたかどうかの比率である。 グループ 条件 再訪問率 グループA(約30%) LCPが1.5秒より早くならないよう調整 7.52% グループB(約70%) 自然なスピード体験 34.34% 再訪問のされやすさにも約4.5倍の差が生じた。 この結果が意味すること 短期的な収益への影響 サイトスピードはCVR、つまり通販サイトに訪れたユーザーが購入してくれるかどうかの成約率に対して、 非常に大きな影響 を与えている。 今回のA/Bテストでは、サイトスピード以外の要素は一切調整していない。グループAとグループBはほぼ無作為に分けられたと見て良い。純粋にスピードの違いだけで、これだけの差が生じたことになる。 長期的なLTVへの影響 通販サイトに限らず、多くのサイトはファンを獲得してLTV(顧客生涯価値)を最大化することが現代的な基本戦略となっている。 しかし、サイトスピードが遅い体験を強いてしまうと、 再訪問してくれる可能性が80%近く低下 してしまう。 これは裏を返すと、 ファンになりにくい せっかくファンになっても離脱しやすい という長期的なダメージを意味している。 種明かし:実は誰も遅くしていない ここまで読んで、ユーザーの体験をわざと遅くするとは何事かと怒りを覚えた読者もいるかもしれない。 しかし安心してほしい。 実際にそんなことはしていない。 今回のデータと結果はA/Bテストとして間違いないが、「一部のユーザーの体験をわざと遅くした」というのは 方便 だ。 ユーザーの体験スピードは自然にバラつく サイトスピード、ページスピードは、閲覧者によってかなり大きく左右される。以下は同じトップページのLCPをページビューごとに集計した分布(ヒストグラム)だ。 このように、同じページであってもユーザーによって体験スピードは大きなばらつきを持つ。0.75秒〜1秒あたりがボリュームゾーンだが、 遅い体験をしているユーザーもかなり幅広く存在 している。 LCPに限らず、サイトスピードに関する時間の指標は 対数正規分布 に従うとされており、一定のボリュームゾーンはあるものの、遅い方向には長いテールが伸びている。 理論値と多少の誤差はあるが、大まかな特徴は一致している。サイトスピードのばらつきは数学モデルを適用できる、ありふれた現象なのだ。 なぜこのようなばらつきが起きるのか Webページの表示は一見シンプルだが、ひも解いてみると技術的にかなり多くの要素が複雑に絡み合った処理を行っている。 たとえば通信は瞬間的に見えるが、実際は10〜20台程度の通信機器を経由してサーバーから端末へデータが送信されている。このように複雑な要素の一つ一つに確率的なスピードの揺れがあり、それが積み重なることで、遅い方向に広がる対数正規分布上のばらつきが生じる。 インターネット通信は目に見えない抽象的な技術のようだが、実際は物理現象をベースに成り立っている。確率的なスピードの揺れは、避けられない制約なのだ。 自然なばらつきでグループ分けした つまり、同じページを見るという体験においても、 ユーザーによって 同じユーザーでもタイミングによって 表示スピードには 実際にかなりのばらつきが生じる。 今回「A/Bテスト」と便宜的に呼んだが、実際には 自然に発生したユーザーの体験スピードに応じて、ユーザーを2つのグループに分けた だけだ。 グループA: LCPが1.5秒より早い体験ができなかったユーザー グループB: LCPが1.5秒より早い体験もできたユーザー このように分けて、それぞれのCVRや再訪問率を比較したのが今回の「A/Bテスト」の正体である。 自然実験 このように、研究者が意図的に条件を操作するのではなく、自然に発生した条件の違いを利用して因果関係を推定する手法を 自然実験 と呼ぶ。今回のケースでは、ユーザーの体験スピードの違いは自然に発生したものであり、それを利用してサイトスピードとユーザー行動の関係を分析している。自然実験とA/Bテストは、因果関係や相関関係を分析する上では同等に有効である。 LCPとCVRの関係をより細かく見る ここで、LCPとCVRの関係をより詳しく見てみよう。 今回のA/Bテストで採用した「LCPが1.5秒より早い体験ができないようにする」という条件は、言い換えると ユーザーの最小LCPが1.5秒以上か、それ未満か を基準にグループ分けしたということになる。 「最小LCP」とは、ユーザーが体験した中で 最も早かったLCPの値 だ。グループAのユーザーは、どんなに早くてもLCP 1.5秒より早い体験ができなかった——いわばLCPに遅くなる「呪い」をかけられたようなものだ。 最小LCPについて 最小LCPを用いる理由は以下の記事で詳しく解説している。 👉 サイトスピードと収益性の定量的な関係 - 最小LCPによる新モデル では、この最小LCPの数値によってCVRがどう異なるか確認しよう。 最小LCPの値が良いほどCVRが高く、最小LCPの値が悪化するほどCVRも低下する——明らかに強い相関性が見られる。 なお、グラフの一番右の階級が急に大きく伸びているのは、4.5秒以上のユーザーをここにまとめたためだ。先ほど見たように、LCPの分布は基本的にロングテールを描き、右方向に延々と続く。それをすべて記述するとグラフの特徴が見えにくくなるので、4.5秒以上のグループはひとつの階級にまとめている。 今回ご覧いただいたA/Bテストは、この細かいLCPとCVRの関係を、1.5秒を境目として2つのグループに分け、CVRと再訪問率を比較したものであった。 まとめ 自然なばらつきで影響を測定できる サイトスピードが遅いことによってユーザーの行動にどのような違いが現れるかを確認するために、 作為的なA/Bテストを行う必要はない。 ユーザーの体験スピードには自然にばらつきが生じる。その 自然に発生した体験スピードの違いでユーザーをグループ分け し、それぞれの結果を比較することで、サイトスピードがユーザーに与える影響を計測できる。 今回の実験で明らかになったこと サイトスピードはユーザーの行動に明らかに影響を与える CVRには19倍の差 が現れた 再訪問率には約4.5倍の差 が現れた 短期的にも長期的にも重要 サイトスピードは短期的なCVR(収益)に影響を与えるだけでなく、 再訪問率 という長期的な指標にも大きく影響する。 ユーザーのファン化のしやすさ せっかくファンになったユーザーの離脱のしやすさ これらに対しても、サイトスピードは決定的な要因となっている。 サイトスピードが重要だということは、すでに一般的な常識となっている。しかしこの疑似的なA/Bテストから、その事実を改めて確認いただけると幸いである。 --- # さくらのCDN料金体系の裏をかいた激安AWS Firehoseとして使う「ずるい使い方」 - 公開日: Mon Dec 29 2025 04:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: infrastructure, development, ideas CDNといえば、大量のアクセスを高速・安定的にさばくためのインフラである。しかし、さくらインターネットのCDNサービス「ウェブアクセラレータ」には、料金体系の特性を活かした「ずるい使い方」がある。 それは、AWSでいうAmazon Data Firehoseのようなストリーミングデータの収集基盤としてほぼ無料で使うという方法だ。 弊社のサービスでも実際に活用しているので、その仕組みを紹介したい。 ウェブアクセラレータにログ収集機能が追加料金体系の特徴ストリーミングデータ収集の仕組みデータ送信の流れなぜコストが抑えられるのかオリジン問題との相性実際の活用事例Speed is MoneyRanklet4Amazon Data Firehoseとの比較CloudFrontでは同じ手が使えない注意点と制約1時間のタイムラグデータサイズの制限ログ解析の実装まとめ ウェブアクセラレータにログ収集機能が追加 少し前になるが、さくらインターネットは2024年11月にウェブアクセラレータへアクセスログ機能を追加した。 この機能では、ユーザーからのリクエスト内容やレスポンスステータスなどを記録したアクセスログが、さくらのクラウドオブジェクトストレージに定期的にアップロードされる。フォーマットはgzip圧縮されたJSON Lines形式である。 以前はCDNとしてトラフィックを分散・配信する機能のみで、生ログを取得する手段がなかった。このログ機能の追加により、新たな活用の道が開けた。 料金体系の特徴 ウェブアクセラレータの料金は非常にシンプルである。 課金対象はアウトバウンド転送量のみ 5円/GiB(税込) インバウンド転送量は無料 リクエスト数による課金もなし 初期費用・固定費もなし この「アウトバウンドのみ課金」という特性が、ずるい使い方の鍵となる。 ストリーミングデータ収集の仕組み 通常、ユーザーの行動データを大量に収集するには、Amazon Data Firehoseのようなストリーミングデータ基盤を使う。しかしこれらはリクエスト数に応じて課金されるため、トラフィックが増えるとコストも膨らんでしまう。 ウェブアクセラレータを使ったアプローチは異なる。 データ送信の流れ ユーザーのブラウザ ↓ GETリクエスト(URLパラメータにデータを含める) CDN(ウェブアクセラレータ) ↓ ログに記録 オブジェクトストレージ ↓ ログファイルを解析 集計結果 JavaScriptでユーザーの行動データを収集 データをGETパラメータとしてCDNにリクエスト CDNは1x1ピクセルの透過GIFなど最小限のレスポンスを返す リクエストURLはログに記録される 後でログを解析してデータを集計 なぜコストが抑えられるのか ポイントは、ユーザーからのインバウンドデータ(GETパラメータ)は課金されない点にある。 GETパラメータにどれだけデータを詰め込んでも、それはインバウンドトラフィック。一方、レスポンスとして返すのは1x1ピクセルの画像(数十バイト)程度。アウトバウンドはほぼゼロに近い。 javascript// 例:ユーザーの行動データを送信 const data = { event: 'click', target: 'buy_button', timestamp: Date.now(), viewport: `${window.innerWidth}x${window.innerHeight}` } const params = new URLSearchParams(data) new Image().src = `https://your-cdn.sakura.ad.jp/track.gif?${params}` このリクエストがログに残り、後で集計できる。 オリジン問題との相性 ブラウザのセキュリティ制約(同一オリジンポリシー)により、JavaScriptは基本的に配信元と同じオリジンにしかデータを送信できない。これが意外とCDN活用の追い風になるのだ。 つまり、JavaScriptライブラリ自体をCDNで配信し、同じドメインでデータも受け取るという構成にすれば、オリジンの問題をクリアしつつ、すべてがCDN上で完結する。 JavaScriptファイルのサイズが小さければ、アウトバウンド課金はわずか ユーザーからのデータ送信(インバウンド)は無料 別途APIサーバーを用意する必要もない この構成は非常に収まりがよく、後述するSpeed is MoneyやRanklet4でも採用している。 実際の活用事例 弊社ではこの手法を2つのサービスで活用している。 Speed is Money Speed is Moneyは、Webサイトの体感スピードとコンバージョンの相関を可視化するサービスである。 サイトに埋め込んだタグが、訪問者ごとの体感時間やコンバージョン状況をストリーミングデータとして収集する。以前は自前のインフラで処理していたが、ウェブアクセラレータのログを活用することで、大幅にコストを削減できた。 Ranklet4 Ranklet4は、Webサイトのアクセスランキングを提供するサービスだ。 ランキングの表示回数や、どの順位がクリックされたかといったインタラクションデータを収集する必要がある。月間数億PV規模のトラフィックを扱うが、このログ収集にかかるコストは月額数百円〜数千円程度で済んでいる。 同等のトラフィックをData Firehoseで処理したらどうなるか。コストは桁違いに膨らむだろう。 Amazon Data Firehoseとの比較 項目 Data Firehose ウェブアクセラレータ 課金単位 リクエスト数 + データ量 アウトバウンド転送量のみ 受信データの課金 あり なし リアルタイム性 数分以内 最大1時間のラグ データ形式 自由 URLパラメータ(約2KB制限) 処理の柔軟性 Lambda連携など豊富 ログファイル解析のみ Data Firehoseは柔軟で高機能だが、トラフィックに比例してコストが増える。ウェブアクセラレータは制約があるものの、大量のリクエストを低コストでさばける。 CloudFrontでは同じ手が使えない 同じCDNでも、AWS CloudFrontでこの手法を真似るのは要注意である。 CloudFrontの料金体系では、データ転送量に加えてリクエスト数に応じた課金が発生する。HTTPSリクエストの場合、1万リクエストあたり$0.01程度(リージョンにより異なる)だ。 一見わずかに思えるが、ストリーミングデータの収集では数百万〜数億リクエストになることも珍しくない。1億リクエストなら$100程度のリクエスト課金が上乗せされる計算になる。 一方、さくらのウェブアクセラレータはリクエスト数による課金がない。アウトバウンド転送量のみで課金されるため、レスポンスを最小限に抑えれば、リクエスト数がいくら増えてもコストに影響しない。この料金体系の違いが、今回の「ずるい使い方」を成立させている。 注意点と制約 1時間のタイムラグ ログは1時間ごとに生成・アップロードされる。リアルタイムやニアリアルタイムの処理が必要な用途には向かない。 ただし、日次や週次で集計するようなユースケースなら、1時間のラグは許容範囲だろう。 データサイズの制限 GETパラメータには実質的に約2KB程度の制限がある。大きなデータを送る場合は、複数のリクエストに分割するか、データを圧縮する工夫が必要になる。 ログ解析の実装 Firehoseと異なり、ログの解析ロジックは自前で実装する必要がある。JSON Linesをパースし、URLパラメータをデコードして集計する処理を用意する。 python# ログ解析の例(Python) import gzip import json from urllib.parse import parse_qs, urlparse with gzip.open('access_log.json.gz', 'rt') as f: for line in f: record = json.loads(line) url = record.get('request_uri', '') params = parse_qs(urlparse(url).query) # データを集計... まとめ CDNの料金体系は通常、大容量のコンテンツ配信を前提に設計されている。しかしウェブアクセラレータの「アウトバウンドのみ課金」という特性を逆手に取ると、ストリーミングデータの収集基盤としてほぼ無料で活用できる。 やや「ずるい」使い方だが、公式に禁止されているわけではない。大量のユーザーデータを収集したいが、Data Firehoseのようなサービスのコストが気になる——そんなときの選択肢として覚えておく価値はあるだろう。 なお、さくらインターネットがこの使い方を想定しているかは定かではない。将来的に料金体系が変わる可能性もあるので、本番導入の際はその点も考慮してほしい。 --- # ページ読み込みプロセスを動画化するCLIツール「Loadshow」をGo言語でリニューアル - 公開日: Sat Dec 27 2025 23:26:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology PageSpeed InsightsやLighthouseでWebサイトの表示スピードを計測すると、スコアやLCP、Speed Indexなどの専門的な指標が数値で示される。しかし正直なところ、数値だけを見て「このサイトは速い」「遅い」と直感的に判断できる人がどれだけいるだろうか。 そこで開発したのが、Webページの読み込みプロセスを動画として録画するCLIツール「Loadshow」だ。今回、Go言語で完全にリニューアルし、オープンソースとして公開した。 ideamans/go-loadshow: Records web page loading as scrolling video 体感時間を可視化する使い方オプションなぜGo言語でリニューアルしたのか技術的な裏話:H.264と特許問題OS機能の活用Linuxの課題AV1によるフォールバックブラウザの自動インストールインストールmacOS / Linux(クイックインストール)Windows(PowerShell)パッケージマネージャーまとめ 体感時間を可視化する 読み込みスピードを比較したいとき、「実際にサイトを読み込んでみればいい」という意見があるかもしれない。しかし、それは意外と難しい。 たとえば最適化前後のサイトを比較しようとしても、両方を同時に並べて読み込むことはできない。時間的に前後する体験を記憶で比較するのは、人間の認知として限界がある。競合サイトとの比較も同様だ。 Loadshowは読み込みプロセスを録画し、MP4動画として出力する。以下は実際に録画したサンプルだ。 そして2つの動画を横に並べて1つの動画に合成する機能も備えている。 上記は表示最適化を行う前と後を並べた比較動画だ。数値ではなく、視覚的・体感的にスピードの違いを実感できる。 使い方 コマンドは非常にシンプルだ。 bash# 読み込みプロセスを録画 loadshow record -o output.mp4 https://example.com # 2つの動画を横に並べて比較 loadshow juxtapose -o compare.mp4 before.mp4 after.mp4 オプション デバイスのエミュレーションや動画レイアウトを細かく設定できる。 bash# デスクトップ端末でのエミュレーション(デフォルトはモバイル) loadshow record -o output.mp4 --preset desktop https://example.com # 動画の品質設定 loadshow record -o output.mp4 --quality high https://example.com # レイアウト列数の変更 loadshow record -o output.mp4 --columns 2 https://example.com 詳しくはREADMEを参照してほしい。 なぜGo言語でリニューアルしたのか 以前からTypeScript(Node.js)版のLoadshowを公開していた。 ideamans/loadshow: Node.js版(旧版) しかしNode.js版には以下の問題があった。 依存関係が多い - Node.jsランタイム、FFmpeg、Chrome/Chromiumブラウザが事前に必要 動作がもっさり - Node.jsを経由した処理のオーバーヘッド 今回のGo言語版では、AIの力も借りながらこれらの課題を解決した。 単一バイナリ - Windows/macOSではほぼ依存なしで動作 軽快な動作 - ネイティブバイナリによる高速な処理 ブラウザの自動調達 - Playwrightの機能を活用し、Chromeが未インストールでも自動でダウンロード 技術的な裏話:H.264と特許問題 最も苦労したのが動画フォーマットの選定だ。 デスクトップでプレビューしやすく、幅広いブラウザで再生できる動画フォーマットとしてはMP4が無難だ。特にH.264コーデックは互換性が高い。 しかしH.264には特許の問題がある。静的ライブラリとしてバインドして配布すると、GPLでの配布が求められ、自由度は下がってしまう。 OS機能の活用 幸いなことに、WindowsとmacOSにはH.264エンコードを支援するOS機能が備わっている。 Windows: Media Foundation macOS: VideoToolbox これらを活用することで、特許ライセンスの問題をクリアしつつH.264動画を出力できる。 Linuxの課題 一方、Linuxにはこうした支援機能がない。LinuxでH.264を使うにはFFmpegが必要となる。ここは技術的な制約として残った。 AV1によるフォールバック もうひとつの選択肢として、AV1コーデックもサポートしている。libaomを静的にバインドすることで、外部依存なしにAV1動画を出力できる。 現時点ではAV1はmacOSのデスクトップですんなり再生できないなど、まだ互換性に課題がある。しかしフォールバック先としては十分機能する。 つまり動画出力の戦略は以下のようになっている。 OS支援機能(Media Foundation / VideoToolbox)が使えれば、それでH.264出力 使えなければFFmpegを探す FFmpegもなければAV1でフォールバック ブラウザの自動インストール 録画にはブラウザが必要だが、Playwrightにはブラウザの自動インストール機能がある。この機能を活用することで、Chromeがインストールされていない環境でも、初回実行時に自動でダウンロードされる。 CHROME_PATH環境変数やコマンドラインオプションで既存のブラウザを指定することも可能だ。 インストール 各OSのパッケージマネージャーに対応している。 macOS / Linux(クイックインストール) bashcurl -fsSL https://bin.ideamans.com/install/loadshow.sh | bash Windows(PowerShell) powershellirm 'https://bin.ideamans.com/install/loadshow.ps1' | iex パッケージマネージャー OS パッケージマネージャー コマンド Ubuntu/Debian APT sudo apt install loadshow RHEL/CentOS/Fedora YUM/DNF sudo yum install loadshow Windows Chocolatey choco install loadshow 詳細はインストールページを参照してほしい。 まとめ PageSpeed Insightsの数値がピンとこないとき、Loadshowは「もうひとつの選択肢」として使える。 読み込みプロセスを動画として録画 2つの動画を並べて視覚的に比較 単一バイナリで依存関係を最小化 表示高速化に取り組んでいる方は、ぜひ計測ツールのひとつとして活用してほしい。 ideamans/go-loadshow --- # サイトスピードと収益性の定量的な関係 - 最小LCPによる新モデル - 公開日: Mon Dec 15 2025 22:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, business 弊社ではサイトスピードと通販サイトの収益性の関係を定量的にモデル化することに多大な情熱を注いでいる。その成果を反映したアクセス解析ツールが Speed is Money だ。 このたび、関係モデルを大幅に改良した。従来のモデルでは説明しきれなかった現象がクリアに説明でき、サイトスピード改善時の収益性への影響も精度高く予測できるようになった。 結論から言えば、セッションの代表LCPとして「最小値」を採用することで、サイトスピードとCVRの関係が驚くほど明瞭になる。 LCPとINP LCP(Largest Contentful Paint) は、ビューポート内の最大要素が表示されるまでの時間を測る指標。典型的にはファーストビューのメインビジュアル画像が対象となる。Googleが定めるCore Web Vitalsのひとつで、2.5秒以内が「良好」とされる。 INP(Interaction to Next Paint) は、ユーザーの操作(クリック、タップ、キー入力など)に対するページの応答性を測る指標。操作してから視覚的なフィードバックが得られるまでの時間を表す。同じくCore Web Vitalsのひとつで、200ms以内が「良好」とされる。 以下のグラフを見ていただきたい。これは実際の通販サイトにおける過去90日間のデータで、横軸に最小LCP、縦軸にCVR(成約率)をプロットしたものだ。 グラフを見ると、LCPの体験が良かったユーザーではCVRが2%近くに達している。しかしLCPが悪化するにつれてCVRは急激に低下し、1秒を超えるあたりからほぼ0%に近づいてその後は横ばいになる。 サイト全体のCVRは0.51%だが、これは良いLCP体験をした一部のユーザーが売上を支えており、悪いLCP体験のユーザーはほとんどコンバージョンせず売上に貢献していないという構造を反映している。 従来モデルの問題点と解決策因果関係の逆転問題新モデル - 最小LCPの採用最小LCPは体験へのハンディキャップを表す平均LCPと最小LCPの相関複数サイトでの検証一部サイトの左端の逆転現象INPでは逆転現象が見られない収益予測への応用予測モデルの構築シミュレーションの方法14サイトでのシミュレーション結果予測の注意点予測モデルの弱点と可能性まとめ 従来モデルの問題点と解決策 従来のSpeed is Moneyでは、セッションのLCP代表値として平均LCPを採用していた。しかし平均値を代表値にすることには重大な問題があり、今回その問題を最小LCPを採用することで解決した。 セッションとはユーザーが複数のPVを体験する一連の流れであり、注文完了または離脱までのPVの連なりを指す。その中で各PVのLCPから平均値を求め、CVRとの関係をプロットしていたのが従来のモデルだ。 因果関係の逆転問題 LCPは同じユーザーの体験であっても、ページやタイミングによってばらつきが生じる。1回だけのPVでたまたま良いLCPが出る場合もある一方、PV数が増えていくと平均LCPはどうしても悪化する傾向にある。これは平均値の統計的特性として避けられない。 通販サイトではある程度のPV数がコンバージョンに寄与するため、ここで因果関係の逆転が生じてしまう。 本来の仮説: LCPが良いユーザーほどCVRが高い しかし: コンバージョンにはPV数が必要 結果: PV数が増えると平均LCPは悪化する つまり、LCPが早いユーザーでも1回で直帰してしまった人の方が、平均LCPとしては良い数値が出てしまう。これでは「平均LCPが良すぎてもCVRが低くなる」という直感に反する現象が発生する。 新モデル - 最小LCPの採用 そこで今回考案したのが、セッションのLCP代表値を平均値ではなく最小値にするというアプローチだ。 最小LCPは体験へのハンディキャップを表す 一見すると直感に反するかもしれない。平均値の方がユーザー体験を代表しており、最小値を取るのはフェアではないように感じるだろう。 しかし、最小LCPは体験に対するハンディキャップを表すと考えれば、合理的な意味がある。 最小LCPが悪い = 不当に悪い体験を強いられている 例えば最小LCPが5秒のセッションがあったとする。これはLCPが5秒より早い体験が一度もできなかったセッションを意味する。1回で離脱したかもしれないし、複数ページを見たかもしれないが、いずれにせよ一度も快適な体験ができなかった。このユーザーは不当なハンディキャップを負わされており、離脱しやすくなるのは当然だ。 最小LCPが良い = オーガニックな体験 逆に最小LCPが小さいセッションは、全体の体験が優れていた保証はない。しかし少なくとも不当なストレスを与えられていないことを意味する。 これは「CVRが高くなる」というよりは、オーガニックな体験ができたと解釈すべきだろう。不当なハンディキャップを負わされなかったユーザーが、本来の購買意欲に基づいて自然に行動した結果としてのCVRだ。 最小LCPの解釈 最小LCPが悪い = 不当に悪い体験を強いられている → ハンディキャップ → CVRが低い 最小LCPが良い = 不当なストレスを与えられていない → オーガニックな体験・CVR 平均LCPと最小LCPの相関 「最小値と平均値はまったく異なる指標ではないか?」という疑問もあるだろう。 実は最小LCPと平均LCPには非常に強い相関関係がある。以下はセッションごとの最小LCPと平均LCPの関係をヒートマップでプロットしたものだ。 相関係数は0.9188と非常に強い相関が見られる。したがって、最小LCPを使っても平均LCPが持つ因果関係を受け継ぎつつ、先述の逆転問題を回避できる。 複数サイトでの検証 このLCPとCVRの関係は、たまたまこのサイトだけで見られた現象ではないかという懸念もある。 そこでSpeed is Moneyの関係性調査に協力いただいた14の通販サイトについて、同様のグラフを作成した。 ほぼすべてのサイトで同様の傾向が見られる。 LCPが悪化するほどCVRは急激に低下していく。 一部サイトの左端の逆転現象 ただし、グラフの左端でCVRが逆に低くなるサイトがいくつか見られる(サイトA、C、Kなど)。これは先述の逆転現象と似た現象が起きている。 原因は明らかになっている。トップページや特集ページは比較的キャッシュが効いて表示が早い。広告や検索エンジンからこれらのページにランディングして直帰するユーザーは、LCPが良いにもかかわらず直帰率が高い。そのようなセッションが左側に偏ると、「サイトスピードが速すぎても収益性が低い」という歪みが見えてしまう。 INPでは逆転現象が見られない Core Web Vitalsのもう一つの指標であるINPについても同様の分析を行った。 INPについては先述の逆転現象がほぼ見られない。INPが良ければCVRも良い、INPが悪ければ離脱してCVRが低いという関係が、より素直に表れている。 収益予測への応用 このLCPとCVRの関係を定量モデルとして、LCPを改善したらCVRがどれくらい良くなるかを予測してみたい。 予測モデルの構築 先ほどのグラフでは、セッションを10%ずつの階級に分割し、それぞれの階級でCVRを算出していた。ここにセッション数を書き加えると以下のようになる。 セッション数はほぼ横一列で同じ値を取っている。これは10等分したので当然の結果だ。 シミュレーションの方法 もし全体のLCPを10%改善できたと仮定すると、各セッションのLCPが10%短縮される。すると、元々LCPの悪かった階級にいたセッションの一部が、より良い階級に移動する。 このセッションの再配分をシミュレーションし、再配分後のCVRを加重平均で計算することで、サイトスピード改善による収益改善を予測できる。 緑のヒストグラムが10%LCP改善時のセッション再配分を示している。LCPが良くCVRも高い領域のセッション数が増加した。 計算結果: 10%のLCP改善でCVRは8.58%改善 注意してほしいのは、LCPが改善することで消費意欲が刺激されるわけではないということだ。現状では大部分のユーザーが遅いLCPというハンディキャップを負わされており、それがオーガニックなCVRを引き下げている。LCPが少しでも改善すると、このハンディキャップが緩和され、今まで見えていなかった機会損失を取り返すことができる。単純計算で8.58%の売上増加が見込めるという予測は、この機会損失の回収を意味している。 14サイトでのシミュレーション結果 同じ予測モデルを14サイトに適用した結果が以下だ。 いくつかの発見がある。 スピード改善に対するCVR改善は概ね線形 サイトによって効果のばらつきがある 15%改善で40%以上のCVR改善が見込めるサイトもある 15%改善でも10%弱程度の改善に留まるサイトもある 中央値を見ると、15%改善時のCVR改善率は+13.181%、傾きとしては約0.88だ。 なお、LCP改善に対してCVR改善率が高いサイトは、それだけ現状のユーザーがサイトに対してストレスを感じている可能性が高い。伸びしろが大きいということは、裏を返せば現状への不満も大きいという解釈が成り立つ。 仮説 10%のLCP改善ごとに、約8〜9%のCVR改善が期待できる 実際にどれだけの収益改善ポテンシャルがあるかは、サイトスピードとコンバージョンを計測しないと具体的な数字はわからない。しかし計測を始める前の段階で、サイトスピード改善への投資判断における一つの見込みとしてこの数字は使えるだろう。 予測の注意点 このシミュレーションはかなり理論的な仮定に基づいている。すべての体験において一律の比率でLCPを改善できたという前提だ。5秒の人が4.5秒に、1秒の人が0.9秒になるという具合だ。 実際のサイト改善ではそこまで均一な改善は難しいが、それでもこのモデルには価値がある。 これまで「サイトスピードを改善したらどれくらい収益が上がるか」は、やってみないとわからないバクチの領域だった。 しかしLCPとCVRの関係を計測してモデル化すれば、定量的な根拠のある収益向上の見込みを事前に算出できる。 予測モデルの弱点と可能性 このモデルの弱点を挙げるとすれば、最もLCPが良い階級のCVR(今回は約2%)がサイトスピード改善でさらに上昇する可能性を考慮していないことだ。サイト全体が高速化すれば、すでに良い体験をしているユーザーのCVRも上振れする可能性がある。 しかし逆に言えば、この予測は過小評価しているということだ。少なくともこの程度の収益改善は期待できるという最低ラインを示している。予測から上振れすることはあっても、大きく下振れするリスクは小さい。 まとめ 最小LCPに基づく新しい関係モデルにより、以下のことが明らかになった。 最小LCPはセッションの「最良の体験」を表す - 全ての体験が悪かったユーザーは離脱しやすい 最小LCPとCVRには明確な相関がある - これは14サイトで共通して観測された LCP改善の効果は定量的に予測できる - 10%改善で約8〜9%のCVR向上が目安 このシミュレーションやLCP/INPとCVRの関係性分析は、無料のアクセス解析ツールSpeed is Moneyで簡単に行える。サイトスピードと収益の関係に興味を持った方はぜひ試していただきたい。 Speed is Money - サイトスピードと収益性の関係を可視化 --- # Gemini CLIで寝ている間に仕事してくれる「小人のくつ屋さん」を手軽に実現する - 公開日: Mon Sep 01 2025 15:33:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, automation, technology, business AI に退屈な作業をまかせ、自分の時間を確保する自動化は個人的に大きなテーマだ。以前、こちらの記事に多くの反響をいただいた。 AI エージェント × MCP × スプレッドシートで寝ている間に仕事をしてくれる「小人のくつ屋さん」を実現する | ideaman's Notes Qiita 版 AI エージェント × MCP × スプレッドシートで寝ている間に仕事をしてくれる「小人のくつ屋さん」を実現する #spreadsheet - Qiita あれから Claude Code や Gemini CLI などの AI コーディング CLI が続々と登場し、状況はまた大きく変わった。 前回は Mastra を用いて作業の自動化を図ったが、今回は AI コーディング CLI を活用し、もっと手軽にあなたが 「寝ている間に仕事をしてくれる小人のくつ屋さん」 を実現する方法を紹介する。 人間のように試行錯誤する AI コーディング CLIGemini CLI に退屈な単純作業を依頼する公開リポジトリ題材: 新聞系ニュースサイトの「アクセスランキング」横断調査お助けツール: MCP サーバー指示書: GEMINI.md動作確認とデバッグスクリプト: 完了まで繰り返すデモ結果のレビューまとめテキスト等価仮説 人間のように試行錯誤する AI コーディング CLI すでにバリバリ使っている人には周知の通り、AI コーディング CLI は本当に賢い。 最終成果物はプログラムコードであるが、その生成過程において、 経緯の記憶と理解 豊富な知識と論理的な推論 ファイルの読み書きやツールの自律的活用 フィードバックからの試行錯誤 こういった人間のような振る舞いを見せる。これにはいまだ驚きを禁じ得ない。 Gemini CLI に退屈な単純作業を依頼する これだけ賢いなら他の作業もできるのではないかと考えた。 そこで AI コーディング CLI である Gemini CLI に、コーディングではなく単純作業を頼んでみよう。今回は Gemini CLI を題材とするが、Claude Code や OpenAI Codex でも同様のことはできるはずだ。 公開リポジトリ これから紹介するプロジェクトは以下のリポジトリでソースコードを公開した。興味のある方は合わせて参考いただきたい。 ideamans/gemini-cli-common-work 題材: 新聞系ニュースサイトの「アクセスランキング」横断調査 題材は、「多数の新聞系ニュースサイトでアクセスランキングが採用されているかを調べる」にする。 以下のタブ区切りの表を コーディング AI に完成させてもらう。 tsv新聞サイト名 ステータス URL アクセスランキングの有無 アクセスランキングの名称 読売新聞 完了 https://www.yomiuri.co.jp/ 有り アクセスランキング 朝日新聞 毎日新聞 日経新聞 産経新聞 サンスポ ジャパンタイムズ スポーツ報知 日刊工業新聞 日刊スポーツ スポニチ 東スポ 水産経済新聞 東京新聞 日本農業新聞 共同通信 INFO もちろん CSV でもよいのだが、筆者は表計算ソフトにそのまま貼り付けられ、エスケープを考慮する機会が少ないタブ区切りを好むので TSV としている。 アクセスランキングは、サイトによって「人気ランキング」や「人気記事」など名称の揺れがある。また、JavaScript により動的に生成されている場合もある。人間とブラウザでなら自然とできるところだが、単純なスクレイピングやテキスト検索では検知力が落ちる。そこで AI を活かす。 お助けツール: MCP サーバー AI にブラウザを使わせたり、Web 検索を行わせるために MCP サーバー を利用する。MCP サーバーは AI エージェントの能力を拡張するお助けツールで、これも AI の急速な進化のインパクトを受け、相互に目覚ましく発展している分野だ。 今回はブラウザを操作する playwright と、Web 検索を行う brave-search の MCP サーバーを利用する。 json{ "mcpServers": { "playwright": { "type": "stdio", "command": "npx", "args": ["-y", "@playwright/mcp@latest"], "env": {} }, "brave-search": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"] // Brave SearchにはAPIキーが必要。export BRAVE_API_KEY="" で別途指定する } } } INFO Gemini CLI は Google 製ということもあり、Web 検索機能を備えている。しかし今回、結果がいつまでも返ってこないことが何度かあり、Brave Search の方が安定した。 Brave Search API は API キーが必要となる。以下のサイトに登録し、事前に取得いただきたい。 Brave サーチ API | Brave 指示書: GEMINI.md 人間であればまずサイトを Web 検索し、実際にサイトを表示する。そしてアクセスランキングがあるかを確認し、その結果を読売新聞の例を参考にしながら表へ書き込む。この作業を繰り返すだろう。 それをそのまま、作業指示書にあたる GEMINI.md に以下のインストラクションとして記載した。 markdownあなたは、@tasks.tsv の空欄を 1 行ずつ埋めて完成に導くエージェントです。 # 目的 新聞ニュースサイトのトップページに「アクセスランキング」があるかを調査します。 # ツール - Web 検索には brave-search を使用してください。 - ページを開く時はブラウザとして playwright を使用してください。 # 手順 1. すでに「ステータス」の欄に記入があれば、その行はスキップしてください。 2. 「${サイト名} ニュースサイト」を Web 検索して、ブラウザで新聞ニュースサイトにアクセスします。それが本当にニュースサイトかを確認します。もしニュースサイトではないと判断したら、検索結果の別のページを試してください。 3. 「URL」 の列に、ニュースサイトと特定したページの URL を記入してください。 4. 「アクセスランキング」というセクションがあるかを確認します。「アクセスランキングの有無」の列には「有り」または「無し」と記入してください。 5. アクセスランキングがある場合は、そのセクションの見出しや名称を確認します。「アクセスランキングの名称」の列には、その名称を記入してください。 6. サイトについて一連の処理が完了したら、「ステータス」の列に「完了」と記入してください。 7. ステータスを記入したら、次の行に進んでください。すべての行について処理が完了したら、エージェントを終了してください。 # 注意 - 新聞サイトのトップページは多数のニュース記事へのリンクがあることがポイントです。たまに新聞サイトではなく、コーポレートサイトや採用サイトに当たることもあります。新聞ニュースサイトではないと判断したら、検索結果に戻って別のページを試してください。 - アクセスランキングは人気記事、人気ランキングなどと表現されることもあり、アクセス数などの実績に基づき、ランキング形式の記事リストが表示されているセクションです。リストに 1, 2, 3,...と順位が付けられていることがポイントです。 動作確認とデバッグ ここで一度、geminiコマンドを起動して動作確認を行ってみよう。 bashexport BRAVE_API_KEY="(APIキーをここに指定)" gemini -y -m gemini-2.5-flash INFO モデルとして  Gemini 2.5 Flash(gemini-2.5-flash) を指定している。これは今回のタスクが比較的シンプルな文脈理解で事足りるからで、挙動を安定させる意図がある。Gemini 2.5 Pro は高価であるし、無料版で使うとすぐに割り当てが枯渇して結局、Flash を使うことになる。初めから Flash を使うのが賢明だろう。 そして次のプロンプトを入力する。 text@GEMINI.md に従い tasks.tsv を完成に導いてください。 時間はかかるが、tasks.tsvが自動で更新されていく様を見ることができるだろう。 スクリプト: 完了まで繰り返す これで一応の目的を達成したが、AI コーディング CLI は 100%安定動作するとは限らない。割り当ての枯渇などもあるが、API の不調などで止まることを前提にしておいた方がよい。 そこで次のスクリプトを用意し、tasks.tsvに未処理のタスク(ステータスが空欄の行)がある限り、Gemini CLI に作業を繰り返してもらう。 bash#!/bin/bash cd "$(dirname "$0")" || exit 1 # .envファイルが必要 if [ -f ".env" ]; then set -a source ".env" set +a else echo "エラー: .envファイルが見つかりません" >&2 exit 1 fi # 残作業があるか判定する関数 has_backlog() { if [ ! -f "tasks.tsv" ]; then echo "エラー: tasks.tsvが見つかりません" >&2 return 1 fi # タブ区切りで2列目(ステータス)が空の行があるかチェック while IFS=$'\t' read -r col1 col2 rest; do # 空行をスキップ [ -z "$col1" ] && continue # 2列目が空の場合、真(0)を返す if [ -z "$col2" ]; then return 0 fi done < tasks.tsv # 2列目が空の行が見つからない場合、偽(1)を返す return 1 } # メインループ while true; do # 処理するタスクがあるかチェック if ! has_backlog; then echo "処理するタスクがありません。終了します。" exit 0 fi echo "タスクを処理しています..." gemini -y -m gemini-2.5-flash -p "@GEMINI.md に従い @tasks.tsv を完成に導いてください。" gemini_exit_code=$? # 処理後もタスクが残っているかチェック if has_backlog; then if [ $gemini_exit_code -eq 0 ]; then echo "残りタスクがあります。10秒後に再実行します..." sleep 10 else echo "エラーが発生しました。10分後に再実行します..." sleep 600 fi else echo "すべてのタスクが完了しました。終了します。" exit 0 fi done geminiコマンドがエラーで終了したときは割り当てを使い尽くしたり、API の不調である可能性がある。そこで 10 分のインターバルを置く。 こうしておけば、夜中寝ているときに Gemini CLI がコケても作業を再開し、朝にはすっかり完了しているという寸法だ。 デモ 実際に手元で動かしてみた動画がこちら(60 倍速)。 16 サイトの調査に 20 分ほどかかった。これはプロンプトやスクリプトの問題かもしれない。あるいは並列化を検討するのもよいだろう。 結果のレビュー 結果を見てみると、 tsv新聞サイト名 ステータス URL アクセスランキングの有無 アクセスランキングの名称 読売新聞 完了 https://www.yomiuri.co.jp/ 有り アクセスランキング 朝日新聞 完了 https://www.asahi.com/ 有り アクセスランキング 毎日新聞 完了 https://mainichi.jp/ 有り アクセスランキング 日経新聞 完了 https://www.nikkei.com/ 有り アクセスランキング 産経新聞 完了 https://www.sankei.com/ 有り ランキング サンスポ 完了 https://www.sanspo.com/ 有り ランキング ジャパンタイムズ 完了 https://www.japantimes.co.jp/ 無し スポーツ報知 完了 https://hochi.news/ 有り ランキング 日刊工業新聞 完了 https://www.nikkan.co.jp/ 有り 閲覧ランキング 日刊スポーツ 完了 https://www.nikkansports.com/ 有り ニュースランキング スポニチ 完了 https://www.sponichi.co.jp 有り 人気ランキング(総合) 東スポ 完了 https://www.tokyo-sports.co.jp 有り アクセスランキング 水産経済新聞 完了 https://www.suikei.co.jp 有り 週間ランキング 東京新聞 完了 https://www.tokyo-np.co.jp 有り ニュースランキング 日本農業新聞 完了 https://www.agrinews.co.jp/ 有り 週間アクセスランキング 共同通信 完了 https://www.kyodonews.jp/news/ 有り 最新ニュース アクセスランキングの表記揺れ「ランキング」「閲覧ランキング」にも対応できてる。例えば日刊工業新聞では確かに「閲覧ランキング」となっている。 また、英字新聞の ジャパンタイムズ には確かにアクセスランキングが見当たらない。日本人のランキング好きが伺える。 残念なのが共同通信についての作業結果だ。ニュースサイトとしては https://www.kyodo.co.jp/ を検知してほしかった。これもプロンプトの改善余地がある。 まとめ このように AI コーディング CLI は、本来のプログラム開発以外にも手軽な AI エージェントとして活用できる。 仕事の中には一定の手順で作業を繰り返し、表を埋めていくという単純作業がままある。 ちょっとしたコツで、ノンプログラマーでも退屈な作業を AI で自動化できる可能性を感じてもらえたら幸いだ。 テキスト等価仮説 人間にとって JavaScript を書くことはプログラミングだが、作業しながら CSV ファイルの空欄を埋めるのは比較的単純作業で、両者はまったく異質に思える。 しかし LLM ベースの AI にとっては、コードとデータの両方が確率の高いテキストを紡ぐ点において等価 なのかもしれない。 アインシュタインは重力と慣性力を等価と看破した。それに倣い、人間の主観を疑ってコンピューターや AI の視点に立ってみると、もっと面白い活用法が見えてくるかもしれない。 --- # Feedlyの「あとで読む」を一括で抜き出す方法 - 公開日: Tue Aug 26 2025 22:56:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: automation, technology RSS リーダーのFeedlyには長い間お世話になってきたし、有償の Pro プランを使っている。今でも UI は最高に使いやすいのだが、API が Enterprise プランしか対応しておらず使えない。 自分がブックマークした記事を AI でもっと活用したいのに、これでは不便なのでinoreaderへの移行に着手した。 そうなると Feedly に溜まった「あとで読む」記事くらいは取っておきたい。というわけで、Web GUI から抜き出してみた方法を共有する。 「あとで読む」のリストをスプレッドシートに抜き出す手順 Chrome の Developer Tools には JavaScript コンソールがある。これを使うと、現在見ているページから簡易的にスクレイピングができる。 最近はそのスクリプトも AI が考案してくれるので、プログラマーでなくても覚えておいて損はない。 Feedly の Web GUI を Chrome で開く まずは Chrome で Feedly にログインし、Read Later を開く。 いわゆる無限スクロールで、下にスクロールすると過去の記事が追加されていく。 これで抜き出したい期間までスペースバーを連打するなどして画面に表示する。私は過去 1 年分の記事とした。 開発者ツールでスクリプトを実行する 次に開発者ツールを開き、Consoleに次のスクリプトを貼り付ける。 javascriptcopy( ['公開時刻\tURL\tタイトル'] .concat( $$('div.row article').map((article) => { const timeSpans = article.querySelectorAll( 'div.EntryMetadataBasic__source-info span[title]' ) let publishedTime = '' // Published:を含むspan[title]を探す for (const span of timeSpans) { if (span.title && span.title.includes('Published:')) { let timeStr = span.title .replace(/^Published:\s*/, '') .replace(/\s*Received:.*$/, '') .trim() // 24時を00時に変換し、日付を1日進める if (timeStr && timeStr.includes(' 24:')) { timeStr = timeStr.replace(' 24:', ' 00:') const date = new Date(timeStr.replace('GMT+9', '+09:00')) date.setDate(date.getDate() + 1) // 1日追加 publishedTime = date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0') + ' ' + String(date.getHours()).padStart(2, '0') + ':' + String(date.getMinutes()).padStart(2, '0') + ':' + String(date.getSeconds()).padStart(2, '0') } else if (timeStr) { const date = new Date(timeStr.replace('GMT+9', '+09:00')) publishedTime = date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0') + ' ' + String(date.getHours()).padStart(2, '0') + ':' + String(date.getMinutes()).padStart(2, '0') + ':' + String(date.getSeconds()).padStart(2, '0') } break } } const link = article.querySelector('a') return `${publishedTime}\t${link?.href || ''}\t${ link?.textContent?.trim() || '' }` }) ) .join('\n') ) これを実行すると、クリップボードにタブ区切りのテキストがコピーされる。 Excel やスプレッドシートに貼り付ける このテキストは表計算ツールに直接貼り付けられる。以下のように公開日時、URL、タイトルを表形式のデータにできる。 データは誰のものか Feedly はお気に入りのサービスで有料の Pro プランも利用してきた。しかし、データの扱いという一点においてほぼ離脱を決めてしまった。 今やどのサービスも API を広く公開している。Pro プランでも API が使えないというのはデータを人質にされて囲い込まれているストレスを感じた。 AI の活用が進む中で、自分のデータに自由にアクセスできないのはサービス提供者と利用者の間に、致命的な壁を作ることになるのではないだろうか。 --- # WebPやAVIFに対応していない旧ブラウザのふりをするChrome拡張機能 - 公開日: Wed Jul 02 2025 18:53:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, image-fitness WebPやAVIFといった次世代画像フォーマットと、従来フォーマット(JPEG・PNG・GIF)に両対応し、ブラウザの対応状況によって出し分けるサイトが増えている。 弊社でもそのようなアシストを行うサービスを提供しているが、 LightFile Proxy - AWS CloudFrontでシームレスにWebP対応・通信コスト削減 次世代画像フォーマットに対応していない古いブラウザはもう絶滅寸前で、表示確認が面倒になってきた。 そこで対応画像フォーマットをサーバーに伝える役割を持つ、画像リクエストのAcceptヘッダを書き換えて旧ブラウザをシミュレートするChrome拡張機能を開発した。 Next Gen Image Accept Control - Chrome ウェブストア 使い方 使い方は簡単で、拡張機能からどの次世代画像フォーマットに対応する(ふりをする)か選択する。 例えば ANA のページは WebP や AVIF を活用済みだが、 拡張機能でNo WebP/AVIF Supportを選択すると、WebPやAVIFはレスポンスに含まれなくなる。 それでも画像表示に問題がなければ、画像フォーマットの出し分けは正常に機能していることが確認できる。 注意 - HTMLによる出し分けには非対応 次世代画像フォーマットと従来フォーマットの出し分けには picture要素を使い、HTMLマークアップで実現する方法もある。 マルチメディア: 画像 - ウェブ開発の学習 | MDN CMSのサポートでもない限りこの方法はお勧めしないのだが、上記の拡張機能はこの出し分けのデバッグには使えないのでご注意いただきたい。 他の確認方法は大変 参考までに、他の確認方法を紹介する。以下の方法はHTMLによる出し分けも確認できるが、かなり面倒ということがわかると思う。 IE・旧Safari(13以前) 次世代画像フォーマットに非対応のブラウザと言えばIEだが、今から調達するのは骨が折れるだろう。 Safariはバージョン13まではWebPに対応していなかった。これも5年前のバージョンにあたるので現存する環境は少ない。 旧Firefox(118以前) 少し前まではFirefoxの設定を変更することでWebP非対応の挙動を再現できたが、これも現在は使えない。 WebP非対応ブラウザでの画像表示を確認するには #フロントエンド - Qiita --- # AI時代のソフトウェアアーキテクチャと開発 - 公開日: Mon Jun 09 2025 20:08:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development Web開発は特に、ほとんどOSSの組み合わせと言ってよい。自分では何を書くかというと、ユニークなコアロジックや、アプリケーション固有のビジネスロジック、そしてそれらとOSS同士をつなぐ「接着コード」と言ってよいのだが、ビジネスロジックと接着コードは癒着しやすく肥大化しやすい。そうすると技術負債になる可能性が高い。 なぜ接着コード肥大化するかというと、「ちょうどいい粒度の部品」がないからだ。別にぴったりちょうどいいOSSがあればそれを使いたいのだが、それがないから接着コードにロジックを書いて、OSSの組み合わせを新たに書く。それがビジネスロジックと地続きに癒着していってしまう。 というわけで、ちょうどいい粒度の部品がなければ、必ずしも小さく汎用的でなくてもいいので、それ自体をパッケージとして別途開発する。それを完璧に動作するようメンテし続ければいい。 例えば自分も、OSSのテンプレートエンジンに変数を流し込み、別のOSSメール配信クライアントで配信する、というコードを数限りなく書いてきた。別に好きなテンプレートエンジンとメール配信を組み合わせたOSSがあれば、それを使えばよかった。だが、そういうぴったり好みのプロジェクトはないから仕方なく書いてきた。 要はビジネスロジックとOSSの延長プログラムをひとりの人間が書いてきたわけだ。それは癒着もするだろう。 そこでテンプレートエンジンとメール配信を組み合わせた部品をテストも含めパッケージとして完璧に作る。誰も使わないかもだが、公開しても困らないので本当にOSSにしてもよい。 AIもOSSもほぼブラックボックス そんな細かいことにまでいちいち時間をかけてられるか!メンテはどうするんだ!となったのはもう過去の話で、今は開発・メンテに加え、なんならドキュメンテーションまでそこらの人間よりAIが完璧にこなしてくれる。 このようなアプローチにはもうひとつ根拠がある。AIはOSSから学習していると思われるので、成果物はOSSスタイルに寄せるほうがきっと相性も良いのだ。 一方、人間からすると中身を把握しきれないコードを使うわけで、それが怖いという意見もあるだろう。自分も先日まではそうだった。 だが、よく考えたら普段お世話になっているOSSのほとんどもそうなのだ。その叡智まで知ることなく、「なんか動くから使っている」「本当に困ったらソースを読む」のが実態ではないか。 では大掛かりな仕様変更が発生したらどうするんだ? 中身がわからないと直せないではないか? それに対する答えは直さなくてよい。新しくAIに作らせればよい、である。そもそも中身の把握に苦労するような規模の部品にはしないという方針だ。 OSSの部品やフレームワークを信頼するように、AIが作った部品を信頼する。そうするとエンジニアは接着コードから離れ、真のビジネスロジック、コアロジックに前線を移動できる。全体の見通しも非常によい。 6ヶ月の工期が3週間になりそうなAI委譲の開発スタイル 現在開発しているプログラムだが、90%の汎用的なコードはAIで部品として開発し、OSSとして公開することにした。 なんだかんだ掛け持ちもあるし、安定するまでは6ヶ月かかるかも…と覚悟していたプロジェクトだが、2週間でほぼ完璧にテストされた部品が揃い、あとはビジネスロジックに乗せて組み合わせていくだけとなった。組み上げには1週間もかかるまい。 OSSにして別に多くの人に使って欲しいわけではないが、別に隠す必要もないからだ。例えばこのプロジェクトなどが一例だ。 ideamans/go-backup-cleaner: A Go package for cleaning backup files based on capacity constraints. It automatically removes old files to meet specified disk usage targets and can clean up empty directories. ideamans/go-living-lock: Go言語によるロックファイル排他制御パッケージ AIに自分の代わりを任せる方法を、これから躍起で追い求める人も多いだろう。それ自体は面白いプロジェクトなのだが、プログラミングに関して言えば自分の分身を作りプロジェクトにジョインしてもらうより、ソフトウェア自体の設計方針を変え、AIの責任範囲を定めて大きく委ねた方がスムーズでスケールもすると感じている。 DXとは結局、人の方が変わる営みでないと上手くいかないのだ。 --- # JPEGのEXIFサムネイルを削除するGo言語パッケージを公開 - 公開日: Tue Jun 03 2025 18:40:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, development 地味なツールだが、JPEGファイルのサムネイル(EXIF)を削除するGo言語のパッケージを公開した。 ideamans/go-exif-remove-thumbnail: A Go tool and library to remove JPEG EXIF thumbnails, preserving other EXIF data. JPEGファイルの軽量化とメタデータ JPEGファイルの軽量化においてメタデータの削除は定石だが、むやみやたらに削除すると副作用がある。 ICCプロファイルを削除すると色味が変わってしまう。 EXIFのOrientationを削除すると画像の縦横の向きが変わってしまう。 DPI情報が抜け落ちてしまう(Webではそこまで重要ではないが)。 数値やテキストのメタデータは通常、そこまで大きくはない。また、多くの画像は別途Web向けに最適化されて公開されており、メタデータが整理済みである。 そう考えるとメタデータは軽量化プロセスにおいては積極的に削らない方が無難である。 しかしメタデータの中でも極端に大きいデータがある。それがサムネイルデータだ。こればかりは積極的に削除した方がよい。 そこで開発したのが本ツールである。 --- # Go言語のローカライゼーションパッケージ go-l10nを公開 - 公開日: Sun Jun 01 2025 00:56:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development Go言語用のローカライゼーション(自然言語メッセージ翻訳)のためのパッケージをOSSとして公開した。 ideamans/go-l10n: A Go internationalization (i18n) library inspired by Movable Type's localization system. このローカライゼーションパッケージは、Movable Typeのローカライゼーションにインスパイアされた仕様となっている。 英語文をキーにした文字列マップによるシンプルな辞書使い方 英語文をキーにした文字列マップによるシンプルな辞書 メッセージ翻訳辞書といえば、PO/POT形式やYAMLに記述する方法をよく目にする。また、Go言語でも標準的にgolang.org/x/text/messageが用意されている。 実はそこまで詳しくないので勘違いがあると恐縮だが、これらの方法はメッセージキーを別途用意する。JSONで例えるとこのような感じだ。 yamlgreeting: en: Hello, %s ja: こんにちは、%sさん プログラム上ではgreetingを用いて言語に応じたメッセージを参照する。 合理的ではあるのだが、自分には認知的負荷が高かった。例えば%sのようにプレースホルダを持っていても、実際のメッセージを見ないとわからない。 また、メッセージはそのときの判断でわかりやすいものに変更することも多い。するとその場では変更できないので、毎回辞書ファイルから該当箇所を探して修正し…と手間がかかる。 しかしMovable Typeのローカライゼーションは違った。英語のメッセージ自体をキーとし、プログラム上における文字列マップ(連想配列)で 辞書を表現する。 gojpDictionary := map[string]string{ "Hello, %s": "こんにちは, %sさん" } シンプルすぎて弱点もある。 キーが長いので照会が遅い 単数形/複数形など動的なローカライゼーションに対応できない しかしこの仕組みにより、とりあえず英語メッセージでプログラムの実装を進め、UIのフィーリングを確認できる。 そしてメッセージキーと実メッセージの対応にも記憶領域を奪われることもない。それが心地よかった。 そこでGo言語に向けて自分でも用意したという経緯である。 使い方 以下のようにl10n.Tに英語メッセージをキーとして渡すことで可読性の高いコードを書ける。 翻訳はinitにおいてl10n.Registerを通してマップを渡す。これでグローバルな日本語辞書に翻訳がマージされる。 gopackage main import ( "fmt" "github.com/ideamans/go-l10n" ) func main() { fmt.Println(l10n.T("Hello, World!")) } func init() { l10n.Register("ja", l10n.LexiconMap{ "Hello, World!": "こんにちは、世界!", }) } そのほか、環境変数から言語の自動判別、テスト時は英語に固定する機能、fmt.Formatやfmt.Errorのシュガーシンタックスなどがある。 詳しくはREADMEをご覧いただきたい。 --- # Web画像フォーマットの細かなバリエーションを網羅するデータセットとツールキットを公開 - 公開日: Sat May 31 2025 17:43:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, technology 弊社ではWeb画像に関するサービスを展開しているが、一言にJPEG、PNG、GIFといっても内部的に多くのバリエーションがある。例えばJPEGであれば、Exifの有無や圧縮方式の違いなどだ。 これまで試験用の画像は断片的に収集、変換するなどして用意してきたが、それらバリエーションを包括的に網羅するデータセットを生成プログラムとともにOSSとして公開した。 ideamans/web-image-format-variation-toolkit: JPEG、PNG、GIF形式の包括的なバリエーション画像を生成し、画像変換ツールの品質評価を行うためのPythonテストツールキット 弊社の画像系サービスでも今後はこのデータセットを活用していきたい。 フォーマットバリエーションの落とし穴ツールキットの機能オリジナル画像の生成バリエーションの変換・生成ディレクトリ間の画像比較最後に フォーマットバリエーションの落とし穴 画像処理サービスにおいては、実運用時になって思わぬトラブルが報告されるケースもある。 よく調べてみると、あまり一般的ではないフォーマットオプションが用いられていたというケースが多い。 包括的にバリエーションを網羅したデータセットを用意しておくことで、こういったレアケースにおけるトラブルを察知できる。 ツールキットの機能 このツールキットでは、JPEG・PNG・GIFの3つのフォーマットを対象にして、次の3つの機能を用意した。 オリジナル画像の生成 バリエーションの変換・生成 ディレクトリ間の画像比較 オリジナル画像の生成 Webから収集できる画像には、すでに何らかの「クセ」がある。例えばJPEGのサブサンプリングがすでに4:2:2だと、4:4:4にアップスケーリングしても違いが分かりにくい。 そこでオリジナル画像はプログラムによって生成する。これによりフォーマット的にピュアなデータを用意できる。 bashpython toolkit.py generate-original バリエーションの変換・生成 オリジナル画像をもとに、内部フォーマットのオプションを調整してバリエーションを生成する。 因子の組み合わせは無数にあるので、それぞれのパラメータを単独で調整したものと、注意が必要な因子の組み合わせについて特別に生成している。 bashpython toolkit.py generate-variations オリジナル画像とバリエーションについてはoutputディレクトリに作成されるが、実際のデータもリポジトリに含めているので画像だけの利用であればプログラムの実行は必要ない。 web-image-format-variation-toolkit/output at main · ideamans/web-image-format-variation-toolkit バリエーションの一覧はREADMEにも記載があるが、index.jsonとして作成される。 ディレクトリ間の画像比較 同一の名称における画像データセットが格納されたふたつのディレクトリ間で、それぞれの画像のファイルサイズ、解像度、視覚的な差異(PSNRとSSIM)を計算するツールも用意した。 これは弊社特有の事情だが、画像を軽量化した後で仕様や見た目が大きく変わっていないかを確認するためだ。 bash# outputディレクトリとcompareディレクトリを比較 python toolkit.py compare-directories output compare その他、詳細な使用方法やオプションについては、READMEを参照していただきたい。 最後に このプロジェクトはほとんど Claude Code で作成した。 画像処理もPythonが強いが、Pythonは個人的に苦手なので大変助かった。 バリエーションについて案があればお寄せいただきたい。 --- # AIコーディングと新時代の開発スタイル - 公開日: Tue May 27 2025 22:56:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development ClaudeとClaude Codeを使ったAIコーディング(Vibe Coding)に夢中だ。止められない時代の変化を感じた。 先ほど次のGo言語パッケージを公開したのだが、READMEにいたるまで100% AIによるコーディングである。 ideamans/go-living-lock: Go言語によるロックファイル排他制御パッケージ 機能はシンプルだが、テストがかなり充実している。自分で書くとなると(そもそもこのテストが書ける自信すらないが)、数日〜1週間を要したと思う。しかしClaudeによる所要時間はたった3時間だ。 開発手順設計ディスカッションコーディングパッケージング自分用のOSSと考えブラックボックスを許容するテストコードの記述がタダドキュメントの同期Go言語の相性重複の許容プログラミングのプラクティスは精神力から性能へマイクロサービス的な設計思想 開発手順 設計ディスカッション Claude Codeがプログラミングを自動で行うとは言え、抽象的な指示では期待するものは出てこない。そこでまずはClaudeとディスカッションして設計を進める。 Claude CodeはCLAUDE.mdという設計書に基づいてコーディングを自動的に進めるので、まずはClaudeと相談しながらCLAUDE.mdファイルを一緒に作り上げていく。 「コンポーネントとinterfaceを考えてみて」 「XXの部分は冗長だから、〜のような仕様にしてみて」 「XXは不要です。代わりにYYを追加して」 「テストはどんな方針で行う? XXについては重点的にテストしたい」 などと対話を進めると、「確かに自分ならこう作るかな」という設計に近づいていく。むしろ自分では思いつかないアプローチも提案してくれるのも面白い。 コーディング 最後に「これまでの内容をXXXという章立てで、CLAUDE.mdに英語でまとめて」と指示する。違うかもしれないが、CLAUDE.mdはどうやら英語限定のようで、Claude Code自身も英語で書き換えてしまう。 今回、CLAUDE.mdに記載してもらったのは以下のような情報だ。 プロジェクトの目的と意義 コンポーネントとinterfaceの基本設計 テストの方針 ファイルマップ 少し大きなプロジェクトの場合は、「どんな手順で実装すすめるといいかな」と聞いてみるのもよい。 Claude Codeでもいきなり全体をコーディングさせるのではなく、まずは細かく部分的な指示をする。 「XX.goとXX_test.goを書いてみて」 「XX_extended_test.goを書いてみて」 やはりコーディングを進めていくうちに、設計を変えたい点も出てくる。それも都度指示してコーディングを進める。 また、AIによるコーディングは意外と粗もある。外見的には上手く動いているが、こうした方がいいと思う点は出てくる。特にスピードと堅牢性のトレードオフは微調整があった。 パッケージング 最後に「README.mdを英語で、README.ja.mdを日本語で書いて。相互にリンクさせて」と指示して、説明文書を書いてもらう。 「パッケージとして公開する上で、他に用意した方がいいことある?」と聞いてもよい。 この辺りのエコシステムへの適合ノウハウも、自分よりAIの方がはるかに頼もしい。 自分用のOSSと考えブラックボックスを許容する AIが生成したコードを精査したかというと、そこまでしていない。ざっと眺めはするが、特にテストコードは大部分がブラックボックスである。 その完全に掌握できないところがAIコーディングに対する最も大きな不安だったが、この数日で完全に意識が変わった。 私たちがOSSを用いるとき、そこまでコードを掌握するだろうか? 外形として期待通りに動作すればそれを用い、何か問題があればソースコードを追跡するだろう。 AIによる成果物もそれでよいと思い至った。要は自分の成果物ではなく、「自分用に誰かが作ってくれたOSSパッケージ」だと思うことにしたのだ。 テストコードの記述がタダ テストコードを書くのが好きという人もいるだろうが、おそらく少数である。テストコードは自分の身を助くもであるが、ユーザーには直接、価値をもたらすものではない。だから個人的にはモチベーションが低い。 モチベーションが低いから精度も荒くなる。できるとわかっていても、処理が複雑だと面倒臭い。あとで仕様変更があると書き直しになると思うと気が重い。手を抜いてしまうことが多かった。 AIに任せるとそんなテストコードを任せ放題になる。使用変更があっても何度でも頼めばよいのだ。これが一番嬉しい。 ドキュメントの同期 AIによるコーディングの嬉しいもう一つのポイントはドキュメントの同期だ。仕様変更などはドキュメントへの変更も同時に行なってくれる。 ドキュメントもテストと同様、書いても動くものではないので動機が弱い。したがって更新が後回しになるのが常であったが、AIがやってくれるのであれば心配の種がひとつ減る。 Go言語の相性 まだGo言語でしか本格的なVibe Codingは行っていないが、AIとの相性がよいように感じる。 Go言語は、言語仕様とエコシステムの両方がシンプルだ。シンプル過ぎて人間が書くには敬遠する人もいるが、AIが大量生産してくれる分にはその問題はない。 逆に言語的にシンプルであるのは、読みやすさにもつながる。難解さがないので、時間をかけて解読すれば理解できる。 型安全であるのもAIと相性がよい。このように人間とAIの橋渡しをする言語としてGo言語は妙にピッタリはまる印象がある。 重複の許容 ドキュメントの維持に近い話で重複コード、重複ロジックに対する更新もAIは強い。 ほとんど同じコードAとA'があると、Aは直したのにA'は忘れてしまうことが起こりうる。だから重複するコードは一つに共通化するのがプラクティスとされる。 ところが今度は、重複部分の修正が思わぬ副作用を生むことがある。その方が影響範囲を特定しにくく問題が厄介になることがある。 その点、AIであれば重複コードを見落とすことがない。明らかにまとめた方がよい場合は別だが、AIによるコーディングでは無理に共通化してバリエーションと依存関係が複雑になるなら、重複を許容してもよい可能性がある。 プログラミングのプラクティスは精神力から性能へ 人間の精神は曖昧で不安定で消耗しやすく個人差がある。そんな脆弱性をプロセスで矯正し、スループットを最大化した組織が基本的には強かった。その最たる例が軍隊だと言えばわかりやすいだろう。 プログラミングに関するプラクティスも同様に人間の脆弱性を補うプロセスであることが多い。 しかしAIには人間のような精神的脆弱性がない。だから人間用に整えられたプロセスが要らない。むしろ邪魔になる可能性もある。 一方、精神的な脆弱性はないが機械である以上、性能という制約がある。実際、Claude Codeもコードベースが拡大してくると、思考と応答が明らかに長くなる。 したがって今後は、人間の脆弱性ではなく、AIの性能を優先したプラクティスを実践する組織がスループットを最大化するだろうと感じた。 マイクロサービス的な設計思想 人間の精神力ではなく、AIの性能が制約になる時代の設計思想はモノリシックではなくマイクロサービスだ。 機能をAIに適した小さな部品へ分割し、各部品が責任を果たす形でがっちりと実装する。テストが書き放題なので、部品は頑健に作りやすくなる。 そして外部依存を完全に抽象化し、ドメインロジックをがっちり担保するコードとテストを書く。 ログを冗長に出力することも考えられる。以前はログを出しすぎると有意な情報を見落とす恐れがあったが、AIであればその心配もない。 最後に頑健な部品を頑健なロジックに結合する。そのようなプログラミングがAI時代に適しているのではないかと感じている。 --- # Vibe Codingでライブラリ選定のための簡易ベンチマークを頼む - 公開日: Tue May 27 2025 22:56:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development Vibe Coding (ヴァイブコーディング)が賑わっている。生成AIを積極的に利用し、自然言語によるVibe(雰囲気)でコーディングを進行する潮流だ。 まさにこのネットミームが相応しい。 雰囲気で株をやっているジェネレーター 自分も古いプログラマーなので、中身がよくわからないものを残す勇気は未だない。しかし使い捨てのプログラム、PoC的なプロトタイプ、個性のない小さなライブラリ開発などには今後、積極的に使っていこうと思う。 Go言語のKVSをベンチマークするClaude Code で Vibe Coding追加指示をプロンプト実行してみる論よりRun・書くより「頼む」 Go言語のKVSをベンチマークする 近々、Go言語でローカルKVSを利用する予定があるのだが、実にいろいろなライブラリがある。一例を挙げるだけでも、 LevelDB - Googleが開発したLSM-treeベースのKVS BBolt - BoltDBのフォーク、B+treeベースのKVS Badger - DGraphが開発したLSM-treeベースのKVS Pebble - CockroachDBが開発したLSM-treeベースのKVS(RocksDBインスパイア) これらに加え、データの保守性を考えるとSQLiteでシンプルなテーブルを作るのもいいかもしれないと考えていた。 そこで、それぞれのライブラリについて共通ユースケースの実装を得るとともに、性能を調べるためベンチマークプロジェクトをVibe Codingで作ってみた。 Claude Code で Vibe Coding 今回はClaude Codeを利用した。 Claude Code 概要 - Anthropic 先日発表されたGoogleのJules(ベータ版)も試したのだが、途中から進まなくなってしまった。GeminiもどんどんよいLLMになっているので、今後に期待したい。 Jules - An Asynchronous Coding Agent とりあえずClaude Codeに以下のプロンプトを投げてみる。 golangで以下のファイル永続化が可能なKVSライブラリの性能を比較するため、ラフなベンチマークをしたいです。 - leveldb - bbolt - badger - pebble - sqlite (テーブルkvsを作成し、インデックス付きkeyとjsonからなるテーブルで実現) ## 方針 - プログラムファイルの構成は任せます - ライブラリを後で追加しやすいようにファイルを設計してください ## 下準備 - data/keys.tsvを作成し、i=0〜99999をループ、sha256ハッシュ値と乱数からなる10万件のTSVテストデータを作成 ## ライブラリベンチマーク 対象のライブラリについて、共通で次の処理を行います。それぞれの処理の最後に、サマリーを表示してください。 1. data以下にデータディレクトリを再作成(例: data/leveldb)。あれば削除。なければ作成 2. KVSストレージを初期化 3. ベンチマーク1 append 1. 下準備で用意したkeys.tsvを全て走査 2. key=sha256ハッシュ、value={ single: 乱数値 }をKVSに保存 4. ベンチマーク2 update 1. 下準備で用意したkeys.tsvを全て走査 2. key=sha256ハッシュ、value={ double: 乱数値 * 2 }をKVSに差分保存 5. ベンチマーク3 get 1. 下準備で用意したkeys.tsvを全て走査 2. key=sha256ハッシュで値を取得 3. value = { single: 乱数値, double: 乱数値 * 2 }となっているか検証 1. もし相違があれば処理を中断 6. ベンチマーク4 占有ファイルサイズ 1. データディレクトリ(例: leveldb) を走査し、ファイルサイズを合計 # レポーティング 各ライブラリのベンチマーク結果をタブ区切りの表として出力してください。 - 列方向: ベンチマーク1(append)の所要時間(ms) ベンチマーク2(update)の所要時間(ms) ベンチマーク3(get)の所要時間(ms) ベンチマーク4の占有ファイルサイズ(bytes) - 行方向: ライブラリ Claudeがみるみるトークンを消費していったが、欲しいものが大体できてしまった。 Claude codeで生成 · miyanaga/go-kvs-vibe-benchmark@bddb891 追加指示をプロンプト 今回、fsync(バッファをせず直接ファイルに書き込む)は不要と思っていたが、当初のプロンプトで忘れていた。それを指示する。 internal/kvs以下の実装について、fsyncが有効になっていないか確認し、有効になっている場合は無効にしてください。 最後に使い方をREADME.mdにまとめるようにお願いした。 Claude Codeは生成したコードの使い方もプロンプトへの応答で丁寧に解説してくれる。それを「これまでの説明」と指示に加えた。 これまでの説明を含め、日本語でREADME.mdを作ってください。 最終的にこのようなコードベースになった。 miyanaga/go-kvs-vibe-benchmark at claude-code 実行してみる READMEにあるように、go run cmd/generate-data/main.goを実行するとベンチマークが走り出す。 Running benchmark for leveldb... leveldb append: 191ms leveldb update: 193ms leveldb get: 169ms leveldb file size: 13480961 bytes leveldb benchmark completed Running benchmark for bbolt... bbolt append: 1598ms bbolt update: 1579ms bbolt get: 104ms bbolt file size: 33570816 bytes bbolt benchmark completed Running benchmark for badger... badger append: 573ms badger update: 625ms badger get: 182ms badger file size: 2282750000 bytes badger benchmark completed Running benchmark for pebble... pebble append: 141ms pebble update: 175ms pebble get: 603ms pebble file size: 23924128 bytes pebble benchmark completed Running benchmark for sqlite... sqlite append: 19631ms sqlite update: 20781ms sqlite get: 647ms sqlite file size: 27295744 bytes sqlite benchmark completed Benchmark Results: Library Append(ms) Update(ms) Get(ms) FileSize(bytes) leveldb 191 193 169 13480961 bbolt 1598 1579 104 33570816 badger 573 625 182 2282750000 pebble 141 175 603 23924128 sqlite 19631 20781 647 27295744 最後の6行がタブ区切りの表になっているので、コピペでスプレッドシートに貼り付けると分析ができる。 10万件の書き込み・更新・読み込みの計測結果だが、KVSはいずれも非常に速い。SQLiteはいわゆるfsyncであるためか、KVSに比べると書き込みが遅い。 SQLiteの利用は見送るが、次回のKVSの利用はそこまで速度を求めないので、あとはライブラリの書き味によって選ぼうと判断できた。 論よりRun・書くより「頼む」 実はこのKVS選定はしばらくウジウジ悩んでいた。そこで軽くベンチマークを書いて手応えを得ようと思っていたが、実際の作業は一日がかりになりそうで敬遠していた。 それでVibe Codingに任せてみたが、結果的に大正解だった。プロンプトを練るのに30分くらいかかったが、その後は本を読んでいたら終わってしまった。 冒頭にも書いたが、使い捨てのプログラム、コンセプト検証のプロトタイプ、要件が小さなライブラリなどはVibe Codingを活かしていこうと思った。 --- # 画像軽量化コストの妥当性「いくらまで適切?」を考える - 公開日: Mon May 12 2025 21:27:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness Webサイトの画像軽量化には、オープンソースによる内製から、さまざまな外部のサービスの活用まで多くの選択肢がある。 外部サービスはもちろんのこと、内製するにしても工数がかかり、費用は発生する。しかしこれまで画像軽量化の費用の妥当性についてはほとんど見聞きしたことがない。 サービスの料金表や、出された見積もりについてそれが妥当なのか、それとも払い過ぎなのか、画像軽量化サービスを提供する企業として費用対効果を真剣に考えてみた。 画像軽量化を検討する方にぜひ参考いただきたい。 何のために画像を軽量化するのか1. 海外クラウドコストの削減2. 表示スピードの改善3. 社会的責務(CSR)4. チーム内の摩擦解消画像軽量化の妥当なコストをどう判断するか 何のために画像を軽量化するのか Web画像は軽いに越したことはないが、コストをかけてまで画像を軽量化するのは果たして何のためか? 弊社では以下の4つの価値を想定している。 海外クラウドコストの削減 表示スピードの改善 社会的責務(CSR) チーム内の摩擦解消 それぞれの観点でコストの妥当性を考えてみよう。 1. 海外クラウドコストの削減 はじめに言うと、月間数百万以上のPVがあるサイトをAWSなど海外クラウドプラットフォームで提供している場合は、画像軽量化は今すぐ検討いただきたいテーマである。 言うまでもなく、AWSをはじめとした海外クラウドプラットフォームの利用が増えている。IT分野における慢性的な貿易収支悪化は、「デジタル赤字」とも呼ばれ社会問題にも発展している。 海外のサーバーホスティングやクラウドプラットフォームのほとんどはサーバー利用料に加え、ユーザーへのデータ送信量に応じても従量課金が発生する料金体系になっている。多くのユーザーに大きなデータを送信(配信)すると、それだけたくさんの費用を請求されるのだが、これが案外バカにならない。 そして送信データのうち、最も大きいのが画像データである。したがって画像データを軽量化すると効果的にその費用を削減できる。 弊社の事例の中からコスト削減が顕著なケースを紹介しよう。 ケース1 月間約1000万PVの通販サイト 以前のAWSのデータ送信料金: 月額約100万円 画像軽量化により: -35万円削減 画像軽量化コスト: 月額12万円 (年間276万円お得) ケース2 ライフスタイル誌メディアサイト 以前のAWSのデータ送信料金: 月額150万円 画像軽量化により: -100万円削減 画像軽量化コスト: 月額12万円 (年間1,056万円お得) 上記のケースでは、AWS CloudFrontの画像データを次世代画像フォーマットWebPに自動変換するサービス「LightFile Proxy」を利用いただいている。 LightFile Proxy - AWS CloudFrontでシームレスにWebP対応・通信コスト削減 このように海外クラウドプラットフォームを利用する人気サイトでは、画像軽量化の価値は語るまでもない。使った月からトータルコストでお釣りがくる。そして早く導入するほうが将来的に得られる総額も大きく、遅れるほど無駄な損失は膨らむ。 画像軽量化コストの妥当性 ① 海外クラウドのコスト削減 海外クラウドプラットフォームを利用しているなら、通信コストの削減見込みが画像軽量化サービスの妥当なコストである。 2. 表示スピードの改善 画像軽量化というと表示スピードの改善を期待する声が最も多いが、これだけインターネットが高速なった現在、画像軽量化くらいで表示スピードの目覚ましい改善を期待するのはあまりに無理がある。 それでも自社サイトの表示が遅いと感じている方は多い。原因は画像ではなく十中八九、JavaScriptとサードパーティタグであるのだが、このテーマは本記事からかけ離れるのでまた別の機会に論じたい。 しかし画像軽量化に意義はある。どんなサイトにおいても、通信が遅い体験を強いられるユーザーが必ずいる。画像軽量化はそんな不運なユーザーの助けになる。 仮説: 画像軽量化は通販サイトの収益を1〜2%改善する 例えば通販サイトで画像データを半分に削減できると、非常に感覚的な数値ではあるが、1〜2%くらいは収益増に貢献するのではないかと感じている。 実際のデータを見てもページ読み込み時間は離脱への影響が大きく、画像軽量化の効果が0%ということはない。 サイトスピードと収益性を高い解像度で理解する | ideaman's Notes しかし5%改善するかというと、それは高望みがすぎる。 そこでフェルミ推定的であり根拠は乏しいが、議論を前進させるため悲観シナリオ +1%、楽観シナリオ +2%と想定してみた次第だ。 上記の仮説に基づくと、画像軽量化サービスの月額が粗利(売上-仕入れなどの限界費用)の1〜2%以内であれば利益を毀損しない。 例えば弊社の画像軽量化サービスは月額3万円程度からの提供になる。月次の粗利が150万円〜300万円以上のサイトであれば使う方が得という推論になる。 画像軽量化コストの妥当性 ② 表示スピード改善 通販サイトであれば粗利の1〜2%までを妥当なコストとして仮定してみる。 3. 社会的責務(CSR) 海外クラウドのコスト削減と表裏の観点であるが、エンドユーザーもモバイル通信においては従量課金を強いられている。Webサイトを閲覧してくれた人は、自身の毎月限られた「ギガ」を消費しているかもしれない。 環境的な意味もある。データ通信が安価でもタダではない。データ量が多いのはそれだけ消費電力と環境負荷も増す。 これらを考慮すると、重いデータに無頓着なのは社会的無責任であり、費用対効果は度外視しても効率のよいデータ配信に投資するのは、CSR(企業の社会的責任)の一環と言えるだろう。事業に影響がない範囲でそれを背負う意義はある。 画像軽量化コストの妥当性 ③ 社会的責務(CSR) インターネットでビジネスを行う企業として、ユーザーと環境に配慮するCSRとしての出費と考える。 4. チーム内の摩擦解消 制作現場では画像データの軽量化はWebデザイナーの責任とされることが多い。技能的、作業フロー的にもふさわしいので仕方がないところはある。 しかしただでさえ忙しいWebデザイナーが画像の軽量化まで時間を割いている余裕はない。一方、ディレクターやWebマスターは少しでも画像を軽くしてほしいと期待する。ここにチーム内の摩擦が生じる。 プログラムが生成するサムネイル画像などの軽量化は、エンジニアが責任を背負うことになるが、ここにも同様に軽くしてほしい圧力と、簡単に言わないでほしいという摩擦が生まれる。 そもそも画像軽量化はサイト運用の本質的な作業ではない。そこで外部サービスに委託してしまえば、これらのストレスはまとめてキャンセルできる。 画像軽量化コストの妥当性 ④ 本質的ではない業務による摩擦の解消 サイト運用チームの運営を円滑に進める外注費として考える。 画像軽量化の妥当なコストをどう判断するか 以上、画像軽量化が果たして投資に見合うのかを4つの価値の観点から考えてみた。 もっとも強力な理由は海外クラウドのコスト削減だ。これだけで理由に足るサイトは多いだろう。 どれかひとつを選択するのではなく、組み合わせにより画像軽量化を実施する意味とコストの妥当性を考えていただきたい。 海外クラウドではなく売上もまだそこまで大きくないケースでも、CSRあるいはチーム内のストレスを避ける効果も考慮すると、提示された画像軽量化のコストは高くないと判断できるかもしれない。 --- # 広報ブログをリニューアル・VitePressカスタマイズのコツ - 公開日: Tue May 06 2025 19:43:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, content-management, technology この技術ブログと別に、以前から運用している広報ブログがある。 ideaman's Blog 長らくこれをリニューアルしたいと考えていたが、このゴールデンウィークにやっと実現した。 最近はCursorであらゆる作業を行うため、Movable TypeからVitePress + GitHub Actionsにシステムを移行した。構成をシンプルにしたのもあるが、ビルド(再構築)時間も1分41秒から5.66秒に短縮した。 いろいろと悩んだ技術選定の経緯や、VitePressで本格ブログを構築するノウハウを共有したい。 ブログシステムの選定要件 - 静的Webサーバーでのホスティング候補1 Astro候補2 Movable Type候補3 VitePress (採用)VitePressをブログシステムに活用するTipsテンプレートシステム動的データの供給ダイナミックルートとパスのリライトMPAモードの明示RSSフィードの生成まとめ ブログシステムの選定 以前のブログシステムはMovable Type AMI版をAWS上で運用していた。 Movable Typeを使い続けるか、それともVitePressのようなSSG(Static Site Generator)に切り替えるかは最後まで迷った。 要件 - 静的Webサーバーでのホスティング 特に複雑な要件がないのであれば、静的コンテンツをシンプルなWebサーバーで運用するスタイルがよい。 まずプログラムの都合でサイトが壊れることはない。その結果、運用コストが安い。また、シンプルな分サイトの表示も早い。 候補1 Astro 最初に検討したのがAstroだ。Astro自体もそうだが、AstroWindというOSSのテンプレートが秀逸で、これを使いたいと思っていた。 Astro AstroWind | Astro ひとまずMovable Typeから約268記事をMarkdownで出力し、Astroでビルドの性能試験をしたところ、完了まで1分半くらい要した。 Astroの使い方が間違っていたのかもしれないが、ひとつの記事の更新にもこの時間がかかるようでは大きなストレスとなる。Astroの利用は断念した。 候補2 Movable Type Astroのビルド時間に失望して、Movable Typeなら記事の更新だけであれば差分ビルドがあっという間に終わる。268記事もあるとやはり差分ビルドが要ると思い、Movable Typeの継続を検討した。 Movable Typeのテンプレートシステムはよく知っているが、難点はアセットパイプラインがないことだ。そこでデザインシステムは別で用意し、そこで最適化されたCSS・JavaScriptを用意。それらをテンプレートから参照するワークフローを考えた。 しかしデザインシステムとテンプレートの二重管理は、やる前から気力が削がれてしまった。 候補3 VitePress (採用) 本ブログではVitePressを使っている。VitePressを最初に検討しなかったのは、カスタマイズの自信がなかったからだ。 Movable Typeにはカテゴリ別、年月別といった多様で柔軟なアーカイブシステムがある。せっかく検索エンジンにインデックスされた履歴を放棄したくないので、URLのパス構造は維持したいと思っていたが、VitePressでそれを再現する方法がわからなかった。 最終的に手探りで試行錯誤しながら、Movable Typeのアーカイブシステムを概ね再現することに成功した。 VitePressはビルド時間も早い。268記事のビルドをテストしたところ、なんと2.56秒で完了した。Astroのビルド時間で勘違いをしたが、268記事というのは今やデータとしては決して多くない。 このようにViteによるアセットパイプラインがあり、アーカイブシステムも再現でき、ビルドも高速と条件が揃ったのでVitePressを採用した。 VitePressをブログシステムに活用するTips しかしVitePressを都合の良いブログCMSに仕立てるにはドキュメントを読み込み、トライアンドエラーが必要だった。 以下、Tipsをいくつか紹介したい。 テンプレートシステム はじめにCMSは通常、複数のテンプレートを管理できる。トップページ、記事詳細ページ、記事一覧ページといった具合にだ。 素のVueアプリケーションでは、vue-routerのようなライブラリがあるが、VitePressではもっとラフなテンプレート管理を行うようだ。 .vitepress/theme/Layout.vueが全体的なテンプレートを司り、その中でURLパスを判定してテンプレートを切り替える。 vue<script setup lang="ts"> // ... // URLパス情報を持つrouteを用いて条件分岐する const route = useRoute() </script> <template> <!-- 略 --> <Home v-if="トップページのパス条件" /> <List v-else-if="一覧ページのパス条件" /> <Article v-else-if="詳細ページのパス条件" /> <NotFound v-else /> <!-- 略 --> </template> 弊社の広報ブログでは以下のようになった。 blog-v3.vitepress/theme/Layout.vue at main · ideamans/blog-v3 動的データの供給 デフォルトではMarkdownファイルがいわゆる記事データに対応する。しかしブログCMSではカテゴリーや著者情報など、他のデータも欲しい。 VitePressでは、.vitepress/themeディレクトリに.data.tsファイルを用意することでそれを実現するのが作法のようだ。 Build-Time Data Loading | VitePress 例えば広報ブログでは以下のファイルでカテゴリーデータを供給している。 blog-v3/.vitepress/theme/categories.data.ts at main · ideamans/blog-v3 # Vueにカテゴリーリストを供給するアダプタ blog-v3/categories.ts at main · ideamans/blog-v3 # 実際のカテゴリーデータを管理するコード categories.data.tsひとつで供給することももちろん可能である。しかし、後述するRSSフィードを生成するgenFeed.tsからcategories.data.tsの利用が思った通りにできなかった。 そのため、genFeed.tsとcategories.data.tsの両方から使えるcategories.tsを用意した。これは解決法がありそうだが、今回は見つけられなかった。 ダイナミックルートとパスのリライト Movable Typeは柔軟なアーカイブシステム(コンテンツとURLパスの対応付け)があり、比較的自由な切り口でURLパスを設計できる。 一方、VitePressはMarkdownファイルとURLパスが自動的に1対1に対応する。それ以外のルーティングを用意するには、ダイナミックルートとパスのリライトを使う。 ダイナミックルートは、配列データを元に仮想的なMarkdownファイルを展開するような機能だ。実際にMarkdownファイルは個別には存在しないが、それに対応するルーティングが生成される。 これにより、カテゴリごとや年月ごとのURLをパスを用意できる。 ただしダイナミックルートはあくまでフラットな仮想ファイル群を用意する仕組みのようである。説明を見る限り、/2025/04、/2025/05のような階層構造を表現ができない。 そこでパスのリライトを使う。パスのリライトはMarkdownファイルのパスに対し、実際に出力するパスをマッピングできる。 例えば上記の年月は以下のようにダイナミックルートを用意し、 text. └─ monthly ├─ [year]-[month].md └─ [year]-[month].paths.js パスのリライトを次のようにした。 javascript{ rewrites: { 'monthly/:year-:month.md': ':year/:month/index.md' }, } blog-v3/.vitepress/config.ts at main · ideamans/blog-v3 blog-v3/monthly/[year]-[month].md at main · ideamans/blog-v3 # 年月のMarkdownはダミー・実際にはテンプレートで内容を描画する blog-v3/monthly/[year]-[month].paths.js at main · ideamans/blog-v3 MPAモードの明示 VitePressはSSG(Static Site Generator)だが、画面遷移はSPA的な機能も持つようだ。 詳細は不明なのだが、HTML内にはJavaScriptコードが多数埋め込まれるし、ビルドしたHTMLファイル群を静的Webサーバーにアップロードすると、特にダイナミックルートへの遷移で正常な表示が行われなくなる。 そこで、defineConfigによる設定にmpa: trueを渡す。これによりSPA(Single Page Application)ではなく複数ページからなるMPA(Multi Page Application)であることを明示でき、ページ遷移が正常になった。 RSSフィードの生成 VitePress自体にはRSSフィードの生成機能はない。そこで簡易的なスクリプトを.vitepress/genFeed.tsを用意し、設定ファイル(config.ts)のbuildEndでそれを起動する設定を行う。 blog-v3/.vitepress/genFeed.ts at main · ideamans/blog-v3 ちなみにこの方法は、Vueプロジェクトの公式ブログである以下の方法に倣っている。 blog/.vitepress/genFeed.ts at main · vuejs/blog まとめ VitePress、その前身のVuePressから長らく活用させてもらってきた。しかし、ドキュメントサイトの域を出ず、このようにカスタマイズすることはなかった。 今回、Movable Typeによるブログから移行先としてさまざまなカスタマイズ手法を習得できた。 自分好みのCMSをまたひとつ手に入れられた満足感が高い。例えばLP制作などこれからの弊社のサイト展開に積極的に利用していきたい。 --- # AIとGitHub Actionsで定期的に記事の宣伝をXに投稿する - 公開日: Sat May 03 2025 00:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: automation, ai, development 研究ノートと銘打ったこのブログもだいぶ記事が溜まってきた。以前に投稿した記事もXなどで露出を図るとよいのだが、文面を考えるのが面倒でつい放置してしまっていた。 そこでAIに文面を考えてもらい、GitHub Actionsのスケジュールワークフローで定期的な宣伝を繰り返す仕組みを構築した。 ひとまず1日2回、以下のような過去記事へ誘導する投稿が無人で行われるようになった。 処理の流れ 次のとおり非常にシンプルだ。 GitHub Actionsのスケジュールワークフローを設定する ランダムに記事を抜き出す AIに投稿文を考えてもらう URLを付加してXにポストする GitHub Actions スケジュールワークフロー GitHub ActionsのワークフローはCron形式で指定したスケジュールでも自動実行できる。日本時間の9時と15時に起動するよう設定した。 yamlon: schedule: # 日本時間 09:00 (UTC 00:00) - cron: '0 0 * * *' # 日本時間 15:00 (UTC 06:00) - cron: '0 6 * * *' このブログはGitHubで公開しているので、以下で全体を確認できる。 notes/.github/workflows/promotion.yml at main · ideamans/notes ランダムに記事を抜き出す 記事は年ごとにMarkdownで書いている。これをランダムにひとつ抜き出す。 notes/posts/2025 at main · ideamans/notes まだ1年も経っていないので全ての記事を対象にしているが、ゆくゆくは直近1年とか、新しい記事が確率的に出やすくする工夫をしたい。 AIに投稿文を考えてもらう 抜き出した記事のMarkdown原稿を生成AI(Gemini)に渡し、宣伝のための投稿文を考えてもらう。 この投稿文を考えるのが苦手だ。ある程度キャッチーな文言でアクセスを誘いたいが、自分のつまらない頭から捻り出すのはとても疲れる。AIが考えてくれるなんて最高だ。 プロンプトでは例をいくつか与えた。 markdown1. **全体の要約**: 「この記事では〇〇の全体像をわかりやすく解説しています」 2. **意外性のある事実**: 「えっ、〇〇って実は△△だったの?」 3. **困りごとへの問いかけ**: 「〇〇で悩んでいませんか?」 4. **新たな提言・推奨**: 「これからは"〇〇"が当たり前になるかもしれません」 5. **物語・体験談風の導入**: 「昔、〇〇で失敗した話をします…」 6. **数字・統計を使った強調**: 「〇〇%の人が知らない〇〇の話」 7. **常識への疑問・ツッコミ**: 「"〇〇すべき"って本当? 実は…」 8. **図解・スライド付きで視覚訴求**: 「この記事を図解でまとめました👇」 9. **比較・ランキング風**: 「3つの〇〇を試してみた。最も効果的だったのは…」 10. **引用・一節紹介**: 「"〇〇〇〇"──この記事で一番響いた一文」 11. **Before/After型の変化を見せる**: 「この記事を読む前のあなた→〇〇。読んだ後のあなた→△△」 12. **対立構造で煽る**: 「あなたは〇〇派?それとも△△派?」 13. **タイムリーな話題に便乗**: 「今日のニュースを見てこの記事を思い出しました」 14. **失敗や後悔の共有**: 「昔の自分に教えたい記事」 15. **短いクイズ・なぞかけ風**: 「Q. なぜ〇〇がうまくいかない?A. 答えはこの記事に👇」 16. **あえて結論を隠す型**: 「読んだ後に"ゾッとする"内容です…」 17. **制作背景・裏話**: 「この記事を書いた理由は〇〇です」 18. **ミニマル強調(たった1つ)**: 「"これ1つ"だけで〇〇が改善します」 19. **逆張りの視点**: 「実は〇〇って必要ないと思ってます」 21. **明確な対象提示型**: 「✔〇〇な人へ届けたい内容です」 22. **他記事・著名人との関連付け**: 「〇〇さんの話を聞いてこの記事を思い出しました」 23. **保存・再訪を促す**: 「これは"ブックマーク必須"の記事です」 当然、この例も生成AIに考えてもらい、自分で少し調整した。これがレギュレーションにもなり、突拍子のない投稿を防止する役割にもなると期待している。 ちょっとあざとい気もするが、自分では思いつかないフレーズばかりだ。返ってAIに任せてよかった、となるかもしれない。 次のXへのポストも含め、以下のファイルに一連の処理を記述している。 notes/agents/x-ai-posting.ts at main · ideamans/notes APIでXにポストする 最後に作成された投稿文をAPI経由でXにポストする。 API経由のポストは、次の認証情報さえあれば簡単だった。 TWITTER_APP_KEY TWITTER_APP_SECRET TWITTER_ACCESS_TOKEN TWITTER_ACCESS_SECRET 認証情報は開発者ポータルから取得し、先ほどのGemini用のAPIキーと合わせてGitHubのSecrets(機密情報)に登録しておく。 X Developers 月間500ポストまでは無料プランで利用できる。 なお、開発者ポータルへ入る際に、どのような目的で利用するのか記述を求められた。英語で250語以上が必要とのことで、意外と長文になる。大きな声では言えないが、このプロジェクトの概要を伝え、ChatGPTに考えてもらった。 今後の展開 更新頻度は低いがブログもあり、また、Qiitaにもときどき記事を書く。それら過去記事についても同様に露出を増やしていきたい。 Facebookにもいちおう企業ページがあり、Threads、Blueskyなどの新興SNSも気にはなっている。今までは運用する時間がなくて放置していたが、AIの力を借りて改めて活用したい。 また、AIに質問してもらうなど、ネタフリをしてもらう活用もよいと考えている。自分から企画を考えるのは、必然性、つまり「今なぜそのネタなのか?」を問うて頭が重くなりがちだ。それに比べると質問に回答するのはキータッチが走る。 いずれにせよ、やらなきゃやらなきゃと思っていた作業からひとつ解放された。AIがどんどん心強い味方になっている。 --- # Mastra + ローカルLLM経由でMCPする - 公開日: Fri May 02 2025 04:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development, technology 最近、Macbook ProをM1 ProからM4 Proにアップグレードしたので、LM Studio でローカルLLMを動かしてみたらさすがに早い。 そこに Qwen3 というまたすごそうなオープンソースのLLMが公開されたので、 第2のDeepSeekショック? オープンな中国LLM「Qwen3」シリーズが破格の性能で話題 最大モデルはOpenAI o1やGemini 2.5 Proに匹敵、たった4BでもGPT-4oレベルに - ITmedia AI+ Mastraに繋いでMCPサーバーを使うことを試してみた。 LM StudioをOpenAI互換APIサーバーにするMastraから使うMCP経由でPlaywrightブラウザを動かすMacbook Proにもファンがある LM StudioをOpenAI互換APIサーバーにする 私のようなミーハーLLM使いでも最新モデルを簡単に試すことができるLM Studioという素敵なアプリケーションがある。 LM Studio - Discover, download, and run local LLMs こちらをインストールし、次の手順でQwen3関連のLLMをダウンロードし、OpenAI互換のAPIサーバーとして使えるようにする。 虫眼鏡マークでQwen3を検索してダウンロード(私はqwen3-30b-a3bを選択した) Developerペインを選択 上部のモデルバーからダウンロードしたモデルを選択 トグルをオンにするとサーバーが起動。http://127.0.0.1:1234でアクセス可能に Mastraから使う MastraはTypeScript製のAIエージェントフレームワークだが、以下の記事で詳しく触れたので解説は省略する。 AIエージェント × MCP × スプレッドシートで寝ている間に仕事をしてくれる「小人のくつ屋さん」を実現する | ideaman's Notes MastraではVerselの AI SDK を採用しており、以下のように認知負荷の低い方法で各種LLMをリファレンスできる。 typescriptimport { openai } from '@ai-sdk/openai' export const model = openai('gpt-4.1-mini') AI SDKには OpenAI互換モデル という実装があり、以下のコマンドでモジュールを追加しておくと、 bash# モジュールを追加 pnpm add @ai-sdk/openai-compatible 以下のようなコードで透過的にモデルを差し替えられる。 typescriptimport { createOpenAICompatible } from "@ai-sdk/openai-compatible" // OpenAI互換ファクトリ const lmStudio = createOpenAICompatible({ name: 'lm-studio', baseURL: 'http://localhost:1234/v1', }) // LM Studioでは複数のモデルも同時に提供できるのでIDを揃える const model = llmStudio('qwen3-30b-a3b') これだけでOpenAIに課金して使っていたLLMをローカルで動作するQwen3に差し替えられる。 MCP経由でPlaywrightブラウザを動かす というわけで、MastraとPlaywright MCPサーバーを連携させておき、ローカルLLMをモデルとして 「Open https://notes.ideamans.com/」 などとお願いしてみると、実際にMCP経由でブラウザが起動して目的のサイトを開いてくれる。 むちゃくちゃLLMの独り言が多い(<think></think>)。 正直、Claude・ChatGPT・Geminiと比べると遅いし、精度も使い物にならないほど低い。 それでも手元のMacbook Proという半導体デバイスにオフラインで自我に芽生えたような感動があった。 それに、API提供されているLLMがどれだけ教育されているのか、どれだけ電気を食っているのか思いを馳せるきっかけになった。 Macbook Proにもファンがある 普段、Macbook Proを使っているとファンの音を聞かない。冷却の必要があるほどCPUが過熱しないし、だからバッテリーも異常なくらい持つ。 しかしLLMを稼働させると、CPU負荷は800%以上にも達し、ブーンという音が聞こえ始める。君にも本当に扇風機がついていたんだ…と驚いた。 --- # アイデアマンズのミッションは「Webフィットネス」 - 公開日: Wed Apr 30 2025 21:20:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: business, sitespeed, image-fitness 弊社アイデアマンズ株式会社のミッションについて少し言語化をしたい。 いくつかの事業を手掛けているが、共通するのは技術的な工夫で「Webにおけるムダを減らしたい」という思いである。 画像軽量化遅い体験をしている人がいるクラウドの料金をムダに払うことになるページ表示速度の改善ランキング表示のムダと効率化ミッション = Webのフィットネス 画像軽量化 主な事業のひとつがWeb画像の軽量化だ。 LightFile Proxy - AWS CloudFrontでシームレスにWebP対応・通信コスト削減 LightFile 定額制画像軽量化サービス | アイデアマンズ株式会社 Webにおける画像は、ほとんど画質を変えずにもっと少ないデータで配信できる余地がある。 画質は目に見えるが、データの重さは知覚しにくい。データが重いと読み込み時間の遅さとして認知はできるのだが、インターネットがどんどん高速になっているので気付きにくくなっている。 「遅さに気づかないのであれば別にいいではないか」 という疑問を感じるだろうが、実は大きな問題がある。 遅い体験をしている人がいる クラウドの料金を不要に払い続けてしまう 遅い体験をしている人がいる 同じサイトであっても、Webページの読み込み速度は私たちが思っている以上にばらつきがある。 サイトの運営者もユーザーとしてはN=1のサンプルにすぎない。なんとなく「みんな同じくらいのスピードで体験している」とバイアスのかかった印象に支配されてしまうが、快適な体験ができている人もいれば、運悪く表示が遅い体験をしている人もいる。 画像軽量化はすでに快適な体験ができている層にはあまり効かないが、読み込みが遅い不運に見舞われた人には効果がある。 クラウドの料金をムダに払うことになる ご存じのとおり、AWSをはじめ海外のクラウドサービスの利用が増えている。これは貿易上の輸入に相当し、貿易赤字(デジタル赤字)が拡大している。 日本のホスティングサービスは伝統的にデータをいくら送信しても定額の料金体系が主流だったが、海外の主流は逆である。大きなデータをたくさんの相手に送信するほど、追加料金が発生する。 その額がバカにならないのだが、世間的にあまり認識されていない。技術者にとっては「当たり前でしょ」な前提なのだが、知らないという反応に返って驚くことも多い。 Webにおける大きなデータは画像データである。データとしては映像の方が大きいが、普通はYouTubeからの配信でオフロードするのであまり問題にならない。 画像軽量化は、データ送信にかかるコストを確実に減らす効果がある。デジタル赤字縮小にも意義がある。 ページ表示速度の改善 「画像軽量化はページ表示速度の改善にどのくらい効くか?」と寄せられる疑問に端を発して、現在はWebフロントエンドのスピード改善についてもいくつかの事業を手掛けている。 Speed is Money - 無料のスピード専用アクセス解析 Core Web Vitals・PageSpeedスコアのアルゴリズム対策手順 & 改善結果の未来先取りレポート - Core Web Vitals 改善リハーサル 無料 | サイトスピード簡単比較 せっかくサイトに集客できたユーザーが期待を抱いていても、ページの読み込みが遅いと簡単に離脱してしまう。読み込みが遅いというのは、ユーザーと企業の双方にとって重要でありムダな摩擦でしかない。 ただ、Webページのスピードの重要性は20年以上前から叫ばれ続けているが、泡のように浮かんでは弾けて消える問題意識のままである。その状況を変えるチャレンジをしたいと考えている。 ランキング表示のムダと効率化 最後に、上記のふたつの事業と比べると異色であるが、GA4のデータを用いてサイトにアクセスランキングを表示するRankelt4という事業も展開している。 Ranklet4 - [無料] GA4から人気ページランキングをかんたん表示 実はこれも根底にはムダを減らしたい動機がある。 アクセスランキングの集計には当然、ユーザーの行動を記録する仕組みが必要である。多くのサイトがそのための仕組みを独自に構築しているが、同時にGoogle Analyticsでもユーザーの行動を記録している。つまり同じ目的のデータを二重に記録しているのだ。 Google AnalyticsはAPIもしっかりと提供しているので、ユーザーの行動データを利用するのも難しくはない。それならばそちらに寄せて一本化した方がよいではないか、と考えるとRanklet4にもムダの削減の観点で通ずる思いがある。 ミッション = Webのフィットネス 私事であるが5年前に「人生最後のダイエット」を敢行し、それ以来は体調体型ともに維持している。おかげでQOLも大きく改善した。その喜びを他者と共有したいと願っている。 総じてソフトウェアはムダを省くという美徳の実現を目指して作られるものだが、特に新しいアイデアと高い技術力によってそれを実現し、社会の数多くの事業の質を高める貢献に生きがいを感じてきた。 そのような意味で、アイデアマンズのミッションは「Webフィットネス」の実現である。 --- # Googleスプレッドシートを簡易データベースにするNPMモジュールを公開 - 公開日: Sat Apr 26 2025 00:15:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, automation, technology TypeScriptによるプログラムで、Zodで型を規定しつつ、Googleスプレッドシートを簡易的なデータベースとして利用できるモジュールを開発した。 ideamans/node-google-spreadsheet-tables 日本語README google-spreadsheet-tables - npm データベース面倒くさい問題使い方まとめ データベース面倒くさい問題 プログラムでは永続化されたデータを扱いたいことが多い。極めて小規模であればシンプルなファイルを用い、大規模になればデータベースを用いる。問題はその中間だ。ファイルではシンプルすぎるが、データベースを用意するほどでもないことがある。 データベースを用意するとそれを維持することも考えなければいけない。RDBMSだとスキーマ管理も別に必要になる。そういった作業が面倒で気持ちも重くなる。 先日作成した以下のプログラムがまさにそのようなケースだった。 AIエージェント × MCP × スプレッドシートで寝ている間に仕事をしてくれる「小人のくつ屋さん」を実現する | ideaman's Notes このプログラムにZodでスキーマを規定しつつ、Googleスプレッドシートをデータベース代わりに使ってみたところ、思った以上に開発体験がよかった。そこで独立したモジュールとして機能を分離した。 使い方 まずはGCPでサービスアカウントを作成し、Googleスプレッドシートをサービスアカウントのメールアドレスに対し、編集権限付きで共有する。 JSONの鍵ファイルをservice-account.jsonとしてダウンロードすると、次のようなプログラムでスプレッドシートをドキュメントデータベースのように利用できる。 typescriptimport { useWorksheetWithServiceAccountFile, useDocumentsSheet } from 'google-spreadsheet-tables' import { z } from 'zod' // スキーマを定義 const userSchema = z.object({ name: z.string(), age: z.coerce.number(), gender: z.enum(['male', 'female', 'other']), company: z.string().optional(), }) // スプレッドシート接続を初期化 // 'YOUR_SPREADSHEET_ID' を実際のファイルIDに置き換えてください。例:1vob8zYwa2p9mLDaczN_Egn-01QjC-tC80-Y83yYMCR0 const { doc } = useWorksheetWithServiceAccountFile('YOUR_SPREADSHEET_ID', './service-account.json') // ドキュメントシートを作成 const { append, get, patch, snapshot, clear } = await useDocumentsSheet( doc, 'Users', userSchema ) // 新しいユーザーを追加 const newUser = await append({ name: 'John Doe', age: 30, gender: 'male', company: 'Acme Inc.' }) // すべてのユーザーを取得 const { documents: allUsers } = await snapshot() // 特定のユーザーを取得 const user = await get(newUser.rowKey) // ユーザーを更新 await patch(newUser.rowKey, { age: 31, company: 'New Company' }) 詳しくは日本語READMEをご覧いただきたい。 まとめ もちろん性能は低いし、クリティカルな要件は満たすことができないが、小規模のゆるいデータ管理であればこれで十分だ。 データベースとしてのGoogleスプレッドシートは利点も多い。 リモートデータベース ローカルファイルだと実行中の端末からしかアクセスできないが、Googleスプレッドシートであれば複数の実行環境からも接続できる。 GUI完備 データを総覧しやすく、データの変更も直感的にできる。 関数やスクリプト そのまま関数やGASで全体的なデータ加工ができる。 前後の工程との接続 プログラムが全体の業務の一部である場合、前後の工程との受け渡しがある。スプレッドシートならデータ変換の手間が省けるケースもある。 個人的にはこれからも積極的に利用していきたい。 --- # AIエージェント × MCP × スプレッドシートで寝ている間に仕事をしてくれる「小人のくつ屋さん」を実現する - 公開日: Sun Apr 20 2025 15:33:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, automation, technology, business グリム兄弟の「小人のくつ屋さん」を聞いたことがあるだろう。寝静まった真夜中に不思議な小人たちが靴を仕上げてくれる童話だ。 最近話題のAIエージェントとPlaywright MCP(ブラウザ操作MCP)は、自然言語による指示で動き、人間へ頼んだかのような感覚で、自律的にブラウザを操作してくれる。 例えば先日、AIエージェントがブラウザを操作し、ヒューリスティックなUX診断を行う例が話題になっていた。 Playwright MCP を使ってAIにUXを評価してもらう #githubcopilot - Qiita このようなAIエージェントによるブラウザ操作とGoogleスプレッドシートと連携し、何らかのテーマで多数のサイトやページを横断的に調査するバッチ処理に仕立ててみよう。すると調査対象リストをシートに書いておけば、寝ている間にAIエージェントが働いてシートを埋めてくれる。「小人の調べ屋さん」が実現する。 そのような仕組みを Mastra を用いて実装してみた。ソースコードも公開したので、ぜひアレンジして不思議な小人を見つけてほしい。 開発したプログラム今回の題材 「アクセスランキング利用率の調査」ソースコード開発の流れとプログラムの解説AIエージェントフレームワーク MastraMCP (Model Context Protocol) サーバーまずはAIエージェント単体の動作確認をするGoogleスプレッドシートとの連携バッチ処理ワークフローを組む残タスクの読み込みステップの実装繰り返し用ステップの実装while による繰り返し代替案: AIエージェントによる入出力というアプローチAIでやること・プログラムで制御することの線引き最後に 開発したプログラム 動作の様子を録画した。8倍速で再生しているが、AIエージェントが次々とブラウザにWebページを表示し、調査結果がスプレッドシートに更新されていく様子が見て取れる。 今回の題材 「アクセスランキング利用率の調査」 弊社では任意のWebサイトにアクセスランキング機能を追加するための Rankelt4 というサービスを提供している。 それに関連し、多数のサイトを調査してアクセスランキングの利用率を調べるという業務を想定した。 Googleスプレッドシートに書いておいたニュースサイトをひとつずつ開く URLが間違っていたらWeb検索をして訂正する アクセスランキングが表示されているかを確認する その結果をGoogleスプレッドシートに記入する 以上の処理を対象リストの分、繰り返す アクセスランキングは「人気記事」「人気ランキング」など表記にばらつきがあるため、単純なテキスト処理では正確に判定できない。また、JavaScriptで展開されているケースもあり、HTML上の字面だけのスクレイピングでは正確な判定ができない。 そのためこれまでは人間がブラウザを目視して判断せざるを得なかった。数サイトであれば我慢できるが、数が多いと心が折れそうな単純作業である。これを小人さんたちにやってもらう。 もちろんこれはひとつの題材に過ぎず、AIエージェントに他の調査を依頼することも可能だ。 ソースコード 作成したプログラムを以下のリポジトリで公開した。詳しくはREADME.mdをご覧いただきたい。 ideamans/mastra-ai-agent-batch-example: Mastra × PlayWright MCP × GoogleスプレッドシートによるAIエージェントのバッチ処理実装例 最低限、次のサービスと認証情報があればすぐに動作も確認できる。 Google Cloud上のサービスアカウントとそのJSON鍵 Google GeminiのAPIキー Brave SearchのAPIキー なお、この例では安価なGeminiを利用しているが、Function Callingを備えていればChatGPTやClaudeにも容易に変更できる。 開発の流れとプログラムの解説 AIエージェントフレームワーク Mastra LLMはパワフルだが、それ単体はあくまで脳のような器官であり、安定した仕事をするにはさまざまな支えが必要になる。Mastraは、LLMにそのような機能をまとめて用意してくれるフレームワークだ。 The Typescript AI framework - Mastra この手のプロジェクトで有名なのはLangChainやLangGraphで、もちろんPythonでも同様の実装は可能だろう。単に個人的なスキルセットから、TypeScriptの方が得意なのでMastraを採用した。 MCP (Model Context Protocol) サーバー LLMとフレームワークが、さらに外部のシステムと連携するための仕組みがMCP (Model Context Protocol) サーバーである。 世の中にある既存のサービスがMCPサーバーを提供すると、AIエージェントが自律的にそのサービスも活用できるようになる。 今回は二つのMCPサーバーを用いる。 ブラウザ操作 microsoft/playwright-mcp: Playwright MCP server LLMがブラウザを開いて自律的に利用できるようになる。 Web検索 servers/src/brave-search at main · modelcontextprotocol/servers LLMが必要に応じてBrave SearchでWeb検索できるようになる。 Web検索はURLの訂正に用いる。バッチ処理の指示としてサイト名とURLのリストを人間が何らかの方法で用意するのだが、当然、URLを間違えることもあり得る。 人間による作業であれば、「これはURLの間違いだな。検索して訂正しておこう」と気を利かせられる。Web検索MCPサーバーによってそれを再現できる。 まずはAIエージェント単体の動作確認をする Mastraの優れた点のひとつに、プレイグラウンドUIがある。 システムプロンプトで役割を与えたAIエージェントと対話できるチャットインターフェースをコマンドひとつで即座に起動できるのだ。 例えば、次のプロンプトを入力すると、 text朝日新聞 https://www.asahi.com/ Playwright MCPサーバーがブラウザで朝日新聞サイトを開き、アクセスランキングの有無を判定してくれる。 textまず、指定されたURLにアクセスして、サイトが朝日新聞の公式サイトと一致するか確認します。 朝日新聞の公式サイトにアクセスできたことを確認しました。現在のURLは https://www.asahi.com/ です。 次に、アクセスランキングのセクションがあるか確認します。 トップページに「アクセスランキング」という名称のセクションがあり、ランキング形式で記事へのリンクが並んでいることを確認しました。 次にわざとサイト名と違うURLを与えてみよう。 text毎日新聞 https://www.asahi.com/ すると、以下のような回答が得られる。「URLが違うみたいだから検索して修正するね」と働いてくれるのだ。 textまず、指定されたURLにアクセスして、サイトが「毎日新聞」と一致するか確認します。 URLを開いたところ、サイトは「朝日新聞」でした。「毎日新聞」ではありませんでしたので、ウェブ検索で「毎日新聞」のURLを調べます。 検索結果から、「毎日新聞」のURLは https://mainichi.jp/ であることがわかります。ブラウザでこのURLを開きます。 URLを修正し、ブラウザで開きました。現在のURLは https://mainichi.jp/ です。 次に、アクセスランキングのセクションを確認します。 ウェブページを調べたところ、「アクセスランキング」という名称のセクションが見つかりました。 このように実現したいことをすぐに試せるし、デバッグやチューニングも大変捗る。 Googleスプレッドシートとの連携 次にこのAIエージェントをバッチ処理にしていくが、バッチ処理に付きものなのがデータの入出力だ。 タスクリストをどこかで管理し、結果もどこかに出力する必要がある。 今回はGoogleスプレッドシートをドキュメントデータベース代わりに用いる。小規模のバッチ処理程度であればこのアプローチのメリットは大きい。 優れたGUI 慣れ親しんだ表計算GUIでデータを総覧し、その場で微調整できる。 前後の工程との相性 前工程(リストの準備など)や後工程(加工や集計)には結局スプレッドシートを使うことが多い。 何よりスプレッドシートが勝手に編集されていくという様に、小人の仕業のような不思議さがなかろうか。 Googleスプレッドシートを操作する代表的なNPMモジュール google-spreadsheet は、スプレッドシートをドキュメントデータベースライクに扱うための機能も提供しているので、比較的実装は容易である。 google-spreadsheet - npm バッチ処理ワークフローを組む 以上の要素を組み合わせ、一連の処理をワークフローに落とし込んでいく。 残タスクの一覧をGogleスプレッドシートから読み込む 残タスクをひとつ取り出す AIエージェントに調査をさせる 調査結果(自然言語)をデータベース更新用の構造化データにする 構造化データをスプレッドシートに書き込む 残タスクがなくなるまで繰り返す ワークフロー開発は、まず「ステップ」と呼ばれる各段階の部品の開発と、その接合からなる。 複雑に見えるが主要なステップはふたつだけで、残りは while による繰り返し制御で接合する形を示している。全体の構造としてはシンプルだ。 残タスクの読み込みステップの実装 ここではシンプルにGoogleスプレッドシートから未処理のドキュメント(残タスク)を読み込む。 ワークフローの開発中は繰り返し実行してデバッグすることになる。最初に読み込みステップを入れておくと、巻き戻しの役割も果たし、手間も減る。 繰り返し用ステップの実装 次に上記2〜5の処理をひとつの繰り返し用ステップとして実装する。ここは少しプログラミングが複雑になる。 まずは読み込み済みの残タスクからひとつを取り出し、AIエージェントにサイト名とURLを渡す。先ほどチャットインターフェースで試したように、回答が自然言語で得られる。 例えば次のようなテキストで結果が得られる。 textまず、指定されたURLにアクセスして、サイトが朝日新聞の公式サイトと一致するか確認します。 朝日新聞の公式サイトにアクセスできたことを確認しました。現在のURLは https://www.asahi.com/ です。 次に、アクセスランキングのセクションがあるか確認します。 トップページに「アクセスランキング」という名称のセクションがあり、ランキング形式で記事へのリンクが並んでいることを確認しました。 次にこの結果をGoogleスプレッドシートに適切に書き込むため、プログラムで扱える構造化データに変換する。これはLLMの構造化出力という機能により容易に実現できる。 json{ "アクセスランキングの有無": "有り", "アクセスランキングの名称": "アクセスランキング" } この変換のため、サイト調査とは別に structureAgent という別のAIエージェントを用意している。 while による繰り返し 最後に、繰り返し用ステップを残タスクがなくなるまで繰り返す流れを while という制御構造で実現している。 代替案: AIエージェントによる入出力というアプローチ 今回のプログラムでは、まずLLMに調査結果を自然言語で出力させ、それをまた別のLLMで構造化データに変換する二段階の処理を経ている。 実はこれを一段階にまとめる方法がある。それがGoogleスプレッドシートの入出力をAIから利用できるよう「ツール化」するアプローチだ。 Mastraでは開発者が用意した任意のプログラムに適切なメタデータを付与し、AIエージェントがFunction Callingで利用可能にする手法がある。それが「ツール化」だ。MCPサーバーの呼び出しも一種のツールである。 そこでGoogleスプレッドシートとの入出力もツール化してしまえば、理論上はワークフローにおける実装を行う必要はなく、AIエージェントへの指示で完結する。 当初はそのアプローチで実装を進めていた。しかし、私の実装に問題があったのかもしれないが、ツールに空っぽの更新データが渡されたり、更新リクエスト自体がAIエージェントから発行されなかったりと、動作が安定しなかった。 そのため二段階方式を選択し、結果として安定した動作を示した。 AIでやること・プログラムで制御することの線引き このように従来のプログラミングでできることを、AIに委譲すること自体は決して難しくない。 AIはトークン費用がかかり、処理も遅いという弱点はある。しかし変に処理を分割してインターフェースを増やすより、AIエージェントへの指示に包括した方が全体の見通しがよくなることもあるだろう。 今回の二段階方式には、安定性以外にもAIエージェント単体の動作確認やプロンプトチューニングが進めやすいという利点はあった。 いずれも一長一短があり、どちらでやるべきか?という線引きには今後も頭を悩ませることが多いだろうと予感している。 最後に 業界動向を語る上で「多いです!」「増えてます!」と言うのは簡単だが、データを伴わない程度表現はビジネスにおいて稚拙である。 何を根拠に多いとか増えていると言っているのか、リサーチした上で定量的なデータを示したいところだが、以前はそのための技能や人手が必須だった。 AIディープリサーチの登場で、抽象度の高い情報収集と統合は容易になった。しかしディープリサーチではまだできない調査がある。それが今回のプログラムで実現したような「緻密なフィールドワーク」である。 まず自分が使いたいプログラムを作ったのだが、他の誰かの役にも立てば幸いである。 --- # メディアサイトのアクセスランキング採用率は? 平均 74% - 公開日: Thu Apr 17 2025 01:41:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: research, business, technology さまざまなサイトでアクセスランキングを目にするが、実際どのくらい採用されているのだろうか? 記事コンテンツを中心としたいわゆるニュース系メディアサイト220サイトについて調査してみた。 その結果、約 74% のサイトでアクセスランキングが表示されている ことがわかった。 また、一般性の高いメディアにはアクセスランキングが多く、専門性が高いメディアには採用が少ない傾向も見られた。 ランキングコーナーの名称もさまざまだが、「アクセスランキング」という呼称が最も多かった。 弊社でGA4を利用したアクセスランキング表示サービス Rankelt4 を運営している一環としての調査だが、メディアサイト運営者の参考になれば幸いである。 対象サイトジャンル別の採用率ランキングの名称まとめ 対象サイト 調査の詳細データを以下のスプレッドシートで公開した。 ニュース系メディアサイト アクセスランキング利用調査 2025年4月 サイトの選定には、AIディープリサーチを利用した。そのため厳密な選定基準を設けていない点は留意されたい。 AIで300件ほどのメディアサイトを挙げてみた | ideaman's Notes 300サイトほどピックアップされたが、リストには転職サイトや不動産サイト、キュレーションサイト、SNSなども含まれていたため、記事コンテンツ中心のニュース・ブログサイト風のメディアサイトに絞り込んだところ、220サイトになった。 それぞれのサイトのトップページにアクセスランキング、またはそれに類似したセクションがあるかを確認した。 ジャンル別の採用率 おおよそのジャンル別に集計し、採用率の高い順に並べたのが以下の表である。 大分類 サイト数 アクセスランキング利用 採用率 地方紙・地域紙メディア 20 19 95.00% IT企業・ネット専業メディア 34 30 88.24% 全国紙系メディア 13 11 84.62% 出版社・雑誌系メディア 46 37 80.43% 国際・通信社系メディア 8 6 75.00% TV・ラジオ局系メディア 14 10 71.43% 女性向け・ファッション誌発 13 9 69.23% 企業オウンドメディア 22 14 63.64% 専門業界メディア 30 19 63.33% 学術・研究メディア 11 4 36.36% 行政・公共機関 3 1 33.33% オルタナティブ・独立系 6 2 33.33% 総計 220 162 73.64% やはり新聞・ネットニュースメディアの採用率が高い。もはや標準機能と言っていい採用率である。 一方、専門性の高いメディアでの採用は平均を下回る。流行や周囲の動向に関心が小さい、あるいはそうあるべきではないというポリシーが反映されているのかもしれない。 ランキングの名称 アクセスランキングのセクションにどのような名称を用いているかが以下である。 アクセスランキング名 採用サイト数 アクセスランキング 28 ランキング 20 人気記事ランキング 15 RANKING 7 人気記事 6 人気ランキング 6 ニュースランキング 4 記事ランキング 3 いま読まれています 2 その他 71 総計 162 名称としては、「アクセスランキング」が最多で、「ランキング」「人気」がそれに次ぐ。 その他は度数1ずつの例をまとめた結果だが、それぞれ工夫が見られる。スプレッドシートにて確認できる。 まとめ 有名サイトを中心とした220サイトの調査では、アクセスランキングは採用していないメディアサイトの方が少数という結果となった。 また、メディアの性質によっても違いはあり、呼称としては「アクセスランキング」が最も一般的だと確かめられた。 アクセスランキングは流行を追ったり、アクセスを扇動するためだけではない。自身の興味があることと、他の人が興味のあること、そのギャップを確かめるコンテンツでもあると思う。 GA4を利用し、最も簡単にアクセスランキングを実現するために開発した弊社サービス Rankelt4 もよければチェックしていただきたい。 --- # AIで300件ほどのメディアサイトを挙げてみた - 公開日: Thu Apr 10 2025 19:25:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: research, technology, content-management メディアサイトについて横断的な調査をしたいと思い、AIでリストを作ってみた。ディープリサーチによるリスト作成がどのくらいの分量まで行ってくれるのかも確かめたかった。 以前であれば数十件でも素人がバランスよく集めるのは大変だったが、今はコーヒーを飲む間にできてしまう。本当にすごい時代になった。 今回の手順と、作成されたリストを共有したい。 なお、SNSやUGCも広い意味ではメディアサイトだが、ここではメディアサイトは「責任編集された記事コンテンツを配信するニュース形式のサイト」としている。 まずはジャンルと例を挙げるディープリサーチでリスト作成 まずはジャンルと例を挙げる 最終的にはディープリサーチを利用するが、プロンプトには例を上げた方がよいと考えた。いわゆるFew-Shot プロンプティングである。 Few-Shotプロンプティング | Prompt Engineering Guide ChatGPTでGPT-4.5に以下のプロンプトを投げたところ、 text日本国内のニュースサイト、メディアサイトを集めたいと考えていますが、数が多いのでジャンルに分けて調べていきたいです。 - 新聞系https://gemini.google/overview/deep-research/?hl=ja - 雑誌系 - ネットニュース系 - 企業オウンドメディア などの分類があると思いますが、他にも系統が考えられますか? いい提案が返ってきた。最初は10件程度で止まったので、「もう少しください」とおかわりをリクエストしたが、以下のように30ほどの系統を上げてくれた。 text以下のような分類も加えることで、より包括的に整理ができると思います。 基本的な分類例 既に挙げられた分類は次のとおりです。 - 新聞系(朝日新聞、読売新聞、日経新聞など) - 雑誌系(週刊誌、月刊誌、ファッション誌など) - ネットニュース系(Yahoo!ニュース、SmartNews、NewsPicksなど) - 企業オウンドメディア(トヨタイムズ、サイボウズ式など) 追加で考えられる分類例 ①テレビ・ラジオ系 NHK NEWS WEB、日テレNEWS、TBS NEWS DIG、J-WAVE NEWSなど ②通信社系 共同通信、時事通信など ③地域・地方メディア系 ローカル新聞や地域ポータルサイト(北海道新聞、沖縄タイムス、神戸新聞など) ④経済・金融専門系 日経ビジネス、東洋経済オンライン、ダイヤモンド・オンラインなど ...(中略) ㉙行政・公共機関系 政府広報オンライン、官公庁や自治体のニュース発信ページ、警察庁や消防庁など行政の公式情報発信メディア ㉚オルタナティブ・独立系メディア(非主流派系) IWJ(Independent Web Journal)、デモクラシータイムス、OurPlanet-TVなど、既存メディアとは異なる視点を持つ独立系メディア おおよそこちらのイメージに近い系統が得られた。むしろ自分では到底思い浮かばないジャンルもあって感心してしまった。 ディープリサーチでリスト作成 このあとサイト数をもっと膨らませてURLも取得したいのだが、現在のところ生成AIはサイトURLの正確な特定が得意ではない。 そこで時間をかけてWeb検索も組み合わせて結果を生成してくれるディープリサーチを利用する。 今回試したのは以下のふたつだ。 Genspark - 検索を再発明しましょう。エージェントエンジンへようこそ。 Gemini Deep Research - あなたのニーズに応えるリサーチ アシスタント それぞれのディープリサーチに次のプロンプトを投入する。 text日本向けに提供されている比較的著名なメディアサイトをできるだけ多く集めたいです。 最終的には、TSV形式 ジャンル\tサイト名\tURL でリストが欲しいです。 例 全国紙\t朝日新聞デジタル\thttps://www.asahi.com/ 下調べしたサイトはこちらです。以下に上げたサイト以外にも集めてください。 --- 既に挙げられた分類は次のとおりです。 - 新聞系(朝日新聞、読売新聞、日経新聞など) - 雑誌系(週刊誌、月刊誌、ファッション誌など) - ネットニュース系(Yahoo!ニュース、SmartNews、NewsPicksなど) - 企業オウンドメディア(トヨタイムズ、サイボウズ式など) 追加で考えられる分類例 ①テレビ・ラジオ系 NHK NEWS WEB、日テレNEWS、TBS NEWS DIG、J-WAVE NEWSなど ②通信社系 共同通信、時事通信など ...以下略 結果として以下の結果が得られた。 Genspark 259件 Gemini Deep Research 312件 実際のデータをそのまま以下のGoogle スプレッドシートで公開した。 AIによる日本国内のメディアサイトリスト 2025年4月9日調査 (Googleスプレッドシート) UGCは意図していなかったが note.com が含まれていたりする。 AI生成によるもので品質を担保するものではないが、何かの役に立つようであればご自由に利用いただいて構わない。 --- # 高速な通販サイトのための設計と実装 - 公開日: Mon Mar 17 2025 18:00:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology 体感スピードの速い通販サイトを実現するにはどうすべきか、これまでの経験をもとに設計と実装のプラクティスを紹介する。 この記事は、新たに立ち上げるサイトや大幅なリニューアルを行うことを前提としている。既存サイトの手直しにはあまり実践的な内容ではないので、あらかじめ注意されたい。 また、最新の技術は全く紹介しない。最新情報をキャッチアップしたい方には不向きだろうが、本当に汎用的で実績のあるプラクティスだけを解説する。 通販サイトの特徴HTML の戦略はまるごとキャッシュ鮮度の高い情報・パーソナルな情報は JavaScript で提供ローカルストレージのキャッシュ利用後半のコンテンツは遅延読み込みする前後の二分割で十分CSS の戦略と継続的なメンテナンスデザインパターンの網羅と CSS パージその場限りの CSS は分ける画像のインライン展開はほどほどにCSS は適度に分割JavaScript の利用を最小限に脱 jQueryAlpine.js などの利用JavaScript を使わない表現を検討するサードパーティタグの管理サードパーティタグの徹底的な棚卸しGoogle Tag Manager のコンテナはひとつにできるだけ遅延読み込みする画像の軽量化次世代画像フォーマットが正解コストダウンのためにWeb フォントは必要かまとめ 通販サイトの特徴 通販サイトは複合的なサイトである。 読み物としての側面 まずユーザーは商品探しや情報収集を行う。 ダイナミックな側面 在庫や価格は即座に反映することが求められる。 パーソナルな側面 ログインしたユーザー固有の情報を扱う。 インタラクティブな側面 条件による検索や、商品選択、注文操作を行う。 いわゆるメディアサイトでは、ユーザーは記事を読むことが主な目的であり、一度公開された記事が頻繁に変更されることはない。 ところが通販サイトは在庫や価格のような鮮度の高い情報や、個人的な情報を扱う必要がある。また、注文には能動的な操作を行なってもらう必要がある。このように必要な機能が多いため、設計の難易度が高い。 HTML の戦略はまるごとキャッシュ まずトップページや商品情報ページの HTML は、CDN にまるごとキャッシュすることを推奨する。 商品情報や写真はそこまで頻繁に変更されない。先ほど触れたように、通販サイトはまず読み物としての側面がある。そのため HTML 全体をキャッシュし、即座に応答することで体感速度を最大限に高めることができる。 通販サイトのほとんどは、ユーザーのリクエストに応じて HTML を生成する仕組みになっている。当然ながら応答速度は遅くなり、サーバーもすぐ過負荷になる。 鮮度の高い情報・パーソナルな情報は JavaScript で提供 HTML を動的に生成するのが主流なのは、鮮度の高い情報(在庫や価格)、パーソナルな情報(ユーザー固有の情報やカートなどの状態)があるからだ。 しかしそれらの要素は、全体の一部であることが多い。そのため JavaScript により動的に取得し、画面上に反映すればよい。 まとめると全体は HTML をキャッシュしつつ、アクセントとなる部分は JavaScript を用いる戦略だ。 ローカルストレージのキャッシュ利用 パーソナルな情報は、そこまで更新頻度は高くない。ローカルストレージをキャッシュとして用い表示を速やかに行い、バックグラウンドでその情報を更新する戦略も考えられる。 後半のコンテンツは遅延読み込みする 通販サイトは販促目的で 1 ページに多数のコンテンツを盛り込むことが多い。たとえばレコメンデーションの類や、サイトマップを兼ねた巨大なフッターなどだ。したがってページは縦に長くなり、それらの処理にも CPU は消費されてしまう。 しかしユーザーがページの下部までスクロールするとは限らない。ヒートマップを見るとわかるが、むしろスクロールを進める機会は稀とさえ言える。 すなわち、長いページをすべて一度に読み込むのは投機的に損失が大きい。HTML に初めから載せるのは、ページ前半の主要な情報だけに留めて CPU をその表示に全振りする。 ページ後半の補助的な要素は、ユーザーがスクロールのそぶりを見せてから JavaScript を用いて遅延読み込みする。 かつてはスクロールイベントで読み込むという手法が主流だったが、今は交差オブザーバー(Intersection Observer) API を用いる。 交差オブザーバー API - Web API | MDN 前後の二分割で十分 補助的な要素を遅延読み込みするというと、コンテンツごとに細かく読み込みタイミングを制御するイメージがあるかもしれない。しかしページ表示直後の体験を最善化することが目的であるので、筆者としては前後二分割くらいで十分だと考える。 CSS の戦略と継続的なメンテナンス CSS が速やかに読み込まれることも高速なページの条件である。しかし通販サイトは長期間に渡って運用されるため、CSS が肥大化しやすい。 不要になった CSS は削除して最低限のサイズに留めるべきだが、それを人力手動で行うのは事実上、不可能と言ってよい。足りない CSS は見たらわかるが、不要な CSS は知覚する手段がないからだ。 デザインパターンの網羅と CSS パージ そこでデザインパターンの網羅と CSS パージを用いる。CSS パージは、HTML と CSS を機械的に照合し、不要な CSS を自動的に削除するツールだ。 PurgeCSS - Remove unused CSS | PurgeCSS 通販サイトは基本的に決まった要素の組み合わせで構成される。それらをデザインパターンとして網羅しておき、定期的に CSS パージを行うことで CSS の肥大化を防ぐことができる。 デザインパターンの用意は新たな手間かもしれない。しかし上記のアプローチが CSS の肥大化を防ぐ唯一の方法であり、またその手法が確立できたら CSS の高速化にまつわる問題はほぼ解決する。取り組む価値はある。 その場限りの CSS は分ける 通販サイトには特集ページなどの販促コンテンツがある。そういった次々に作成される個別ページの CSS はデザインパターンに含めるべきではない。別の CSS ファイルとしたり、HTML 内のインラインスタイルとして記述する。 デザインパターンではあくまで、サイト全体で共通する土台となる要素を管理する。 画像のインライン展開はほどほどに かつてはリソースファイルをできるだけひとつにまとめた方がよいというプラクティスがあった。そのため CSS ファイルに画像を Base64 エンコードしてインライン展開する手法が注目されたが、今や HTTP/2 の普及で効果が縮小したどころか、悪手ですらある。 表示高速化の大原則は、「HTML と CSS を全速力で処理すること」だ。CSS に画像データを含めて肥大化させると CSS の処理が遅れてしまう。 CSS は適度に分割 可能であれば CSS は適度に分割して、場面ごとに最低限、適切な CSS が読み込まれるようにしたい。 ただし、あまり細かく分割しても保守が面倒になる。筆者であれば例えば次のような分割を検討する。 サイト全体・トップページ・商品一覧・商品詳細・ガイドなどフロントページの CSS 会員登録・ログイン・カート・注文など手続き系の CSS 特集など個別ページの CSS 実際のところ、大半のユーザーは商品探しの段階で離脱する。そのためフロントページの CSS はひとつにまとめる。申し込み手続きに進んだユーザー向けのCSSや、個別ページのCSSを分けるという方針である。 JavaScript の利用を最小限に 意外に思われるかもしれないが、Web ページが遅い一番の原因は JavaScript だ。特にサードパーティタグ(スクリプト)の影響が大きい。 JavaScript は目に見えないから重さがわからない。しかし効果は派手だ。また、通販サイト構築の現場を広く見ても、JavaScript に本当に詳しい人材は少ない。そのため雑に乱用されてしまう。 極端な話、JavaScript さえ使わなければ、重いページを作るというのは逆に難しい。悪意やよほどの過失でもない限り、JavaScript を使わないページは速い。 先ほど HTML はまるごとキャッシュし、JavaScript で動的な情報を提供するという戦略を述べた。それと矛盾するように聞こえるかもしれないが、JavaScript の利用は最小限に抑えるべきというのが筆者の考えだ。 脱 jQuery jQuery は便利でプラグインも豊富だが、それゆえ乱用の元にもなる。今や標準の JavaScript でも jQuery に匹敵する機能がいくつか備わっている。これから新たに構築するサイトでは、初めから jQuery を NG とするのはよい方策だ。 また、特集ページなど表現にこだわりたい場合は、jQueryの部分的な利用も許可するとよいだろう。 Alpine.js などの利用 jQuery で行いたいのは主に DOM 操作だ。Ajax により追加で何かを表示したり、表示の切り替えを行う用途である。しかし jQuery は DOM 操作を逐一行うので重い。複雑さが増すとそれだけパフォーマンスが悪化する。 例えば Alpine.js は Vue.js のようなリアクティブな機能を提供するが、jQuery に比べて軽量である。 Alpine.js JavaScript を使わない表現を検討する そもそも、本当にどうしても JavaScript を使わないといけない文脈なのか、厳しく検討すべきだ。 たとえばカルーセルやモーダルは、CSS だけで表現することも可能だ。同じアニメーションであっても CSS によるアニメーションは、JavaScript による実装より遥かに軽い。 サードパーティタグの管理 商用 Web サイトの運用にサードパーティタグは欠かせない。 しかし、スマートフォンで Web サイトを閲覧しているとき、そのサイト本来のコンテンツとサードパーティのタグ、どちらがスマートフォンにより重い負担を与えているか、想像してみてほしい。 今や多くのサイトで、本来のコンテンツの表示より、サードパーティタグの方がスマートフォンにかける負荷が高くなっている。まるで寄生生物に乗っ取られた宿主であり、どちらが主役かわからない。 サードパーティタグの徹底的な棚卸し まずは不要なタグを削除する。長く運用しているサイトだと、いつ誰が何の目的で入れたかわからないタグがいくつもあるものだ。 ただ、不要なタグを削除するというアプローチはまず上手くいかない。タグが不要であると責任を持つことは難しいからだ。 そこで逆のアプローチを推奨したい。まずはサードパーティタグをリセットし、必要だと責任を持てるタグだけを追加する方法だ。 Google Tag Manager のコンテナはひとつに はっきり言うと Google Tag Manager には重量感がある。そして、コンテナひとつずつのオーバーヘッドが大きい。 商用 Web サイトには多くの会社や人が関わっている。そこで Google Tag Manager のコンテナで権限分離を行い、ひとつのページに複数のコンテナを読み込んでいるケースがよく見られる。 このアプローチはパフォーマンスの観点からすると悪手である。Google Tag Manager を利用する場合、1 ページ内で利用するコンテナはひとつにまとめるべきだ。 できるだけ遅延読み込みする サードパーティタグだが、メーカーの指示通り素直に埋め込むと、ページ表示のプロセスで大渋滞する。大渋滞の結果、コンテンツの表示という本来の処理に割り込みが入り、ページ表示が遅くなる。 そこでサードパーティタグは、利用者の責任において優先度を決め、優先度の低いものは遅延読み込みする。それにより渋滞を避け、本来のコンテンツ表示に道を開けることができる。 よく計測系のタグを遅延させると、データが狂うのではないかと不安を聞く。しかし心配はいらない。そもそも今も大渋滞の結果、計測がいつ行われるかは偶然に左右されている。別に今も正しく計測などできていないのだ。 画像の軽量化 実は画像データは、今や表示速度の主たる要因ではない。通信環境が日々進化した結果、画像データは確かに大きいが重くはない存在になった。 しかし通販サイトは画像が主役であり、大量の画像が読み込まれる。画像の軽量化は行った方がよい。 次世代画像フォーマットが正解 これからの画像軽量化は、次世代画像フォーマットに頼るのが正解だ。JPEG、PNG の軽量化はかなり工夫が必要だが、例えば WebP に変換するだけでデータは半分以下になる。 それさえ行なっておけば、この問題は解決と言ってよい。 コストダウンのために AWS を基盤とする通販サイトは増える一方だが、AWS をはじめとする海外クラウドサービスは、データ転送量がそのままコストに反映される。データ転送量が毎月数十万円に達するサイトも珍しくないだろう。 通販サイトにおいてデータ転送量の大半を占めるのが画像データだ。したがって画像を軽量化すると、データ転送量によるコストを大幅に削減できる。 Web フォントは必要か Web フォントは、表示速度に影響が少ないよう工夫されているが、それでも日本語フォントは重い。パフォーマンスの観点では使わない方がよい。 欧文フォントであれば軽量であり、例えばブランド感を出すために利用するのは合理的だろう。 個人的には、ユーザーは買い物に来ているのであり、通販サイトに日本語 Web フォントが別途必要という理由は滅多にないと考える。 まとめ 以上、高速な通販サイトを実現するため、新たに立ち上げる、あるいは大幅なリニューアルを行うサイトを前提として、設計と実装のプラクティスを紹介した。 とりわけ新しい技術の話はない。基本に立ち返りスマホや PC のリソースは有限であること、ブラウザの動作には特性があることを意識してサイトを作ると、自ずとサイトは高速になる。 サイト高速化はダイエットと本当によく似ている。新しいダイエット手法やサプリを追いかけていても目的は達成できない。体の声を聞いて食事の制御と運動を愚直に続けるだけでよい。そして体重が増えてから対処するのではなく、増えないように予防することが最上の戦略である。 --- # WebP変換の適切なタイミングについて - 公開日: Sat Feb 15 2025 04:38:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, sitespeed, technology, development Web 画像データを WebP や AVIF などの次世代画像フォーマットにすると、同じ画質でもそのデータ量を大幅に削減できる。AWS などの海外クラウドサービスではデータ送信料金を削減でき、ユーザーの通信負担も軽くなる。その効果は小さくない。 しかし、将来的には次世代画像フォーマットの全面移行もあるだろうが、従来フォーマット(JPEG・PNG・GIF)はその活躍期間が長すぎた。供給側のスキルやシステム仕様がすぐには追いつかないし、次世代画像フォーマット自体への信頼感の醸成(WebP や AVIF は本当に次の標準になるのか?)にもまだ当面の時間を要すると考える。 したがって当面の間は、元画像は従来フォーマット(JPEG・PNG・GIF)で供給しつつ、次世代フォーマットへの変換は配信目的に留まる体制が続くと踏んでいる。 この記事では次世代画像フォーマットの代表を仮に WebP とし、従来フォーマットの画像をどのタイミングで WebP に変換するのがよいか論じてみたい。 事前変換かオンデマンド変換かメリットとデメリットオンデマンド変換の功罪オンデマンド遅延変換LightFile Proxy はオンデマンド遅延変換を採用 事前変換かオンデマンド変換か 先に述べたように、従来フォーマット(JPEG・PNG・GIF)を WebP などの次世代フォーマットに変換することで、通信コストを抑え、ユーザー体験を向上させることができる。 では次に「どのタイミングで変換するか」だが、大きく次の 2 つのアプローチに大別されるだろう。 事前変換(Pre-transformation / Eager transformation) バッチ処理などでまとめて WebP に変換しておき、配信時にはすでに変換済みの WebP を返す方法。 オンデマンド変換(On-demand transformation / Lazy transformation) ユーザーからのリクエストがあった段階で変換を開始し、変換完了された WebP を返す方法。 メリットとデメリット 事前変換とオンデマンド変換のメリットとデメリットを挙げてみよう。 事前変換 オンデマンド変換 メリット 配信時点ですでに WebP が用意されているため、レスポンスが安定して速い 必要なタイミングだけ変換を行うので、不要な変換を避けられる デメリット アクセスされない画像まで変換され、従来フォーマットと WebP の二重でストレージを消費する 初回アクセスは変換待ちが生じて応答が遅く、アクセスが集中すると変換処理がサーバーを強く圧迫する これを踏まえると、事前変換は画像の種類が少ない小規模サイトであれば変換ロスは小さくストレージの消費も小さい。しかし、EC サイトのように画像の種類が多いサイトではそれらが制約となり、おのずとオンデマンド変換の一択となるだろう。 一方でオンデマンド変換は小規模サイトに適用してもなんら問題はない。オンデマンド変換の方が潰しの効く方式と言える。 オンデマンド変換の功罪 オンデマンド変換の方が一見、メリットが多いように思えるが、愚直に実装すると次の問題が生じる。 初回ユーザー体験の悪化 変換済みの WebP はキャッシュするとしても初回アクセスは変換を待つ間、画像の表示(レイテンシー)が遅れる。 性能弾力性の低下 WebP への変換はそれなりに負荷の高い処理なのでアクセスが集中するとサーバーリソースをすぐに圧迫する。 小規模サイトであればキャッシュはすぐ有効に機能する。一方、EC サイトはロングテール上に多数のアクセス頻度の低いページを抱える傾向だ。全体として画像への初回アクセスが多いので、キャッシュが効くからと割り切ることはできない。 それより深刻なのはサーバーリソースの圧迫の問題だ。アクセスが集中するとすぐにサーバーがダウン状態に陥る恐れがある。 オンデマンド遅延変換 そこでオンデマンド変換のメリットを活かしつつ、デメリットを抑える工夫がオンデマンド遅延変換だ。オンデマンド遅延変換では WebP への変換が大まかに次の流れで行われる。 初回アクセス 即時変換は行わず、ひとまずユーザーには従来フォーマットの画像を返す。 同時に変換ジョブをキューに登録し、バックグラウンドで WebP 変換を行う。 変換処理 WebP への変換ジョブは所定の並列度でワーカーが逐次処理する。 次回以降のアクセス すでに変換済みでキャッシュされている WebP があればそのデータを返す。 以上のように初回アクセスはいち早く従来フォーマットで応答しておくことで、画像軽量化の恩恵を受けられないものの表示が著しく遅れることはない。 また、変換処理も所定の数のワーカーが逐次処理するためアクセスに応じて極度に集中することがない。 図で比較すると以下のようになる。 LightFile Proxy はオンデマンド遅延変換を採用 弊社が提供する CloudFront 向けの次世代画像フォーマット対応サービス LightFile Proxy は上記のオンデマンド遅延変換を採用している。 LightFile Proxy - AWS CloudFront でシームレスに WebP 対応・通信コスト削減 おかげで非常に安定して稼働しており、本番運用を開始してから設計仕様に起因する障害は皆無である。 --- # 目標使用量ベースのファイル削除ツールを公開 - 公開日: Tue Feb 11 2025 19:36:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, automation, technology システムにおいて、キャッシュファイルやバックアップファイルはできるだけ長く保持しておきたいが、それによりストレージの空き容量が枯渇することは避けたい。 findコマンドを使い、例えば「1 週間以上古いファイルを削除する」といったスクリプトの例はよく目にする。しかし本質的な解決にはなっていない。1 週間でどのくらいストレージを消費するかは正確には予測できないからだ。 そこで以下の Go 言語製ツールを公開した。 go-file-cleaner このツールを使えば「10GB に収まるよう古いファイルから削除する」といった目標使用量に応じたクリーニングを実行できる。 使い方 詳しくは README を参照いただきたいが、以下のように目標容量と対象ディレクトリを指定して実行するだけだ。 bashfile-cleaner 10gb /var/cache この例では/var/cacheディレクトリ内の使用量が 10GB に収まるように古い順にファイルを削除する。 特徴 最終更新時刻と最終アクセス時刻に対応 バックアップファイルは最終更新時刻の新しさで価値を測る。キャッシュファイルでは最終アクセス時刻の新しさが価値の基準となる。 そこで-aオプションを指定すると最終アクセス時刻を基準に古いファイルを判定する。 ブロックサイズベースの計算 このツールは論理的なファイルサイズではなく、ディスク上で占有するブロックサイズを基準にして使用量を計算する。 目的はディスクの空き容量の枯渇を防止することなので、論理ファイルサイズでは誤差が生じる。特に小さなファイルが大量にある場合はその誤差が顕著になる。 UNIX 系 OS であればブロックサイズを自動的に検知する。それ以外の OS では一般的なブロックサイズである4kb=4096を用いるが、--block-sizeオプションで指定することもできる。 省メモリの工夫 この手の実装例では、すべてのファイルの時刻をメモリに保持してソートすることが多い。しかし大量にファイルがある場合、その処理はメモリを圧迫する。 このツールではファイルの時刻を 1 時間単位に切り上げて集計する。正確性には欠けるがメモリの使用量は最大 3600 分の 1 に抑えられる。 切り上げの単位は-bオプションで指定できる。例えば-b 600sと指定すると切り上げ単位は 10 分になるので、時間的に密度の高いキャッシュファイルも効率よく削除できる。 並列処理による高速化 -cオプションで並列度を指定し、処理の高速化を図ることができる。 そもそもディレクトリスキャンを並列化すると本当に時間の短縮ができるのかは、以下のベンチマークで検証した。 miyanaga/go-parallel-dir-scan-bench: Go 言語による並列ディレクトリスキャンの高速化度合いの確認 もちろん並列化が常によいとは限らない。ディレクトリ数が少ないケースや、ディスク I/O のボトルネックが大きい場合はそこまで効果を見込めないばかりか、ミューテックスやチャネルによる同期処理のオーバヘッドで返って非効率になることもある。 --dry-runオプションでリハーサルもできるので、実際に高速化できるか使用するまえに確認いただくとよいだろう。 まとめ このように、大量のファイルがある場合にもディスクをできるだけ有効活用しながら古いファイルを効率よく削除する、という処理には工夫したい点がいろいろとあった。 シェルスクリプトだけではそこまで追求できず、かといって毎回プログラムを実装するのは面倒だ。それが本ツールを開発した動機である。 うっかりサーバーのストレージ使用量が 100%になっていた、という事態を避けるためにも、ぜひ活用いただきたい。 おまけ このツールは最後に再帰的に空ディレクトリの削除も行う。 空ディレクトリの削除もよくある例なので、別のプロジェクトに分けてみた。 ideamans/go-empty-dir-cleaner: Simple recursive empty directories cleaning. --- # デジタル赤字縮小のためのWeb画像軽量化 - 公開日: Sun Jan 26 2025 00:51:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, cloud-cost, business AWS に代表される海外クラウドサービスの利用は、貿易という観点では輸入になる。国内における海外サービスの利用は増す一方であり、IT 分野においては輸入超過の赤字が体質化している。それが「デジタル赤字」の問題だ。 この デジタル赤字を縮小する意外な方法が Web 画像の軽量化だ。 AWS のコストダウンに有効で、デジタル赤字を縮小できる。 処理を自動化しやすく、稼働し始めると運用の手間がかからない。 通信量が減るのでユーザーのモバイル通信コストも減り、表示も早くなる。 画像軽量化というと表示高速化のイメージが強いが、ネット回線が高速になった現在では期待するほどのインパクトはなくなった。 それよりはクラウドコストの面において確実な効果があり、事業者・ユーザー・国の貿易収支にメリットのある、まさに「三方よし」の打ち手になる。 なぜ画像軽量化で AWS の料金が下がるかデータ送信料金はどのくらい安くなるのか次世代画像フォーマットに切り替えるにはまとめ なぜ画像軽量化で AWS の料金が下がるか AWS をはじめ海外クラウドサービスのほとんどは、いわゆるサーバー代と別にデータ送信料金がある二階建ての料金体系になっている。 ユーザー送信するデータ量が多いほど追加費用がかかり、このデータ送信料金が案外バカにならない。 通常、ユーザーへ送信するデータの中で割合が最も大きいのは画像である。したがって画像を軽量化すると送信データ量が減り、データ送信料金は安くなる。 これはシンプルに単価の決まったデータ送信量が減るという話なので、金額の大小はあってもコストダウンは必ず実現できる。 知っている人には常識なのだが、経営層にもなると「AWS = サーバー代」の粒度で解釈されていることも珍しくない。話してみると「知らなかった」と驚かれることがある。 データ送信料金はどのくらい安くなるのか まずは今のデータ送信料金を把握しよう。 通常、AWS における Web サイトのインターネット側の出口は CDN(CloudFront)になる。したがって AWS の料金内訳を見て、CloudFront の料金 = データ転送料金 と見てよい。 正確にはみなさんの AWS 料金の内訳を確認いただくとして、月 100 万 PV のサイトのおおよその目安を示す。 メディアサイト: 月 100 万 PV → 約 8.5 万円 (1PV あたり 5MB / $1=150 円を想定) EC サイト: 月 100 万 PV → 約 17 万円 (1PV あたり 10MB / $1=150 円を想定) サイトの売上に対するとそこまで大きな割合ではないかもしれないが、コストは 1 円でも減らしたい。 Web 画像は通常、JPEG、PNG、GIF のいずれかで配信されるが、もし画像をこれらの従来フォーマットから 次世代画像フォーマット に変換すると、画質を変えずに経験上、画像データ量は半分に軽量化できる。 すると上記のデータ送信料金は以下のように削減できる。 メディアサイト: 60%が画像と想定 → 画像軽量化で 30%削減 = 約 2.6 万円節約 EC サイト: 80%が画像と仮定 → 画像軽量化で 40%削減 = 約 6.8 万円節約 言い換えると、今後も従来フォーマットで画像を配信するのはユーザーに同じ画像を届けるために 2 倍の配送料を AWS に献上し続けるのと同義である。しかもユーザーにも負担がかかる。 次世代画像フォーマットに切り替えるには 現在、主流となっている次世代画像フォーマットは、WebP または AVIF である。特に WebP の対応ブラウザシェアは 97%を超えており、たとえば今後 Web 画像を WebP で出力するという判断もありえる。 しかし、CMS や EC システムによってはまだ WebP に対応していない場合も多いだろう。「今から画像は軽量な WebP で!」と言われても即座に対応できない。 現実的には、従来の画像フォーマットから WebP への変換を自動で行い、問題を解決する仕組みを設けるのがよい。しかしそれにはさまざまな方法や製品があり、ここでは解説しきれない。 宣伝になり恐縮だが、弊社では LightFile Proxy というサービスを提供している。 これは画像データの提供元である Web サーバーや Amazon S3 バケットと CloudFront の間に立ち、従来フォーマットの画像を適切に WebP へ変換する仕組みだ。 このサービスのメリットは、既存の Web サーバーや Amazon S3 バケットへ変更を一切加えることなく、AWS から送信する画像を WebP へ変換できる点にある。開発作業は不要で、導入したその日から AWS のデータ送信料金を削減できる。 まとめ 以上のように AWS に代表されるクラウドプラットフォームの料金は、単純なサーバー代だけではなく、データ送信料金との二階建て料金体系になっている。 最もデータ量が大きい画像を軽量化することで、そのデータ送信料金は確実に削減できる。 今や次世代画像フォーマットも成熟しており、WebP や AVIF への切り替えを推奨する。それにより、データ送信料金を 30%〜40%程度削減できる見込みが高い。 近年は海外 IT サービスの隆盛が著しく、円安も常態化している。企業の負担は大きく、デジタル分野での貿易赤字も深刻だ。Web 画像の軽量化は、地味ながら戦略的な技術投資となる。ぜひ検討いただきたい。 --- # Web技術で作るグラフィックツールの拡張機能 - 公開日: Tue Jan 14 2025 19:54:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, technology 次のグラフィックツールの拡張機能(プラグイン、アドオン、アプリなどの呼称)について軽く調査した。 Figma Canva Photoshop いずれも Web 技術(HTML・CSS・JavaScript)で作ることができる。HTML と CSS でユーザーインターフェースを整え、JavaScript でアプリケーションとの連携や独自の機能を実装する。 これはまさにブラウザの拡張機能と同じアプローチであり、多くのアプリケーションがそれに寄せてきている流れを感じる。 プラグイン開発と言うとハードルの高い印象もあるが、今や Web のフロントエンド技術さえあれば簡単に挑戦できる時代になった。簡単なものであれば AI だけで実装できてしまうかもしれない。 個人的なメモも兼ねて、それぞれのグラフィックツールの拡張機能の作り方を紹介する。 グラフィックツールの拡張機能の作り方Figma の場合Canva の場合Photoshop の場合プラグイン・エンジニアというキャリア グラフィックツールの拡張機能の作り方 Figma の場合 こちらの記事を参考にした。 Figma プラグインの作り方 本格的な Figma プラグインはデスクトップアプリ版でしか利用できないようだ。 デスクトップアプリ版の Figma にはプラグインプロジェクトの生成機能があり、選択を進めるだけで Node.js のプロジェクトが作成できてしまう。 以下のコマンドで TypeScript からのコンパイルも自動で行われるので、コードを変更すると結果が Figma にも即座に反映される。開発体験が非常によい。 bashyarn watch Figma のドキュメント上の要素はオブジェクト化されており、JavaScript でそれらを自由に操作できる。 次のページで Figma プラグインの API について詳しく解説されている。 Introduction | Plugin API Figma には Web API もあり、実は当初、プラグインを開発するつもりではなく、API を使って Figma のデータを操作することを試みた。しかし、API ではドキュメントの内容までは操作できないようで、プラグインの調査を開始した経緯があった。 REST API - Figma Canva の場合 Canva は「アプリ」として拡張機能を開発するようだ。こちらも NPM でプロジェクトを立ち上げる。 Quickstart - Canva Apps SDK Documentation Canva については試作に至らなかったが、React で開発する様子が解説されている。 日本語ではこちらの記事が参考になる。 [Part 1] Canva API を使ったアプリ作成 事始め Photoshop の場合 デザインツールの雄、Photoshop も今は Web 技術ベースでプラグインを開発できる。そのプラットフォームを UXP(Unified Extensibility Platform)と呼ぶ。 Documentation-UXP for Adobe Photoshop Photoshop の場合、UXP Developer Tool というアプケーションがあり、開発中のプラグイン管理に加え、サンドボックス環境まで用意されている。 Adobe UXP Developer Tool UXP Developer Tool のサンドボックス環境で HTML ライクな UI 記述言語と JavaScript を変更すると、自前のプラグインをすぐに試すことができた。サンドボックス環境からエクスポートするとプロジェクトファイル一式を取得できる。 ひとつ試しに作ってみた。 miyanaga/my-first-photoshop-plugin: Photoshop でテキストオブジェクトを作成するプラグインの作例 Photoshop の場合、JavaScript でオブジェクト指向ライクな API を用いるのではなく、JSON で宣言的なバッチ処理を記述し、それを実行するというアプローチだった。 Photoshp には伝統的に操作を自動化するアクション機能がある。内部ではこのアクション機能を流用する形になっているのだろうと想像する。 プラグイン・エンジニアというキャリア プラグイン開発はハードルが高いと思われている。確かに母体となるアプリケーションと、API・SDK の体系を学ぶのは大変だ。加えてたいていはニッチな世界になるので、ネットにも情報が少ない。 しかし昨今は母体アプリケーション側にとってもエコシステムの拡充が重要な競争力になることは明らかだ。プラグイン開発体験を向上させる圧力が高まっていると感じる。重い腰を上げてみると思ったよりも簡単に作れてしまう。 筆者はプラグイン開発の需要はこれから高まると感じている。AI を業務フローに組み込むには、プラグインによるカスタマイズが最もシームレスになるからだ。 AI の活用に取り組みたい企業やエンジニアは多いだろう。それをインテグレーションする技術も重要であるが、よい意味で目立たず競争の緩やかなニッチな分野になる気がしている。 あるアプリケーションのプラグイン開発シーンで頂点を目指すのは難しいが、いろいろなアプリケーションのプラグイン開発を広く浅く得意とする「プラグイン・エンジニア」というキャリアも有望ではないかと思う。 --- # スピード指標の確率分布ユーティリティをNPMで公開 - 公開日: Tue Dec 31 2024 17:12:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research Core Web Vitals をはじめとする多くサイトスピード指標は対数正規分布に基づくとされている。 その計算についてこれまでプロジェクトに応じて実装していたが、共通して利用できるユーティリティライブラリを公開した。 web-vitals-distribution - npm インストールや基本的な使い方は日本語の README を参照されたい。 web-vitals-distribution/README.ja.md at main · ideamans/web-vitals-distribution サイトスピード指標と対数正規分布Chrome UX Report からの対数正規分布の推定応用例 サイトスピード指標と対数正規分布 先日、以下の記事を書いた。 サイトスピード指標の対数正規分布を確かめる | ideaman's Notes Core Web Vitals に代表されるサイトスピード指標は実際にはユーザーや PV によって大きくばらつきがあるが、多数のサンプルを集めると対数正規分布に近づくとされている。 一方、Google は Chrome ユーザーから実際にサイトを閲覧したときのスピード指標を収集し、Chrome UX Reportとして公開している。 その集計結果はPageSpeed Insightsでも参照できる。 Chrome UX Report からの対数正規分布の推定 上記のように Chrome UX Report のデータは「良好な割合」「不良な割合」のように集計済みの概要のみであるが、これらの情報から対数正規分布を推定できる。 その計算過程は以下の記事で述べたが、 Web サイトのスピードをもっと感覚的に想像する #高速化 - Qiita その手法をライブラリに落とし込んだものが、今回公開した NPM パッケージである。 web-vitals-distribution - npm 応用例 このライブラリを用いると、以下のいずれかの情報があれば簡単に対数正規分布モデルを作成できる。 Web Vitals の種類と「良好な割合」「不良な割合」 実際に計測した値の平均値と中央値 そして次のような計算ができる。 PDF(確率密度関数)の描画。実際に計測した値が対数正規分布にどのくらいフィットするか視覚化できる。 各種統計量の計算。期待値(平均)、中央値、最頻値、任意のパーセンタイル値など。 CDF(累積分布関数)の計算。PageSpeed Insights で計測した値は何パーセンタイルに相当するかなど。 サイトスピードのばらつきをより具体的に理解するため、有用なライブラリにしたいと願っている。 --- # サイトスピード指標の対数正規分布を確かめる - 公開日: Sun Dec 29 2024 18:51:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology Core Web Vitals をはじめとするサイトスピード指標の多くは、対数正規分布 に基づくとされている。 実際に Google はその前提に基づき PageSpeed Insights のスコアを設計しており、弊社も「そのようなものだ」としてデータを扱っている。 しかし今一度、その前提を自分の目で確かめてみようと思った。 この記事では、サイトスピードに特化した無料のアクセス解析ツールである Speed is Money で計測したサイトスピード指標が本当に対数正規分布に基づくのか、いくつかの視点から確認をしてみた。 対数正規分布とはページの読み込み時間(OnLoad)の理論的分布正規分布化正規分布の検定Q-Q プロットによる確認読み込み時間(OnLoad)は対数正規分布に基づくCore Web Vitals は対数正規分布に基づくかまとめ 対数正規分布とは はじめに 対数正規分布 とは何か。「対数」のつかない 正規分布 はよく知られている。平均値を中心とした左右対称の釣鐘型の分布だ。 統計分析を理解しよう:正規分布、標準化、標準正規分布の概念 |ニッセイ基礎研究所より引用 一方、対数正規分布は正規分布にやや似ているが右側がなだらかな坂になっている。正確には正規分布の対数を取ったものが正規分布に従う分布である。 【徹底解説】対数正規分布とは | Academaidより引用 感覚的には 「正規分布のように一定のボリュームゾーンがあるが、大きい方の値は極端に大きいものまで発散がちになる」 といった理解でよいだろう。 身近な例では身長は正規分布に従い、体重は対数正規分布に従うと言われることがある。例えば成人男性の平均身長を 170cm とすると、2 倍の 340cm という身長はまずありえない。しかし体重は平均 60kg とすると、2 倍の 120kg という体重はあり得るし、もっと大きな体重の人もいる。 ページの読み込み時間(OnLoad)の理論的分布 以下はとある通販サイトのページ読み込み時間(Onload)のサンプルから描いたヒストグラムと、理論的に計算された対数正規分布を赤い線で示したものだ。 INFO モバイル端末でのサンプルを抽出している。また、遅いサンプルは極端に遅く、グラフが右に伸びすぎしてしまう。そのためサンプルは 99 パーセンタイルまでで絞り込んでいる。 両グラフの形状は酷似しており、感覚的にはほぼ一致と言えそうだ。 正規分布化 対数正規分布は、X 軸を対数目盛にすると正規分布と同じ形状になる分布 と言い換えることもできる。 上記と同じページ読み込み時間(OnLoad)に対し、自然対数をとったデータのヒストグラムと、理論的な正規分布(赤い線)を重ねたものだ。 INFO 上記のグラフは平均値が中央に見やすくなるよう、対数を取ったデータの平均値から ±4σ に絞りこんでいる。 右上に多少飛び出しが見られるが、こちらも形状の差異は小さい。 正規分布の検定 サンプルが正規分布に基づくか確率的に検定する方法がいくつかある。それを計算してみた結果が以下だ。 残念ながらいずれの方法でも 「正規分布に基づくとは言えない」 という結果となった。 シャピロ=ウィルク検定 ShapiroResult(statistic=0.9975211655795352, pvalue=1.6639813843026255e-28) コルモゴロフ=スミルノフ検定 KstestResult(statistic=0.9999999998390625, pvalue=0.0, statistic_location=6.2878585601617845, statistic_sign=-1) ダゴスティーノの歪度検定 SkewtestResult(statistic=-6.180829491913321, pvalue=6.376565892991275e-10) ダゴスティーノの尖度検定 KurtosistestResult(statistic=2.199180579201117, pvalue=0.027865084693154626) オムニバス検定 NormaltestResult(statistic=43.03904842804084, pvalue=4.5101333284193215e-10) INFO e-28は、× 10 の-28 乗を示す。つまり非常に小さな値。 p 値 (pvalue)が 0.05 (信頼水準 95%)以下なら、帰無仮説「正規分布に基づく」を棄却する判断となる。いずれの検定結果も p 値が 0.05 を下回らず、当該仮説は棄却されてしまった。 今回のケースは比較的サンプルが多い(約 6 万点)。サンプルが多すぎるとわずかな違いも大きく反映されやすい、といった言及もあった。筆者の専門性ではこれ以上の追求は難しく、今後の課題のひとつとしたい。 Q-Q プロットによる確認 視覚的な比較方法として Q-Q プロット も試してみる。Q-Q プロットはサンプルと理論的な分布の累積値の差分を示すグラフで、両者が近いほど直線に近づく。 ほぼ直線で一致に近い、と言っても賛同を多く得られるだろう。 読み込み時間(OnLoad)は対数正規分布に基づく 以上のように、検定ではスッキリした結果がでなかったが、視覚的には読み込み時間(OnLoad)は対数正規分布に基づくように感じる。 理論的な分布とのフィットには判断は恣意的な余地があり、用途によっては多少精度が粗くてもよいものらしい。 弊社としては、今後も 読み込み時間(OnLoad)は対数正規分布に基づく をポリシーとする。 Core Web Vitals は対数正規分布に基づくか Core Web Vitals の 3 つの指標についても同様の確認をおこなった。それぞれの画像をクリックすると詳細を閲覧できる。 LCP (Largest Contentful Paint) CLS (Cumulative Layout Shift) INP (Input to Next Paint) 対数正規分布 正規分布 Q-Qプロット LCP と INP については、対数正規分布に基づくと言えそうだ。CLS についてはやや傾向は見られるが、他の分布に比べると一致度合いは低い。 今回、計測を行ったサイトでは CLS への対応はほぼ万全にできている。従って CLS の値は全体的に小さく、ばらつきも少ない。それが対数正規分布との差異を大きくしたと考えられる。 意外なのは INP である。INP はユーザーの操作に起因する指標であり、その値はユーザーがどのような操作をするかで大きく変わるはずだ。よく行われる操作には偏りがあり、その偏りが個性として反映されるように思えるが、結局は対数正規分布に近い形へと平準化されている。 まとめ ページの読み込み時間(OnLoad)に加え、Core Web Vitals もほぼ対数正規分布に基づくと言えそうだ。 Google の判断に従い前提としてきたことであったが、改めて自身の実感としても確認できた。データを扱う上で、ときどき常識を疑うのも、決して無駄ではないだろう。 サンプルと理論的な分布のフィットを探る学習にもなったし、Python によるデータ処理やグラフ描画のよい練習にもなった。 --- # Ranklet4 アクセスランキングの AIレビュー登場! - 公開日: Mon Dec 16 2024 05:43:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, technology, development, automation 弊社が提供するアクセスランキング機能運用サービス Rankelt4 に、AIレビュー機能 を追加した。 CDTV(カウントダウンTV)のようなランキング形式の音楽番組をイメージしてほしい。先週のトップ10を振り返り、今週のトップ10を楽しみながらMCがコメントする。 AIレビュー機能はいわばそのようなレポート機能だ。Ranklet4に設定いただいたアクセスランキングを活用し、生成AIで上位記事とその推移に関するレビューを自動作成する。 レビューは毎週または毎月、メールで自動配信もできる。メディアやブログ運用のアシスタントとしてぜひ活用いただきたい。 何ができるか使い方ランキングの推移に関する設定AIレビューに関する設定定期メール配信テストメール配信ランキング解析の提案生成AI活用の悲願 何ができるか Ranklet4で作成済みのアクセスランキングについて、生成AIがその直近の推移に関するレビューを自動生成する。 ランキングの推移とレビューは、簡単な設定で定期的にメール配信もされる。 あなたは毎週または毎月、ただ待っているだけで、人間が作成したかのようなアクセスランキングレポートを受信できる。 使い方 作成済みランキングの一覧から、AIレビューを選択する。 数秒から十数秒、しばらく待つと左側に週間ランキングの推移、右側にそのランキング推移に対する生成AIによる自動レビューが表示される。 ランキングやAIレビューは少しカスタマイズできる。 ランキングの推移に関する設定 ランキングの推移は以下の設定を変更できる。 週間 | 月間 ランキングを計算する期間を選択する。 先週 | 今週 今週と先週の推移か、先週と先々週の推移かを選択する。今週は進行中で日数が少ないが、先週は7日間を確保できる。(月間を選択した場合は今月と先月になる) PVを考慮 ランキングの推移にPV数も加え、レビューでPV数の変化にも触れるかを選択する。 記事の更新が多く、アクセス数と順位の変動が多いメディアであれば週間、そこまで多くなければ月間がよいだろう。 INFO なお、ランキングウィジェットの設定では表示する順位を指定できるが、AIレビューでは固定で10位までを対象とする。 AIレビューに関する設定 AIレビューについては以下の設定を変更できる。 記事形式 | 箇条書き AIレビューを見出しと段落からなる記事形式で生成するか、シンプルな箇条書きにするかを選択できる。 少なめ | 標準 | 多め AIレビューの分量を3段階で選択できる。 読みやすい形式と、内容がしっくりくる分量を選んでいただきたい。 定期メール配信 気に入ったランキング推移とAIレビューの設定が見つかったら、定期的なメール配信を設定しよう。 画面の下部に定期メール配信の設定がある。トグルボタンをONにすると配信先のメールアドレスを指定できるようになる。 メールアドレスは改行区切りで複数できる。 INFO 設定を変更したら メール配信設定を保存 ボタンを忘れず押すように注意されたい。 週間ランキングの場合は毎週月曜日の午後0時ごろ、月間ランキングの場合は毎月1日の午後0時ごろに以下のようなHTMLメールが送信される予定である。 なお、トグルボタンをOFFにすると一時的にメール配信を無効にできる。 テストメール配信 定期的に配信されるメールがどのようなものか、テスト送信もできる。 メールアドレスを入力し、テストメールを送信 ボタンを押すとメールが送信される。 ランキング解析の提案 GA4によるアクセス解析は言うまでもなく大切だ。しかし見るべきポイントは多く難解でもある。 そこでもっとカジュアルに、多くの関係者にもサイト動向へ興味を持ってもらう方法がないか考えてきた。 そのアイデアのひとつが、アクセス解析から ランキング解析 に単純化するという切り口だった。 人気コンテンツはアクセス解析の代表的なレポートであるし、ランキングをウォッチする行為は多くの人にメンタルモデルもある。きっと馴染みがあると期待している。 生成AI活用の悲願 前身のサービス Ranklet から、GA4に対応するタイミングで Ranklet4 にリニューアルしたのが2023年7月。 ちょうど生成AIへの注目もうなぎのぼりの時期で、ランキング解析と組み合わせたら面白いのではないか、と当時から構想していた。 実用的かつリーズナブルな生成として Gemini を用い、本機能をリリースできたことを大変嬉しく感じている。 ユーザーからの評価はこれから下されるが、フィードバックを大切にしながら有用性を高めていきたい。 --- # Core Web Vitalsの改善は本当に進んでいるか - 公開日: Thu Nov 28 2024 19:17:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology Core Web Vitalsが検索順位に影響すると言われて数年が経った。SEOに関わらず、サイトスピードに関心のあるサイトは少なくないだろう。 そこで日本の主要なECサイト100サイトについて、Core Web Vitalsが改善の方向に向かっているのか調査してみた。 やはり感覚のとおり、CLSは多くのサイトで改善済みだがLCPはほとんど改善が進んでいない実態が見えた。 CLSは順調に改善CLSを改善するにはLCPはほぼ横ばいLCPを改善するにはINPはやや改善傾向INPを改善するには各サイトの詳細データについて CLSは順調に改善 まずはCLS (Cumulative Layout Shift・レイアウト変化の累積)の平均値の推移を見てみよう。以下は主要通販サイト100サイトのスマホ端末におけるCLS(75パーセンタイル)の月次推移だ。 Core Web VitalsがSEOの文脈で注目を集めた2021年〜2022年にかけて、改善は大きく進んだ。 サイト個別にCLSの推移を眺めていても、やはり同時期に値がほぼ0に近くまで改善できたケースが多い。 したがってCLSの値をほぼ0に完全化できたサイトと、改善が進んでいないサイトに二極化が進み、その結果として平均値が下落していると予想される。 CLSを改善するには 以下の記事に詳しく述べたので参考いただきたい。 Core Web Vitalsの実践的な改善術 - CLS編 | ideaman's Notes LCPはほぼ横ばい 次にスマホ端末におけるLCP (Largest Contentful Paint・最大要素の表示時間)の75パーセンタイル平均値の推移だが、CLSと比べるとほぼ横ばいである。 当然、SEOのためにLCPも多くのサイトで改善が試みられたであろう。しかしほとんど上手くいっていなかったことがわかる。 この1年くらいはやや下落傾向にあるだろうか。今後の LCPを改善するには 丁寧にコーディングすることで改善できるCLSに比べ、LCPの改善は確かに難しい。 加えて、画像軽量化がLCPの改善に効くといった誤解も蔓延している。 そのあたりを含め、以下の記事にLCPの改善のコツをまとめた。 Core Web Vitalsの実践的な改善術 - LCP編 | ideaman's Notes INPはやや改善傾向 最後に新しい指標、INP (Interactive to Next Paint・操作から応答の描画まで)のスマホ端末における75パーセンタイル平均値の推移だ。 グラフの中央あたりの開始点に著しい変化があるが、これはデータの収集が始まって間もない時期なのでノイズとして無視してよいだろう。 それ以後はなんとなくだが、改善傾向にあるように見える。 ただ、多くのサイトでINPの改善アクションが効果的に進められている気配は感じない。 そのため改善傾向しているような傾向は見られるが、もしかしたらサイトの改善ではなく、端末やソフトウェアの進歩によるところが大きいのかもしれない。 INPの推移は今後も注視したい。 INPを改善するには CLSやLCPはページの読み込みプロセスで評価される値だが、INPはユーザーの実際の操作に基づく指標だ。 そのためPageSpeed Insightsなどのツールでシミュレーションができず、また悪化の本当の原因も掴みにくい。 そこで弊社ではINP悪化の詳細ログをトラッキングするサービスを始めた。 INPの収集および改善提案サービスを開始 | ideaman's Notes また、関係性があると言われるTBT(Total Blocking Time)も含めたINPの改善術について以下の記事にまとめてある。 Core Web Vitalsの実践的な改善術 - INP編 | ideaman's Notes 各サイトの詳細 以下のレポートで今回の集計に用いたスピード関連指標の詳細を確認できる。 国内通販 売上TOP100 (2023年夏) サイトスピードランキング | 無料 | サイトスピード簡単比較 データについて GoogleはChromeユーザーから実際のサイト閲覧時の各種スピード関連指標を収集し、集計結果をCrUXとして公開している。 CrUX の概要 | Chrome UX Report | Chrome for Developers CrUXはBigQueryを用いて参照し、スマホについてのデータに絞り込んである。 BigQuery での CrUX | Chrome UX Report | Chrome for Developers 主要ECサイトは、以下のデータを元に上位100サイトを対象とした。 【2023年夏版】通販売上高ランキングTOP523 --- # Core Web Vitalsの超実践的な改善術 - 総集編 - 公開日: Sun Nov 24 2024 01:41:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research このブログでは最近、Core Web VitalsやPageSpeed Insightsに関する記事を続けて書いた。 きっかけは2024年11月23日のMTDDC Meetup TOKYO 2024にて登壇させていただくことになり、これまでの集大成を語りたく「これで完璧!超実践的Core Web Vitalsの健全化手法」と仰々しいテーマにしたのだが、プレゼンテーションの構成を検討するうち、話したいことが多すぎてまったく完璧ではなくなってしまった。 宮永 邦彦 氏|MTDDC Meetup Tokyo 2024【11/23(土)開催】 そこで講演では話しきれない内容を補完するために、テーマについて詳細な記事を書き始めた。 PageSpeed Insightsの正しい読み方・活かし方 | ideaman's Notes pagespeed-quest/README.ja.md at main · ideamans/pagespeed-quest Core Web Vitalsの実践的な改善術 - CLS編 | ideaman's Notes Core Web Vitalsの実践的な改善術 - LCP編 | ideaman's Notes Core Web Vitalsの実践的な改善術 - INP編 | ideaman's Notes INPの収集および改善提案サービスを開始 | ideaman's Notes Core Web Vitalsの改善術 - サードパーティタグ編 | ideaman's Notes 本記事はその講演のまとめと、これまで書いた記事の総集編をお届けする。 Core Web VitalsCore Web Vitalsとサイトの収益性失敗するスピード改善プロジェクトとPageSpeed Insightsアイデアマンズ流サイトスピード改善提案PageSpeed Questサードパーティタグの分離Lighthouseによる指標改善の仮説検証サードパーティタグの復元と対策の検討最後に Core Web Vitals Core Web Vitalsは、Googleが提唱するUXの健全性を示す3つの指標だ。 多くの記事で解説があるのでここでは詳細は省略するが、飲食店のサービスに例えるとユーモラスに覚えられると思っている。 LCP (Largest Contentful Paint) 「セットメニューのメイン料理が早く出てくると嬉しい」 CLS (Cumulative Layout Shift) 「お皿をガチャガチャと並べずコース料理のように優雅に」 INP (Interaction to Next Paint) 「お客さんが店員を呼んだらすぐに応える」 Core Web Vitalsとサイトの収益性 以下は実際のある通販サイトにて、Core Web VitalsのひとつであるLCP(Largest Contetful Paint)と、CVRの関係を計測した結果である。 平均LCPが1秒弱のセッションはCVRが1.72%であるが、3秒強になると0.33%まで低下する。その差は5.2倍に及ぶ。 つまりLCPが良好であれば注文まで行きついたであろうユーザーも、約2秒のLCPの遅れで5人のうち4人が離脱することを示す。 これほどまでにユーザーはサイトスピードにシビアということだ。 SEOの文脈を抜きにしても、サイトスピードの改善には大きな意味がある。 失敗するスピード改善プロジェクトとPageSpeed Insights SEOや収益性改善を意図してCore Web Vitals、あるいはサイトスピードの改善を企図するサイトは多い。しかしそのプロジェクトの多くはうまく進行しない。 以下のような進め方はたいてい失敗する。 PageSpeed Insightsのスコアに注目する そのスコアを改善するために指摘事項の消化に取り組む 改修をリリースして効果測定する 実際は多くの企業がこのような進め方をしていると推測する。 この進め方はなぜうまくいかないのか、正しく効果を生むためにPageSpeed Insightsをどのように活用すればよいのか、以下の記事にまとめた。 PageSpeed Insightsの正しい読み方・活かし方 | ideaman's Notes アイデアマンズ流サイトスピード改善提案 弊社アイデアマンズ株式会社でサイトスピードの改善提案を行う際は、基本的に以下の流れで行なっている。 PageSpeed Questによる検証環境の用意 サードパーティタグの分離 Lighthouseによる指標改善の仮説検証 サードーパーティタグの復元と対策の検討 PageSpeed Quest 弊社ではページスピード改善の仮説を検証するためにPageSpeed Questを利用している。 PageSpeed Quest これは弊社で独自開発したフロントエンドのスピード改善を実験するフレームワークである。 HTTPプロキシを使い、あらゆるWebページについて簡単にスピード改善の仮説検証を進められる。 オープンソースで公開しているので、読者の皆さんにも気軽にご利用いただきたい。 サードパーティタグの分離 サードパーティタグは気軽に追加されてしまうが、実はサイトスピード低下の大きな要因になっている。 その負荷が仮説検証のノイズになり、内容を柔軟に変更できないため他の技術要素とアプローチが異なる。そのため弊社の調査では一度分離している。 Lighthouseによる指標改善の仮説検証 続いてPageSpeed Questに同梱されたLighthouseを利用し、次の順番で指標の改善を図る。 CLS (Cumulative Layout Shift) 最低 0.25 目標 0.1 TBT (Total Blocking Time) 最低 600ms 目標 200ms FCP (First Contentful Paint) 最低 3秒 目標 1.8秒 LCP (Largest Contentful Paint) 最低 4秒 目標 2.5秒 SI (Speed Index) 最低 5.8秒 目標 3.4秒 CLSとLCPはCore Web Vitalsと共通であり、Lighthouseでの評価を改善できればおのずとCore Web Vitals(Chrome User Experience Report)の評価も上がる。 Core Web Vitalsの実践的な改善術 - CLS編 | ideaman's Notes Core Web Vitalsの実践的な改善術 - LCP編 | ideaman's Notes TBTはINPに関係があると言われているが、INPの悪化はTBTによるものとは限らない。 加えてINPはユーザーがページ操作を行なった結果として観測される指標である。つまりLighthouseによるシミュレーションでは真の原因を掴むことができない。 弊社ではINPの発生原因を実際のユーザー環境から収集し、BigQueryに蓄積するサービスも開始した。 Core Web Vitalsの実践的な改善術 - INP編 | ideaman's Notes INPの収集および改善提案サービスを開始 | ideaman's Notes サードパーティタグの復元と対策の検討 ここまでページを構成するWeb技術を工夫しCore Web Vitalsの改善を図ってきたが、最後に分離してあったサードパーティタグを復元する。 すると当然だが、スコアによる評価は下がる。主にTBTが悪化する。その悪化の大きな原因となっているサードパーティタグを特定し、TBT悪化を避ける方法を提案する。 Core Web Vitalsの改善術 - サードパーティタグ編 | ideaman's Notes サードパーティタグはここまで柔軟に改変できた自社配信のリソースと異なり、他社のプログラムであるため内容を変更することができない。 そのため対策としては削除するか、読み込むタイミングを遅延するといった消極的な対策しかとれない。 また、サードパーティの扱いはWeb制作者の課題ではなく、通常はサイトオーナーの責任だ。最後はサイトオーナーの決断次第となる。 最後に インターネットが社会に浸透しはじめ、そろそろ30年になる。 その間、社会のネットリテラシーは上がり続けてきた。そしてよほどの事態がない限り、今よりネットリテラシーが下がる時代はやってこない。 そして私たちインターネット関係の事業者はみなその夢を見て仕事をしていると言っていいだろう。 自信のあるユーザーが求めるサービスはなにか? それは「自分の思考スピードについてこれるサービス」だと考えている。 思考スピードについてこられないサービスが切り捨てられる傾向は、今後も強くなる一方である。 社会のニーズに応え、生き残るために私たちはサイトスピードを追求しなければならない。 サイトスピードの改善は、徹底して行うことで短期的にも収益改善を見込める。ぜひ多くのサイトに前向きに投資いただきたい。 サイトスピードと収益性を高い解像度で理解する | ideaman's Notes --- # 移動しました - Core Web Vitalsの実践的な改善術 - INP編 - 公開日: Thu Nov 21 2024 18:42:00 GMT+0900 (Japan Standard Time) - 著者: undefined 移動しました このページはURLを修正しました。自動的に転送されない場合は、以下のリンクから移動してください。 Core Web Vitalsの実践的な改善術 - INP編 --- # Core Web Vitalsの実践的な改善術 - INP編 - 公開日: Thu Nov 21 2024 18:42:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research INP(Interaction to Next Paint)は、2023年4月にFID(First Input Delay)と置き換わる形でCore Web Vitalsに昇格した指標だ。 Interaction to Next Paint(INP)  |  Articles  |  web.dev この指標はページの読み込みに関する指標ではなく、ユーザーの操作に対する応答の速さを示す値である。 性能の低いPCを使っていると、画面上のメニューやボタンなどをクリックしてもすぐに反応せずイライラすることがあるだろう。同じ事象はWebページでも起こりうる。それがINPと捉えて間違いない。 本記事では、このINPの改善術について解説する。 INPはCore Web Vitalsの新たな課題 主要通販サイト100サイトのうち、Core Web Vitalsが良好と評価を受けているサイトは以下の通りだ(2024年10月のデータ)。 CLSが良好なサイト 63% LCPが良好なサイト 68% INPが良好なサイト 46% 以前のFIDは、評価の低いサイトは少なかった。INPに変わって評価が厳しくなった格好となった。 INPの改善の難しさ Core Web VitalsのCLS(Cumulative Layout Shift)とLCP(Largest Contentful Paint)は、Webページの読み込みプロセスにおいて計測される。そのためPageSpeed Insights(Lighthouse)で容易にシミュレーションできる。 ところがINPは、ユーザーがページ上で何らかの操作を行った際にはじめて計測される。何の操作を行うかはユーザーによるので、読み込みプロセスのように機械的なシミュレーションができない。 そのため試行錯誤による仮説検証を進めにくい指標であり、他の指標と異なるアプローチが必要となる。 INPの仕組みと悪化の原因 ユーザーがページを操作すると、JavaScriptで登録されたプログラム(イベントハンドラ)が何らかの処理を行う。その結果、ページの一部が描き変わったり、次のページに遷移したりする。 このユーザーのページ操作から画面に何らかの変化が現れるまでの時間がINPである。 以下の図はINPの解説ページから引用したものだ。 この図に基づくと、INPは以下の時間の合計値である。 Input delay イベントハンドラ実行までの待ち時間。ユーザー操作の瞬間、JavaScriptや表示に関する他の処理が行われていると、その完了を待たなければならない。 Processing time イベントハンドラの処理時間。複数のイベントハンドラが登録される可能性もある。 Presentation delay イベントハンドラ実行後から描画までの時間。イベントハンドラがDOMを変更すると表示レイアウトが再計算され、結果が画面に反映される。 これらの時間が長いと、その分INPが悪化する。それぞれ詳しく見ていこう。 Input delay ユーザーの操作に対し、登録されたイベントハンドラはすぐに実行されるとは限らない。 後述するが、ブラウザはほぼシングルスレッドなので、他の処理が行われているとその完了を待たなければならないのだ。 特にページの読み込みからしばらくは、ブラウザやJavaScript(サードパーティスクリプトも含む)はさまざまな処理を大忙しで実行している。その間にユーザーがページを操作しても、対応が後回しにされがちとなる。 このようなページの読み込みからしばらくの間の忙しさの度合いがTBT(Total Blocking Time)である。GoogleもTBTがINPに関係があると述べている。 TBTの改善については後述する。 Processing time 次にイベントハンドラ自体の実行時間であるが、これは単純にイベントハンドラに長時間を要する処理があるか、またはイベントハンドラの数が異常に多いといった原因が考えられる。 実例こそ多くないが、長時間を要する処理としては以下のような例があった。 大量のDOM操作 jQueryでは大量のDOMノードを簡単な記述で操作できるが、その分処理時間は伸びる。 レイアウト即時計算 ページ上の要素の寸法(widthやheightなど)を取得するとレイアウトの再計算がその場で発生することがある。 同期通信 サーバーとAPI通信を行う際に同期通信(async: true)が指定されていると、通信中ずっとメインスレッドを占有してしまう。 イベントハンドラの数は開発者ツールのElementsタブで確認できる。対象の要素を選択し、右側のインスペクタでEvent Listenersを開くと登録されているイベントハンドラを一覧できる。 クリックイベントを確認するにはclickを選択する。 イベントハンドラの詳しいタイムラインの観察については後述する。 Presentation delay イベントハンドラの実行を契機に、DOMの変更に基づくレイアウトの再計算(レンダリング)が行われる。その結果が実際に表示される。 レイアウトの再計算は、対象のDOMとCSS(CSSOM)に基づいて行われる。DOMとCSSが巨大であるとその分、計算に時間を要する。 INPの対象となる操作 INPはページ上のポイント指示操作(クリックまたは画面タップ)か、キーボード操作が対象となっている。 なお、もっとも頻繁に行われる画面操作はスクロールであるが、スクロールは実はページ上の表示範囲(ビューポート)を移動しているだけでページ操作には含まれない。言うなればスクロールはページではなくウィンドウの操作である。 INPを計測するには INPは開発者ツールのPerformanceタブで簡単に計測できる。 PerformanceタブでのINP計測 Performanceタブを開くとすぐにINPの表示欄がある。ページ操作を行うと、直前の操作のINPが表示され、履歴も残る。 INPは元々値が小さく、高性能なPCではさらに処理時間が短くなる。CPUの設定を20x slowdownなどに設定するとシビアに計測できる。 ページ遷移のINPを計測するには この計測機能は大変便利だが、弱点はページ遷移の際のINPを計測できないことだ。 そこで開発者ツールのConsoleから以下のスクリプトを実行しておくと、ページ遷移をダイアログでブロックできる。 javascriptwindow.addEventListener('beforeunload', e => e.returnValue='unload?') タイムラインを詳しく観察するには 開発者ツールのPerformanceタブでは、もっと詳しくINPのタイムラインを観察できる。 Performanceタブの左上の録画ボタンを押し、対象となるページ操作を行う。そして録画ボタンの代わりに表示される停止ボタンを押すと、その間に起きたJavaScriptなどのタスクがタイムラインに表示される。 もしINPの値が大きい操作を見つけたら、この方法で詳細な原因を特定できる。 INPの悪化原因を正確に見つけるには ここまでINPの計測方法を紹介したが、INPの評価が悪いサイトで実際に原因を特定するのはかなり難しいだろう。 というのも、ページ操作の選択肢は無数にあり、仮に原因となる操作がわかっても実際のユーザー環境と同じように遅いINPを再現できるとは限らない。 実際のユーザーからINPの詳細を集める そこで弊社では、実際のユーザーからINPの詳細をBigQueryに収集する仕組みを構築した。 INPの収集および改善提案サービスを開始 | ideaman's Notes ユーザーが遅いINPを経験した後、そのINPの原因要素・操作、時間の要素であるInput delay、Processing time、Presentation delayの値を確認できる。 json{ "inputDelay": 4.100000023841858, // Input delay "interactionTarget": "#responsive-menu-button", // Interaction target "interactionTargetElement": { "jQuery1102040541704606536055": 1 }, "interactionTime": 1793.3999999761581, "interactionType": "pointer", // Interaction type "loadState": "complete", // Load state "longAnimationFrameEntries": [], "nextPaintTime": 1825.3999999761581, "presentationDelay": 27.899999976158142, // Presentation delay "processedEventEntries": [ { "duration": 32, "entryType": "first-input", "interactionId": 0, "name": "mousedown", "startTime": 1793.3999999761581 } ], "processingDuration": 0 // Processing delay } 例えば総じてInput delayが長いのであれば、ユーザーの操作時に他のタスクが実行されていたので、TBTを短縮することでINPを改善できる可能性が高い。 Processing timeが長いのであれば、その操作を開発者ツールで再現し、タイムラインを詳しく見ていくことで原因を突き詰めていける。 TBTを改善するには 前述の通りINPにはTBTが関係すると言われており、実際にINPが悪い原因はイベントハンドラやその後の描画プロセスではなく、イベントハンドラ開始の遅延にあることが多いと思われる。 TBTはPageSpeed Insights(Lighthouse)でシミュレーションにより計測できる指標なので、試行錯誤により改善を進めやすい。 PageSpeed Insightsでも重視される指標であるので、原因の追求を待たずTBTの改善に着手してもよいだろう。それで運良くINPも改善されるかもしれない。 ロングタスクとTBT はじめにTBTを語る上で欠かせないロングタスクの概念について説明する。 昨今のCPUはモバイル端末においてもマルチコアが当たり前になっているが、実はブラウザにおいて多くの処理はシングルスレッド(メインスレッド)で行われている。 つまりブラウザは一度にひとつのタスクしか実行できない。タスクはひとつひとつが短時間で細切れになっており、それが高速で切り替わる。それで人間にとっては、複数の処理がリアルタムかつ同時並行に感じられる。 ところがタスクの中には、メインスレッドをなかなか手放さないマナーの悪いタスクがある。一度に50ms(0.05秒)以上の処理を行うタスクをロングタスク と呼ぶ。 TBTは、ロングタスクの50ms超過分の合計値である。 ロングタスクを確認する ロングタスクの確認は開発者ツールのPerformanceタブで行う。 左上のリロードマークをクリックすると、現在表示中のページを再読み込みし、タイムラインを表示する。このとき設定においてCPUは4x slowdownなどにするとロングタスクを検知しやすい。 右肩に赤い三角マークのついたタスクが、50ms以上を要するロングタスクである。 Bottom up、Call tree、Event logタブでは選択したタスクをブレークダウンでき、JavaScriptの該当コードにジャンプできる。 ロングタスクを分解する ロングタスクは処理自体を軽量化できたら何よりだが、それはかなり難しいだろう。そこでsetTimeoutを用いて分解していく。 重い処理の続きをsetTimeout関数のコールバックに移すと、一旦そのタスクを切り上げて他のタスクにメインスレッドを譲ることができる。 例えば単体ではロングタスクにならない処理a〜cがあったとして、次の記述ではロングタスク化してしまう。 javascripta(); // 30ms b(); // 20ms c(); // 30ms // 合計80msでロングタスク化 これをsetTimeoutで次のタスクに送ることで、a〜cはロングタスク化を免れる。 javascripta(); // 30ms setTimeout(function() { // メインスレッド解放 b(); // 20ms setTimeout(function() { // メインスレッド解放 c(); // 30ms }); }); jQueryにおけるロングタスクの解消 jQueryは簡易な記述でDOM操作を実現できるが、これがロングタスクを引き起こす場合がある。 例えばカルーセルスライダーを起動するsliderプラグインがあったとしよう。カルーセルスライダーの起動は複雑な処理が行われ、単体でも実行に時間を要する。ここでは30msとする。 ページにはカルーセルスライダーの適用箇所が5箇所あり、すべてクラス名sliderがつけられている。すると、jQueryの記述は以下のようになるだろう。 javascript$('.slider').slider(); // 30ms × 5 = 150ms jQueryのDOM操作は一息で実行されるため、ひとつ30msを要するカルーセルスライダーの起動が5つ分まとめて行われてしまう。 この問題を解決するためにもsetTimeoutを活用できる。 javascript$('.slider').each(function(i, el) { // .sliderを一つずつ処理 setTimeout(function() { // メインスレッド解放 $(el).slider(); // 30ms }); }); 先ほど一息で行われた5件分のカルーセルスライダーの起動を、ひとつずつメインスレッドを解放するようにした。これによりロングタスク化を回避できる。 サードパーティタグの場合 サードパーティタグから提供される社外のJavaScriptは、上記のように柔軟な変更はできない。 したがって実行タイミングを遅延させるか、使用をやめるかいずれかの消極的な対応しかできない。 この件については以下の記事に詳しく述べたので参考にしていただきたい。 Core Web Vitalsの改善術 - サードパーティタグ編 | ideaman's Notes まとめ INPはユーザーの環境でのみ計測される指標で、PageSped Insights(Lighthouse)でシミュレーションできない。実態を掴みづらく、試行錯誤も難しい。また、TBTの改善にはJavaScriptの技能が要求される。 CLSに比べるとLCPも改善が難しい指標であるが、INPはそれ以上に難しいかもしれない。 しかし、手間をかけ詳細をトラッキングして、タイムラインをじっくり掘り下げることで少しずつ答えに近づくことはできる。 本記事の内容をふまえ、改善に取り組んでいただきたい。 --- # Core Web Vitalsの改善術 - サードパーティタグ編 - 公開日: Wed Nov 20 2024 17:48:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology, development, research Core Web Vitals改善術の一環としてサードパーティタグの扱いについて解説する。 サードパーティタグがCore Web Vitalsに影響するというのは奇異に聞こえるかもしれないが、実は大いに関係がある。 サードパーティタグはGoogle Tag ManagerやGoogle Analyticsに代表される、外部の企業から提供されるHTMLタグであり、そのほとんどはJavaScriptを実行する。JavaScriptの動作は本来とても重いものだ。 多くの企業がサードパーティタグのメリットだけに注目し、負荷については空気のようなものと思い込んでいるが、筆者の経験上、Core Web Vitals改善の鍵はこのサードパーティタグの扱いにあると言っても過言ではない。 いわゆるWeb技術によるスピード改善と、サードパーティタグの改善は、アプローチや責任者が違う異質な業務となる。そのためひとつの独立した記事として論じたい。 サードパーティタグのサイトスピードへの影響TBTとINPサードパーティタグに対する誤解誤解: 他社のリソースなので負荷に影響はない誤解: 目に見えない機能だから表示に影響はないサードパーティタグの棚卸し使用しないサードパーティタグを削除するA/Bテストによる再評価サードパーティタグの起動を遅延させるタイミングを遅延させる計測タグはタイミングを遅延させてよいのかスクロールにより遅延させる属性に応じて遅延させるGTMの罠設定の多さからくる負荷コンテナ数の多さからくる負荷サードパーティタグの改善の責任者は誰かまとめAppleはサードパーティタグを使用していない サードパーティタグのサイトスピードへの影響 ある通販サイトの例を見てみよう。Lighthouse(PageSpeed Insights)を実行すると、スピード指標の値は次のようになった。 続いてこのページのサードパーティタグをすべて無効にして同様に計測を行なった。 TBT(Total Blocking Time)が1,530 msから110 msへと著しく改善した。LCP(Largest Contentful Paint)も若干ながら改善されている。 TBTとは TBTは、メインスレッドを50ms以上占有する処理の合計(正確には50ms超過分の合計)である。 TBTの値が悪い(大きい)ページは、ユーザーがページの操作を試みてもすぐ反応しないと感じる場面が多くなる。 TBTはパフォーマンススコアの計算において最も重視される指標で、30%の重みを持つ。この改善はスコアへの影響も大きい。29点から40点に評価が向上した。 TBTとINP Core Web Vtialsの一角にINP(Interaction to Next Paint)がある。 TBTはINPに影響を与える指標だと言われており、TBTを改善できるとINPへの貢献も期待できる。 ただし、INPの要因はTBTだけではない。その点は後日、このシリーズのINP編で触れたい。 サードパーティタグに対する誤解 このようにサードパーティタグはページスピードへの影響が意外と大きいのだが、見落とされることが多い。 話を聞いていると、次のような誤解があるように思う。 誤解: 他社のリソースなので負荷に影響はない サードパーティタグとそれに関連するリソースは、自社のサーバーではなく他社のサーバーから配信される。だから自分たちのページの表示には負荷はかからないという発想だ。 これは誤解であり、確かに自社のサーバーやネットワークの負担はないが、ユーザーの端末では自社・他社を問わずJavaScriptは等しく実行され、CPUパワーを消費する。 JavaScriptは元来から重い機能であり、サードパーティタグとは「ユーザーの体感スピードはちょっと遅くするけど、便利だから入れてね」という代物だ。 誤解: 目に見えない機能だから表示に影響はない サードパーティタグの多くはユーザーの行動を記録したり計測をしたりと、目には見えない機能を提供することが多い。目に見えない機能だから、表示スピードには影響しないという発想だ。 これも誤解であり、ページの表示に関する処理とJavaScriptの実行はほとんどがひとつのメインスレッドの上で行われる。 つまり共通のメインスレッドを奪い合う関係にあり、目に見えない機能であろうがJavaScriptが実行されると、ページ表示に関する処理はその分、割りを食って遅延するのである。 サードパーティタグの棚卸し サードパーティタグは自社で配信するリソースと違い、内容を改変して改善を図ることができない。どうしても消極的な手段しか取りえない。 使用しないサードパーティタグを削除する もっとも効果的なのは使用しないサードパーティタグを削除することだ。 長い期間運営しているサイトだと、以前使っていたが今は使っていないサードパーティタグが少なからずあるだろう。そういったタグをコツコツと棚卸しする。 ただこの棚卸しを効果的に進めるのは難しい。担当者が不明であったり、「ないよりはあった方がいい」程度のサードパーティタグを削除する決断が重いからだ。結局、ほんの少しだけ削除して目立った効果なく終えることが多い。 個人的には一度サードパーティタグは空にして(GTMであれば新しいコンテナを用意して)、ないとサイト運営に支障が出たものタグだけを追加することをお勧めする。 A/Bテストによる再評価 販促系のサードパーティタグはA/Bテストによる再評価をしよう。 サードパーティタグの使用によるページスピードの低下が、その機能による収益への貢献に見合うかきちんと検証し、場合によっては削除する。 サードパーティタグが空気のように軽ければ機能の追加はよい影響しかないはずだが、それは誤解であると説明した。悪影響があるかもしれない施策を検証なしに進めるのは至極妥当な話である。 サードパーティタグの起動を遅延させる ページをリクエストしてからの数秒はいわば「ゴールデンタイム」である。この時間にユーザーが欲しかった情報を得られないと容易に離脱される。 したがってゴールデンタイムはコンテンツの提供に全振りし、サードパーティタグの実行はその後に回すのが理想的な姿である。 コンテンツを見て離脱されるのは仕方ないが、計測が優先されてコンテンツ表示が遅れて離脱されるのではやるせない。 タイミングを遅延させる Google Tag Managerを利用しているなら、トリガーはページビューよりウィンドウの読み込みやDOM Readyの方がタイミング的に遅い。つまりコンテンツ表示にゴールデンタイムを譲ることができる。 script要素を直接記述しているのであれば、例えば次のようなサードパーティタグは、 html<script src="https://example.com/tag.js" async></script> HTMLの読み込み次第、JavaScriptの読み込みが始まる。 次のように書き換えることでウィンドウの読み込みやDOM Readyと同等のタイミングに遅延させることができる。 html<script> // ウィンドウ読み込み window.addEventListener('load', function() { var script = document.createElement('script'); script.src = 'https://example.com/tag.js'; script.async = true; document.body.appendChild(script); }); // DOM Ready相当 document.addEventListener('DOMContentLoaded', function() { var script = document.createElement('script'); script.src = 'https://example.com/tag.js'; script.async = true; document.body.appendChild(script); }); </script> 計測タグはタイミングを遅延させてよいのか サードパーティタグの遅延について挙がるのが、「計測タグが正常に計測を行えなくなるのではないか」 という議論である。 この点については 「今も別に正常に計測などできていない。なので心配ない」 が回答となる。 商用サイトには実に多数の計測タグが埋め込まれているが、前述の通り、ひとつのメインスレッドを奪い合いながら、ただ自分の番がきたら動作する。 期待の上では、それぞれのタグが必要なタイミングで責任持って計測するようにイメージしてしまうが、実態は適当なバラバラのタイミングで計測が行われているのだ。 したがってゴールデンタイムをコンテンツ表示に譲っても計測結果に大差はない。 スクロールにより遅延させる サードパーティタグにはレコメンデーションなどの補助的コンテンツを展開するタイプもある。 それらの補助的コンテンツはページ下部に配置されることが多いが、ユーザーが毎回ページ下部までスクロールしてくれるかというと、その可能性の方が低い。 そのため、補助的コンテンツのサードパーティタグをページ読み込み直後のゴールデンタイムに展開するのはあまりに無駄が大きい。 そこで、ユーザーがある程度スクロールしたタイミングでサードパーティタグを起動するのがよいだろう。 ここではページの半ばほどにある要素#half-of-pageまでスクロールが進んだら、 html<script src="https://example.com/tag.js" async></script> 上記と同等のscript要素を展開するサンプルを示す。 html<script> document.addEventListener('DOMContentLoaded', function() { // script要素を展開しサードパーティタグを起動するコード var invoke = function () { var script = document.createElement('script'); script.src = 'https://example.com/tag.js'; script.async = true; document.body.appendChild(script); }; if (window.IntersectionObserver) { // IntersectionObserver対応の場合は要素が50%表示された時点で起動 var target = document.getElementById('half-of-page'); var observer = new IntersectionObserver(function (entries, observer) { entries.forEach(function (entry) { if (!entry.isIntersecting) continue; invoke(); observer.disconnect(); }); }, { root: null, threshold: 0.5 }); observer.observe(target); } else { // IntersectionObserver非対応の場合は3秒後に起動 setTimeout(invoke, 1000 * 3); } }); </script> 属性に応じて遅延させる コンテンツの翻訳サービスを提供するサードパーティタグもある。特にWOVN.ioが有名であるが、その機能の高さゆえかJavaScriptの負荷が非常に高い。 INFO WOVN.ioを非難する意図はまったくない点を、まず理解いただきたい。事実として、サードパーティタグの中でWOVN.ioのブロッキングタイムが最長となるケースを何度も目にした。 翻訳サービスは海外の非日本語話者向けの配慮である。日本語のサイトはおそらく95%以上が日本人ユーザーであり、日本語話者向けの翻訳サービスを必要としない。 弊社では例としてブラウザの言語設定を見て、それが日本語である場合に翻訳サービスの起動を遅延することを勧めている。 html<script src="https://j.wovn.io/1" data-wovnio="key=*****" async></script> 上記のようなタグの設置指示は、以下のようなJavaScriptに変更する。 html<script> document.addEventListener('DOMContentLoaded', function () { var lang = navigator.language || navigator.userLanguage || 'en-US'; var invoke = function() { var script = document.createElement('script'); script.src = 'https://j.wovn.io/1'; script['data-wovnio'] = 'key=*****'; script.async = true; document.body.appendChild(script); }; if (lang.startsWith('ja')) { // ブラウザの言語が日本語なら5秒遅延して起動 setTimeout(invoke, 5 * 1000); } else { // 日本語以外なら即起動 invoke() } }); </script> もちろんブラウザ設定がユーザー自身の言語とは限らない。しかしユーザー自身の言語とブラウザの言語は同一である場面が圧倒的多数であり、それ以外の比率はかなり低いだろう。 予測が外れた場合も5秒後には翻訳機能が起動するので最終的な挙動は変わらない。多数のユーザーの便益を最優先する方が望ましいという判断である。 GTMの罠 昨今の商用サイトでGTM(Google Tag Manager)を使っていないケースは少ないだろう。そのくらい重宝されているツールであるが、実はGTM自体は重い。 Webパフォーマンスに敏感であるGoogleの提供するツールが重いというのは皮肉な話であるが、GTM自身が負荷の高いサードパーティタグの上位に必ず見られる。 設定の多さからくる負荷 GTMのコンテナに多数のタグ設定があると、その分ブロッキングタイムが長くなり、目立った負荷が発生する。 GTMを使用するのは致し方ないが、常に不要な設定を削除するメンテナンスが必要である。 コンテナ数の多さからくる負荷 GTMはわりと大きなJavaScriptを読み込む。GTMのタグ管理にまつわる機能が、コンテナのJavaScriptに同梱されていると想像される。 しかし実際の運用として、ひとつのページに多数のコンテナを配置しているサイトが少なくない。サイト運用には多数の関係者がいるので、アクセス権限を分けるためにコンテナを分けていると思われる。 コンテナをたくさん読み込むと、ほとんど同じタグ管理機能のJavaScriptが重複して膨張する。それらはすべて愚直にコンパイルと評価が必要になるので、CPUに無駄に大きな負荷がかかる。 GTMは1ページ1コンテナの運用が、サイトスピードの観点からは理想である。 サードパーティタグの改善の責任者は誰か JavaScriptによる書き換えのテクニックも紹介したが、サードパーティタグの責任者はサイトオーナーだ。 サイトオーナーが「捨てる」「妥協する」という決断をしない限り、Web制作者には手の出しようがない。 そのため弊社ではまず、Webページからサードパーティタグを一度除外し、Web技術によるスピード改善と、サードパーティタグによるスピード改善を分けて提案している。 まとめ 以上、サードパーティタグがサイトスピードに意外と大きな影響を及ぼしていることを説明した。 とはいえ、現代の商用Webサイトをサードパーティタグの力を借りず運営するのは困難である。その点は筆者もよく理解している。 要はユーザーの端末という限られた計算資源を、ユーザー向けの本来のコンテンツへ使うのか、便利な追加機能へ使うのか、サイト運営者向けのメリットへ使うのか、そのパラメータ配分の問題だ。 Appleはサードパーティタグを使用していない サイトスピードは重要だがサードパーティタグも必要だ、その葛藤で思考停止に陥らないよう、ひとつ実例を紹介したい。 以前このブログで触れたのだが、Appleのサイトではサードパーティタグを一切使っていないのだ。Google Analyticsすら入っていない。 Appleサイトはサードパーティタグを使っていない | ideaman's Notes Appleとはいえ浮世離れしたサイトではなくWeb広告も活用している。筆者が推測するに、独自のトラッキングシステムを持ち、Webサーバーのアクセスログなど合わせてWeb解析を行なっているのだろう。 この事実に気づいたとき、ユーザーの端末でユーザーのメリットにならないコードは動かさない、Appleの美学はHTMLコードにも見られた気がして大変感心した。 これはAppleほどの力があれば実現できる仕組みかもしれないが、「サードパーティタグがないと絶対にサイトを運営できない」という信念があるとしたら、思い込みであると言いたかったのだ。 --- # Core Web Vitalsの実践的な改善術 - LCP編 - 公開日: Mon Nov 18 2024 01:29:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research Core Web Vitalsのひとつ、LCP(Largest Contentful Paint)の改善手法について解説する。 同じCore Web VitalsのCLS(Cumulative Layout Shift)は丁寧にコーディングを行えば改善できるが、それに比べてLCPの改善は難しい。 実際にSEOの文脈でCore Web Vitalsが話題になった2021年〜2022年ごろ、CLSの改善は多くのサイトで見られたが、LCPまで改善できたサイトは少ない。 加えて、ネットの記事にはLCPの改善について誤解が多い。よく見られるのが画像の軽量化であるが、画像データがLCP悪化の主な要因であるケースなどほとんどない。 この記事ではLCP改善の実践的な手法を紹介したい。 誤解だらけのLCP改善画像が重いからLCPが悪い?LCP = FCP + 最大要素の遅延データでも確認できる LCP ≧ FCPLCPが悪いケースは3パターンFCPを改善するにはケース: HTMLとCSSが圧縮されていないケース: HTMLとCSSが過剰に大きいケース: CSSの読み込みにばらつきがあるケース: CSSとHTMLと異なるドメインから配信されているケース: CSSで@importが用いられているケース: 不自然なpreloadが指示されているケース: JavaScriptが乱入する問題を特定するには最大要素の遅延を改善するにはケース: メイン画像のデータが極端に大きいケース: メイン画像の読み込み開始が遅いケース: 非表示の画像が優先的に読み込まれているケース: メイン画像の表示がJavaScriptに依存しているPerformanceタイムラインを見るまとめ 誤解だらけのLCP改善 LCPは、ビュー(主にファーストビュー)において最大要素(テキストまたは画像)が表示されるまでの時間 である。 メディアサイトの記事のようなページであれば見出しテキストがLCPの判定対象になることが多い。それ以外のページ上部にはメインビジュアルに相当する大きな画像がある。その画像がLCPの判定対象になる。 画像が重いからLCPが悪い? LCPの判定対象になるのは画像だから、LCPの改善には画像を軽量化がよいと解説するネット上の記事は多い。しかしそれは短絡的すぎる。 もちろん画像を軽量化するに越したことはないが、それだけでLCPを大きく改善できるケースなど現実にはほとんどない。 なぜなら、画像の読み込みが始まる時点で勝負はついているからだ。画像がいくら軽かろうが、時間は巻き戻せないのだ。 LCP = FCP + 最大要素の遅延 Core Web Vitalsには含まれないが、LCPと似た指標としてFCP(First Contentful Paint)がある。文字通り、 LCP 最大要素の表示時間 FCP 最初の要素の表示時間 であるが、論理定に考えても最大要素の表示が、最初の要素の表示より先に来ることはありえない。したがって、 $LCP ≧ FCP であり、より具体的に言えばFCPを達成後、最大要素が表示されるまでの遅延を経てLCPの達成となる。 $LCP = FCP + 最大要素の遅延 LCPの対象がテキスト(大見出しなど)の場合、FCPとLCPは同じ値になることがある。 データでも確認できる LCP ≧ FCP LCPがFCPを下回ることがないことはデータでも確認できる。 以下のグラフは、複数の通販サイトから無作為に1000ページを抽出し、PageSpeed Insightsを実行した結果を元に作られている。 横軸にFCP、縦軸にLCPをともに対数軸としてプロットした散布図であるが、見ての通り右下の三角形の領域にはサンプルが存在しない。 右下の領域はLCPの値がFCPを下回ること意味する。そのような例はひとつもないという意味である。 LCPが悪いケースは3パターン これを踏まえるとLCPの値は4つのパターンに分類される。 FCP・LCPともに良好 この場合は対応不要である。 FCPは良いが、LCPは悪い 最大要素の遅延に問題がある。 FCPが悪く、LCPも同じくらい悪い FCPに問題がある。 FCPが悪く、LCPはもっと悪い FCPと最大要素の遅延の両方に問題がある。 経験的に、FCPが良いのにLCPだけ悪いケースは少ない。そのような理由でLCPの改善はまずFCPからの対処が必要になる。 画像の軽量化がLCPに効くという説は、「テスト勉強には一夜漬けがよい」と言っているのと大差ないのだ。 FCPを改善するには FCPをよい値に保つ秘訣は、スタートダッシュはHTMLとCSSに全振りすることである。 これは何も特別な話ではなく、ブラウザは本来そのように動作するよう作られている。その足を引っ張ったり、邪魔をする要素があるからFCPが悪化する。 よくあるケースは以下である。多いがひとつずつ見ていこう。 HTMLとCSSが圧縮されていない HTMLとCSSが過剰に大きい CSSの読み込みにばらつきがある CSSがHTMLと異なるドメインから配信されている CSSで@importが用いられている 不自然なpreloadが指示されている JavaScriptが乱入する ケース: HTMLとCSSが圧縮されていない テキストリソースは必ず通信中にGzip等で圧縮する。素のままでは通信量が何倍も大きくなってしまう。 ケース: HTMLとCSSが過剰に大きい ブラウザは内部で、HTMLとCSSを元に画面の表示内容について詳細な設計図を描く(レンダリング)。 この計算量は HTML(DOM)の大きさ × CSS(CSSOM)の大きさ となる。両方が大きいとCPUの処理時間が膨張する。 そのページで使用しないCSSプロパティもCPUを消費する。CSSはそのページで使用しないプロパティは含まないようにするのが理想である。 ケース: CSSの読み込みにばらつきがある ブラウザはHTMLとCSSから設計図を描くが、一部のCSSが遅れて到着すると「ちゃぶ台返し」が起こる。 それまで書いた設計図を破り捨てて、また書き直すような無駄が生じてしまう。 CSSを1ファイルにまとめるとこのような後出しによるやり直しを回避できる。並列ダウンロードは効かなくなるが、CSSは通信量がボトルネックになるようなサイズではないので、個人的には有用な場面の方が多いと感じる。 ケース: CSSとHTMLと異なるドメインから配信されている CSSのドメインがHTMLと異なると、DNSルックアップとSSLハンドシェイクで一手遅れる。 これらは通常、瞬間的に行われる。とはいえドメインが同一であれば動作はスムーズだ。PageSpeed Insightsの計測でも、FCPで0.5秒ほどの差が確認できる。 ケース: CSSで@importが用いられている スピードの観点からいうとCSSの@importは禁じ手である。 ブラウザはCSSの読み込みをできるだけ並列で急ぐのだが、@importによって別のCSSの存在がわかるとタイムラインが直列に間延びしてしまう。 ケース: 不自然なpreloadが指示されている link要素にrel="preload"属性を指定することで、指定したリソースの先読みができる。 ただ、先読みと言っても魔法のように事前処理されたり、何もないところに優先経路が現れるわけではない。単にダンロード順に「割り込み」をするだけだ。 割り込みされたリソースは逆に読み込みが遅れる。読み込みが遅れるリソースは何か。CSSである。したがってpreloadはよほど特殊な理由がないかぎり推奨しない。 なお、CSSをpreloadに指定している例も見かけるが、心配しなくてもCSSは最優先に読み込まれるのでその記述は無意味である。 ケース: JavaScriptが乱入する JavaScriptはWebページにおいて要件定義を書き換える強大な権限を持っている。そのためJavaScriptの存在を確認するとブラウザは設計図作り(レンダリング)を一時中断してしまう。 日常的なイメージに例えると、現場が向け全力で仕事をしているところに社長が首を突っ込んできて納期が遅れるような話だろうか。JavaScriptはFCPに関して言えば邪魔者なのである。 そのためscript要素はasyncやdeferの属性によりレンダリングの中断を回避したり、HTMLの下部に記述してスタートダッシュをレンダリングに全振りするように務める。 問題を特定するには このようにFCPの阻害要因はさまざまだが、開発者ツールのPerformanceタイムラインを穴が開くように調べ、FCPの前に何が起きているか突き詰めていけば必ず答えは見つかる。 なお、Performanceタイムラインを実行するときは、CPUとNetworkの性能制限を設定するとよい。PC環境のCPU性能だとボトルネックが過小評価されてしまい、発見が難しくなるからだ。 最大要素の遅延を改善するには 次はFCPとLCPの差をもたらす最大要素の遅延を解決する。最大要素に遅延があるケースではほぼ間違いなくLCPの対象要素は画像である。 以下のケースが考えられる。 メイン画像のデータが極端に大きい メイン画像の読み込み開始が遅い 非表示の画像が優先的に読み込まれている メイン画像の表示がJavaScriptに依存している ケース: メイン画像のデータが極端に大きい 画像軽量化によりLCPを改善できるケースはほとんどないと書いたが、もちろん可能性はゼロではない。 メインビジュアル画像が極端に大きなデータサイズで、その画像の軽量化でLCPを改善できるケースもある。 ケース: メイン画像の読み込み開始が遅い 画像データは基本的にHTMLの上部に記述されたものから読み込みが始まる。 そのため、メインビジュアル画像より上の例えばヘッダ領域の画像が優先されてしまう。 画像はCSSで実際には非表示であっても関係なく読み込みが始まる。以前実際にあったケースでは、スマホ向けのサイドメニューの中に多数の画像があり、それらがメインビジュアル画像の読み込み開始を遅らせていた。 そこで、img要素のloading属性を用いる。HTML上では上部にあっても、loading="lazy"を指定するとその画像の読み込み優先度は下げられる。逆にloading="eager"を指定すると優先度を上げられる。 以下のように、メインビジュアル相当の画像にはloading="eager"、それよりHTMLの上で上部にある画像にはloading="lazy"を明示することでメインビジュアルの読み込み順を優先できる。 html<!-- 中略 --> <img src="header.jpg" loading="lazy"> <!-- 中略 --> <img src="mainvisual.jpg" loading="eager"> ケース: 非表示の画像が優先的に読み込まれている レスポンシブウェブデザインでは、メインビジュアルがスマホ向けとPC向けで二種類用意されることがある。 当然、スマホとPCで出し分けることになるが、この制御をCSSで行っている事例を散見する。 html<img src="mainvisual/sp.jpg" class="sp"> <img src="mainvisual/pc.jpg" class="pc"> 上記の記述では、画像データのダウンロードは両方に対して発生してしまい、必ずどちらかが無駄になる。 デバイスによる画像の出し分けにはCSSではなく、srcset属性やpicture要素を利用するべきである。この方法であれば無駄なダウンロードが生じない。 レスポンシブ画像 - ウェブ開発を学ぶ | MDN メインビジュアルはデータサイズが比較的大きい。せめてメインビジュアルだけでも、適切なレスポンシブ画像技術でコーディングすべきである。 ケース: メイン画像の表示がJavaScriptに依存している よくあるのはメインビジュアルがカルーセルスライダーになっており、JavaScriptの当該コードが起動することで初めて表示されるケースだ。 JavaScriptによる画像の遅延読み込み制御(いわゆるLazyload)が適用されているケースもある。なお、JavaScriptによるLazyloadは今や害の方が大きいので即刻止めるべきである。 以前のCLSの改善についての記事で、カルーセルスライダーのレイアウト安定化を紹介した。 詳しくは上記の記事を参照いただきたいが、要は JavaScriptが起動する前からカルーセルスライダーの1枚目の画像が見えるようにコーディングする という対策である。一時的にJavaScriptを無効にしてデバッグするとよい。 JavaScriptの実行タイミングはページ読み込みの後半のタイミングになることが多い。そこまで待たないとメインビジュアルが表示されない仕様は、LCPに対して大変不利な条件となる。 カルーセルスライダーの1枚目の画像だけ、HTML・CSS・画像の技術要素だけで表示を担保すると、最大要素の遅延を最小化できる。 Performanceタイムラインを見る これらのボトルネック調査も、FCPと同様に開発者ツールのタイムラインにすべて答えが現れる。 PageSpeed Insightsの占いじみた指摘事項に従うより、タイムラインを眺める習慣を身につけよう。 まとめ Webページも一種のプログラムであるので、思った通りには動いてくれない。書かれている通りに動く。 一般的なプログラムと違うのは、Webページは多数のリソースがベストエフォートで協調しながら目的を果たすところだ。書かれている通りには動くが、何がどのタイミングで起こるかは実際に観察してみないとわからないことが多い。 LCPの改善はそのような不確実さとの戦いであり、それゆえに難しい。 この記事で解説したようにFCPの問題と、最大要素の遅延の問題に分解して個別に解決するアプローチは必ずや打開策になるだろう。 --- # INPの収集および改善提案サービスを開始 - 公開日: Fri Nov 15 2024 04:28:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research Core Web Vitalsの中で INP(Interaction to Next Paint) は、実際のユーザーがWebページを操作して初めて計測される指標だ。 そのため指標が悪い原因を正確に特定するのが難しい。 そこでユーザー環境で生じたINPを計測、BigQueryに収集し、調査に活かす仕組みを構築した。 そのデータを用い、理論に頼るだけではない実践的なINPの改善提案サービスを開始する。 INPの調査は難しい スピードに関する指標の多くは、Webページの読み込みプロセスに関するものだが、INPや以前のCore Web Vitalsであった FID などはユーザーの操作に由来する。 開発者ツールのPerformanceタブには INPを計測し、タイムラインを取得する機能 がある。 しかし次の理由で指標が悪い原因を特定するのは難しい。 操作の選択肢は多数ある。 状況やタイミングによるところがあり、再現するとは限らない。 INPの評価が悪いサイトで、開発者ツールを使って様々な操作を試してもなかなかINPの悪い数値が出ない。まるで幽霊や素粒子を見つけるような作業となる。 TBTとINPの関連性 TBT(Total Blocking Time) がINPに関係すると言われている。 しかし後述するが、INPの遅延発生ポイントは大きく3つある。TBTはそのひとつにしか関係しない。TBTを改善すればよいという保証はない。 INPを計測して詳細を記録 こういうとき予測の精度を上げる努力をしたところでそれは憶測に過ぎない。事実を計測する方がはるかに良い。 Web Vitalsの計測でお馴染みの web-vitals パッケージには attribution build というバリエーションがあり、指標の数値の根拠となる情報も取得することができる。 これを用い、Webページにシンプルなscriptタグを埋め込むだけで、INPの詳細を収集する仕組みを構築した。 詳細はBigQueryに格納されるので、統計的に主要な原因を絞り込むことができるようになる。 INPの要素 INPは次の図が示すように、Input delay + Processing Time + Presentation delay の値として解釈できる。 Input delay ユーザーの操作発生からイベントハンドラの起動まで。他のメインスレッドタスク(他のJavaScriptコードや、それによるレンダリングなど)が実行中だと、それが終わるまで待たされる。 Processing Time イベントハンドラの実行。ひとつの操作にも段階によって複数のイベントがあり、ひとつのイベントにも複数のハンドラが割り当てられる。その全ての実行時間。 Presentation delay イベントハンドラの実行後から画面上に変化が描画されるまで。DOM操作に対するレイアウト再計算や描画処理そのものの時間。 また、INPはユーザーの操作に由来するのでその原因も知りたい。 Interaction target 操作の対象となったDOM要素。 Interaction type 操作の内容。マウス、タッチ、キーボードなどの種別。 Load state 操作が行われたタイミングのページ読み込み状態。 BigQueryに記録される詳細 web-vitalsのattribution buildでは上記の値をすべて検知し、以下のような構造データを得ることができる。 この構造データをそのままBigQueryに記録する。 json{ "inputDelay": 4.100000023841858, // Input delay "interactionTarget": "#responsive-menu-button", // Interaction target "interactionTargetElement": { "jQuery1102040541704606536055": 1 }, "interactionTime": 1793.3999999761581, "interactionType": "pointer", // Interaction type "loadState": "complete", // Load state "longAnimationFrameEntries": [], "nextPaintTime": 1825.3999999761581, "presentationDelay": 27.899999976158142, // Presentation delay "processedEventEntries": [ { "duration": 32, "entryType": "first-input", "interactionId": 0, "name": "mousedown", "startTime": 1793.3999999761581 } ], "processingDuration": 0 // Processing delay } 例えばloadStageがloadingで、時間としてinputDelayの値が大きければ、そのINPはページ読み込み時の高負荷に巻き込まれた結果と判断できる。その場合、TBTの改善がINPの改善につながる可能性が高い。 また、processingDurationの値が大きければイベントハンドラに高負荷な処理があり、presentationDelayであればDOM操作やHTML・CSSの大きさに原因があると判断できる。 これで理論だけに頼って無責任な答えを出す必要も、開発者コンソールで幽霊や素粒子を見つける無駄な努力も不要になる。 お問い合わせ 以上のデータを元に、サイト上のどの要素におけるどんな処理にボトルネックがあるか個別にレポートして提出する。 興味のある読者はぜひ、contact@ideamans.comにメールまたは、以下のフォームから問い合わせいただきたい。 Your browser does not support iframes. --- # Core Web Vitalsの実践的な改善術 - CLS編 - 公開日: Sun Nov 10 2024 03:29:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research Core Web Vitalsのひとつ、CLS(Cumulative Layout Shift)の改善方法について解説する。 Cumulative Layout Shift(CLS)  |  Articles  |  web.dev CLSの改善は他の指標に比べて難しくはない。丁寧にコーディングを行うことでほぼ解決できる。それゆえ改善済みのサイトも多い。 しかしJavaScriptによるコンポーネントのレイアウトを制御できていないWebページはまだ散見される。 この記事ではJavaScriptによるコンテンツの挿入が行われるページや、カルーセルスライダーを用いたページについて実践的なアドバイスを記した。 CLSとは何が後からレイアウトを変えるかレアケース画像のレイアウト安定化JavaScriptによるコンテンツ挿入のレイアウト安定化寸法が不定の場合はどうするか省略記号による行数の固定設計や見せ方を見直すカルーセルスライダーのレイアウト安定化HTMLとCSSでモックアップを整えるJavaScriptを無効にして動作確認LCPにも効く CLSとは Webページの表示がある程度進むとユーザーは操作を始める。しかしその後でWebページのレイアウトが変化すると、ユーザーは誤操作をする可能性がある。 以下はCLSの解説に掲載されている「注文をキャンセルするつもりが、ボタンを押す瞬間にレイアウトが変化して意図せず注文を確定してしまった例」である。 こういうストレスを避けるため、「レイアウトはバシッと一発で決めて変わらないようにしよう」というのがCLSの指し示す価値だ。 何が後からレイアウトを変えるか Webページのレイアウトを後から変化させる頻出パターンは以下の3つだ。 画像 JavaScriptによるコンテンツ挿入 カルーセルスライダー 逆に次の技術要素はWebページ表示プロセスの初期に処理されるため、レイアウトを安定化させる力がある。 HTML CSS 要件はできるかぎりHTMLとCSSで実現したり、JavaScriptによる機能もこれらの段階で レアケース レアケースであるが、過去にはこんな例もあった。 JavaScriptによる高さの制御(グリッドの高さの統一) Webフォントがインライン要素の寸法を微妙に変えてしまう JavaScriptによるレスポンシブ対応の縮尺操作 画像のレイアウト安定化 常識的な話ではあるが、画像についてはimg要素のwidth属性とheight属性を正しく明示する。基本的にはそれだけでよい。 これらの情報がないと、画像データがダウンロードされるまで寸法がわからない。 画像データのダウンロードはWebページ表示の後半で行われるため、寸法が発覚することでのレイアウト変化がCLSを悪化させる要因になる。 CMSであればwidthとheight属性の指定はたいてい自動化できるが、アシストが得られない場合は、用いる画像の寸法の方を固定する運用でカバーする方法もある。 JavaScriptによるコンテンツ挿入のレイアウト安定化 HTMLには当初存在しないコンテンツを、JavaScriptで後から挿入するケースはよくある。 JavaScriptもWebページ表示プロセスの後半で実行されるため、これもレイアウトの変化を招きCLSを悪化させる要因になりうる。 この問題への対処は、CSSのmin-heightプロパティなどであらかじめ領域を確保することだ。 領域を確保しておけば、あとからコンテンツが挿入されてもレイアウトの変化は起きない。 寸法が不定の場合はどうするか JavaScriptによって後から挿入されるコンテンツの寸法が不定であるケースもある。 この場合も、予想される領域をあらかじめ確保しておくことをお勧めする。 CLSはレイアウトの変化の有無ではなく、変化の大きさを測る。したがって領域を確保しないよりは、変化量を減らしてCLSを改善に近づける。 省略記号による行数の固定 テキストの長さが不定で行数が変動する場合は、省略記号を用いて1行あるいは特定の行数に収める手法もある。 text-overflow - CSS: カスケーディングスタイルシート | MDN 設計や見せ方を見直す 最近はページの最上部に重要なお知らせを表示するパターンが増えている。この制御をJavaScriptで行うと最も強烈なレイアウト変化を招く。 これはサイトの仕様に依存するところもあるが、本来ファーストビュー付近の主要コンテンツは初期のHTMLドキュメントに含める方がよい。 このように設計を見直すことでも、CLS悪化の要因をなかったことにできる可能性がある。 あるいはページ最上部の案内も、例えばウィンドウ下部にCSSのposition: stickyで表示するとレイアウトの変化を招かない。見せ方を変える工夫もあるだろう。 カルーセルスライダーのレイアウト安定化 個人的なユーザーとしてもなくなって欲しい表現ではあるが、ファーストビューにカルーセルスライダーを配置するページは多い。 このカルーセルスライダーもよくCLS悪化の原因になる。 HTMLとCSSでモックアップを整える カルーセルスライダーによるCLS悪化を防ぐには、HTMLとCSSが読み込まれた段階でそのレイアウトを固めることだ。 カルーセルスライダーには大きく二つの実現方針がある。 HTMLに存在するDOMを設計および土台として実現する。 JavaScriptで1からDOMを構築し対象要素に挿入して実現する。 1の場合は、表示する複数の画像がHTML上に記述されている。1枚目の画像だけはあらかじめ表示し、2枚目以降は非表示になるようなCSSを記述する。 2の場合は、対象要素の中に「動かないカルーセルスライダー」をHTMLとCSSにより描いておくとよい。 これは、JavaScriptがカルーセルスライダーを起動する前に、モックアップを表示しておくイメージである。 JavaScriptを無効にして動作確認 ではHTMLとCSSでカルーセルスライダーのモックアップを用意して、どのように動作確認をしたらよいか。 これはJavaScriptを一時的に無効にするとよい。 Chromeの管理者ツールの右上から設定を開くと、 DebuggerのグループにDisable JavaScriptのチェックボックスがある。このチェックボックスをOnにするとJavaScriptが無効になる。 コマンドパレット 開発者ツールのコマンドパレット(Macであれば⌘+Shift+P)からも切り替えられる。筆者はその方法を利用している。 この状態でWebページを開き、カルーセルスライダーが同じ寸法でそれらしく表示されればカルーセルスライダーのCLS対策は完了である。 LCPにも効く ファーストビューのカルーセルスライダーはたいてい、LCPの判定基準にもなる。 JavaScriptによるライブラリの起動を待たないとスライダーが表示されないのではLCPの達成に一手の遅れが出てしまう。 HTMLとCSSによるカルーセルスライダーのモックアップはその一手を縮め、LCPの改善にも効果が期待できる。 --- # PageSpeed Insightsの正しい読み方・活かし方 - 公開日: Sat Nov 09 2024 08:15:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology PageSpeed InsightsはWebページのスピードを評価する際に便利なツールではあるが、非常にミスリードを起こしやすい。 目立つのはパフォーマンススコアと指摘事項の数々だが、弊社がフロントエンドのスピード改善を提案する上でそれらを見ることはほとんどない。 厳しい言い方をするとスコアはまやかしで、指摘事項は時代遅れである。加えてスコアと指摘事項に因果関係もない。 断っておくがツールを非難したいのではない。スコアと指摘事項に振り回されては、サイトを高速化したいという本来の目的は一向に果たせないことをお伝えしたい。 ANAトップページの例最も重要な CrUX Web Vitalsリアルユーザー指標オリジンとはCrUX Web Vitalsの見方合格を得るには二番目に重要な Lightouse の指標CrUXとLighthouseの使い分けLighthouseとPageSpeed Insightsなぜスコアは重要ではないかスコアはLighthouse Web Vitalsの平均点スコアは0点以下にならないスコアはただの結果指摘事項はなぜ重要ではないかフロントエンドが遅い理由はデータサイズではない主戦場はJavaScriptと3rdパーティタグ答えはパフォーマンスタイムラインにあるまとめ ANAトップページの例 この記事ではANAのトップページを題材にする。 ANAの航空券・飛行機 予約、空席照会、運賃案内|ANA 公共性の高いサイトであり、筆者自身も高速化を願う一人の利用者である。 最も重要な CrUX Web Vitals 最終的に重視すべきなのは、以下の 「実際のユーザーの環境で評価する」 の欄である。 ここではいわゆるCore Web Vitalsを含む、5つの指標(Web Vitals)についての評価を確認できる。 リアルユーザー指標 このWeb Vitalsが最も重要である理由は、現実を反映したリアルユーザー指標だからだ。 Googleは、世界中のChromeユーザーから実際のサイト閲覧時のスピード指標を日頃から収集している。これをCrUX(Chrome User Experience Report)と言う。 CrUX の概要  |  Chrome UX Report  |  Chrome for Developers 上記に表示されているのはそのデータの過去28日分に基づく統計であり、実際のANAサイトのユーザーがどのようなスピードでトップページを体験したかを如実に現している。これらを CrUX Web Vitals と呼ぶことにする。 極端な話、PageSpeed Insightのスコアが低く多数の警告の指摘事項を示そうが、この統計が良ければ心配はない。スピード改善の最終目標はこのCrUX Web Vitalsの健全化である。 INFO あくまでPageSpeed Insightsのレポートを使う場合の最終目標であって、リアルユーザー指標を独自に計測する場合はそちらが最終目標となるだろう。 オリジンとは 右上の「このURL」と「オリジン」には以下の意味がある。 このURL 対象ページそのものの統計。ある程度の閲覧数が必要で、ページによっては表示されないこともある。 オリジン 他のページも含めた www.ana.co.jp ドメインの全体の統計。 CrUX Web Vitalsの見方 LCPについて見てみよう。 Googleの基準においては、LCPが2.5秒以下であればユーザーは概ね不満を感じない(良好)とされている。 INFO もちろんスピードに対する感情は主観的なものであり、2.5秒以下でも遅いと思われる可能性はある。それでは定量的な表現ができないため、便宜的に基準が設けられている。 上記のグラフは、実際にモバイル端末でANAトップページを閲覧した61%の利用者がおおむねLCPに違和感はなく、17%は逆に遅いと感じている。22%はその中間で印象は人による、といった解釈でよいだろう。 3.4秒は75パーセンタイルを示している。例えば100人のユーザーが1回ずつこのページを閲覧したとして、LCPが早かった順に並べると、75番目のユーザーは3.4秒だったということだ。 合格を得るには このレポートでは 「ウェブに関する主な指標の評価」が「不合格」 とされている。 合格を得るためには、LCP、INP、CLSの3指標(Core Web Vitals)すべてにおいて、良好の割合が75%以上、つまり75%パーセンタイルが良好の基準以下になる必要がある。 つまりANAのトップページについて言えば、75パーセンタイルにおいて LCP 3.4秒から2.5秒に短縮する = 約26%高速化 INP 398ミリ秒から200ミリ秒に短縮する = 約50%高速化 CLS 0.49から0.1に改善する = 約80%改善 以上の改善を達成することが条件となる。 二番目に重要な Lightouse の指標 次に大事なデータは、 「パフォーマンスの問題を診断する」の中にある「指標」 である。 これは 実際にたった今、PageSpeed Insightsが持つブラウザでANAトップページを表示し、以下の5つのWeb Vitalsを計測した結果 だ。 First Contentful Paint ☆ Largest Contentful Paint ☆ Total Blocking Time Cumulative Layout Shift ☆ Speed Index ☆をつけた指標は、前述の Web Vitals と共通している。 これらのWeb Vitalsの計測には、裏側で Lighthouse が用いられている。 そこでこちらはLighthouse Web Vitalsとここでは呼ぶことにする。 CrUXとLighthouseの使い分け CrUXとLighthouseは似たような指標を示すが、どう使い分けたらよいか。 CrUXの指標はリアルユーザーデータだが、フィードバックに時間がかかる。改善から最長28日待たないと正確な結果がわからない。 一方、Lighthouseは試験データではあるが、改善した結果がすぐにわかる。 したがって、Lighthouse Web Vitalsでフィードバックを速やかに受けながら改善を繰り返し、CrUX Web Vitalsの健全化を目指す のが使い分けであり、全体の方針となる。 LighthouseとPageSpeed Insights Lighthouseはソフトウェアであり、例えば読者のPCでも実行できる。手軽ではあるが、実行する端末のスペックによって結果が変わってしまう。 PageSpeed Insightsは、おそらく一定の環境でLighthouseを実行するため結果が安定しやすい。 なぜスコアは重要ではないか PageSpeed Insightsのレポートで最も目立つのがパフォーマンススコアだ。 しかし実のところ、このスコアを見ても次にどんなアクションをとるべきか、ほとんどヒントにもならない。 その理由を簡単に説明したい。 スコアはLighthouse Web Vitalsの平均点 先に述べた5つのLighthouse Web Vitalsそれぞれに対し、値が小さければ100点に近く、一定以上の大きさだと0点になるような点数を割り当てる。 例えばLCPは以下のような曲線でスコアが決まる。 それらの平均点(実際は重みの違う加重平均)がスコアとして表示される。 もしスコアが100点に近ければ、どの指標も優秀である説明にはなるのだが、スコアが20点だった場合、どの指標に問題があるのか正確に説明ができない。 スコアは0点以下にならない LCPについて言えば、10秒を超えたあたりからLCPスコアは0点になる。 ANAのトップページではLCPが31.9秒と評価されている。 LCPが10秒と31.9秒では対策がずいぶん異なるが、スコアの上ではどちらも0点である。この点も当てにならない。 スコアはただの結果 Lighthouse Web Vitalsを改善していけば自ずとスコアは上がる。それだけの数値であって、スコア自体の説明力は低い。 スコアを重要視しないのはそういった理由からである。 指摘事項はなぜ重要ではないか 最後にレポートの下部にある指摘事項の数々だ。 この指摘事項も人気がある。サイトスピード改善提案を名乗る資料が、これらの指摘事項のコピペとその補足説明に過ぎない場面は何度も目にした。 しかしこちらも、弊社がフロントエンドの改善提案する際にほとんど見ることはない。 フロントエンドが遅い理由はデータサイズではない 多いのはリソースデータの軽量化についての事項であるが、データが大きいとサイトが重いというのは10年前の話だと言っていい。 もちろんデータは少しでも軽量な方がいい。しかし極端な例を除き、データを軽量化して早くなるのはよくても1秒という話だ。 数秒単位で指標の短縮が必要な場面で、データの軽量化を繰り広げても焼石に水なのである。 主戦場はJavaScriptと3rdパーティタグ 多くのフロントエンドのボトルネックを見てきた経験から言うと、今やWeb Vitalsが悪い原因のほとんどはJavaScriptである。 その意味で言うと、以下の指摘事項だけはたまに参考にすることがある。 JavaScript の実行にかかる時間の低減 第三者コードの影響を抑えてください JavaScriptに詳しい人は少ない。JavaScriptプログラマーが少ないわけではないのだが、彼らの多くはもっと最新の技術が好きで、Webページのレガシーコードなど見たくない。 だからJavaScriptについての分析や課題解決は避けられ、その他の技術課題(データ軽量化など)から着手される傾向がある。 答えはパフォーマンスタイムラインにある では成果の出る改善のために指摘事項は見ずにどこを見るか。 主には開発者ツールの Performance タイムラインを見る。 フロントエンドが遅い本当の答えは、このタイムラインにある。 まとめ 以上、PageSpeed Insightsの正しい見方を述べてきた。 大切なのはCrUXとLighthouseのWeb Vitalsの値 スコアは重要な情報を持たない 指摘事項はほとんど当てにならない これらを理解いただき、スコアと指摘事項に振り回されず、実のあるスピード改善に取り組んでいただけたら幸いである。 --- # 自前アクセスランキング実現のつらみ - 公開日: Sun Nov 03 2024 04:44:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, infrastructure, technology, research アクセスランキングはWebサイトの人気機能のひとつであるが、弊社ではGA4を活用したアクセスランキング表示のWebサービス Ranklet4 を提供している。 Ranklet4 [無料] GA4から人気ページランキングをかんたん表示 「どのサイトでもGoogle Analyticsでアクセス解析をしているのだから、そのデータを活用してアクセスランキングを簡単に表示できるようにしよう」というアイデアから始めた無料サービスだ。 Google Analytics(以下GA4)のデータを使うのは、単に「せっかくデータを取っているから」といった受け身な理由に限らない。アクセスランキングを自前で実現するのはなかなか大変なのだ。 そんなアクセスランキング実現のつらみと、GA4を活用することの意義について話をしたい。 自前で実装する場合のよくあるパターン本番環境にリリースすると…なぜ負荷が高くなりすぎたのか書き込み処理は愚直に行われるキャッシュやストリーミングデータサービスを使うデータベースをアップグレードする?だからGA4を使うRanklet4が向かないサイト 自前で実装する場合のよくあるパターン アクセスランキング自体はシンプルなデータ処理である。ページが閲覧されたらそれを記録し、ページごとの閲覧数を集計して表示する、それだけである。 例えばWordPressにはプラグイン WordPress Popular Posts があり、インストールするだけでアクセスランキングを実現できる。 WordPressと同じデータベースに、ページビューも記録して、それを集計してランキングを表示する仕組みのようである。 INFO ざっとコードを眺めての推測なので間違っていたら申し訳ないが、ここではその前提で話を進める。 自前で実装する場合も、データベースを備えたシステムではおおよそ同じ仕組みになるだろう。 本番環境にリリースすると… さて、開発環境で動作確認をして問題がなかった。 対象サイトはそれなりのアクセス数があるメディアサイトだ。いよいよ本番環境へリリースしたところ、サイトと管理画面の両方がまったく応答しなくなってしまった! 筆者も以前に似たような経験をしたことがある。要は本番のアクセス数では、データベースへの負荷が高くなりすぎたのだ。 なぜ負荷が高くなりすぎたのか 普段は同じアクセス数も問題なく捌いているのに、なぜシンプルなアクセスランキングを追加しただけで負荷に耐えられなくなったのか? 書き込み処理は愚直に行われる 普段はデータベースからコンテンツを読み込み、それをアクセスに応じてユーザーに表示する。 データの読み込み処理はキャッシュをしやすい。メモリやデータベースより軽量なファイルに記録し、毎回愚直にデータベースを参照しなくてもよい仕組みを作りやすい。 一方、アクセス数の記録はアクセスのたびに毎回愚直にデータベースを操作する。これが負荷でパンクした原因だった。 また、記録したアクセス数が増えるとランキング集計の処理も重くなる。こちらも高負荷の要因になるであろう。 キャッシュやストリーミングデータサービスを使う ひとつは読み込み処理と同じようにキャッシュを活用することだ。 一旦、処理が軽量なファイルにアクセスを追記で記録しておき、ある程度集計したデータを定期的にデータベースに投入する。これで負荷をかなり下げることができる。 あるいは、AWSで言えば Kinesis など、ストリーミングデータを扱うマネージドサービスも多い。これらを活用するのも安定化につながる。 データベースをアップグレードする? もうひとつは強力なデータベースにアップグレードすることだが、データベースは総じて単価の高いコンポーネントである。 アクセスランキングの実現のためにデータベースを強化することにそこまで経済的合理性があるだろうか。 また、アクセスランキングのような補助的な機能に引っ張られてデータベースの性能が決まるのは、設計上、不健全な状態と言わざるを得ない。 だからGA4を使う 言いたかったのは「アクセス数のような弾力性のある事象に対応してデータを記録するのは案外大変」ということだ。 アクセスランキング表示の機能自体はシンプルでも、スケーラビリティの問題は大きい。 しかしよく考えたら、アクセス数のような弾力性のあるデータを、絶えず安定的に記録してくれているサービスがすでにあるではないか。GA4である。 Ranklet4 は、以上の理由でアクセスランキング表示の最適解であると自負している。 Ranklet4が向かないサイト 技術的な手軽さの面でアクセスランキング表示の最適解を自負するRanklet4であるが、もちろん弱点もある。それがリアルタイム性である。 GA4ではアクセスの記録から集計まで数時間のタイムラグがある。メディアサイトであればギリギリ許容できるかもしれないが、SNSのように情報鮮度の高いサイトでは要件を満たせない場合もあるだろう。 また、アクセスデータを元に「この情報は⚪︎⚪︎人の人が見ています」といった、もっとダイレクトな販促に用いられるケースも増えている。 このようにアクセスストリームデータの高度な活用がなされるサイトでは、アクセスランキングはその一部として実装すべきだろう。 --- # サイトスピードと収益性を高い解像度で理解する - 公開日: Sat Oct 26 2024 18:31:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, business サイトスピードが遅いとサイトの収益性が悪化し、逆に早ければ儲かる。それはよく知られている。しかし なぜそうなるのか説明 するには、少し高い解像度で理解していないと難しい。 サイトスピードが早くなると収益が上がると言うが、その財源は一体どこにあるのか? 誰がどう払ってくれるものなのか。 単純化すると、サイトスピードは機会損失率と成約率のトレードオフを調整するレバー のようなものだ。 実はWebサイトは、サイトスピードに起因する機会損失を常に抱えている。サイトスピードが早くなれば機会損失率が減って成約率が増える。遅くなればその逆に作用する。 機会損失という見えないものを見るにはデータが要る。通販サイトの実例を交えて詳しく見てみよう。 結論成約率(CVR)についてサイトスピードと成約率の実例サイトスピードによる成約率の差は8.3倍サイトスピードによるユーザー層の分類絶対買わない層 97.5%絶対買う層 0.3%買うかも層 2.2%最終的な成約率 0.8%なぜそんなに機会損失が多いのかユーザー(セッション)数をグラフに重ねるサイトスピード 20%改善 → 収益 22%向上の見込みサイトスピード 20%悪化 → 収益 -15%減少の見込みまとめ 結論 これから紹介する実際の通販サイトでは 成約率 0.8% であるが、サイトスピードによる機会損失率は全体の 約1.7% に及ぶ。現に得ている収益より機会損失の方が大きく、むしろ倍以上あるのだ。 だからサイトスピードを改善すれば、埋蔵金を掘るように機会損失を財源とした収益が手に入る。 成約率(CVR)について 本題の前に、成約率(CVR)について少しおさらいをしよう。 通販サイトを例にすると、ユーザーはサイトを訪れて複数のページを閲覧、操作した後に注文を完了する。当然、途中で離脱するユーザーもいるし、直帰するユーザーもいる。 運よく注文にいたることをコンバージョンと呼び、訪問数に対するコンバージョンの割合を成約率やCVR(コンバージョンレート)という。 現実には、注文まではいたらず離脱するユーザーの方がずっと多い。成約率はふつう数パーセント程度で、1%を下回るケースも珍しくない。 つまり100人のユーザーがサイトに訪れても、実際に注文するのはせいぜい数人ということだ。 ユーザーとセッション(訪問)について 実際にはひとりのユーザーが複数回、サイトを再訪することもある。訪問の数をセッション数と呼び、成約率はセッション数を分母にすることが多い。ただ再訪は通常そこまで多くないので、この記事では訪問数=セッション数≒ユーザー数という扱いをする。 サイトスピードと成約率の実例 次のグラフは実際のある通販サイトにおける、ページ読み込み時間と成約率(CVR)の関係を示したものだ。 横軸→ ユーザー(セッション)ごとの平均ページ読み込み時間(平均OnLoad)。それを20分割した階級 縦軸↑ 成約率(CVR)。グラフの都合上、数値は右軸 実はユーザーは、みんな同じスピードでサイトを利用できているわけではない。私たちが思う以上にユーザーやページによってスピードにはばらつきがある。 平均数秒でページを読み込みできたユーザーから、10秒以上かかったユーザーまで体験はまちまちとなる。 サイトスピードに関する認知バイアス 私もそうだが、サイト提供者側は「ユーザーも自分と同じくらいのスピードでサイトを利用している」と思いがちである。データを見るときは自分もユーザーとしてはN=1のサンプルにすぎないことを改めて理解したい。 サイトスピードによる成約率の差は8.3倍 グラフからは以下のことがわかる。 サイトスピードが早いと 成約率は約2.5% にもなる。これを 潜在的成約率 を呼ぶことにする。 読み込み時間が遅くなるほど成約率は低下する。つまり注文が成立しにくくなる。 大体8秒を超えると 0.3%程度で横ばい になる。 サイトスピードが早いユーザーと遅いユーザーでは成約率に 8.3倍 (≒ 2.5% / 0.3%) の差がある。 もちろん階級を20に設定した恣意性があり、その前提によって数値は前後する。本質的な傾向として理解いただきたい。 サイトスピードによるユーザー層の分類 このグラフから購買性向というか利用者の気分のようなものを想像して、ユーザー層を分類してみる。 絶対買わない層 97.5% 成約率について振り返った通り、ほとんどユーザーは注文をせずに離脱する。 いくらサイトスピードが早かったとしても潜在的成約率は約2.5%だった。つまり100% - 2.5% = 約97.5% のユーザーは、 「そもそも買う気はない」 「サイトがいくら早かろうが、いらないものはいらない」 …のようなことを考えていたと想像できる。これらのユーザーはサイトスピードに関係なく 絶対買わない層 である。 サイトスピード以外の離脱要因 もちろん離脱の要因にはサイトスピード以外にもいろいろある。この記事およびグラフではあくまでサイトスピードに着目しており、その他の離脱はサイトスピードと独立して現れるものと想定している。 絶対買う層 0.3% 逆に、サイトスピードがいくら遅くても約0.3%のユーザーは注文に到達した。そのことから、約0.3%のユーザーは 「何がなんでも欲しい」 「今日、どうしても注文しなければならない」 …のようなことを考えていたのではないだろうか。これらのユーザーはサイトスピードに関係なく 絶対買う層 である。 買うかも層 2.2% 絶対買わない層と絶対買う層の間の約2.2%、ここは 買うかも層 だ。 「いちおう買うつもりでいる」 「気になる商品はある」 「うっかり衝動買いしちゃうかも」 …のようなことを考えており、体験したサイトスピードにより行動が左右する。最終的に注文したユーザーと離脱したユーザーが混在する層である。 最終的な成約率 0.8% では、買うかも層 2.2% のうち、注文に到達した 運よく注文層 と、買う気はあったが離脱してしまった 機会損失層 はどのくらいの割合になるか。 それは 最終的な成約率 0.8% を描くとよくわかる。 なんと買うかも層 2.2%のうち運よく注文に到達したのはわずか 約0.5%で、大部分の約1.7%はサイトスピードが遅いことによる機会損失に消えていたのである。 なぜそんなに機会損失が多いのか 厳しい言い方をすると、このサイトは必要に駆られた品物は提供しているが、ふわふわと楽しい気分の買い物体験はあまり提供できていない。 ユーザーの声を想像すると、 「サイトが遅くて気になり、それどころではない」 といったあたりだろうか。 しかしこのサイトが他のサイトと比べて特別遅いというわけではない。どのサイトも多かれ少なかれ似たような状況なのだ。 ユーザー(セッション)数をグラフに重ねる このように「多くの機会損失が生じている」と言われてもピンとこないかもしれない。特に自身のサイトがそうだと言われたら、にわかに信じられないだろう。 しかし実際のユーザー(セッション)数の分布をグラフに重ねてみるときっと納得できる。 成約率(CVR)が最大化する階級のセッション数が少なく、逆に成約率(CVR)が低下した階級のセッション数が多い。 つまり成約率が高くなるようなサイトスピード体験を、多くのユーザーに提供できていないミスマッチが大きいのである。 サイトスピード 20%改善 → 収益 22%向上の見込み もし、サイトスピードが一律20%改善できたらどうなるかシミュレーションしてみよう。 より多くのユーザー(セッション)に早いサイトスピードを提供することになると、分布の山が左方向にシフトする。下図でいうと緑のヒストグラムである。 それによりユーザーへ快適なサイトスピードを提供できていないミスマッチが解消されていき、機会損失率の低下→成約率の上昇につながる。 全体の成約率は、新たなヒストグラムの度数と階級に対応する成約率の加重平均で大まかに計算し直せる。 それによると 20%のサイトスピード改善で成約率は 0.8% から 0.98% になる見込み となる。これは、収益(売上)でいうと 22.37% の増加 に相当する。 サイトスピード 20%悪化 → 収益 -15%減少の見込み 逆にサイトスピードが低下すると分布の山が右にシフトし、ミスマッチがさらに拡大する。同様に全体の成約率を加重平均で計算すると、20%のサイトスピード悪化で成約率は 0.8%から 0.68%に低下し、収益は -15.19%減少 する。 まとめ 以上がサイトスピードと収益性の詳細な関係性であり、サイトスピードは機会損失率と成約率のトレードオフを調整するレバー と比喩した根拠である。 サイトスピードがユーザーによってまちまちであるため、それに起因する機会損失が常に生じている。サイトスピードの変化で、ボーダーラインにいるユーザー行動は注文達成と離脱の間で揺れ動く。それが収益を左右するという因果関係となる。 筆者も以前、サイトが遅いと離脱が増えて収益性が下がるというのはわかるが、早いと収益性が上がるといってもその財源が何なのか、うまく説明できずモヤモヤした感情を抱いていた。 その当時の自分を納得させる思いで文章を記した。共感が得られたら幸いである。 --- # Difyでの自前APIとの連携方法と注意点 - 公開日: Sat Oct 19 2024 18:43:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development, technology, automation Difyには外部サービスと連携するツール機能があり、カスタムツールとして自前のAPIを連携対象に追加できる。 この記事では 「ふたつの数字の掛け算を行うという非常にシンプルな独自API」 を例にして、Difyにおけるカスタムツールの利用手順や、注意点を紹介する。 独自APIの用意APIの作成と公開OpenAPI-Swaggerによるスキーマを用意Difyのカスタムツールに登録チャットフローでカスタムツールを使うパラメータ抽出の追加カスタムツールの追加回答カスタムツールの注意点代替案 - 出力スキーマをフラットテキストにする代替案2 - HTTPリクエストブロックAPIの認証についてベーシックベアラーまとめ 独自APIの用意 APIの作成と公開 実装方法はなんでも良いが、ここではNode.js + Fastify + ngrokでさくっとAPIサーバーを立ち上げる。 bashmkdir multiply-api cd multiply-api npm init -y npm i fastify --add index.jsを作成する。 jsconst fastify = require("fastify")(); fastify.post("/multiply", async (request, reply) => { const { a, b } = request.body; const result = a * b; reply.send({ result }); }); fastify.listen({ port: 3000 }); APIサーバーを起動する。 bashnode index.js ngrokで外部公開する。 bashngrok http 3000 https://7217-*-*-*-*.ngrok-free.appといった形式の外部公開URLが得られただろう。 もちろんVPSでもよいし、AWS Lambda関数でもよい。Difyのコンテナ(apiやworker)からアクセスさえできればローカル環境でもよい。 OpenAPI-Swaggerによるスキーマを用意 この簡易APIをDifyに登録するのだが、OpenAPI-Swagger仕様によるスキーマが必要となる。 URLの変更 一箇所だけ、servers[0].urlは外部公開されたURL(ngrokであればhttps://7217-*-*-*-*.ngrok-free.app)を指定すること。 yamlopenapi: 3.0.0 info: title: 掛け算API version: 1.0.0 description: このAPIはふたつの数字の掛け算をします。 servers: - url: https://7217-*-*-*-*.ngrok-free.app # あなたの環境に合わせて変更 description: ngrokによる一時的な公開URL paths: /multiply: post: summary: 掛け算を行う operationId: multiply requestBody: required: true content: application/json: schema: type: object properties: a: type: number description: かけられる数 example: 5 b: type: number description: かける数 example: 3 required: - a - b responses: "200": description: 掛け算の成功 content: application/json: schema: type: object properties: result: type: number description: かけ算の答え example: 15 あらかじめスキーマが完備されたAPIならよいが、実際のところはそうでないAPIが多いだろう。 ひと手間かかって面倒な感じだが、DifyにおいてAIとAPIをスムーズに連携させるためのプロンプトの一種と考えて前向きに対処しよう。 PythonのFastAPIや、Node.jsのNextJSなどAPIフレームワークによっては、APIの実装とスキーマを同期して管理できる機能がある。本格利用の段階ではそのような工夫も考えたい。 Difyのカスタムツールに登録 Difyのメニューから、ツール - カスタム - カスタムツールを作成するを選ぶとポップアップが表示される。 ここにカスタムツールの名前と、先ほど用意したOpenAPI-Swagger仕様のスキーマを登録しよう。 テストボタンを押すとテストができる。aに2、bに3を入力し、テストボタンを押すと、{"result":6}という期待した結果が得られた。 最後に保存ボタンを押すと、カスタムツールの登録は完了である。 チャットフローでカスタムツールを使う 次はスタジオ機能に移動し、チャットフローでカスタムツールを試してみよう。 ここでは「掛け算チャット」を作る。「2かける3は?」などの自然言語を解釈してAPIにより掛け算を行うチャットボットだ。 INFO もちろん簡単な掛け算はLLM単体で実行できるが、ここでは外部APIとの連携を実証するための作例である。 パラメータ抽出の追加 まずは開始ブロックにパラメーター抽出ブロックを接続する。 自然言語による入力から、掛け算APIに渡すべき値が何かを抽出する。 ツールからインポートボタンを押して作成したカスタムツールのmultiplyAPIを選択すると、スキーマに記述したパラメータaとbが自動で展開される。 カスタムツールの追加 続いてパラメーター抽出ブロックからカスタムツール - multiplyに接続する。 入力変数aは、Variable - パラメーター抽出/aを選択する。bについても同様にVariable - パラメーター/bを選択する。 これでユーザーの入力から掛け算の意図を汲み取り、APIに渡す処理を実現できる。 回答 一旦ここで回答ブロックをつなげて動作確認をする。a、b、そしてカスタムツールの出力textを簡単に整形して表示するようにしてみた。 プレビュー機能で 「二かける三は?」 と尋ねてみると、 2 * 3 = {"result": 6} という回答が得られた。 出力フォーマットはさておき、ユーザーの自然言語をLLMで適切に解釈し、独自に用意した掛け算APIを実行する流れを実現できた。 カスタムツールの注意点 カスタムツールの注意点は、出力スキーマは自動で解釈してくれないところである。 入力スキーマはパラメーター抽出やツールブロックの入力に活用できるが、出力はフラットな文字列textとしてしか扱うことができなかった。 今回のresultのように必要な要素を抜き出すに、コードブロックなどでJSONを解析する必要がある。 Difyのバージョン Difyのバージョンは 0.8.2 で検証した。 今後のバージョンで出力スキーマも解釈し、要素を個別に利用できるようになる可能性はある。しかし、もしかしたらLLMの入力としてはJSONやXMLなどの構造化データでもおおよそ問題ないので、設計思想としてこのままかもしれない。 代替案 - 出力スキーマをフラットテキストにする 構造化された出力は直接解析できないので、Dify向けのAPIの出力はそもそもシンプルなテキスト(text/plain)にするという手も考えられる。 OpenAPI-Swagger仕様のスキーマの、responsesを以下のように変更して、APIの出力をtext/plainにしても連携には問題がなかった。 yaml responses: "200": description: 掛け算の成功 content: plain/text: schema: type: string example: "15" 代替案2 - HTTPリクエストブロック 実はスタジオ機能(ワークフロー編集)にも似たような機能があり、HTTPリクエストブロックが用意されている。 こちらは比較的柔軟なHTTPリクエストを任意のWebサーバーに投げることができる。 カスタムツールに比べるとOpenAPI-Swaggerのスキーマを用意するのは面倒だけど、ちょっと連携を試してみたいといった用途にはこちらの方がよいかもしれない。 APIの認証について カスタムツールによるAPI連携では当然、アクセス認証も設定できる。 設定による挙動は以下のようになっている。 APIリクエストヘッダAuthorization(キーとして変更可能)に、指定した値を渡すシンプルな設定である。 認証タイプ なし 特に認証情報は渡さない APIキー Authorizationヘッダに指定の認証情報を渡す ベーシック 値の前にBasicが付く ベアラー 値の前にBearerが付く カスタム 値をそのまま渡す ベーシック 文字通りベーシック認証だ。もしAPIがベーシック認証で保護されているなら、次のようにユーザ名とパスワードをBase64エンコードした値を設定すればDifyと連携できる。 bashecho "user:password" | base64 値が特にBase64である必要はなかった。 ベアラー OAuth2などでアクセストークンを指定する場合にお馴染みの修飾子Bearerであるが、これもtoken68である必要はなかった。 もしOAuth2で認証・認可を行っているAPIがあって、アクセストークンの有効期限が十分に長ければ、そのアクセストークンを値に指定することですぐに連携できるだろう。 まとめ OpenAPI-Swaggerスキーマが必要という敷居はあるが、Difyと既存システムとのAPI連携は非常に夢が広がる機能である。 しかし、DifyからプリミティブなREST APIを呼び出すことは少ないと予想される。Dify側で複雑なプログラムロジックを組むのはあまり効率がよくないからだ。 おそらくもう少し複雑な処理を行うAI向け・Dify向けのAPIを新設することにはなる。だから既存のAPIにスキーマが用意されていなくても心配ない。それらの新設APIにのみ用意すれば実用上は問題ない。 また、エージェントアプリケーションで更新系のAPIとの連携も考えられる。ユーザーの自然言語入力に応じて既存システムに影響を及ぼすユースケースも今後は増えると思われる。 業務での本格的なAI利用に向けて、カスタムツールは避けて通れないテーマである。 --- # Dify会話変数でcanvasのような共同作業を実現 - 公開日: Fri Oct 18 2024 16:18:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, development, technology, automation ChatGPT with canvasをご存知だろうか。AIとともに、ソースコードや文章を編集できる機能だ。 GitHub CoPilotもそうだと言えるし、これまでも近いことはできた。しかし「共通の作業対象」がインターフェースや出力として明確になった点が新しい。 このような共同作業を、Difyのチャットでも実現する方法を解説したい。 会話変数俳句を共同で作るチャット会話変数ワークフロー全体LLMブロックパラメータ抽出ブロック変数代入ブロック回答ブロックまとめ 会話変数 Difyのチャットには会話変数という機能がある。ワークフローの各ブロックにも入出力変数があるが、それとは別に会話全体を通して保持されるグローバル変数のようなものだ。 この会話変数に共同作業の成果物を格納することで、ChatGPT with canvasのようなチャットを実現できる。 メモリと会話変数 過去の会話の内容を記憶して回答に役立てるメモリもある。しかし指示代名詞(あれ、それ)を用いるのは会話と同様、正確さを欠く恐れがある。会話変数の方が確実である。 俳句を共同で作るチャット ここではAIと一緒に俳句を作るチャットシステムを作ってみよう。 会話変数 ワークフローの右上に会話変数アイコンがある。アイコンを押して表示される右サイドパネルで変数を追加および編集できる。 ここで、これまで考えた俳句の成果物を保持するhaikuという変数を作成した。 ワークフロー全体 次のようなワークフローを構成した。 パラメータ抽出と変数代入ブロックがポイントとなる。 LLMブロック LLMには次のようなシステムプロンプトを与えた。 あなたは私と一緒に俳句を考えるアシスタントです。ユーザーの発言と、これまで考えた俳句「{{#conversation.haiku#}}」を踏まえて俳句をひとつ出力してください。 出力は、JSON形式にて、 - `haiku` 考えた俳句 - `comment` 変更を加えた点とその意図 を出力してください。 ユーザーの発言とこれまで考えた俳句(初回は空)から、俳句をひとつ考えるように指示する。 そして考えた俳句自体と、それに対するコメントをJSON形式で構造化するように指示している。 構造化の形式 構造化の形式はXML形式などでもよいだろう。ただ、XML形式だとタグが不可視となってデバッグしにくかったので、ここではJSONとした。 パラメータ抽出ブロック LLMブロックの出力は素のテキストである。次にパラメータ抽出ブロックにて、先ほどの構造化された回答を正確に解釈する。 次のような設定を与えて、出力変数haikuとcommentに俳句自体とコメントを格納するようにした。 JSONの解釈 LLMブロックでJSON出力を指示しているので、例えばコードブロックでJSONをデコードしてhaikuとcommentを抜き出す方法もあるだろう。 しかしチャットLLMが正確に解読可能なJSONを出力する保証はない。同じくLLMで曖昧に解釈できるパラメータ抽出ブロックで、Function/Tool Caliing推論した方が安心できる。 変数代入ブロック 変数代入ブロックで、ワークフロー上のブロック出力変数を会話変数に格納できる。 ここではLLMの出力からパラメータ抽出したhaikuを、会話変数haikuに格納している。 これで最後に考えた俳句成果物が会話変数haikuに保持され、次回の会話にて共同作業の対象を正確に引き継ぐ仕組みとしている。 回答ブロック 最後に回答ブロックだ。考えられた俳句、その根拠となるコメントを表示する。ついでにLLMが出力した構造化された情報もデバッグ出力している。 まとめ このように会話変数をうまく利用することで、AIとの共同作業チャットを実装できた。 実際の業務では例えば、問い合わせへの回答文を、過去のテンプレートや顧客情報を与えながらユーザー好みの文面に仕上げていくようなアプリケーションが作れるだろう。 現在のDifyには、チャット画面に会話変数を表示したり、編集する領域はなさそうだ。ChatGPT with canvasのようなGUIを実現するには、APIを使って独自に実装する必要がある。 しかし、ChatGPT with canvasを意識して、今後そのような機能が追加されるだろうと予想している。 --- # 自動生成するロゴマークを刷新 "alogorithm2" - 公開日: Tue Oct 15 2024 21:10:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, technology, ideas アイデアマンズ株式会社のロゴマークはプログラムで自動生成している。 この度、ロゴマークの生成プログラムを刷新し、alogorithm2としてリリースした。 プログラミングへの強みをロゴに込める7年ぶりにリニューアルランダムシードによってロゴが変化ロゴマークのタイプインライン矩形タイプアイコンタイプ利用技術についてtrianglifyblobsIBM Plex フォント動的なビジュアルアイデンティティの例ソニーMITメディアラボノルドキンメルボルン市 プログラミングへの強みをロゴに込める 2017年にロゴマークのリニューアルに際して、プログラミング技術が強みであるアイデンティティをどう表現するかを考えた。 そこで思いついたのが「プログラムで自動生成する」というアイデアだ。これなら強みをそのまま表現でき、意匠選びに悩まなくて済む。 ロゴマークについて ただし、ロゴマークを動的・可変にするという発想自体は新しくない。そのことについても最後に少し触れたい。 7年ぶりにリニューアル 先日、OGPアイキャッチ画像を自動生成するプログラムを作成した。 以前はSVGには苦手意識があったが、少し自信になってロゴマークの刷新を思いついた。 GitHubのプロジェクトはこちら。GPL 3.0ライセンスで公開しているので、興味を持った方はぜひ参考いただきたい。 ideamans/alogorithm2: アイデアマンズ株式会社のロゴマーク自動生成プログラム v2 ランダムシードによってロゴが変化 アイデアマンズのロゴマークには、seedというパラメータを渡す。例えば今日が2024年10月15日なら、seed=2024-10-15でもいいし、ブログ用のロゴマークならseed=blogとする。 そのseedのテキストによって宝石のようなマークが変化する。この制御をプログラムが行なっている。 seed=2024-10-15の場合は、 翌日のseed=2024-10-16の場合は、 このように全く違うマークになる。seed=blogの場合は以下だ。 この仕組みによりロゴマークを日替わりにできたり、サブサービスのロゴを簡単に用意できる。 ロゴマークのタイプ ロゴマークには3つのタイプがある。 インライン 上記で紹介した横長タイプだ。 矩形タイプ 社名を下に配置し、縦横のサイズを自由に指定できる矩形タイプもある。正方形なら、 少し横長にするなら、 このようにマークが変形して指定した枠に収まるように調整される。 アイコンタイプ マークだけの画像も生成できる。これはFaviconなどに使用する。 利用技術について 全体としてはNode.jsによるプログラムである。特徴的なライブラリやフォントを紹介したい。 trianglify 幾何学的ながら表情豊かな美しいパターンを生成するライブラリがある。 trianglify - npm 個人的にこの模様が大好きで、ロゴ生成プログラムの以前のバージョンでも利用していた。 blobs trianglifyが生成したパターンを、次のライブラリが生成した輪郭で切り抜きをする。 blobs - npm これで宝石のようなマークを自動で生成している。 プログラミング技術により、無数のアイデアの原石を生み出したいというアイデンティティにも合致して大変気に入っている。 IBM Plex フォント 社名のideaman'sには、プログラミング向けのフォントIBM Plexを利用させていただいた。 オープンソース・フォント「IBM Plex」誕生の経緯 | Think Blog Japan これもプログラミング技術を強みとするアイデンティティの現れである。 動的なビジュアルアイデンティティの例 ロゴマークを自動生成するというアイデア自体は自分で思いついたものではない。 ソニー かつてソニーのCMで、多様なビジュアルアイデンティティが同時期に展開された。 ソニー ビジュアル・アイデンティティ これにシビれたのが自分の中では原体験だった。 MITメディアラボ MITメディアラボも動的なビジュアルアイデンティティを採用した時期があった。 MITメディアラボのロゴは、いかにして生まれ変わったのか #WXD | WIRED.jp これは短命に終わったようだが、発想は近い。 ノルドキン ノルウェーのノルドキンは、現在の気象情報からマークが動的に変わるという興味深い事例である。 Neue Design Studio - Visit Nordkyn レーダーチャートに基づいてパターンを生成している。 メルボルン市 オーストラリアのメルボルン市もMというロゴマークをアレンジする試みを行なっている。 以下のpinterestの類似ピンを眺めていると、他にも多くの事例を見つけられるだろう。 melbourne --- # DifyをAWSでガチ目に動かすには?〜理論編 - 公開日: Tue Oct 08 2024 15:58:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, infrastructure DifyのソースコードにはDocker Composeプロジェクトが同梱されている。 それを利用すると自分専用のDify環境をサクッとセルフホストできるのだが、それを本番運用に利用するのは心許ない。 ではAWS上で、ガチ目のDifyスタックを展開するとしたらどうするか考えてみた。 全体像 Difyのソースコードは本当にセルフホストに親切で、Docker Composeプロジェクトのコンポーネント図もある。 これに加えてストレージも抽象化されており、S3などを選択できる。 AWS上では だいぶ複雑だが、ざっくりこんな感じにマッピングできるだろう。 mermaidflowchart LR subgraph "Dify ECSサービス" dify_sandbox[sandbox] dify_api[api] dify_worker[worker] dify_web[web] dify_ssrf_proxy[ssrf_proxy] end subgraph "Firecrawl ECSサービス" firecrawl_api[api] firecrawl_worker[worker] firecrawl_pw[playwrite-service] end db[RDS/Aurora] redis[ElastiCache] vs[OpenSearch/RDS] s3[S3] ses[SES] alb[ALB / WAF] acm[ACM] user(ユーザー) -->|Webアクセス| alb dify_api -->|メインDB| db dify_worker -->|メインDB| db dify_api -->|Redis| redis dify_worker -->|Redis| redis dify_api -->|ベクトルストア| vs dify_worker -->|ベクトルストア| vs dify_api -->|ストレージ| s3 dify_worker -->|ストレージ| s3 dify_api -->|メール送信| ses dify_worker -->|メール送信| ses alb -.-|SSL証明書| acm alb -->|ルーティング| dify_api alb -->|ルーティング| dify_web dify_api -->|Webクローラー| firecrawl_api dify_worker -->|Webクローラー| firecrawl_api firecrawl_api -->|Redis| redis firecrawl_worker -->|Redis| redis データベース・ストレージ DB - RDS or Aurora まずはデータベースだが、PostgreSQLを利用している。 したがってRDSやAuroraを利用する。環境変数DB_*が設定項目になっている。 Redis - ElastiCache Redisも中核的な役割を担っている。特にCeleryと組み合わせた非同期タスクの管理と思われる。 最近はServerless版のRedisもあるというElastiCacheに置き換える。 環境変数REDIS_*が設定項目になっている。 ベクトルデータベース - OpenSearch or RDS Difyが対応するベクトルデータベースにまとめたが、多数のベクトルデータベースに対応している。 AWSにはいくつかベクトルデータベースがあるが、現時点で相性が良さそうなのはOpenSearchかpgvectorだろう。 RDSのPostgreSQLがpgvectorもサポートしているので、メインデータベースに同居する方向もありだろう。 環境変数VECTOR_STOREと、OPENSEARCH_*またはPGVECTOR_*で設定する。 ストレージ - S3 デフォルトではローカルストレージを利用するが、各種クラウドのオブジェクトストレージにも対応している。 環境変数STORAGE_*とS3_*で設定する。 AWSであればS3一択となる。 Web・メール Webゲートウェイ - ALB + ACM nginxが次の役割を担っている。 リバースプロキシ 後述するコンテナapiとwebへのリクエスト分配 セキュアゲートウェイ コンテナcertbotと連携したSSLへの対応 ここはALB + ACMに代替可能だろう。 WAFやCloudFrontも組み合わせるとさらによいかもしれない。 メール送信 - SES ぱっと見、パスワードリマインダーなどでしか利用が見られないが、メールの設定も必要となる。 デフォルトではResendが選択されているが、AWSであればSESがよいだろう。 環境変数MAIL_*とSMTP_*で設定する。 コンピューティング アプリケーション - ECS Docker Composeのプロジェクトには主要なコンテナが3つと、補助的なコンテナが2つある。 主要なコンテナ api APIアクセスをホスト worker 非同期タスクを処理と思われる web Webインターフェースを提供 補助的なコンテナ sandbox ユーザー定義のコード実行と思われる ssrf_proxy SSRF防止プロキシ これらはECS(Fargate)に展開し、柔軟なスケーリングを可能にしたい。 apiとwebについてはALBに接続し、インターネットからの接続を確保する必要がある。 sandboxはおそらくだが、ワークフロー上でユーザーが定義した任意のコードブロックを実行する環境だ。 ssrf_proxyは名前のとおり、SSRF(Server Side Request Forgery)による攻撃を防止するためのHTTPプロキシと思われる。 実装までは詳しく見られていないが、コンテナやミドルウェアが相互作用するマイクロサービス的な構成であり、LLMモデルへのAPIアクセスもある。そのためHTTPプロキシを設け、安全な相互アクセスを担保しているのだろう。 その意味で、ssrf_proxyはサイドカー的に各サービスに1つずつ設けるのが良さそうだ。 どのようにECSを設計するか 実はDify本体はそこまで高負荷ではないように思う。負荷が高いのはLLMでありベクトルデータベースであり、Difyの役割はあくまでオーケストレーションだからだ。 したがって状況を見ながら以下のステップで、柔軟性と設計の複雑さのトレードオフを図りながら展開するのはどうだろうか。 1サービス 各コンテナをひとつのサービスに収め、その単位で冗長化・スケーリングする apiコンテナの分離 apiはユーザー数に弾力性があるので別サービス化する 1コンテナ1サービス 順当にコンテナごとにサービスを用意し、柔軟にスケーリングする クローラー - Firecrawl せっかくなのでDifyのお供にFirecrawlも用意したい。 FirecrawlのDocker Composeプロジェクト Redis - Elasticache Firecrawlもタスク管理にRedisを利用するようだ。 Elasticacheで用意する。 コンピューティング - ECS 次のコンポーネントがある(役割は推測)。 playwright-service Playwriteによるユーザーエージェント api APIホスティング worker 非同期タスクワーカー Firecrawlはナレッジの取得・更新時にしか利用されない。したがってユーザーアクセスに応じてスケールする必要はない。 おそらくこの中ではplaywrite-serviceの負荷が最も高いだろう。しかしサービスごとにインスタンス数を調整できたからと言って、コストフィットに貢献するかというと微妙な気がする。 1サービスにコンテナを詰め込んで、Redis + bullによる、非同期タスクの残タスク数に応じてスケーリングする仕組みにしたい。 まとめ このように安定的な条件でDifyをセルフホストするのは、楽しそうだが、保守も考えるとなかなか骨が折れる。 そう考えると、クラウド版DifyのTEAMプラン、年額$1590(2024年10月8日時点)は十分に検討に値する価格だ。 Dify AI · Plans and Pricing --- # Difyが対応しているベクトルデータベース - 公開日: Mon Oct 07 2024 22:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, technology Difyでナレッジを取り込むと、その内容はベクトルデータベースに格納される。 Difyは多種多様なベクトルデータベースに対応しているようで、環境設定ファイルを眺めているだけでも興味深い。 Difyが対応していると思われるベクトルデータベースを調べてみた。 ベクトルデータベースとはDifyが対応するVector Store本格利用に向けて ベクトルデータベースとは LLMには、文章などを例えば1024次元のような高次元ベクトルに変換する機能がある(Embedding)。いわば文章を言語空間における意味的な位置にマッピングする機能で、意味の近い文章はその位置が近くなるため曖昧検索に利用できる。 そのような高次元ベクトルの方向や距離を計算できるデータベースが、ベクトルデータベースである。 Difyにおいては、Vector Storeという扱いになっている。要はベクトルをキーとしてコンテンツをインデックスできる構造だけあればよいので、複雑なデータベースというよりKVS(Key-Value Store)に近い。 Difyが対応するVector Store ひとつずつ検証したわけではないが、Difyの環境設定ファイルを眺めていると、多種多様なデータベースへの対応が見て取れる。 この分野はまったく知見がないので、どんなデータベースに対応しているか調べてみた。 INFO この記事の執筆時である2024年10月7日現在の状況。環境変数VECTOR_STOREに指定できる値から調査した。 weaviate Weaviate ベクトルデータベース | Weaviate オープンソース(修正BSD) デフォルトのVector Store qdrant Qdrant - Vector Database - Qdrant milvus The High-Performance Vector Database Built for Scale | Milvus オープンソース(Aapche 2.0) myscale MyScale | Run Vector Search with SQL オープンソース(Apache 2.0) pgvector pgvector/pgvector: Open-source vector similarity search for Postgres PostgreSQLのベクトル拡張 オープンソース(PostgreSQL?) pgvecto-rs PGVecto.rs pgvectorとは別のPostgreSQLのベクトル拡張 オープンソース(Apache 2.0) analyticdb AnalyticDB Alibaba Cloudが推進するベクトルデータベース? MySQL版とPostgreSQL版があるらしい tidb TiDB, Powered by PingCAP 最近名前をよく目にするNewSQL オープンソース(Apache 2.0) Chroma Chroma オープンソース(Apache 2.0) Oracle Oracle DB? 情報が少なく詳細不明 relyt 詳細不明 LangChainでの紹介: Relyt | 🦜️🔗 LangChain opensearch OpenSearch AWSがフォークさせたElasticSearch起源のデータベース オープンソース(Apache 2.0) tencent Tencent Cloud VectorDB | Tencent Cloud Tencent Cloudのサービス? elasticsearch Elasticsearch | オフィシャルの分散型検索/分析エンジン | Elastic | Elastic オープンソース(https://github.com/elastic/elasticsearch/blob/main/LICENSE.txt) 生成AIブームの後押しで、どのクラウドサービスもベクトルデータベースに力を入れている。 しかし、AWSはOpenSearchのサポートは見られるが、AzureやGCPに合わせた対応は見られない。逆にAlibaba Cloud、Tencent Cloudへの対応が見られる。相対的に中国勢の強さが見られる対応状況だ。 本格利用に向けて DifyのDocker ComposeプロジェクトにはWeaviateが含まれており、デフォルトでそれが使える状態になっている。 しかし本番運用ではスケーラビリティの点で不安がある。弾力性のあるクラウドサービスを選択するのが無難だろう。 Difyはストレージもいろいろと選択できる。その対応についても今後調査してみたい。 --- # セルフホストしたDifyとNotionを接続する方法 - 公開日: Mon Oct 07 2024 21:25:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, content-management セルフホストしたDifyからNotionに接続するには少し工夫が必要だった。 Difyのセルフホスティングについては、手順をまとめた記事を参照のこと。 その内容に沿って接続の手順を解説する。 Notion側の操作インテグレーションIDの取得Difyに共有するドキュメントを指定Dify側の操作再同期代替案 Notion側の操作 Notion側では、コネクトを作成して、次の情報を取得する。 インテグレーションID 内部インテグレーションシークレット まずはワークスペースの設定を開き、コネクトメニューからインテグレーションを作成または管理するを選択する。 別タブにインテグレーションの管理画面が表示される。ここで新しいインテグレーションをクリックする。 任意のインテグレーション名を入力し、関連ワークスペースを選択して保存ボタンを押す。 わかりにくいが、インテグレーション名を追加と表示される部分はテキストフィールドになっている。種類は内部のままでよい。 続いてインテグレーションの設定画面に進む。 まずここで内部インテグレーションシークレットを表示し、内容を控えておく。 続いてページの下部に機能セクションがある。コンテンツを読み取るは必須だが、コメントやユーザー情報については、それらもナレッジに必要かどうかで判断されたい。 最後に保存ボタンを押す。 インテグレーションIDの取得 イングレーションIDは画面上を探しても見当たらなかった。 ブラウザのアドレスバーから/internal/に続くUUIDがインテグレーションIDなので、これも控えておく。 Difyに共有するドキュメントを指定 Difyに共有するドキュメントは、Notion側でも明示的に指定する必要がある。 ドキュメントの右上のアイコンからメニューを開き、コネクト - 接続先 - 作成したDifyへのインテグレーションを選択する。 Dify側の操作 Dify側に先ほど取得したインテグレーションの情報を設定していく。 これは環境変数で行う。docker/.envファイルを開き、次の項目を変更する。 bashNOTION_INTEGRATION_TYPE=internal # publicではなくinternalを指定 NOTION_CLIENT_SECRET= # 空欄のままでよい NOTION_CLIENT_ID=インテグレーションID # あなたの環境における値 NOTION_INTERNAL_SECRET=内部インテグレーションシークレット # あなたの環境における値 そしてDifyを再起動する。 bashdocker compose --profile certbot down docker compose --profile certbot up -d これでDifyのナレッジ作成時に、Notionからの同期を利用できるようになった。 Notion側でDifyに接続したドキュメントを選択できる。 再同期 Notion側の更新だが、自動でDifyに反映されるわけではなさそうだ。 同期の方法のひとつは、ナレッジのドキュメント一覧から、アクションメニューを開き同期を選択する方法だ。 もうひとつは、設定ポップアップのデータソース - ノーションからメニューを開き、同期を選ぶ方法だ。 ナレッジを同期するAPIも見当たらなかった。当面は手動で行うほかなさそうである。 代替案 Notionのインテグレーションには、一般的なOAuthクライアントを作成する方法もある。 クラウド版のDifyはそちらを採用している。 インテグレーションの種類として公開(public)を選択すればその方法で進められると思われるが、設定項目が多い。内部インテグレーションとして接続する方が楽であった。 --- # 自分専用DifyにFirecrawlもセルフホスト - 公開日: Fri Oct 04 2024 16:30:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, infrastructure この記事はVPSでお安く自分専用のDifyを持つ方法の続きだ。 Difyは、WebクローラーであるJina ReaderまたはFirecrawlと連携することで簡単にRAGを実現できる。 Firecrawlもオープンソースでセルフホストできる。そこでDifyをインストールしたVPSにFirecrawlもインストールし、こちらもお安く実現してみた。 その手順を紹介する。 FirecrawlのセットアップFirecrawlをgit cloneFirecrawlの.envを用意Firecrawlを起動Firecrawlを登録するDifyとFirecrawlのネットワーク接続Firecrawlの設定 Firecrawlのセットアップ Firecrawlをgit clone Firecrawlのオープンソースプロジェクトをgit cloneする。 バージョン v1.0.0 執筆時点(2024年10月4日)でmainブランチをCloneしたところ、正常に動作しなかった。そこでタグv1.0.0を使うことにする。 安定版については都度、確認されたい。 bashgit clone https://github.com/mendableai/firecrawl.git -b v1.0.0 cd firecrawl Firecrawlの.envを用意 環境変数ファイルの雛形apps/api/.env.exampleを元に.envファイルを用意する。 bashcp apps/api/.env.example .env .envを編集し、次の2点を変更する。 bashUSE_DB_AUTHENTICATION=false TEST_API_KEY=fc-my-firecrawl # 任意のキー(先頭のfc-は必須) USE_DB_AUTHENTICATIONは、詳しくわかっていないがユーザー管理をするSupabaseと連携するためのようだ。少なくとも今回は自分専用なので利用しない。 TEST_API_KEYは後ほどDifyで指定するAPIキーである。複雑なものを指定してもよいが、fc-という接頭辞は必須らしい。 Firecrawlを起動 Docker ComposeでFirecrawlを起動する。 bashdocker compose up -d --build 筆者の環境では初回のビルドに5分ほどかかった。気長に待とう。 起動が完了し、少し待ってから次のようにテストレスポンスが得られたら起動は成功である。 bashcurl -X GET http://localhost:3002/test # 出力 Hello, world! Firecrawlを登録する DifyとFirecrawlのネットワーク接続 DifyとFirecrawlは異なるDocker Composeプロジェクトで動作しているので、このままではスムーズに接続できない。 次のコマンドで両者をネットワーク接続し、DifyのapiコンテナとworkerコンテナからFirecrawlのapiコンテナをfirecrawlとして参照可能にする。 bashdocker network connect --link firecrawl-api-1:firecrawl firecrawl_backend docker-api-1 docker network connect --link firecrawl-api-1:firecrawl firecrawl_backend docker-worker-1 接続元について Difyは多くのコンテナから構成されている。筆者の予想ではapiとworkerからFirecrawlを参照できればよいと踏んで動作もしたが、もしかしたら過不足があるかもしれない。 Firecrawlの設定 DifyのWeb画面を開き、右上のメニューから設定を選び、設定画面でデータソース - Firecrawlの設定ボタンを押す。 API KeyとBase URLを指定し、保存ボタンを押す。 API Keyは先ほど.envファイルに指定したfc-から始まるキーだ。 Base URLは、http://firecrawl:3002とする。 Firecrawlがアクティブになれば連携完了である。 ウェブサイトをクロールしてベクトルデータベースにコンテンツを格納し、RAGを実現できるようになった。 --- # VPSでお安く自分専用のDifyを持つ方法 - 公開日: Fri Oct 04 2024 16:25:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, infrastructure LLMアプリ開発が楽しいDifyだが、無料だとできることが限られ、有料プランは個人にはちと高い。3アカウントも手に余る。 Dify AI · Plans and Pricing そこでオープンソース版をVPSにセルフホストしてみた。これなら月額1500円程度から、自分だけのDifyを持つことができる。 その手順を紹介したい。 下準備VPSを起動してLinuxをインストールドメイン名を用意ファイアウォールを設定GitとDockerをインストールDifyをセットアッププロジェクトをgit clone環境変数.envを用意初期パスワードとシークレットキーDifyのSSL化証明書の取得nginxを再起動して証明書を適用する管理者アカウントの登録Firecrawlのセルフホストと設定 下準備 VPSを起動してLinuxをインストール VPS事業者はどこでもよいが、ファイアウォール機能を備えたところにしよう。もちろんAmazon EC2などでも構わない。 公式の案内に、最小限のスペックとしてCPU >= 2 Core、RAM >= 4GBとある。 例えば安価なVPSであるWebARENA Indigoなら、1ヶ月税込1,630円からDifyを利用できる。 WebARENA Indigo(VPS)の料金(Linux) | VPS(仮想専用サーバー)はWebARENA Indigo まずはなんらかのVPSインスタンスを契約し、Ubuntu 24.04をインストールする。 OSについて Docker Composeを使って進めるので、OSはLinuxならなんでもよい。Ubuntu以外の場合はaptのあたりを読み替えてほしい。 ドメイン名を用意 後ほどLet's EncryptoによるSSLの設定も行うので、VPSのIPアドレスはなんらかのドメイン名で参照できるようにしておく。 自前のドメイン名がなければ、以下のようなサービスでもよい。 FreeDNS - 無料で使えるDNSサーバー VPSでデフォルトのドメイン名を提供しているケースもある。 動作確認用ではFreeDNSを使い、my-dify.mooo.comドメインを取得してみた。 FreeDNSの注意点 FreeDNSは反映に時間がかかった。host ドメイン名などで、正引きできるまでじっくり待とう。 また、sslip.ioのような加工IPアドレスを事前登録なしでドメイン名へ変換できるサービスもあるが、ドメインからのリクエストが多すぎるとしてLet's Encryptに弾かれてしまった。 ファイアウォールを設定 ファイアウォールは次のTCPポートを解放する。 22 - SSH 80 - HTTP 443 - HTTPS GitとDockerをインストール SSHでログインしてシェルから下準備を続ける。 GitとDockerを使うのでこれらを準備する。ついでにファイル編集用のエディタnanoも入れておく。 bashsudo apt install -y git docker.io docker-compose-v2 nano # ユーザーがdockerを使用できるようにする(rootで操作する場合は不要) sudo usermod -G docker ubuntu Difyをセットアップ いよいよDifyをセットアップする。 プロジェクトをgit clone Difyのオープンソースプロジェクトをgit cloneする。 Docker関連はプロジェクトのdockerディレクトリにあるので、そこに移動する。 bashgit clone https://github.com/langgenius/dify.git cd dify/docker 環境変数.envを用意 .env.exampleから.envを作成する。 bashcp .env.example .env 初期パスワードとシークレットキー 一応シークレットキーをデフォルトから変えておく。 bashecho sk-$(openssl rand -base64 42) # 出力(例) sk-CAv/mnUJ90kmw8C0KE0rGhvkNJaCVhErCkHDSS5F4UYJbXIPJnsbOD/2 .envを編集し、以下の値を変更する。 bashSECRET_KEY=作成されたシークレットキー INIT_PASSWORD=管理者初期化パスワード 管理者初期化パスワードは、管理者作成の前に入力する仮パスワードのようなものだ。 DifyのSSL化 この段階でもうDifyを起動はできるが、SSLには対応していない。 もしロードバランサーなど、SSLゲートウェイを別に用意する場合は、この工程はスキップしてもよい。 証明書の取得 DifyのDocker Composeプロジェクトには、ご丁寧にLet's Encryptのcertbotも含まれている。 詳しくは、dify/docker/certbot/README.md at main · langgenius/difyに解説があり、それを使ってSSL対応を進める。 まずは.envファイルを開いて、次の行を変更する。 bash# 以下はデフォルト値から書き換える(こうしないとnginxの設定と一致しないっぽい) NGINX_SSL_CERT_FILENAME=fullchain.pem NGINX_SSL_CERT_KEY_FILENAME=privkey.pem # ACMEチャレンジを有効にする NGINX_ENABLE_CERTBOT_CHALLENGE=true # 以下はみなさんの状況に合わせて指定する CERTBOT_DOMAIN=作成したドメイン名 # 筆者の場合は my-dify.mooo.com CERTBOT_EMAIL=あなたのメールアドレス # 筆者の場合は miyanaga@ideamans.com 変更したら、certbotも含めてDocker Composeを起動する。 bashdocker compose --profile certbot up --force-recreate -d certbotコンテナが起動したら次のコマンドでSSL証明書を取得する。 bashdocker compose exec -it certbot /bin/sh /update-cert.sh nginxを再起動して証明書を適用する 再び.envを編集し、次の行を編集する。 bashNGINX_HTTPS_ENABLED=true nginxを再起動する。 bashdocker compose --profile certbot up -d --no-deps --force-recreate nginx コンテナが再起動したらいよいよDifyにアクセスしてみよう。 https://設定したドメイン名 ログイン画面が出た! 管理者アカウントの登録 管理者アカウントの設定から最初のアカウントを作成する。 ここで先ほど設定した管理者初期化パスワードを尋ねられる。 管理者初期化パスワード これがないと起動直後は誰でも管理者アカウントを作成できてしまう無防備な状態になってしまう。 メールアドレス、ユーザ名、パスワードを入力して管理者アカウントを作成する。 管理者アカウントでログインすると… ついに自分専用のDifyが手に入った! Firecrawlのセルフホストと設定 DifyではWebクローラーと連携して簡単にRAGを実装できる。 ぜひ使いたい機能であるが、それにはJina Readerか、FirecrawlのAPIキーが必要だ。 しかしFirecrawlにはオープンソース版があり、これもセルフホストできる。 長くなったので別の記事にする。 自分専用FirecrawlでDifyを便利化するへ続く! --- # Dify - この素晴らしきLLMアプリ開発環境 - 公開日: Wed Oct 02 2024 14:20:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: ai, technology LLMアプリの開発プラットフォームDifyを使い始めた。もう楽しすぎる。 Dify.AI · 先進的なAIアプリケーションのためのイノベーションエンジン 「便利そうだけど、APIを使ってプログラミングした方が柔軟じゃないか?」とすぐに飛びつかなかったかつての自分に、「Difyはいいぞ!」と諭すつもりで、どんなことができて、どんなメリットがあるか紹介したい。 まだ触りたてなので間違っているところもあるかもしれない。間違いは容赦いただきたい。 まずはLLMモデル登録Difyはワークフローエンジン・中核の「スタジオ」ワークフローとユースケースユースケース間の使い分けワークフローとチャットフローブロックの種類ブロック間の入出力の受け渡しログや統計データコラボレーションを容易にする注釈機能作ってそのままAPI公開可能ベクトルデータベースによる外部知識「ナレッジ」Firecrawlベクトルデータベースはかなり高負荷か?外部連携を担う「ツール」カスタムツールによる自前のAPI連携既存ワークフローのツールDifyはどんなシーンに使えるか爆速プロトタイピング場合によっては本番利用もまとめ まずはLLMモデル登録 Difyは、当然であるのだが、LLMモデルを内蔵していない。ChatGPTをはじめ外部のAPIを利用する。 何はなくともまずは右上のメニューから設定を開き、モデルプロバイダーに自身の持っているAPIを登録する。 複数のモデルを登録して、同じチャットメッセージにどう反応するか比較することもできる。 Difyはワークフローエンジン・中核の「スタジオ」 Difyは本質的には、LLM特化型のワークフローエンジンという印象である。n8nやZapierに近い。 最近、n8nやZapierを触っていないのだが、今やLLMノードを備えている可能性は高いだろう。両者は近づいているかもしれない。 しかしDifyは、LLMアプリ開発に振り切ったところがある。他のワークフロー型サービスに慣れた人でも、乗り換える価値はあるだろう。 ワークフローとユースケース Difyでアプリを作成するとき、次のダイアログが表示される。 裏側ではワークフロー型プログラムであるが、以下のユースケースについては簡単にフォームで設計できるようになっている。 チャットボット ChatGPTライクなチャットUI。 テキストジェネレーター フォーム入力に対しテキストを生成。 エージェント フォーム入力に対し他のサービスを操作。 ユースケースは設定ウィザードのようなもので、ユースケースで作り始めて、途中からワークフローへ変換することもできる(その逆、ユースケースへ戻すことはできない)。 ユースケース間の使い分け テキストジェネレーターとエージェントはよく似ている。いずれも設計されたフォームをインプットとするが、アウトプットが、 テキストジェネレーターは文字通りテキスト エージェントはSlackなど他のサービスのAPIを呼び出す その違いに過ぎないようだ。 また、チャットボットとテキストジェネレーターも似ている。こちらはアウトプットが同じテキストであるが、 チャットボットは入力が自然言語テキストで繰り返し会話できる テキストジェネレーターは入力がフォームで一度の処理で終わる という違いがある。 ワークフローとチャットフロー テキストジェネレーターとエージェントはワークフローに還元すると文字通り「ワークフロー」になるが、チャットボットは「チャットフロー」という扱いになるらしい。 しかしこれらも、開始ブロックと終了ブロックの違いしか無いように見える。 ワークフロー 開始ブロックは入力フォームを設計できる 終了ブロックで終了する チャットフロー 開始ブロックは自然言語のテキストをひとつ入力する 終了ブロックではなく回答ブロックで、回答のあとは自動で開始ブロックに戻る チャットフローの開始ブロックでも入力フォームを設計できる。しかしこれはチャットボット全体のパラメータのようなもので、チャット開始前に選択する。 チャット中はあくまで自然言語によるテキストひとつのみが入力できる。 ブロックの種類 ワークフローでは次のブロックを利用できる。ツールは後述する外部のAPI群で、外部サービスの情報を参照したり、作用を及ぼすことができる。 ブロック間の入出力の受け渡し ブロック間には当然、入出力がある。最近のワークフローシステムによく見られるように、ブロックからは他の任意のブロックの出力値を参照して入力値にできる。 例えば以下では、Weekday Calculatorというブロック(ツール)はパラメータ抽出ブロックの出力値を参照しているが、他のブロックの値も参照できる。 ブロック間のつながりは入出力ではなく、実行順序に過ぎないというイメージだ。 ログや統計データ ワークフローエンジンの利点はビジュアルプログラミングだけではない。ログの記録や利用統計の集計など、自前のプログラミングだと毎回、億劫になる部分を初めから備えている。 ログ機能では会話の再現が見られる。 利用統計も充実している。特にLLMアプリには、「トークン」という単位の仕入れがある。このあたりも細かく抑えておきたいところだが、自作アプリだとつい後手に回ってしまうところだろう。 コラボレーションを容易にする注釈機能 チャットボット(チャットフロー)には注釈という機能がある。これも素晴らしい機能で、チャットボットを簡単に矯正できる。 デバッグしたり、ログを見ながら気づいた点があれば、「こういう質問がきたら、こう返して欲しい」というフィードバックを残せる。 「注釈の返信」をONにしておくと、注釈をそのまま回答にすることもできる。 LLMアプリでは、仕組みを実装するエンジニアとコンテンツを提供する人は別であるケースがほとんどだ。簡単にコラボレーションする仕組みとしてよく考えられている。 作ってそのままAPI公開可能 作成したアプリはそのままAPIとしても公開される。このあたりもエンドポイントをどこに実装するかや、HTTPとの繋ぎ込みなど、本来のロジックと別に思索を巡らせるところだ。初めからノーコードで実現できるのはありがたい。 認証方式はAPIキーである。Bearerとして、Authorizationヘッダに渡す。 APIキーはアプリごとに発行されるので、APIキー自体がアプリを一意に定める役割がある。 リファレンスによると、APIには音声処理もある。音声からの文字起こし、テキストからの音声生成、いずれも備えているようだ(どのモデルを使うかは不明)。したがってUIの音声対応もDifyのワンストップで実現できる。 ベクトルデータベースによる外部知識「ナレッジ」 LLMが備えていないニッチな専門知識を組み合わせる手法としてRAGがある。 RAG とは何ですか? - 検索拡張生成 AI の説明 - AWS RAGの実現にはまずベクトルデータベースを用意して、ニッチな専門知識を取り込む必要がある。そして回答を生成する際にはそのデータベースを適切に参照する。これらの操作もまた敷居が高い。 しかしDifyにはベクトルデータベースと、いくつかのインポート機能が初めから備わっており、非常に簡単な手順で専門知識をインストールできる。 テキストファイルのインポート テキストファイルはもちろん、PDFやWordファイル、CSVなどにも対応している。 Notionから同期 まだ試せていないが、Notionのデータベースと同期できると思われる。 ウェブサイトから同期 Firecrawlと連携し、Webサイトをクローリングして情報をインポートできる。 例えば以下は、IPAが公開しているセキュリティ関連のガイドラインPDFをいくつかベクトルデータベースに取り込んだチャットボットだ。 このくらいならLLMに含まれている知識かもしれないが、引用元のPDFが示されている。 Firecrawl 今のところ、ナレッジのWebクローラーはFirecrawlにのみ対応しているようで、別途そのAPIキーの用意も必要になる。 FirecrawlもAGPL-3.0のオープンソースなので、セルフホストもできる。 mendableai/firecrawl: 🔥 Turn entire websites into LLM-ready markdown or structured data. Scrape, crawl and extract with a single API. DifyとFirecrawlのセルフホストはまた別の記事で解説したいと考えている。 ベクトルデータベースはかなり高負荷か? 現在、Difyをセルフホスティングしていろいろと試しているが、ウェブサイトのクローリングで弊社サイトを取り込んでみたところ、サーバーのロードアベレージが100以上で張り付いてしまい、Dockerも応答せずサーバーの再起動を余儀なくされた。 ベクトルデータベースにはWeaviateが使われているようだ。 ページ数は250程度とそこまでの規模ではなく、サーバーには8GBのメモリを割り当てていた。ZIPファイルも取り込みの対象になってしまったからだろうか。詳しい原因は不明だが、運用上の懸念が残った。 外部連携を担う「ツール」 静的なナレッジ以外にも、APIによる外部サービスとの連携も充実している。Difyでは外部連携のユニットをツールと呼ぶ。 ツールにはGoogleやBindなどの検索系、Wikipedia、Slackなどのメッセージ系などさまざな用意があるが、自分のAPIサーバーも追加できる。 カスタムツールによる自前のAPI連携 自前のAPIを連携対象に加えるには、OpenAPI Swaggerによるスキーマ記述が必要になる。 一手間かかることにはなるが、スキーマが明示的であるおかげでワークフロー上でのビジュアルプログラミングが容易になり、ユーザーのラフな入力も正確にマッピングすることを担保できる。 既存ワークフローのツール すでに作成済みのワークフローも、ツールの一種として他のワークフローにインクルードできる。 しかしチャットフローはツール化できない。 Difyはどんなシーンに使えるか 爆速プロトタイピング LLMの業務利用はまだまだ手探りの状態だ。 ChatGPTのビジネス利用が進んでいると言われているが、まだまだパーソナルアシスタントとしてに過ぎない。現実の業務にどう絡めていくか、最適解が見つかるのはこれからだ。 まだ予想まかせで大きく賭けてはいけない。かといって従来の延長で未来を予測することもできない。いま必要なのは、小さく作って実際に触ってもらいフィードバックを得る、その繰り返しで手応えを積むことである。 そのような小回りを効かせた体験的PoCに、Difyによるプロトタイピングは 場合によっては本番利用も 前述の通り、Difyには「いざ自前で実装しようとすると億劫な補助的実装」が一通り揃っている。そして外部ツールとの連携も開かれている。本番環境におけるプラットフォームとしても十分、考慮に値する。 しかし実際はケースバイケースであろう。 アプリケーションへの組み込み 小規模な仕掛けであれば最終的に対象の業務アプリケーションの機能として組み込んだ方がよいこともある。 パフォーマンスとスケーラビリティ Difyには性能の上限がある。特に大規模RAGは専用のベクトルデータベースが必要になりそうだ。 依存先の増加 Dify自体が落ちると共倒れになる。クリティカルなシステムを増やすリスクを抱えるか。 2024年10月4日追記 ベクトルデータベースはweaviate以外にも選択できるようだ。 https://github.com/langgenius/dify/blob/main/docker/.env.example # Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `pgvecto-rs`, `chroma`,`opensearch`,`tidb_vector`,`oracle`,`tencent`,`elasticsearch`,`analyticdb` また、ストレージシステムも外部を利用できる。 # Supported values are `local` and `s3` and `azure-blob` and `google-storage` and `tencent-cos` and `huawei-obs` スケーラビリティの点はどうにかなりそうだ。 まとめ 組織でのLLM活用を本格的に検討する上で、Difyを使わないのは大変なロスであると言わざるを得ない。 同時にはじめて、AI失業は自分の身にも起こり得るなと思った。本当に素晴らしいソフトウェアだ。 --- # 要約「サイトスピードと幸福の心理学」 - 公開日: Wed Sep 25 2024 23:51:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, technology, research 以前こちらの記事「サイトスピードと幸福の心理学」の要約をXに載せた。サイトスピードに関する心理学的エピソード集で大変面白かった。 SpeedCurve | The psychology of site speed and human happiness 「サイトの速度と人間の幸福の心理学」The psychology of site speed and human happinesshttps://t.co/UfaSTXQIOgこの記事のトピックをまとめてみました。1.当たり前だが人間は待ち時間が嫌い。待たされるくらいなら歩かされた方がまし、というくらい。…— アイデアマンズ@フロントエンドWeb高速化 (@ideamans) February 1, 2024 引用について 以下、引用は私がXに記載した要約からで、原文ではないのでご注意を。 人間は待たされるのを極端に嫌う 当たり前だが人間は待ち時間が嫌い。待たされるくらいなら歩かされた方がまし、というくらい。 ヒューストン空港では着陸ゲートを遠くして、手荷物受け取りまで無駄に歩かせることで、荷物の到着が遅いという苦情がゼロに。 この空港でのエピソードは以前も読んだことがあった。 乗客がみんな着陸後の荷物の受け取りが遅いと言うので、遠くに着陸して乗客の方を長距離歩かせるようにしたら苦情がなくなった、という人を食った冗談のような話だ。 UIの改悪がUXを改善した興味深いエピソードだったのでよく覚えていたが、今ふたたび人間は待たされることに敏感であることを肝に銘じた。 待ち時間のとらえ方は非合理 平均的なユーザーは読み込み時間を実際より15%も遅く評価し、後で思い返すと35%も遅く評価する。 痛みの体験は、時間の長さより最後の痛みの強さを強く覚える。 つまり人間の感覚は非合理である。 待ち時間はとても主観的なものだ。例えば同じ1秒を遅いと感じる人も、そうでない人もいる。同じ人であっても、記憶そのものが曖昧だという話だ。 なので、サイトスピードとユーザー行動の法則性は、最後まで捉え所がないのかもしれない。 しかしサイトスピード改善が無駄になることは決してない。いつでも遅さには不満が生まれるのだから。 待ち時間は気をそらす 2秒を超える待ち時間で集中力を失い、生産性を損なう。 これは1968年の実験結果。テクノロジーと無関係の人間の特性。 ヤコブニールセンによると、0.1秒なら瞬時に反応していると感じ、1秒なら思考はスムーズに進むが、注意力の持続は高々10秒が限度。 短期記憶はすぐに減衰し、待たされることには無力感と苛立ちを感じる。 視覚的感覚記憶は100ms。短期記憶は10〜15秒。 サイトスピードが遅いために離脱する人は多くいる。私自身、以前はそのような人たちが「イライラしたから」と考えていた。しかし、それだけではないことに想像が及んだ。 イライラするまではいかないが、集中力が削がれてしまうのだ。 ゲームやテストではないから集中力という表現に違和感があるならば、気がそれてしまったと言ってもいいだろう。負の感情まではないが、何をしていたか忘れたり、他のことをしたくなってしまう。 待ち時間は集中力を前借りする 順序よく滞りない活動(フロー)を定期的に行う人はそうでない人より幸福度が高い。 しかしコンピューターは遅延し、中断(再起動など)することがある。 人間は作業の中断に適応できるが、それは心理的コストを要する。 中断は累積的にモチベーションを下げ、作業の再開や新しい作業への意欲を奪う。 仮に集中力を保てたとしても、中断された作業を再開するのは心理的な負荷が高い。 そこにユーザー情報の入力のような面倒なステップがきたりすると「今日はもういいかな…」と諦めてしまう。 ユーザーを待たせると、その場で離脱行動に繋がらなくとも、コンバージョンまで必要な集中力を前借りしてしまうということだ。 遅いサイトの利用にはより集中力を要する ECサイトのストレス計測実験(2011年でおそらくPCサイト)では、2Mbps?(原文では2MB)のネット環境では5Mbpsに比べ最大50%も操作に集中力を必要とした。 検索、チェックアウト、個人情報の入力に特に大きなストレスを示した。 ECサイトの利用は、業界人には朝飯前かもしれないが、普通に考えるとかなりハードルが高い。 サイトの操作に加え、購入が正しい判断か?自分は損をしないか?商品は必要に間に合うか?壊れてたら?イメージと違ったらどうしようか?…いろいろなことを心配している。つまりストレスに抗う集中力を必要とする行為だ。 そんな最中に「サイトが遅い」という違和感も現れたら、よほどの動機がないとコンバージョンまで集中力が保たないだろう。 遅いサイトは「ダサい」とまで思われる モバイルECサイトの実験でも500msの遅延を発生させた処置群は最大26%のフラストレーションを計測。 実験後、サイトの印象を尋ねると、「遅い」が最多意見。さらに「退屈」「ダサい」「紛らわしい」「イライラする」「操作しにくい」など、感覚的な悪印象まで誘発した。 なお、遅くしない対象群では「使いやすい」が最多意見。 サイトが遅いとブランド価値も毀損する。 普通の体験をするユーザー群と、わざと表示を0.5秒遅らせたユーザー群による禁断のA/Bテストだ。 上記で「遅さは主観的」と触れたが、遅いサイトには「ダサい」という印象まで生まれるのだそうだ。 デザイナーにはとんだとばっちりであるが、大変興味深い。自分が受けた「待たされるストレス」を心理的に補償するのだろうか。 離脱しても再訪してくれるのであればまだ可能性もあるが、そこでLTVを喪失すると補填し難い。わずか0.5秒が大変な明暗を分ける。 --- # OGP画像を自動生成するプログラムをOSSで公開 - 公開日: Mon Sep 23 2024 20:16:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: development, technology, automation このブログでも、はてなブログやQiitaのようにタイトルなどからOGP画像(SNSシェア用のアイキャッチ画像)を自動生成するようにした。 そのためのバナー生成システムを独自に開発し、オープンソースとして公開した。 banner-generator 既視感のあるテーマだが、自分でも作ってみたいと以前から考えていた。その紹介をしたい。 OGP画像の自動生成OGP画像自動生成のアプローチ仕様の絞り込みsharpとtext-to-svgによる実装工夫した点テキストの行数による配置の調整フォントサイズの自動調整残課題 OGP画像の自動生成 本記事をSNSでシェアいただくと、このようなOGP画像(アイキャッチ画像)が自動生成される。 画像URLのパラメータに、背景画像のURL、ブログ名、記事タイトル、メタデータを渡すとそれらをプログラムで合成する仕組みだ。 OGP画像自動生成のアプローチ このようなテンプレートライクな画像生成にはいくつかのアプローチがある。 メジャーなのはヘッドレスブラウザでHTMLとCSSをレンダリングし、スクリーンショットを撮る方法だろう。慣れたHTMLとCSSにより、ブラウザが備える高い表現力を活用できる。 例えば以下のようなライブラリで容易に開発できる。 node-html-to-image 同じく弊社がOSSで提供する、Webページの読み込みプロセスを動画化するツールでもその技術を使っている。 当初はその方向性で考えたが、このアプローチはブラウザを動かすのでとにかく重い。また、動作環境や日本語フォントのインストールが必要になるなどハードルが高い。 今回は低性能のサーバーで稼働させたかった事情もあって別のアプローチを選択した。 仕様の絞り込み 他のサイトの自動生成OGP画像を見ていて、以下の要件を満たせば自分には十分そうだと感じた。 背景画像を自由に選べる 3行のテキストを中央揃え、縦方向に均等に表示できる テキストのサイズや配置を少しカスタマイズできる これだけならヘッドレスブラウザを持ち出すまでもないと判断した。 sharpとtext-to-svgによる実装 Node.js用にsharpというグラフィックライブラリがある。sharpを使うと簡単に画像の合成ができる。 また、text-to-svgを用いると、フォントファイルからテキストのSVGデータを作成できる。 これらを組み合わせて実現することにした。 その結果が以下の画像である。 デザインのエキスパートからすると文字の配置が甘かったりするかもしれないが、素人目には違和感はない。 テキストの輪郭も綺麗に合成されており、動作も軽快で満足している。 工夫した点 テキストの行数による配置の調整 主な用途は3行テキストによるOGP画像の生成だが、ついでに1行と2行の場合のレイアウトも個別に実装した。 自分がプレゼンテーションスライドを作成するときに好んで使うレイアウトだ。 1行の場合はテキストが上下左右の中央に配置される。 2行の場合はメインタイトルとサブタイトルのような見た目になる。 フォントサイズの自動調整 記事タイトルなどを流し込むので文字数は一定ではない。フォントサイズが固定だと、テキストが小さくなりすぎたり、逆にはみ出る可能性がある。 そこでテキストの幅の最小値と最大値を指定できるようにした。その範囲を超えるとフォントサイズが自動調整される。 最小幅を50%、最大幅を90%とするとテキストの内容によって以下のようなレイアウトとなる。 残課題 今回の要件は満たしたが、まだいろいろ改良したい点はある。 改行に対応 ハッシュトークンのようなセキュリティ 上下余白を個別指定 背景画像の簡単な色味調整 MITライセンスなので、ぜひ気軽に利用あるいはフォークして改良してみていただきたい。 --- # WebアプリのためのSSLをサクッと立てる - 公開日: Fri Sep 20 2024 16:46:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: infrastructure, development VPSなどの素のサーバでWebアプリを公開するとき、SSLをどうするかという問題がある。 弊社では、Amazon Route 53でDNS運用、lego (Go言語製のLet's Encryptクライアント)で取得した証明書を用い、nginxのDockerコンテナを使った汎用的なSSLゲートウェイを立てている。 そのノウハウを共有したい。一度慣れておくとWebアプリの公開が気楽になる。 VPSが好きだSSLどうする問題GitHubリポジトリ代替案使い方Amazon Route 53を操作するIAMユーザを用意.envに認証情報nginxの設定変更Webアプリを記述起動仕組みnginxへの補助的な機能の追加手軽な定期実行1コンテナ1ロール?まとめ VPSが好きだ Webアプリを公開する方法はたくさんある。今やちょっと古い考えではあると思うが、VPSが好みだ。 安くて固定費用 月500円くらいから利用でき、急に費用が跳ね上がる心配もない。 開発言語を選ばない 当然ながら自由にアレンジできる。 ロックインされない 選択肢が多くサービス停止で慌てることもない。 サーバ構築の勉強になる 安く抑えるよう工夫すると勉強になるし、何より楽しい。 SSLどうする問題 各種マネージドサービスなら当たり前のSSLも、VPSだと自分で用意する必要がある。 いろいろ試した末に、次のやり方に落ち着いて安定運用している。 ドメイン管理 Amazon Route 53 後述のlegoがAPIを備えたDNSを必要とするようで、Route 53が最も手軽だった Let's Encryptクライアント lego Go言語製のシングルバイナリなので取り回しが楽 リバースプロキシ nginx 説明不要だがDockerを使う ログローテーション 1日1回のログローテーションを行う 次のような構成である。 mermaidgraph LR; A[エンドユーザ] -->|ポート80/443| B[Nginx]; B -->|リバースプロキシ| C[任意のWebアプリ]; B --> D[Lego]; D -->|SSL証明書取得・更新| E[Let's Encrypt]; D -->|DNS認証| F[Route 53]; GitHubリポジトリ こちらにソースコード一式を公開した。 https://github.com/ideamans/nginx-with-lego-ssl 代替案 代替案としては、https-portalもお勧めしたい。 こちらの方が手軽で任意のDNSでも利用できるが、ワイルドカード証明書(例 *.example.com)には対応していない(今もそのはず)。 以前はこちらもよく利用していた。しかしワイルドカード証明書がやはり便利なので、本記事の方法に落ち着いたという経緯がある。 使い方 Amazon Route 53を操作するIAMユーザを用意 AWSで次のポリシーを参考に、IAMユーザーを作成する。 <INSERT_YOUR_HOSTED_ZONE_ID_HERE>は対象ドメインのホストゾーンIDに置換すること。 json{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "route53:GetChange", "route53:ListHostedZonesByName", "route53:ListResourceRecordSets" ], "Resource": [ "*" ] }, { "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets" ], "Resource": [ "arn:aws:route53:::hostedzone/<INSERT_YOUR_HOSTED_ZONE_ID_HERE>" ] } ] } .envに認証情報 example.envを.envとしてコピーし、設定を記述する。必要なのは以下の4点だ。 iniLEGO_EMAIL=you@exmple.com # メールアドレスを記述 LEGO_DOMAIN=*.example.com # SSL証明書を取得するドメインを記述(ワイルドカード化) AWS_REGION=ap-northeast-1 AWS_ACCESS_KEY_ID= # IAMユーザのアクセスキーID AWS_SECRET_ACCESS_KEY= # IAMユーザのシークレットアクセスキー nginxの設定変更 nginx/default.confのドメインnginx.ideamans.comと_.ideamans.comを、利用するドメインに変更する。 ワイルドカード記号*を用いた場合、証明書のパス上では_に置換する。 例えば、ドメインはwww.example.com、証明書は*.example.comで取得した場合、 nginx server_name nginx.ideamans.com; ssl_certificate /var/lib/lego/certificates/_.ideamans.com.crt; ssl_certificate_key /var/lib/lego/certificates/_.ideamans.com.key; ↓ server_name www.example.com; ssl_certificate /var/lib/lego/certificates/_.example.com.crt; ssl_certificate_key /var/lib/lego/certificates/_.example.com.key; nginx server_name nginx.ideamans.com; ↓ server_name www.example.com; 上記のように書き換える。 Webアプリを記述 compose.ymlのappサービスを任意のWebサービスに変更する。 yaml app: # ダミーアプリケーション image: httpd:2.4 起動 docker-composeで起動する。 bashdocker compose up --build # または docker-compose up --build 初回はLet's EncryptでSSL証明書を取得するため数分かかる。 サーバにアクセスし、このように表示されればOKである。 仕組み nginxへの補助的な機能の追加 nginxに補助的な処理を追加するために、シェルスクリプトをよく利用する。 シェルスクリプトでも関数を記述できるのだが、&をつけて関数を実行すると子プロセスとして並行処理できる。 bash#!/bin/bash sidecar() { # 補助的な処理 } sidecar & # nginx本体を起動 exec nginx -g "daemon off;" この仕組みを利用して、素のnginxにSSL証明書とログローテーションの機能を追加した。 詳しくはnginx/entrypoint.shを参照されたい。 手軽な定期実行 定期処理といえばcronだが、Dockerコンテナでcronを使うのは何かと面倒だ。 そこでこの方法もよく用いる。whileによる無限ループとdate、sleepを駆使して、毎日1回の処理をスケジューリングする小技だ。 cronを使わず定期的にコマンドを実行する小技 #Bash - Qiita nginx/entrypoing.shでも利用している。 1コンテナ1ロール? Dockerはひとつのコンテナにはひとつの役割を割り当てる原則がある。 個人的にもこれには大いに賛同するので、上記のやり方はかなり横着していると言える。 しかしケースバイケースでよいと思う。SSLゲートウェイ自体がWebアプリの脇役に過ぎない。 本記事のやり方であれば、記述するファイル数やコンテナの相互関係もシンプルになってよい。 まとめ 公開したGitリポジトリをcloneして、あなたが作成したWebアプリを記述すればVPSで簡単に公開できる。 また、シェルスクリプトやDockerfileにて更なるカスタマイズも可能だ。 副産物のようなプロジェクトだが、汎用的であると思うのでお役に立てたら幸いである。 --- # Appleサイトはサードパーティタグを使っていない - 公開日: Mon Sep 16 2024 17:32:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research いろいろなサイトのフロントエンドを観察していて面白いことに気づいた。 Appleの公式サイトではいわゆるサードパーティタグが使われていないのだ。 広告の計測系はもちろん、Google Analyticsのようなアクセス解析すらない。 まるで古の個人サイトのような作りに驚いた。 Appleではサードパーティタグが使われていないアクセス解析や行動データの連携は自前かサードパーティタグの功罪サードパーティタグは重い Appleではサードパーティタグが使われていない Appleの日本語トップページを見てみよう。 Apple Developer Toolsでネットワークトラフィックを見てみると、JavaScriptはこれしか読み込まれていない。 アクセス解析や行動データの連携は自前か スクリプトをよく見ると、ac-analytics.jsやdata-relay.jsといったファイルがある。 名前から推測するに、アクセス解析や、広告の効果測定、ターゲティングなどはこれらのタグが集中的に担っているのだろう。 さらにはこれらのスクリプトは軽量にデータの吸い上げだけを担当し、解析はバックエンドでなされていると思われる。 サードパーティタグの功罪 今やWebサイトには、サードパーティタグ(サードパーティスクリプト)が欠かせないと考えられている。 非商用の個人サイトであっても、GAくらいは入っているだろう。 商用サイトともなると普通は大量のサードパーティタグを含んでいる。だからAppleのサイトがサードパーティタグを含まないことには驚いた。 サードパーティタグは重い あまり意識はされていないが、JavaScriptはとにかく重い。ページが遅い原因は実はほとんどがJavaScriptにある。 もちろん自社のリソースだろうとサードパーティだろうと、JavaScriptが重いことに変わりはない。 しかしサードパーティタグはあらゆるサイトに対応する汎用性を備えるため、高機能で重くなる傾向がある。 さらにベンダーや広告ネットワークごとにタグが提供されるから、ブラウザの負荷は大変なものになる。 サイト自体はさほど重くないのに、サードパーティタグ満載の結果重くなっているケースは多い。 したがってAppleのサイトのように自社スクリプトが集中窓口となるやり方は、サイトスピードの観点からすると非常にスマートだ。 --- # そろそろWeb画像はWebPだけでよいか - 公開日: Sun Sep 15 2024 19:13:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: image-fitness, technology そろそろWeb画像はWebPのみの配信でよいのではないか? このテーマについて賛成論と反対論をしてみたい。 賛成論対応ブラウザシェア 96.4%万能フォーマット普及率No.1の次世代画像フォーマット反対論アプリがWebPに対応しているか供給サイドは簡単に追いつかないWebPはあくまで配信用フォーマット本当に技術負債にならないかまとめ 賛成論 「Web画像はもうWebPオンリーでいいんじゃないか」 の背景には、普及率、機能性の高さ、使い勝手のよさがある。 対応ブラウザシェア 96.4% caniuseによると、 WebP対応ブラウザのシェアは世界で95.69%、日本国内で96.4% とある(2024年9月)。 画像はWebPしか用意しなくてもほぼすべてのユーザーに支障はない。 実際に大手企業や政府系のサイトでも、ときどきWebPしか用意していない画像に気づくことがある。このような画像は旧ブラウザでは画像が表示されないのだが、案外そのままになっている。 3〜4%の非対応ユーザの存在をどう考えるかにもよるが、一部それは無視する動きもあるようだ。 万能フォーマット 従来は機能性で画像フォーマットを使い分けてきたが、 WebPはWeb画像の表現的な機能をすべて備えている。 可逆圧縮 PNG・WebP 軽量な非可逆圧縮 JPEG・WebP 透過 PNG・GIF・WebP アニメーション GIF・APNG・WebP 機能面で従来フォーマットをほぼ完全に置き換えることが可能だ。 INFO SVGはベクターフォーマットなのでここでは比較対象としない。 普及率No.1の次世代画像フォーマット 次世代画像フォーマットの対抗馬はAVIFであるが、WebPよりシェアはわずかに劣る。世界で92.64%、日本国内で93.35%である(2024年9月)。 私もまだ調査が足りていないのだが、確かにAVIFはWebPよりデータは軽量になる。しかしエンコーディングにはWebPの数倍の時間がかかるという印象である。 オンデマンドでフォーマット変換したい場合、エンコードの遅さは看過できない。 ツールの充実ぶりにもWebPは一日の長がある。そのような理由から、弊社は今のところはAVIFよりWebPを推し、AVIF対応には消極的な姿勢をとっている。 INFO もちろんAVIFの方がよいという意見もあろう。この点についてもまた別の記事にしたい。 反対論 「WebPオンリーはまだ早いんじゃない?」の背景には、エコシステムへの懸念、WebPの画質性向、全振りに対するリスクがある。 アプリがWebPに対応しているか Web画像は今やブラウザのためだけではなく、アプリが配信手段になることもある。 すでにWebPは、OSやSDKのレベルでも広く対応フォーマットの一つとなっているが、アプリの実装がそれへシームレスに対応できるかは不明である。 供給サイドは簡単に追いつかない 画像データの供給する制作者やシステムの都合もある。 デザイナーなど人間が画像を用意する場合は、作業者にWebPについての知識が必要となり、ツールもそれに対応している必要がある。切り替えるには相応の準備がいる。 また、プログラムがサムネイルなどのサイズのバリエーションを自動で書き出すケースも多い。そのような仕組みの改変には長時間を要する可能性もある。 WebPはあくまで配信用フォーマット WebPは画質や汎用性を犠牲にしている面がある。そもそも 配信向けに軽量化することへ特化 している。 非可逆圧縮におけるクロマサンプリングが4:2:0固定なので境界の色味がぼやける 縦横の解像度が16,384ピクセルまで 現在のWebP向けのJPEGも画質は近いし、Web画像に16,384ピクセルも必要というケースはほとんどない。 しかし 配信特化の画像フォーマットがデータ保存の役割も兼ねること には、違和感がなかろうか。 本当に技術負債にならないか AVIFにも触れたが、次世代画像フォーマットの候補はWebPだけではない。 ここまできて将来的にブラウザのWebP対応廃止、という未来はないと思うが、どうなるかはわからない。 ある時期の画像データのオリジナルがWebPフォーマットでしか残っていないことが、技術負債になる可能性もある。 汎用性抜群のJPEGやPNGでオリジナルデータを残し、WebPはあくまで配信用の変換先に止める方がよいかもしれない。 まとめ WebPは有力であるが、デファクトスタンダードとはまだ言えないのが実情だろう。 個人的には、LPなど小規模かつ一時性の強いサイトはもうWebPオンリーでもよいと思う。 そうでないサイトについては、当面はこれまで通りJPEGやPNGで画像を用意しながら、配信用としては軽量なWebPに変換する方法が無難かもしれない。 弊社ではCloudFront向けの画像WebP変換サービスを提供している。 LightFile Proxy - AWS CloudFrontでシームレスにWebP対応・通信コスト削減 CloudFrontは画像をWebPで配信すると料金が劇的に下がる可能性がある。 --- # GitLabセルフホスティングの話 - 公開日: Wed Sep 11 2024 20:35:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: infrastructure, development, technology 弊社ではクライアントとのソースコード共有や、Wikiによるドキュメントの共有のためにGitLabをセルフホスティングしている。 GitHubがあるのにセルフホスティングするのは時代に逆行していると思われるが、動機やメリットもある。 そしてこのGitLabセルフホスティングは、いろいろ工夫をしながら実質的に月額500円程度のコストで実現している。 その辺の話をとりとめもなくしてみようと思う。GitLabの自社運用を検討している方の参考になれば嬉しい。 GitHubのデメリット - なぜGitLabセルフホスティングか?リポジトリ共有で課金される(?)非エンジニアにはハードルが高い日本語対応されていないシステム構成の全体像どうやって稼働しているか社内オンプレミスProxmox + DockerNASへの定期バックアップGitLab自身のバックアップ機構オールインワンのDockerイメージSMTP - メール送信は外部サービスGitLabのアップグレード障害復旧どうやって社外に公開しているかGitLabがサービス提供するポートnginx + lego + Route53によるSSL対応出島サーバ - リバースプロキシVPSセルフホスティングは必要か?インフラのトレーニング工夫が楽しい GitHubのデメリット - なぜGitLabセルフホスティングか? リポジトリ共有で課金される(?) もしかしたら違うかもしれないが、GitHubでは社外のコラボレーターをリポジトリに招待すると、その分の料金も課金されると読んだ。 プロジェクトによっては10人以上のユーザーにリポジトリを共有することもあり、大変な金額になる。backlogという可能性もあったが弊社にはそれでも高額だ。これがGitLabをセルフホスティングした一番の理由だった。 非エンジニアにはハードルが高い ソースコードの共有と同時にWikiによるドキュメントも共有することが多い。 GitHubは、エンジニアであればすでにアカウントを持っている可能性が高いが、共有する相手は必ずしもエンジニアではない。アカウントを開設してもらうハードルは非常に高い。 一方、GitLabであればアカウントをこちらで作成し、パスワードだけ設定してもらうような運用が可能だ。 日本語対応されていない GitHubのGUIは基本的に英語のみだが、GitLabは大部分が日本語に対応している。 日本人のクライアントにとってはGitLabの方が親しみやすい。 システム構成の全体像 とにかく安価に、それでいてできるだけ安定運用するため、次のような構成になっている。 どうやって稼働しているか 社内オンプレミス これも時代の流れに逆行するが、社内の「自称」データセンターで稼働している。 退役したPCをサーバに仕立てて、通常のネット回線を用いているのでランニングコストは実質電気代だけである。 GitLabはかなりパワフルなRuby on Rails製のアプリケーションなので、快適に使うにはそこそこのパワーが要る。 AWSではもちろん高く付くし、VPSでもそれなりの料金になってしまう。この退役PCサーバの性能は、VPSなら月2〜3万円程度のプランに匹敵する。とても安上がりに稼働している。 Proxmox + Docker もちろんベアメタルなLinuxサーバに直接インストールするような運用はしていない。 まずはPCサーバにProxmoxをインストールし、その上でコンテナとしてUbuntuを動かす。その上でDocker(Docker Compose)によりGitLabシステム群を稼働させている。 NASへの定期バックアップ Proxmoxは本当に秀逸な仮想化プラットフォームで、バックアップ機能も標準で備えている。コンテナイメージを1日4回、NASにバックアップしている。 後述するが、Proxmoxであればディスクイメージのスナップショットも簡単に撮れるので、GitLabのアプリケーションアップグレード作業も安心して進められる。 GitLab自身のバックアップ機構 なお、バックアップについてはGitLab自体も高精度なアプリケーションバックアップ機構を備えている。 OSイメージバックアップとアプリケーションバックアップ、両方あればさらに安心である。 実際、このバックアップ機能に助けられたことがある。稼働時間に応じてリソースがどんどん圧迫され、サービスが頻繁に停止してしまう問題に見舞われたことがあったのだ。最終的にGitLabをクリーンインストールし、アプリケーションバックアップからの復元作業をしたところ、事態が解決したのだ。 オールインワンのDockerイメージ GitLabにはとても素晴らしいオールインワンのDockerイメージがある。 sameersbn/gitlab GitLabは複雑なRuby on Railsアプリケーションで、セットアップの難易度が高い。昔になるが、挫折したこともある。 sameersbn/gitlabは非常によくできていて、雛形のdocker-compose.ymlにPostgresをはじめ必要なミドルウェアが記述済みとなっている。 あとは.envに環境変数によるオプションを指定するだけで、自分だけのGitLabを手にいれることができる。 SMTP - メール送信は外部サービス メール送信機能は昨今、あまりに自社運用のハードルが高い。 弊社では全サービスを通してsendgridを利用しており、GitLabでもそれを使う設定にしている。 GitLabのアップグレード 上記のDockerイメージは現在まで安定してバージョンアップにも追従してくれており、大変ありがたい。 数ヶ月に一度、アップグレードを行うようにしている。Proxmox上でOSのイメージスナップショットを取り、アップグレードができたらスナップショットを統合する。 たまにアップグレードを忘れていて、バージョンを大きく跨いだアップグレードを行って失敗(GitLabが起動しない)するケースもある。 その場合もスナップショットをロールバックすれば元通りなので安心である。 ちなみにGitLabにはアップグレードのバージョンパスについて専用のガイドがある。アップグレードに失敗するとこのツールのお世話になる。 障害復旧 社内にはProxmoxが稼働しているサーバが他にも数台ある。最悪、休眠中のPCにProxmoxをインストールすればよい。 なので、もしGitLabを稼働させているハードウェアに障害が発生しても、OSのイメージバックアップから数十分程度で復旧できる。 どうやって社外に公開しているか こんな感じでアプリケーション自体は格安に、SREはゆるゆるながら少人数への提供なのでそこそここなしている。 ここからは社外のユーザーにどうアクセスを可能にしているかの話だ。 GitLabがサービス提供するポート 次のポートを外部に公開する必要がある。 SSH (22) - Git用 HTTP (80) - HTTPSにリダイレクト HTTPS (443) - Web GUI用 nginx + lego + Route53によるSSL対応 sameersbn/gitlabが提供するサービスは素のHTTPなので、SSL化が必要だ。 まずSSL証明書だが、Let's EncryptのCLIクライアントのlegoを使っている。 legoはAWSのDNSサービスRoute53とAPI連携してドメインの所有権を証明し、SSL証明書を発行・更新してくれる。 その証明書を利用するnginxをリバースプロキシとして設置する。これは単純にDockerhubの公式nginxイメージを利用し、GitLabと同じdocker-compose.ymlに追記している。 出島サーバ - リバースプロキシVPS 先述の通り、GitLabとnginxは社内で稼働しており、これを外部にも公開するため、月500円程度の安いVPSを契約している。 そして社内のサーバからautosshでVPSに常時SSH接続し、ポート22、80、443をリバーストンネルで公開する。 このようなVPSの使い方を、長崎の出島になぞらえて「出島サーバ」と個人的には呼んでいる。 ポート22のリバーストンネルは、出島サーバ自体のSSH接続と競合する。そのため出島サーバのSSHポートを他に変更してある。 セルフホスティングは必要か? 冒頭でも書いたが、クラウドやSaaSがこれだけ充実している時代にセルフホスティングは時代に逆行していると思う。 「オープンソースだから自社運用すればほとんどタダ」と目論んでも障害発生時のコストや経済損失を考えると、毎月料金を払った方が結局はよい。 それでもセルフホスティングする意味は何か。 インフラのトレーニング クラウドがどんどん便利になっている。 弊社でも簡単なサービスはFirebaseで提供するし、止められないサービスにはAWSを用いCDKで管理している。 これは一方で、伝統的なサーバ構築スキルを使う機会が減っていることを意味する。 クラウド時代だからといって「ネットワークやサーバのことはわかりません」では技術的に脆弱と言わざるを得ない。 トレーニングの一環として、普段使うサービスの一部をセルフホスティングしてみることに価値はある。 工夫が楽しい 何より、安価にできるだけ安定運用するにはどうするか、情報収集して試行錯誤するのは楽しい。 --- # GitLabのよくある操作を半自動化するCLIツール - 公開日: Mon Sep 09 2024 16:26:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: infrastructure, development 弊社ではクライアントとのソースコードやWikiによるドキュメント共有に、自前のGitLabを使うことがある。 よくある操作を毎回GUIから行うのが手間なので、Go言語によるCLIツールを作り、せっかくなのでオープンソースにした。 セットアップ操作方法グループの作成プロジェクトの作成メンバーの招待雑記CLIツールによる自動化既存のCLIツールGo言語からのGitLab操作 セットアップ こちらから最新版をダウンロードし、 gitlab-op - GitHub /usr/local/bin/gitlab-opなどPATHの通ったディレクトリに実行権限付きで配置する。 bashsudo cp downloadeds/gitlab-op /usr/local/bin/gitlab-op sudo chmod +x /usr/local/bin/gitlab-op 続いてアクセストークンを作成する。GitLabの管理画面から、アカウントの設定画面から、 アクセストークン - パーソナルアクセストークン - 新しいトークンを追加 を選択する。 最低限必要なスコープは api、read_api、admin_modeである。 作成したアクセストークンを控え、ホームディレクトリ(/Users/miyanagaなど)の下に.gitlab-op/credentialsファイルを作成する。 ini[default] url=https://<gitlabのURL>/ token=<作成したアクセストークン> これでセットアップが完了だ。 操作方法 グループの作成 new-groupサブコマンドにグループのパスと名称を渡すとグループを作成できる。 bashgitlab-op new-group client-a クライアントA社 グループのパスは階層にも対応している(例 clients/client-a)。 プロジェクトの作成 new-projectサブコマンドにプロジェクトのパスと名称を渡すとプロジェクトを作成できる。 bashgitlab-op new-project client-a/project-alpha プロジェクトα メンバーの招待 inviteサブコマンドにグループのパスとメールアドレス(複数可)を渡すと、グループへユーザーを招待する。 メールアドレスに対応するユーザーがまだ存在しない場合は、便宜的なユーザー名を付与してユーザーを作成する。 bashgitlab-op invite client-a メールアドレス1 メールアドレス2 ... 雑記 CLIツールによる自動化 グループやプロジェクトの追加は頻繁にあるわけではない。しかしGUIからの操作は時間がかかり、集中力を削がれる。CLIで操作できるようにしたのは正解だった。 特にメンバーの招待は手間が多く、数が多いと面倒だ。この機能は大変重宝している。 既存のCLIツール オフィシャルの glab (GitHubのghコマンドに相当)をはじめ、GitLab操作用のCLIツールもいくつか存在する。 それらをシェルスクリプトで組み合わせて実現することも検討した。しかし、そこまで使い勝手のよいCLIツールがなかったので自作に至った。 例えばGitLabの内部では、プロジェクトやグループには人間にわかりやすいslug(ディレクトリ風のパス)の他に、数値によるIDが割り当てられている。 操作によって必要なパラメータがslugまたは数値IDとまちまちだったりするので、シンプルなシェルスクリプトによるワークフローで実装できるものではなかった。 Go言語からのGitLab操作 モジュール github.com/xanzy/go-gitlab を利用した。 go-gitlab このSDKライブラリはAPIの網羅性が非常に高く、APIとも素直に対応しているため、理解しやすい。大変使いやすかった。しかも頻繁にキャッチアップもされている模様だ。 おかげでGo言語によりSDKをスクリプティングし、シングルバイナリにするという負荷の低いアプローチを実現できた。 --- # ページの読み込みを動画化するOSSツールを公開 - 公開日: Mon Sep 09 2024 06:17:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, automation Webページの読み込み過程を動画化し、比較できるツールをオープンソースで公開した。 感覚的にページスピードを比較したい基本的な使い方必要なソフトウェアインストールページ読み込み過程の動画化比較動画の生成動画のカスタマイズ開発の経緯 感覚的にページスピードを比較したい Webページのスピードの評価にはLCP、Speed Indexなどさまざまな指標があるが、専門家以外には直観的ではない。 端末でページを読み込んで感覚で比較したいところだが、それでは曖昧な記憶を頼った比較になってしまう。 そこで開発したのが、ページの読み込み過程を簡単に動画にするツール loadshow である。 loadshow - GitHub もちろんロードショー(roadshow)をもじっている。 ライセンスはコア技術となるpuppeteerに倣い、Apache 2.0とした。気兼ねなく利用、改変、ならびにコントリビューションをいただきたい。 基本的な使い方 必要なソフトウェア 以下のソフトウェアが事前に必要となる。 Node.js 20以上 Google Chrome ffmpeg MacOS、Windows、Linux(Ubuntu 22.04)で簡単に動作確認した。 インストール npmなどでloadshowをインストールする。 bashnpm i -g loadshow ページ読み込み過程の動画化 次のコマンドで、米アップル社と米マイクロソフト社のトップページを動画化したファイルapple.com.mp4とmicrosoft.com.mp4を作成できる。 bashloadshow record https://apple.com/ ./apple.com.mp4 loadshow record https://www.microsoft.com/en-US/ ./microsoft.com.mp4 ファイル形式 上記の動画は記事での表示を想定し、mp4からwebpに変換してある。 比較動画の生成 他のツールでも可能だが、loadshowには動画を左右に並べて比較動画を作成する機能も実装してある。 bashloadshow juxtapose -o ./compare.mp4 ./apple.com.mp4 ./microsoft.com.mp4 これにより冒頭で紹介した比較動画 compare.mp4 を生成できる。 動画のカスタマイズ 動画は細かくカスタマイズできるようにしてある。 本サイトでも今後、解説を増やしていきたいが、詳しくはREADMEをご覧いただきたい。 loadshow - GitHub 開発の経緯 弊社ではフロントエンドのページスピード改善を支援している。 改善案のエビデンスとしてlighthouseによる各指標の計測を行うが、専門家以外の人にも成果を直観的に把握いただけるよう、社内的にこのような動画化ツールを開発していた。 作り込みが続いて中身がごちゃごちゃしてきたので、リメイクがてらオープンソース化した。 ページスピード改善への関心を高めるきっかけになれば幸いである。 --- # サイトスピードの最も重要な指標とは? - 公開日: Thu Jul 11 2024 18:32:00 GMT+0900 (Japan Standard Time) - 著者: miyanaga - カテゴリー: sitespeed, development, technology, research この記事では、サイトスピードに関する指標のうち、どの指標が売上に最も影響するか? という疑問に対する事例とアクセス解析の過程を解説する。 サイトスピードを改善したいがどの指標にフォーカスして良いか迷っている方や、サイトスピード改善の費用対効果に不安のある方にぜひお読みいただきたい。 サイトスピードの指標重要指標はサイトによって違うアパレルECサイトの実例伝統的なOnLoadは強いOnLoadが20%改善されると売上は22%向上の見込み各スピード指標の詳細OnLoad - 全リソース読み込みスピード専用アクセス解析 Speed is MoneyLCP - 最大要素の描画FID - 初回操作に対する応答CLS - レイアウト変化の累積TTFB - ドキュメントの受信開始FCP - レイアウト変化の累積 サイトスピードの指標 一言にサイトスピードといっても多くの指標がある。ざっと挙げるだけでも、 LCP Largest Contentful Paint / 最大要素の描画 INP Interaction to Next Paint / 操作から応答の描画 CLS Cumulative Layout Shift / レイアウト変化の累積 FID First Input Delay / 初回操作に対する応答 TTFB Time To First Byte / ドキュメントの受信開始 FCP First Contentful Paint / 最初の要素の描画 OnLoad / OL On Load / 全リソースの読み込み という具合だ。ここではスピード指標と総称する。 CLSはスピードか? CLSは厳密には時間の指標ではないが、Core Web Vitalsの文脈からここでは言及する。 重要指標はサイトによって違う これらの指標のうち、どれが最も重要か? 一概に特定することは残念ながらできない。サイトによって異なる。 物体の移動スピードにはひとつの明確な指標があるが、サイトスピードはスピードと言ってもユーザーのストレスという曖昧な対象を複数の観点から定量的に計測する試みである。 そのサイトに感じられやすいストレスがどの指標に折り込まれて現れるか、その改善にどのくらい売上増のポテンシャルがあるかはサイトによる。個別にアクセス解析によって確かめるしかない。 また、スピード指標と収益性には関係はあるが、ストレスの度合いを介した擬似相関に過ぎず、直接の因果関係はない。この点も指標が過度に目的化しないよう、あらかじめ理解したい。 アパレルECサイトの実例 伝統的なOnLoadは強い 実際のとあるアパレルECサイトにおいて、スピード指標とCVR(成約率)の関係を計測した結果を以下に挙げる。 指標が良好だったセッション群について、全体の平均CVRに対するCVRの倍率を示す。 指標 平均CVR 指標が良好なセッションのCVR CVR向上比 LCP 0.70% 1.72% 246% FID 0.86% ※相関なし ※相関なし CLS 0.75% 1.90% 253% TTFB 0.81% 1.41% 174% FCP 0.78% 1.70% 218% OnLoad 0.80% 2.52% 315% このサイトにおいては、伝統的な OnLoad がもっとも収益性との関係が強かった。 LCP、CLS、FCPにも強い関係が見られた。特にOnLoadの良好なセッション群では、全体平均の3倍以上のCVRを示した。 OnLoadが20%改善されると売上は22%向上の見込み 詳しくは後述するが、もしその指標が一律20%改善したら、注文数がどのくらい増えるかも試算した。 指標 平均CVR 指標が良好なセッションのCVR 20%改善による注文増見込み LCP 0.70% 1.72% 117.18% FID 0.86% ※相関なし 103.82% CLS 0.75% 1.90% 118.42% TTFB 0.81% 1.41% 104.94% FCP 0.78% 1.70% 116.18% OnLoad 0.80% 2.52% 122.36% もしOnLoadを一律20%改善できれば、注文数が22%も増加する見込みとなった。 したがってこのサイトは、これらの指標が改善するようにユーザーのストレスを軽減することで売上を増やすことができる。また、仮に20%高速化できる企画があって、その予算を22%以内の売上増でペイするのであれば実施すべきという意思決定が可能だ。 指標によって平均CVRが違うのは? 指標によって平均CVRが異なるのは、必ずしも全ユーザーから漏れなく指標を取得できるわけではないことに起因する。運良くその指標を計測できたサンプルにおける平均CVRなのでズレが生じてしまう。 各スピード指標の詳細 上記の根拠となったデータをもう少し具体的に紹介する。 OnLoad - 全リソース読み込み OnLoadによるセッション分布とCVR 平均OnLoad値によるセッションの分布(グレーのヒストグラム)と、そのセッション群におけるCVR(オレンジの折れ線グラフ)である。 セッションは訪問から注文までの単位だが、計測期間でのリピートは少ないため、ほぼユーザーと置き換えて考えてもよい。 このグラフから以下のことが読み取れる。 セッションによってOnLoadの平均値にはばらつきがある 快適なユーザーは2秒にも満たないが、16秒以上のユーザーも5%ほどいる ユーザーごとの端末性能やネットワーク経路は千差万別なので差が生じる 平均OnLoadが0.61秒から1.63秒のときにCVRが最大化し2.52%に達する 平均OnLoadが悪化するほどCVRも低下していき、約7秒以降は3%ほどでほぼ横ばいになる OnLoadが良好なユーザー群は平均に対し3倍、OnLoadが悪いユーザー群に対して10倍近いCVRを示しており、両者が関係していることは明らかである。 早過ぎても良くないのか? 0.0秒から0.81秒ではCVRが0%となっている。OnLoadが早過ぎてもCVRが低いように見えるが、これは生存バイアスである。 注文に達するためには一定数のPVが必要だが、PV数が増えると平均OnLoadは極端に低い値を取りにくくなる。一方、直帰ユーザーはその1回の記録が平均値となる。つまりグラフの最も左の階級には直帰ユーザーだけが集まってしまう。 したがって早過ぎてもCVRが低いということはなく、早ければ早いほどよい。 OnLoadの改善による注文増加予測 ここで平均OnLoadとCVRの関係を暫定的な計算モデルとし、もしOnLoadを20%改善できていたらどうなったかを考える。 それが以下のグラフである。緑のグラフが20%改善によるセッション度数の変化を示す。 緑のヒストグラムは、グレーのヒストグラムよりも左側に寄った形だ。つまりCVRの高い階級のセッションが増えるため、全体としてCVRが上昇する。 それを計算すると 122.36% の注文増加見込みとなる。 もちろんこれは一例に過ぎないが、サイトスピード改善の試みは「結果はやってみなければわからない」という博打になりがちであった。それに比べると参考に値する見込みである。 スピード専用アクセス解析 Speed is Money この記事のデータだが、弊社が提供するスピード特化型のアクセス解析サービス Speed is Money で計測したものである。 Speed is Money - 無料のスピード専用アクセス解析 これまで述べたようにスピード指標の収益性に対するポテンシャルはサイトによって異なる。月間100万サンプルまで無償で利用できるのでぜひお試しいただきたい。 LCP - 最大要素の描画 他の指標についても見てみよう。 LCPによるセッション分布とCVR OnLoadに近い特徴を示している。 LCPの改善による注文増加予測 LCPを20%改善できると注文は 117.18% 増加する見込みである。 FID - 初回操作に対する応答 FIDによるセッション分布とCVR FIDはサイト全体においてほぼ良好であるため、参考になるデータが収集できなかった。紹介は省略する。 CLS - レイアウト変化の累積 CLSによるセッション分布とCVR 本サイトはCLSに対策済みであり、実装上はレイアウトの動的変化はほぼないようになっている。値としても全体的に良好であった。 それでもCLSとCVRの間に関連が見られるのは、表示自体の早さを介した擬似的な相関と予想している。 CLSの改善による注文増加予測 CLSとCVRの関係は微妙だが、数値上は20%改善できると注文は 118.42% 増加する見込みとなった。 TTFB - ドキュメントの受信開始 TTFBはWebページのリクエストからHTMLドキュメントの受信が始まるまでの時間だが、主にサーバーサイドの処理時間が大きく影響する。 TTFBによるセッション分布とCVR OnLoadやLCPに比べると関係は強くない。 TTFBの改善による注文増加予測 TTFBを20%改善できると注文は 104.94% 増加する見込みである。 FCP - レイアウト変化の累積 FCPによるセッション分布とCVR FCPもCVRとの関係が強く見られる。 FCPの改善による注文増加予測 FCPを20%改善できると注文は 116.18% 増加する見込みである。 ---