今年も、株式会社サイバーエージェントさん主催の Web Speed Hackathon 2025 に参加させていただきました!
結果はタイトルにないので察してください。
試合中にログを残した参加記です。殴り書きです。
下に行くにつれてどんどん雑になっていきます。ご了承ください。
参加までの経緯
去年も一昨年も参加していたので、今年も参加します。
当日まで
Maximum からでも何人か参加するっぽいので、みんなで練習してました。
- saitamau-maximum/web-speed-hackathon-2023-scoring-tool-for-2025
- saitamau-maximum/web-speed-hackathon-2024-scoring-tool-for-2025
を作っておきました。
Sor4chi が作問に関わっているため、サークル内の WSH 練習関連ほぼすべてを丸投げされました。
対策リスト
3/20 やることリストを作っておきました。
- 準備
- ルールをチェックする
- VRT ありますか
- ラスト 1 時間はちゃんとレギュレーションに従ってるかチェックしよう
- 開いておく
- 2023 の記事: https://a01sa01to.com/articles/2023/03/wsh2023/
- 2024 の記事: https://a01sa01to.com/articles/2024/03/wsh2024/
- ReDoS Checker: https://devina.io/redos-checker
- ルールをチェックする
- 設定
- Vite に置換して
rollup-plugin-visualizer
を導入する - dev → prod にする
- target を最新版のみにする:
chrome134
- Docker ビルド設定速くできない?
- Vite に置換して
- 見る
- 実際に開いてみて DevTools のネットワークでどこがボトルネックになってるか見る
- 特に preload
- HTTP/2 or 3 になってるか確認
- 対応させるのはめんどくさそうなので、できるなら Cloudflare に丸投げするのはありかも
- 圧縮が有効になってるか確認
- Hono Compress を使ったり 2024 で出てきた
zlib
を使ったりする
- Hono Compress を使ったり 2024 で出てきた
- 実際に開いてみて DevTools のネットワークでどこがボトルネックになってるか見る
- 開発
- polyfill 消し + ライブラリ減らす
- めんどくさいのはとりあえず後回し!
- Tree-shake
* as
で検索する
- ReDoS
mail
,pass
とかで検索
- 全 package.json を目 grep する
- canvas なくす
- キャッシュ
- ETag も使えるとよさそう
- DB に index 貼る
- CSS-in-JS やめたい
- Link タグ使ってますか
- polyfill 消し + ライブラリ減らす
- 動画系 (今回は動画サイトらしいので)
- Stream
- Hono Streaming Helper: https://hono.dev/docs/helpers/streaming
- HLS (HTTP Live Streaming)
- https://qiita.com/mt877/items/7a52b230584b79b6541b
- Chrome は未対応なので video-dev/hls.jsを使う
- FFmpeg を使う
winget install ffmpeg
- 使い方
- いったん後回しにして WebM 変換はありかも
ffmpeg -i input.mp4 output.webm
- 必要に応じて解像度下げる
ffmpeg -i input.mp4 -vf scale=-1:720 output.mp4
-> 720p
- Stream
- サムネイル
- avif 化 + loading=lazy
react-lazy-load-image-component
も使える
- avif 化 + loading=lazy
- 大事
- ちゃんと食べよう!
- ちゃんと寝よう!
ほとんどが去年の使いまわしです。
全部できるかわからんけど参考にはなりそう。
Sor4chi が Hono Contributor なのでちょっと意識しました。
(後日談: Sor4chi が作問に参加した時にはすでに Fastify に決まっていたそうです)
あと去年と同じ感じで「内容を明らかに推察できる状態で配信してはならない」とかあるんじゃないの?
そうなったらグレイコードで対応する: Commit e9821c5 a01sa01to/wsh2024-practice-2025
上の実装だと 32bit にファイルサイズ入れているが 4GiB 超えると困りそう。さすがにスペック的にそんなことしないか。
どうせ最初 video タグで全部埋め込みだろうし
最初にやることをメモしておく。
Fork したら Private Repo に移すために以下を実行する
git clone https://github.com/a01sa01to/web-speed-hackathon-2025.git cd web-speed-hackathon-2025 git remote set-url origin https://github.com/a01sa01to/wsh2025.git git push
その他
3/17-19: 京都に旅行に行ってました。たのしかった~
つまり対策は全然していません
3/21: 頭痛で 1 日ダウンしていました。やばい。
開幕
Sor4chi 9:09 「まだ完成していません」
!?
去年 Zoom 待合室みたいなのあったけど、今年はありませんでした。
30 分前に入場して一番乗りしたけど、さすがに早すぎたので 5 分前に再入場しました。
オフライン参加勢の状況により 10:10 からオープニングらしいです。
緊張で心拍数めっちゃ上がっていて Fitbit に心配されました。
レギュレーション、VRT あるらしいです。
全 1200 点満点なのか、 4 桁行きたいな~
ページの操作 200 点満点で 200 点以上、満点取らなきゃいけないのか そんなことある???
後の訂正で、ページの表示 900 点満点中 200 点以上らしいです。
いざ本番です。ぞい!
Web Speed Hackathon 2025、ぞい!!
(以下、対応するコミットのリンクを載せておきます。コミットタイトルは雑です)
Day 1
初回デプロイ
[10:29]
どうせ CyberAgentHack/web-speed-hackathon-2025 だろ!と思い、 10:26 くらいからリロードしまくり。
10:29 に公開されたので、早速 Fork & Clone した。
Deploy しようとしたが Heroku なのか... Heroku アカウント消しちゃったよ...
運営の Heroku を使う手もあったが、 Public にしたくなかったので自分のアカウント作り直してデプロイ。
10:45 デプロイ完了。
67.00 点。
コード読み
[10:45]
えー、 Webpack じゃん
package.json を目 grep すると、ご丁寧に analyzer が入っていることがわかるので、 plugin に追加。
wireit が遅すぎて困る、最初はこんなもんなのか?
サーバー側 Hono じゃなかった。草
遅すぎて何もできないので、まず svg たちを最適化する。
svgoptimizer でやろうとするもエラーで困ったので Figma でやった。
が、フォントが効いていなくて困ったので svgo で最適化してみるも、何も変わらず。
そんなこんなで 1h 経過。
Adobe Illustrator でやるといけそうな気がしたので、インストールして使ってみることに。
でも結果使えなかった。 Adobe のサブスクだけが残った。
polyfill 消し
[11:48]
イラレのインストール中に polyfill 消し。
polyfills.ts のすべてがいらなさそうだったので、消しました。
ReDoS
[12:06]
困ったので、どうせ ReDoS あるだろと思い mail で検索すると、はいありました。
/^([A-Z0-9_+-]+\.?)*[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i.test(email)
ローカル部が .
で終わってないかをチェックすれば、あとは /^[A-Z0-9_+-.]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i.test(email)
でよさそうです。
パスワードもありました。/^([A-Z0-9_+-]|[A-Z0-9_+-]){3,}$/i.test(password)
これは /^[A-Z0-9_+-]{3,}$/i.test(password)
でよさそう。
icon 系
[12:17]
いつものです。
今回 tailwind でわかりづらい。
よくわからんけど、 node_modules
内にある json ファイルを必要なものだけ取り出せばいいんじゃない?
ということでやってみるとよさそう。非常に面倒。
途中 wireit が止まらない問題に遭遇し、いろいろやってみると BundleAnalyzer が終了していないからっぽい?ので修正。
Commit 4f40be6 a01sa01to/web-speed-hackathon-2025preload
[12:50]
仮で作っておいた anime copy.svg
が読み込まれているので、どっかで ls
して preload に追加されていそうな気がします。
はい、ありました。消します。
DB index
[12:55]
DB に index をいろいろ張ります。
今回は migration があっていいですね。
サークルで作っている Maximum IdP( saitamau-maximum/id ) で Drizzle をいじっているので、ちょっと参考に見に行きました。
index 張ったはいいけど、あまり効果はなかったかも?
Commit fa3017f a01sa01to/web-speed-hackathon-2025FFmpeg
[12:17] (時間軸がずれます)
FFmpeg が bundle に含まれているのはさすがによくないので、消したいです。
[13:11]
取り掛かります。
ffmpeg の使用箇所を見ると、コマンドをそのままやればいい気がします。
そのままやったら jpeg が得られたので、これでいいのかな?
このコミットには mp4 が含まれてしまってデプロイ時にエラーが出てきたので、 mp4 を消すコミット: Commit fa977ed a01sa01to/web-speed-hackathon-2025
ところで Webpack のコンパイルが 5 分とかかかって遅すぎて困るんですが...
Version 消し
[13:35]
各サムネイル画像を見てみると、 version が付与されています。
しかしこれは使われていなそうなので、消しておきます。
ついでに server 側の Cache-Control
を変更しておきました。
SVG
[13:48]
やっぱり重すぎるのでどうにかしたいです。
Figma でフォントを入れてあーだこーだしてみることにします。
位置がずれる。絶対正攻法じゃない気がする。
とりあえずサブセット化して Woff2 にして asset においてみることに。
完了。右往左往していたので 1.5h かかりました。
Commit 1f4fdb7 a01sa01to/web-speed-hackathon-2025いったん SSR やめる
[15:22]
全然使いこなせていないので、 SSR をやめました。
gif 軽量化
[15:25]
Not found の gif が重すぎるので、どうにかしたいです。
適当なサイトで軽量化しました。 半分くらいになりました。
このへんで一応計測しておきます。
42.65 点。下がった。
SVG その 2
[15:32]
やっぱり SVG がうまく表示されません。
ここに時間かけるのもったいないと思ったので、いったん Revert しておきます。
Commit d73ceeb a01sa01to/web-speed-hackathon-2025ホームをよくする
[15:55]
初期表示の API リクエストがとんでもなく重いので、どうにかしたさがあります。どうにかしようとしたら何も表示されなくなってしまって困った
Client 側を再ビルドしたら直った
[16:22]
ほかの部分もどうにかしたいね
何気なくコードを見てみると、 useScrollSnap
とかあります。
2024 で見覚えのあるやつです。
直しました。minmax
も CSS でうまく書き換えられそうな気はしていますが、なんかうまくいかなかったのでそのままにしています。
ビルドしながら、これユーザー回り以外の DB 必要ないんじゃない?とか思い始めます。
DB を更新する処理がユーザー認証以外で存在しないので、すべてを JSON 化して static として置いてもいいんじゃない?ということですね
どうせ時間が足りなさそうだけど
Cloudflare Pages に static を置く
[17:02]
Cloudflare Pages の準備だけしておきます。/public
にあるものを全部 Cloudflare でホストするようにしました。
feature-explain.png
[17:16]
さすがにこれを bundle に含めるのはよくないだろ!/public/assets
に移動しておきました。
ついでに avif に変換 + リサイズをすることで、 92% 削減しました。
Unocss 消し
[17:29]
たぶん Tailwind を使っているので、 CSS の削減 phase が走ってると思うんですよね
なので classname を使わずに inline style props に徐々に移行していってみます。
(ちょっとネタバレすると、 style props にすることでかなり苦痛を味わうことになります)
ホームをよくする 2
[18:19]
データを DB からではなく、あらかじめ取得しておいた JSON を見るようにします。
[18:22]
すべてを inline style にします。
Copilot に tailwind から inline の style props に変換して
といえば一発で変換してくれてありがたかった。
Hoverable とかもやっておきたい気はするが、 :hover
を扱う関係上どう書き換えようかで悩む。
[19:13 くらい]
いったんサムネイルを avif にしておく
さすがに手作業でやるのは時間がかかってしまうので、適当に optimizer を作って対応。
いくつかのサイズ違いも作っておきました。
19:40 作業終了
あ、そういえば画像 lazy にしてないじゃん
使用用途を見て、適宜 eager にもしておきます。
19:51 done
Cloudflare まかせ
[19:51]
動画の stream を Cloudflare に任せることにした。
[19:56]
サムネイルも任せた!
[20:06]
ロゴも任せた!
video 系
[20:11]
video 系どうにかしたいよなぁ
これ全部同じようなことしてるよね? HLS だけにしてみる。
[20:18]
なんか画像周りでエラー出ていたので修正new URL
で相対パスだったせいでエラーが出てしまったので、それも修正
[20:21]
さて、ここで計測してみましょう。
ついでに VRT も回しておきます。
すると全然ダメ見たいです。
diff を見た感じスタイルが適用されてないときに撮られているようだったので、いったんスキップします。
[20:37]
レイアウト全体で tailwind 使ってないか見てみるため、 createRoutes を見てみると、なんか lazy に minimumDelay オプションついているんですが
え、お前 react.lazy
じゃないのか
そして inline style に変更。
Commit d980212 a01sa01to/web-speed-hackathon-2025[20:46]
夕食。そういえば昼食食べてなかったや。
21:31 帰還。
lodash
[21:35]
そういや lodash 使うのやめたいな...
調べてみると、 lodash/merge
とかやると軽量になるらしい。
最適解ではないけど、いったん書き換えに困っていたのでそうしてみます。
Home CLS
[21:42]
ほぼすぐに表示してくれるようになったものの、 CLS が高めなのでどうにかしたいところ。
まあまず icon を直で置くところからかな
22:21 完了。
[22:25]
次に AspectRatio かな、これは別に JS 使わなくても CSS だけでよさそう。
[22:34]
画像とかこれどうすればいいんだ やっぱり minmax
の部分を CSS で実装できないとつらそう
だが代替案がないので、いったん飛ばすことに。
Not Found
[22:35]
NotFound をどうにかしたい気持ちになったので、まずは API を保存する。
処理を追ってみると、どうやら最初のデータ 1 つだけでいいみたいなのでそれだけ保存。
次に style inline 化。
Commit 9e55131 a01sa01to/web-speed-hackathon-2025最後に Cloudflare から gif 読み出し。
Commit 87c33bc a01sa01to/web-speed-hackathon-2025[22:50]
うーんつらいなぁ
どうせサムネイルとか動画とかは 16:9 だし AspectRatio 設定するのありな気がする。
これで NotFound 部分割といい感じになった気がする。 (とはいえ若干 CLS があるが)
このへんでいったん計測。
98.85 点。そこまで伸びず。困った。
番組表
[22:59]
次は番組表かな。
重い SVG はいったん無視で。
~はみがき~
[23:11]
チャンネルロゴ、全部 280x60 だから width, height 指定したほうがよさそうだな
あとは適当に inline style とかに変換していく。
episode の n+1 とかも解消。
[24:01]
Hoverable をやったら今日は終わりかな。
ひとまず useId
を使ってみたところ、 :r0:
みたいな形で CSS には使えなかったので適当に値を入れてみた。
が今度は unique ではなくなってしまったので useId.replaceAll(":", "_")
をして got kotonaki.
00:59 いったん完了。
[24:59]
うーんやっぱり compress してから終ろうかな
とおもって compress 入れてみたが、うまくいかなかった...
測るだけ測って寝るか
[25:35] 寝
Day 2
番組表続き
[05:43] 適当につけたアラームで起きた
ん、なんか channelService の fetch で batch とか書いてる
適当に引き上げてみるか
(どうやらこの部分は maxBatchSize
を引き上げるのではなく windowMs
を引き下げるのが正解だったらしい)
というか channel 埋め込んだらそんな処理すらいらないじゃん
Commit 16dfd21 a01sa01to/web-speed-hackathon-2025エピソードページとか
[06:03]
エピソードページをやってみるか
まず inline style に
[06:27]
なんか HLSjs、 xhr してない?
まあデフォルト設定にしてみるか
[06:28]
なんか recommended の部分、 1 個しか使ってない部分は limit つけてよさそうだよな
[06:42]
必要最小限のデータに抑えたい
...抑えたいなぁと思いつつどこも変更しなかったみたい (後日追記)
[07:11]
recommendation の limit が効いていなかったので修正
ついでに JSON 化したものを Cloudflare に任せた!
Program まわり
[07:40]
次は Program まわりかな
inline style して
[07:58]
次何すればいいんだ
いったん channelId を指定しての timetable 取得かな
[08:20]
さすがに放送開始まで画面更新はつらいよなぁ
interval を使うようにするか
が、うまくいかないので Revert した。
Revert しても遷移時に「この配信は終了しました」って出るな、これ元々が悪い?
Unocss 完全消し
[08:31]
そろそろ tailwind 完全撤廃したくなってきたな
09:34 done
計測に入る。
ユーザーフローが出てきた。ということは 200 点超えてるっぽい。ありがとー!
とはいえユーザーフロー全落ち。なんでや!
216.70 点。ギリギリ。
[09:34]
いったん VRT まわしてみる。
めっちゃずれてる。
よく見てみると、 SeeriesEpisodeItem の再生アイコンに position: absolute
をつけ忘れていた。
Bundle 削減 (そこまでできず)
[10:00]
Bundle 削減したいね
drizzle が client にあるの意味わからんな
ご飯食べながらこれを検証してみる。
Revert しても遷移時に「この配信は終了しました」って出るな、これ元々が悪い?
お ssssssssssss っそい
がどうやら自分が悪そう。
[10:37]
luxon -> dayjs に置換していきます。
timezone まわりよくわからんが、どうせ全部 tz にしておけば dayjs 側でよしなにやってくるだろ!
(ここでちゃんと考えないせいでレギュ落ちしました、ちゃんと考えましょう)
X-AREMA-INTERNAL
[11:59]
dayjs 置換と修正 done
「配信終了」はよくわからんな
よくわからん過ぎるので計測してみる。
ん、 stream 内に randombytes(3 * 1024 * 1024)
あるな、 X-AREMA-INTERNAL
とか書いてあるしいらなそう
番組表のロゴ
[12:52]
よくわからん過ぎるので放置。 もしレギュ落ちしたら、え?最初からでしたよ?とか言ってみるか
いい加減ロゴどうにかしたい。
SVG to AVIF にでもしてみるか?
いったん PNG に変換してみることに。
https://amamamaou.github.io/svg2png/このページだと (たぶん) canvas で実行してるからフォント問題もうまく解決してくれそうだったので、試すと案の定うまくいった。たすかる~
ん、もしかして AVIF って背景透明とかにできないの? 困るなぁ PNG のままでいいや (特に調べてないだけでできなくはなさそう)
Commit 639c4d1 a01sa01to/web-speed-hackathon-2025Flipper とかの修正
[13:17]
なんか番組表の height scroll がいないっぽい。
どうやら Flipper には style を持たせられない?まじか
style タグで埋め込んでみる。
ついでにレギュレーションチェックもしていく。
Revert しても遷移時に「この配信は終了しました」って出るな、これ元々が悪い?
これよくわからんけどなおったな、何だったんだ
Commit 749a311 a01sa01to/web-speed-hackathon-2025 Commit 40af124 a01sa01to/web-speed-hackathon-2025 Commit 309a16f a01sa01to/web-speed-hackathon-2025style タグから CSS へ
[14:00]
たぶん良さそうなので CSS-in-JS やめたいなぁと思うなど
というか ReactRouter どうにか改善できないかなぁと思ってドキュメントを見てみると、 SSR のやり方が違うっっぽい?
いったん SSR っぽいの実装してみた。
が、そこまで早くはならなそうなので、一旦保留。
[14:30]
CSS-in-JS をやめる方向にシフト。
間に合うのか?と思っているときに 15:45 ありがたい連絡。 1h 延長らしいです。
どうせミスってるけど 17:34 done です。
3h もやってたのか...
tailwind をなんで途中で inline style にしちゃったかなぁ... 反省
CSS に書き換えたら、 CSS を Cloudflare に乗せます。
Commit 5047708 a01sa01to/web-speed-hackathon-2025最後の足掻き
[17:41]
デプロイ完了したところで計測します。
ラスト 1h も切っているので、 VRT チェックとかもしておきます。
258.00 点。まずまずの結果。
すべてをキャッシュして Cloudflare で提供とかありだなぁ
そんなことやる暇はない!
試しに SSR を有効化してみる。
Commit 938bb1b a01sa01to/web-speed-hackathon-2025VRT の結果でなんかずれていそうだったので修正。ほかの部分も変わっていたら困るけどそんなことはないと信じて。
Commit d8392f6 a01sa01to/web-speed-hackathon-2025最後のあがきとして、 root.css に全部の CSS を minify して集約しました。
Commit d225bfd a01sa01to/web-speed-hackathon-2025[18:17]
Last Sub! 頼んだ!
お祈りしながら VRT をまわしてみる。
たぶん読み込み途中 + フォントのせいかな~という diff があったが、それ以外は特に問題なさそう?
316.90 点。うーんまあまあ
解説タイムとか
#WebSpeedHackathon 2025 お疲れさまでした~むずすぎ!
すべてを Cloudflare に乗せられるな...? とか思ったけど時間がない!
解説聞きながら全然解析せずにここだろ!でやっていたので反省。
毎年 Web Speed Hackathon とか ISUCON とかでもこれしちゃってるので Sor4chi に計測しようねと言われてます。善処します。
そういえば bundle 改善しようと思ったのにしてなかったや。
あ、 scroll-padding
じゃなくてふつうの padding
つかっていた!まずい!
結果発表
総合順位第 3, 2 位が該当者なしとかいう異例な展開に、ワンチャンあるか...?と思っていたが、結果はレギュ落ち!かなしい
#WebSpeedHackathon レギュ落ち!タイムゾーンまわりの処理よく考えずやっちゃったな~
番組表の番組の編成内容がオリジナルと異なります (オリジナルの編成より9時間遅く表示されています)
とのこと。絶対 Timezone のせいじゃん...
tz +09:00 を HH:mm:ss でリクエストする -> utc として解釈して +09:00 にまた直す とかしちゃってそうだな
その後 Sor4chi が「これ落としたの俺です」とか言ってきました。
ちゃんと公平に採点してくれてありがとう 😡
みんなレギュチェックされてなくてレギュ落ち慰め会をやる相手がいなかったので、研究室顔合わせで慰めてもらいました (はい?)
まとめ?
レギュ落ちには気を付けましょう。
対策リストの振り返りもしておきます。
- ✅: 使った、役立った
- 🤔: 微妙
- ❌: 使わなかった、役立たなかった
- (-): 該当なし
です。
- 準備
- ルールをチェックする
- VRT ありますか ✅
- ラスト 1 時間はちゃんとレギュレーションに従ってるかチェックしよう 🤔
- 開いておく
- 2023 の記事: https://a01sa01to.com/articles/2023/03/wsh2023/❌
- 2024 の記事: https://a01sa01to.com/articles/2024/03/wsh2024/❌
- ReDoS Checker: https://devina.io/redos-checker✅
- ルールをチェックする
- 設定
- Vite に置換して
rollup-plugin-visualizer
を導入する ❌ - dev → prod にする ✅
- target を最新版のみにする:
chrome134
❌ - Docker ビルド設定速くできない? ❌
- Vite に置換して
- 見る
- 実際に開いてみて DevTools のネットワークでどこがボトルネックになってるか見る 🤔
- HTTP/2 or 3 になってるか確認 ❌
- 圧縮が有効になってるか確認 ❌
- 開発
- polyfill 消し + ライブラリ減らす 🤔
- Tree-shake 🤔
- ReDoS ✅
- 全 package.json を目 grep する ✅
- canvas なくす (-)
- キャッシュ 🤔
- DB に index 貼る 🤔
- CSS-in-JS やめたい 🤔
- Link タグ使ってますか (-)
- 動画系
- Stream
- Hono Streaming Helper: https://hono.dev/docs/helpers/streaming❌
- HLS (HTTP Live Streaming) ✅
- いったん後回しにして WebM 変換はありかも ❌
- 必要に応じて解像度下げる ❌
- Stream
- サムネイル
- avif 化 + loading=lazy ✅
react-lazy-load-image-component
も使える ❌
- avif 化 + loading=lazy ✅
- 大事
- ちゃんと食べよう! ❌
- ちゃんと寝よう! ❌
毎回変わるから結局準備しても役立てづらいんですよね。うーむ。
しかも今回そこまでしっかりは食べてないししっかり寝てもない。パフォーマンスが出せないのは当然。反省。
scoring tool が公開されたら感想戦もしたいですね。
特に DevTools の Performance タブは全然見ていないので、それも役立てられればなと。
今回はギリ 300 に載ったことでレギュチェック内ビリで、しかもレギュ落ちしてしまうとかいう不甲斐ない結果だったので、次回はもっと良い成績を残せるように頑張りたいです。