【ITニュース解説】How We Optimized ScribeToAny: From 3.5s Cloudflare Cold Starts to a 95+ Lighthouse Score
2026年09月08日に「Dev.to」が公開したITニュース「How We Optimized ScribeToAny: From 3.5s Cloudflare Cold Starts to a 95+ Lighthouse Score」について初心者にもわかりやすく解説しています。
ITニュース概要
ScribeToAnyはCloudflare Workersの起動遅延により初期表示に3.5秒かかっていた。大きなプログラムの読み込みが原因だ。エッジHTMLキャッシング、JSバンドル分割、遅延ロードでプログラムを最適化し、初期表示を45ms未満に短縮、Lighthouseスコア95を達成した。
ITニュース解説
ScribeToAnyは、音声や動画を文字に起こすことができるオンラインサービスだ。このサービスは、最新のウェブ技術であるReact 19を使って作られ、Cloudflare Workersという技術を使って「エッジ」と呼ばれるユーザーに近い場所に展開されている。重たい処理、例えばAIを使った文字起こしや翻訳などは、Modalという別のサービスで並行して実行される。一方で、ウェブサイトの表示、ユーザー認証、データベースへの問い合わせ、そしてサーバー側でページを生成する処理(SSR)はすべてCloudflare Workersで動いている。
当初、ScribeToAnyの裏側にある重い文字起こし処理は速かったものの、ウェブサイトの利用者から見たときの性能には大きな問題があった。実際のユーザーの利用状況を調べると、最初のデータがブラウザに届くまでの時間(TTFB)が、多くのユーザーで3秒以上もかかっていた。特に、初めてサイトを訪れた人が遭遇する「コールドスタート」と呼ばれる現象では、ウェブページの表示に3.4秒から3.6秒もかかっていた。これは、ユーザー体験にとって非常に悪い影響を与え、検索エンジンの評価にも響く問題だった。
不思議なことに、Cloudflare Workersが実際にプログラムを実行するのにかかるCPU時間はわずか5ミリ秒だった。たった5ミリ秒で終わるはずの処理が、なぜユーザーには3.5秒もかかってしまうのだろうか。この疑問を解明し、サイトの速度を大幅に改善するまでの詳しい経緯を説明する。
この「プログラムは速いが、ユーザーへの応答は遅い」という矛盾の理由は、Cloudflare WorkersのCPU時間の計測方法にあった。Cloudflare Workersが計測するのは、実際にプログラムが動き始めてからの時間だけだ。プログラムが動き出す前の準備にかかる時間、例えばプログラムのファイルをダウンロードしたり、JavaScriptコードをコンピューターが理解できる形にコンパイルしたりする時間は含まれていなかった。
ScribeToAnyでは、この準備時間がボトルネックとなっていた。主な原因は二つある。一つは、プログラムのファイルサイズが約11MBと非常に大きかったことだ。ScribeToAnyには80種類以上の文字起こしや変換ツール、さらにはブログ記事を表示するための機能など、多くの機能が詰め込まれていたため、一つの大きなファイルになってしまっていた。もう一つは、初期のトラフィック、つまりサイトへの訪問者が少なかったことだ。訪問者が少ないと、Cloudflareのサーバーは、使われていないプログラムの実行環境(V8アイソレートと呼ばれる)を頻繁に終了させてしまう。
そのため、Google検索などから来るほとんどの新規訪問者は、毎回新しく「コールドスタート」の状態の実行環境にアクセスすることになった。つまり、わずか5ミリ秒で処理を始める前に、V8というJavaScriptエンジンが11MBもの大きなファイルを読み込んでコンパイルするという、約3秒間の待機時間が発生していたのだ。これはユーザーがサイトを初めて開いた時の体験を台無しにし、検索エンジンの順位にも悪影響を与えていた。
さらに、スマートフォンなどのモバイル端末では、「トータルブロッキングタイム(TBT)」という、ページがユーザー操作を受け付けなくなる時間が長かった。これは、サードパーティ製の認証スクリプトや、必要以上に大きなプログラムの塊が、ページの読み込み中にウェブブラウザのメイン処理を占有してしまっていたためだ。
これらの問題を解決するために、私たちは四つの段階にわたる最適化戦略を実行した。
第一層:エッジHTMLキャッシュと無停止更新
Cloudflare Workersは、動的に生成されるサーバー側レンダリング(SSR)のページを自動的にはキャッシュしない。そのため、サイトへのリクエストが来るたびにWorkerが動き、コールドスタートのコンパイルが発生していた。ScribeToAnyのマーケティングページやブログ、多くのツールページは、誰が見ても同じ内容が表示される公開ページだ。そこで、ユーザーに最も近いCloudflareのエッジサーバーでHTMLをキャッシュする仕組みを導入した。
このエッジキャッシュを動的なウェブサイトで使う際には、いくつかの厳格なルールを設ける必要がある。例えば、ユーザーがログインしている場合は、キャッシュを使わず常に最新のパーソナライズされたページを表示するようにした。また、キャッシュの対象は、トップページや料金ページ、ツールページ、ブログページなど、匿名でアクセスできる公開ページのみに限定した。管理画面やAPIなどの動的なページは決してキャッシュしない。さらに、キャッシュに保存するのは、HTTPステータスが200(成功)、コンテンツタイプがHTMLで、クッキーを設定しないレスポンスのみとした。これにより、安全にキャッシュを利用できるようになった。
しかし、Cloudflare Workersのキャッシュ機能には大きな課題があった。新しいプログラムをデプロイしても、古いキャッシュが自動的に削除されないのだ。このままでは、新しいブログ記事を公開したり、バグを修正したりしても、何日も古いHTMLがユーザーに表示され続けてしまう。
この問題を解決するために、私たちはプログラムのビルド時に生成される一意の識別子をキャッシュキーに含めることにした。新しいプログラムがデプロイされると、このビルド識別子が変わる。すると、古いビルド識別子で保存されたキャッシュは瞬時に無効になり、新しいリリースがすぐにキャッシュに保存され始める。これにより、古いコンテンツがユーザーに表示される心配なく、長期間キャッシュを保存できるようになり、エッジサーバーからの応答時間は45ミリ秒未満にまで短縮された。コールドスタートの問題は、ほとんどの匿名ユーザーにとって完全に解消されたのだ。
第二層:メインバンドルの分割と依存関係の最適化
HTMLを速く表示できるようになったとしても、ブラウザが最適化されていない大量のJavaScriptファイルを解析するのに時間がかかってしまっては、ユーザーはサイトをすぐに操作できない。この問題を解決するために、JavaScriptの最適化を行った。
まず、サイト全体の設定ファイルに、ウェブサイトのメタ情報やナビゲーションメニュー、多言語の料金表、80種類以上のツールの名前などがすべて含まれていた。そのため、どのページを訪れても、ユーザーは利用しない80種類のツールの情報までダウンロードさせられていた。そこで、ツールのメタ情報や料金計算のロジックを別々のファイルに分離し、それぞれのツールページを訪れたときだけ必要な情報が読み込まれるように変更した。
次に、ウェブサイトのビルドツールであるViteが、ReactやTanStack Query、Zodといった主要なライブラリを一つの大きなファイルにまとめてしまっていた問題を解決した。設定を変更することで、これらの共有されるライブラリをそれぞれ個別のファイル(ベンダーチャンクと呼ぶ)に分割するようにした。これにより、ブラウザが一度ダウンロードしたライブラリをより長くキャッシュできるようになり、一部のコードを変更しても他のライブラリを再ダウンロードする必要がなくなったため、効率が大幅に向上した。
さらに、Radix TooltipProviderという、ツールチップを表示するための機能がアプリケーション全体を覆う形で組み込まれていたため、ログインしていない一般のページでも不要なツールチップの初期化処理が実行されていた。これを、実際にツールチップが使われるログイン後のダッシュボードや編集画面にのみ限定してロードするように変更した。同様に、Markdown形式の文章を表示するためのCSSスタイルも、ブログやドキュメントページでのみ読み込まれるようにし、マーケティングページでの不要なCSSの読み込みをなくした。
第三層:画面外要素の遅延読み込みとインタラクションの高速化
特にモバイル端末では、ページが読み込まれた後にJavaScriptがHTML要素と連携して動くようになる「ハイドレーション」の処理がCPUに大きな負担をかけ、ユーザーがページを操作できなくなる時間が長かった。
この問題を解決するため、私たちはウェブページを二つの部分に分けた。「Above-the-Fold」と呼ばれる、スクロールせずに最初に見える範囲の要素(ヒーローセクションなど)は、同期的に読み込み、サーバー側でレンダリングされると同時に、すぐにユーザーが操作できるようにした。一方、「Below-the-Fold」と呼ばれる、スクロールしないと見えない範囲の要素(機能紹介セクションなど)は、React.lazy()とSuspenseを使って遅延して読み込むようにした。
この遅延読み込みによって、ページが部分的に表示された後で、後から読み込まれた要素によってレイアウトがガタガタと崩れる「Cumulative Layout Shift(CLS)」という現象を防ぐ必要があった。そこで、Suspenseのフォールバック機能を使って、遅延読み込みされるコンポーネントが表示されるまで、そのコンポーネントが占める予定の高さに合わせた最小限の高さを持つ仮の領域(スケルトン)を表示するようにした。これにより、CLSを完全にゼロに抑えつつ、初期のJavaScriptの読み込み量を40%以上削減することに成功した。
また、モバイル端末でのトータルブロッキングタイムの主な原因の一つであったGoogle One Tapという認証機能のスクリプトについても対策を講じた。以前は、このGoogle Identity Servicesのスクリプトが、Reactの初期ハイドレーション処理中に実行され、iframeを起動したりネットワークチェックを行ったりしていたため、ユーザーがページを操作しようとしている間にメインスレッドがブロックされていた。そこで、このGoogle One Tapの処理を、ウェブブラウザがアイドル状態になったときに実行されるように遅延させた。具体的には、requestIdleCallbackという機能を使うか、それが使えない環境では数秒後に実行されるように設定し、さらにユーザーがページを触り始めたときにも実行されるようにした。これにより、初期のページ表示時にメインスレッドが一切ブロックされなくなり、ユーザーはすぐにページを操作できるようになった。
第四層:LCPとアクセシビリティの改善
ウェブサイトの最初のデータが届くまでの時間(TTFB)と、ページが操作できるようになるまでの時間(ハイドレーション)の問題が解決した後、視覚的な表示タイミングやウェブサイトの品質評価(Lighthouseスコア)をさらに向上させるための細かな調整を行った。
まず、ウェブサイトの見た目を決定するCSSファイルが、ページのレンダリングをブロックするのを防ぐために、重要なCSSファイルを事前に読み込むための設定を追加した。次に、サイトの主要な見出し(H1)が以前は少し遅れてフェードインするアニメーションが設定されていたため、これを削除した。これにより、ページが描画された瞬間に、Google Lighthouseが「Largest Contentful Paint(LCP)」、つまり画面上で最も大きなコンテンツが表示されるまでの時間をより早く記録できるようになり、評価が向上した。
さらに、ウェブサイトのアクセシビリティも改善した。ボタンのグループ表示におけるARIA属性の修正、意味のあるaria-labelの追加、主要なボタンの色のコントラスト比を上げることで、誰もが使いやすいウェブサイトにするための国際的な基準(WCAG AA)を満たした。また、「詳しくはこちら」のような曖昧なリンクテキストを、「セキュリティについて学ぶ」のように具体的にリンク先の内容がわかるように変更し、検索エンジンのクローラビリティも向上させた。
結果と教訓
これら四つの段階にわたる最適化戦略を展開した後、私たちはGoogle PageSpeed InsightsでScribeToAnyの性能を評価した。
デスクトップ端末での評価は、以前のパフォーマンススコアが約68だったのに対し、パフォーマンス95点、アクセシビリティ100点、ベストプラクティス96点、SEO100点と大幅に向上し、ほぼ完璧なスコアを達成した。
モバイル端末では、4G回線の速度制限やモバイルCPUの制約をシミュレートした環境で、パフォーマンススコアが以前の約42点から78点へと大きく改善した。アクセシビリティとSEOのスコアはデスクトップと同様に100点だった。最初のデータが届くまでの時間(P75 Edge TTFB)は、以前の3,128ミリ秒以上(57%が「悪い」評価)から50ミリ秒未満(エッジキャッシュヒット)へと劇的に改善した。コールドスタート時のWorkerのTTFBは、匿名ユーザーにとっては完全に回避されるようになった。レイアウトの崩れ(CLS)も0.03から0.00へと完全に解消された。
この経験から、Cloudflare Workersのようなエッジ環境で最新のReactフレームワークを使ったアプリケーションを構築する上での重要な教訓を学ぶことができた。
一つ目は、「プログラムの処理は速いのに、実行環境の準備が遅い」という落とし穴に注意することだ。たとえWorkerがわずか数ミリ秒で処理を終えても、プログラムのファイルサイズが大きすぎると、ユーザーはJavaScriptエンジンのコンパイルを待つことになり、結果として遅延が発生する。
二つ目は、サーバー側でページをレンダリングする(SSR)場合、エッジでのキャッシュが必須だということだ。静的なウェブサイトジェネレーターを使わなくても、Cloudflareのキャッシュ機能とビルド識別子を使った無効化戦略を組み合わせることで、静的なウェブサイトと同等の高速な体験を提供できる。
三つ目は、キャッシュキーにはビルドタグを含めて、常に新鮮なコンテンツを保証する自動的な無効化戦略を導入すべきだということだ。これにより、新しいバージョンをデプロイしても古いコンテンツが配信される心配がなくなる。
四つ目は、ページの上部にあるコンテンツは同期的にレンダリングし、画面外のコンテンツは適切な高さの仮の領域を置きながら遅延して読み込むことだ。そして、Google Identity Servicesのような重いサードパーティスクリプトは、ブラウザがアイドル状態になったときに実行されるように遅延させることが重要だ。
速度は、ウェブサイトにとって重要な機能である。エッジコンピューティングのアーキテクチャと、ブラウザがページをどのように実行するかという優先順位を合わせることで、ScribeToAnyは世界中のユーザーに瞬時に応答する体験を提供できるようになった。