こんにちは。Cyber Agentさんが開催しているWeb Speed Hackathon 2025に初めて参加させていただいたので、その感想とかもろもろを書き連ねます。
Web Speed Hackathonとは「予め準備してあるWebアプリケーションのパフォーマンスを改善することで競い合うハッカソン」1であり、めちゃくちゃ重たいWebアプリケーションのチューニングをし、どこまで軽くできるかを競うイベントです。スコアは各ページのLighthouseのスコアと決められたフローのスコアを合計し計算します2。今回の題材は「架空の動画配信サービス」3ということで自分にとっては比較的馴染みの深い題材でした。
参加時のリポジトリ
初めての参加でしたが順調にスコアが上がっていき、最終的にスコア単体では1位だったもののレギュレーション違反により失格になってしまいました。というかそもそもレギュレーションチェックを受けた上位陣はほとんど失格し残った一人の方が新たな1位になるという… もう少しで1位になれた悔しさを供養するためにもなにをしたかを振り返っていきます。
やったこと
時系列順でコミットログを抜粋します
1日目
webpack-bundle-analyzerを使用してファイルを見てみると、100MB以上あったのでいらないところを削っていく事から始めていきました。
- e133138
lodash
を置き換え - b2a6bad iconを全て読み込んでいたため、
@iconify/react
に変更 - 86438d8 画像のpreloadとHydrationDataを削除、SSRからSPAへ
- 6743232 JPEGのサムネ画像をAVIFに置き換え
- f71a019
p-min-delay
の削除 - 4be745d SVGのロゴもAVIFに置き換え
- ea780db 番組表のチャンネルロゴをPNGに置き換え
SVGにfont情報がbase64で埋め込まれていて既存ツールでの変換ができなかったため、Playwrightでスクショを撮るスクリプトを作成。 - 445eca0 適当にimg要素に
loading="lazy"
を追加 - 592b3fc 不必要なpolyfillを削除
今回の公式デプロイ環境はherokuでしたが、privateにできないなど色々問題がありそうだったので、去年と同様の Koyebで始めました。
[15:25] 計測 58.25 / 1200.00
- 962a3d7 番組表の説明画像をpublicに移動し、AVIFに置き換え
- 7d21b0d 404ページのGIFを圧縮
- 1a4c4b9 ReDOSを修正
- 47a0fb6 WebpackからViteへ移行
Webpackでのチャンク分割がうまくできず、ビルドも遅かったためわかりやすいViteに移行。rollup-plugin-visualizer
も導入し簡単にバンドル分析ができるように - 5b5b201
hls.js
とvideo.js
を削除しshaka-player
に統一 全部を試してみて、一番バンドルサイズが小さくなったshaka-player
を採用。 - fbc5816
Dialog
でHeadless UIの全てをimportしていたのを修正 - 391430d シークプレビューの画像を生成済みのものに変更。ffmpegとかを削除 これでバンドルからwasmも消せてサイズ大幅削減に。
[18:20] 計測 52.95 / 1200.00
- 71e4dd7
react-use
を置き換え
[19:24] 計測 53.50 / 1200.00
- 8ae02d6 DB初期化時のサムネイルURLからランダムデータであるversionを削除
ここで乱数が狂い、「表示されるコンテンツがオリジナルのものと異な」4ってしまう現象が発生 - b9198cc
createPlayer
のdynamic importを修正
ここから乱数のズレによりテストの対象idが存在しなくなってしまうせいか、視聴ページなどのスコアで上振れするため嘘スコアになります。
[20:21] 計測 461.50 / 1200.00 (嘘)
本人はまさかの暫定2位に驚いていました。嘘スコアなのは気づいていません。
??? "Web Speed Hackathon 2025" に挑戦中です!
— kasi (@kq5y__) 2025年3月22日
スコア 461.50 / 1200.00 で、現在 2 位です https://t.co/t4CXqwwp99 #WebSpeedHackathon
- 8a37616 各要素のdescriptionが全て5000文字ほどあるが、表示するのは200文字程度なので、いらないdescriptionは消し必要なのは短縮することで、レコメンドAPIのレスポンスを小さく
- 6128282 404ページのレコメンドAPIを更に小さく
- 5f9dccb
Hoverable
をCSSに置き換え - a0b65be シーク処理を置き換えてポインター関連を削除 <- ここで悲劇
- a5e7214
Ellipsis
をline-clamp
に置き換え - 9197f7d
AspectRatio
をCSSに置き換え
[00:29] 計測 381.60 / 1200.00 (嘘)
ここらへんでログインモーダルがおかしくなっていてフォーカスすると全てが固まるため、hydrateRootの引数などを修正してみたりしています。
[01:18] 計測 25.85 / 1200.00 (嘘)
NO_FCP
が大量発生し何故かスコアが一気に低迷しました。おそらくhydrateRootとか起因?
[01:38] 計測 27.05 / 1200.00 (嘘)
ここでKoyebが遅いのかと思い、Cloudflare Tunnelを用いて自身のPCから配信する形に変更
[01:56] 計測 23.55 / 1200.00 (嘘)
[03:10] 計測 434.00 / 1200.00 (嘘)
- 1baa8a6
aspect-video
を追加
[04:04] 計測 451.70 / 1200.00 (嘘)
- 4e0ac9b
@sinclair/typebox
,@sinclair/typemap
を削除 - 222f0ee
@iconify/react
をSVGへ置き換え
生成済みのサイトから一つ一つコピペしていきました。
[05:07] 計測 459.25 / 1200.00 (嘘)
眠くなったのでベッドイン。UnoCSSのドキュメント5を見ながら就寝。1日目の稼働時間は03/22 10:30から03/23 05:30でした。
2日目
顔を洗って、アセットを複数種類作るのとUnoCSSの調整をした後、APIの調整とフローを通すことを目標に2日目開始。
[10:35] 計測 458.75 / 1200.00 (嘘)
[11:49] 計測 595.65 / 1200.00 (嘘)
フローを成功させるために、aria-labelをつけてみたり6いろいろしてみた。
[13:46] 計測 239.65 / 1200.00 (ここから本当)
おまけにここからフローで得点がつくようになった
[14:18] 計測 376.22 / 1200.00
[14:43] 計測 391.86 / 1200.00
何回か試しても番組表のフローでPROTOCOL_TIMEOUT
7が発生。調べてみても謎だ…
[15:11] 計測 380.08 / 1200.00
- 6e54597 polyfillのsetimmediate削除 (悲劇の弊害)
[15:24] 計測 319.23 / 1200.00
回線速度などでランダム性が出るため9、ここからKoyebに戻る
[15:50] 計測 413.86 / 1200.00
残り2時間を切ったのでレギュレーションチェックを実施。テストケースの大丈夫でなさそうなところを手元で確認する。
そこで1時間延長のお知らせ10。ラッキーと思い各ページの調整を行う。
- 3e47488 episodeページの調整。画面下部のレコメンドAPIのリクエストをページ読み込み後にしたり、コンポネント分割、その他CSSの調整。
- 9eab063 404ページの調整。
useLoaderData
を使ったり - 708edc7 homeページの調整。上と同じく。
- e2d6557 episode, programページの調整。上と同様にprefetchやコンポネントの調整
- a3c48de seriesページの調整。上と同じく。
[17:06] 計測 460.67 / 1200.00
- 8dc6256 次の番組を取得するAPI(
/api/programs/:programId/next
)を作成。programページで都度timelineを取得していたためそれを置き換え。 - 7bc4a81 番組の詳細を取得するAPI(
/api/programs/:programId/detail
)を作成。番組表ページの読み込み時に呼ばれるAPIのサイズを小さくし、dialogを開く際に追加で情報を取得する。 - 4fb56b1 その他のAPIにおいても、descriptionがとても長いため最初の300文字を抜粋する処理を適応。
[18:22] 計測 728.49 / 1200.00
最後の/retry
、まさかの暫定1位に心臓バクバクです。
レギュ落ちしなければー "Web Speed Hackathon 2025" に挑戦中です!
— kasi (@kq5y__) March 23, 2025
スコア 728.49 / 1200.00 で、現在 1 位です https://t.co/t4CXqwwp99 #WebSpeedHackathon
2日目の稼働時間は03/23 08:30から03/23 18:30で、食事や休憩を含めた総稼働時間は27時間でした。
結果
結果は最初に書いた通り、レギュレーション違反で脱落になりました。違反の内容は「シークサムネイルが表示されず、ポインターに追従しない」4であり、実際に確認してみるとサムネが0sの状態のまま動かない不具合がありました。ここでClineに頼り切ってしまい眠いのもあってreviewしないまま変更を acceptしてしまったことが原因という。最後にルール一覧を見ながらレギュレーション違反を確認したときも、ここはできていたから大丈夫だろうという風に考え、その箇所の確認を飛ばしてしまいました。(意味がない)
revertすればすぐ動く内容であり、しかも逃したものが1位のため山のように押し寄せてくる悔しいの感情。
リーダーボード
やりたかったこと
まだLate Submissionができないっぽいので(できるようになりました)やりたかったこともついでに書いておきます。
- luxuonなど置き換え可能なライブラリがあったのでその削減
- zodなどのバリデーション系はよくわかっておらず手を付けなかったが、型情報のみしか関与していない場所を削除したかった。
- GIFファイルをWebMとかにしてみる
- 番組表のフローを成功させたかった(
未だ原因不明11) - setIntervalとかあえて複雑な処理をしているところを修正しきれていない
- 番組表のリサイズ処理が重いのがそのまま
- カルーセルをCSSに置き換え(
scroll-snap-type
)
特に、main.jsの大半をzodとluxonが締めているのでここを消せれば更に良くなりそう
あと解説の放送を聞いていると、CSSで色々できるみたいで最近のCSSはすごいなと
他にも、公式でVRTが用意されていたのですが、その画像がdarwinのものだったり、WSL環境だと失敗したり、GitHub Actionにうまく搭載できなかったりで活用できなかったので次回以降は活用したいですね。
まとめ
DevToolsのPerformanceやLighthouseの使い方など、たくさんのことを学べてとても楽しく参加できました。スタッフの方には感謝いっぱいです。来年こそリベンジします。
あとClineも他の人の書いたコードなので、ちゃんとPRみたくreviewして内容を把握しましょう。
追記(2025/04/25)
Late Submissionができるようになったので、スコアを計測してきました。 なお、デプロイ環境がHeroku+Cloudflare Proxyになっています。
まずは大会本番中の最終サブミットと同一コードでスコアを確認します。なお、システムの都合で大会本番は計測できていなかった11部分を除いた点数がカッコ内です。
計測 589.71(586.71) / 1200.00
環境の変化がおそらく要因なのですが何故か結構下がりましたね。ついでにCloudflare Tunnelで試してみてもあまり変わりませんでした。ジャッジ側でなにか変化があったのか、それともKoyebがそんなに良かったのか…
次にミスを見つけていれば大会本番中に達成できた点数を確認します。
計測 580.42(572.42) / 1200.00
んーしかしそれでも1位の人は500超えてないので…
ここからは大会期間中にできなかった改善をします。
- 923e5cd 1431a2e
SeekThumbnail
をusePointer
を使わないで実装。要は悲劇の正規実装 - d83c0dd アニメーションGIFをWebMに置き換え
- c9fff0f schedulePluginを削除
- d39660a cacheの追加
計測 583.18(579.43) / 1200.00
まだまだ改善できるところはあるので時間ができたら取り組んでみますが、今回は一旦ここまで。やっぱり悔しいですがリベンジを夢見て頑張ろうと思います。
付録
他の参加者さんの記事


今回参考にした過去イベントの参加記



脚注
詳しくはdocs/scoring.md ↩
名前はArema、どこかで聞いたことあるような… ↩
効果は不明 https://qiita.com/thithi7110/items/b42025e78cb64455ba9b ↩
ここで何故か発生 https://github.com/CyberAgentHack/web-speed-hackathon-2025-scoring-tool/actions/runs/14016035455/job/39241821905#step:7:78 ↩
やり方があっているかわからないし、効果も軽微 ↩
ここで家族がドラマを見始めたため帯域を圧迫 ↩
Heroku 関係でたくさん不具合が起きていたらしく、それ以外を選択して正解だった。 ↩