このブログは新歓ブログリレー2026のブログではありません。
はじめに
2026年3月20日~3月21日にかけて CyberAgent が主催する Web Speed Hackathon 2026 が開催されました。ざっくり言うとめちゃめちゃ重いWebサイトを渡されて、それをいかに速くできるかを競う大会です。詳しいことが知りたい人はコンテストのページとか過去の参加記とかを読んでみると面白いと思います。

結果
負けました。

でも言い訳をすると、本当にちょっとの変更で落ちてて、かなり惜しかったです。苦しい。
これで1位が失われたと考えると、本当に悔しい これを糧に再発防止策を考えるのが人間です pic.twitter.com/Gqxd0aGrvn
— しーぴー 🍀 (@__cp20__) March 21, 2026
やったこと
リポジトリはこれです。運営が用意した fly.io にデプロイしてたので、全ての情報がここに入ってます。
ゆるっと時系列順に並んでますが、やや入れ替えてるところもあります。
最初
とりあえず最初は重すぎてアプリを開くのもままならない感じなので、デグレ同行とかをあんまり考えずにガシガシ変更を入れてました。
- バンドラーの設定いじる
- サーバーの配信設定がデチューニングされてるの直す
- AIに適当に調べさせたところで簡単に直せそうなところをAIに投げる
バンドラー移行する
将来的にバンドラー移行したいと思ってたので、変なパッケージを削る作業もしてました。
- ALT周りの操作をサーバーサイドに寄せる (画像は常にwebpで扱うように)
- bm25/kuromoji による検索周りもサーバーサイドに寄せる
- 動画・音声周りの ffmpeg もサーバーサイドに寄せる
その上で webpack → vite の移行をしました。AI に書かせたらスッとやってくれたので、楽ちんでした。前にやった時は tsup → vite があんまり上手く行かなかったのでドキドキしてたんですが、今回は全然詰まらなくて良かったです。
重そうな箇所を適当に直す
適当にページを見て Lighthouse 回して重いところ探したり、bundle analyzer 見て重いパッケージ消したりしてました。どこを直すかは自分で決めて、改善はほとんどAIにやらせてました。
- 嘘 infinite scroll を直す
- negaposi analyzer 周りをサーバーサイドに寄せる
- moment を Temporal / Intl に置き換える (Temporal が PlayWright で動かなかったので後で Date にした)
- LLM 周りのパッケージを lazy loading
- lodash 消す (実はこの時点では redux が依存しててまだいる)
- 翻訳周りを lazy loading
- Tailwind CDN やめる (Vite Plugin に)
- AspectRatioBox やめる
- 画像に loading=lazy つける
E2E回しつつ、改善する
ここら辺までくると割とまともに E2E が回るようになってくるので、回してました。VRT は expected を上手く作れないので全然機能してなかった気がしますが、機能面でのテストはいくつか本当のバグを発見してくれました。
具体的には検索でそもそも 500 エラーで何も返ってこなかったり、なんか件数が少なかったり、エラー文言が違ったりといったバグを見つけられました。
ただアプリケーションを改善するにつれて、E2E テストがアプリケーションにそぐわなくなってしまうところは E2E テスト自体をいじって改善していました。例えば動画が canvas で再生されること前提になっていたので、video で再生してもちゃんと E2E が通るように修正していたりしました。
- タイムラインとかの LCP 要素を preload
- プロフィール画像を圧縮する
- 音声の svg を事前生成
- フォントのサブセット化
- sendJSON の gzip 消した
- redux-form を剥がす (redux も同時に剥がせる)
- database.sqlite をリポジトリに含めずに、Docker ビルド時に動的に生成させる
- プロフィールヘッダーの色を事前計算 (Tailwind のところでバグらせていたが、ここで直った)
- Crok のサジェストのリクエストを debounce
- ReDoS を直した (何個か)
- タイピング入力中のリクエストを throttling
- 翻訳を Web Translator API に置き換え
SSRしたい
基本 JS とかメディアとかを削って転送量を削減して FCP/LCP を改善しつつ、変なリレンダリングを削って CLS を改善していたんですが、ここら辺で FCP/LCP が結構頭打ちになってきました。ということで奥の手 SSR を発動します。
SSR すると TTFB は悪化しますが、JS を読み込まなくても FCP/LCP の要素を読み込めるのでこれらの指標の改善が期待できます。もっと言えばキャッシュできる部分をキャッシュして ISR っぽくすることで TTFB の悪化をできるだけ抑えることができます。
2~3時間格闘した結果、SSR できました。が、あんまりスコアは改善せず、、
ファーストビュー速くする
せっかく SSR したのでファーストビューを極限まで速くしたいですよね。
- ALT の dialog を初期レンダリングから除外
- タイムラインの初期表示件数を少なく
- Crok ページを lazy loading
- アイコン周りを個別に svg を読み込むように
- react-helmet / react-router を自前実装に置き換え
サーバーも速くする
- DM一覧のAPIのレスポンスを小さく
- DM詳細のAPIのレスポンスを小さく
- アップロード時の圧縮は控えめに
- 不要なバリデーションを削る
- sendFile に gzip 圧縮を噛ませる
所感
基本はAIが全部書いてくれて楽で速いんですが、結構壊してくるのでそこの担保をちゃんとやる必要がありますね。あと得意不得意はかなりハッキリあって、サーバーサイドで使われてた sequelize というライブラリはかなり苦手そうでした。特に今期テストの外で宣言されているものが重要だったりして、バグったコードを平気で書いてくることもありました。
途中から E2E は結構回してて、かつ最後は initial commit の状態と目視で比較して差分を調べてて、レギュレーションは気を付けてたつもりなんですが、落ちちゃいました。最後の1時間でDM一覧の画面が結構壊れてた (APIの返すものが全然違った) のを直したのはかなりドキドキしました。
おわりに
参加者の皆さんお疲れさまでした。最後の方はかなりチューニングが頭打ちになってたのでボクよりもスコア高い人は本当にすごいと思ってます。
そして運営の皆さんも本当にお疲れさまでした。いつも面白い問題と厳格なレギュレーションチェックをありがとうございます。無事引っ掛かりました。
来年こそは優勝します。

