WebSpeedHackathon2025に無謀すぎる挑戦をしてきました
はじめに
3/22、3/23に開催されたWebSpeedHackathon2025に参加してきました。
ISUCONではチーム戦でえっちらおっちらミドルウェアの設定をいじっており、今回はフロントエンドということで全くいける気がしなかったのですが、まあ個人戦も良い経験になるかなーと思い参加しました。
ちなみにTypeScriptとReactは何も分からないです。
この記事では2日間に自分がやったこととか課題をまとめて、その際の学びと記録を共有したいと思います。
1日目
10:10 スタートと環境セットアップ
10:10から大会の説明があり、10:30にリポジトリが公開されました。
無事に起きれたので0回戦突破。
まずはマニュアルを確認。今回はPRを送ると自動的にデプロイされる環境が用意されている模様。
Herokuってそんなこともできるんだーとか思いつつ、リポジトリをフォーク&クローン。
他の人のリポジトリをクローンするのが久しぶりすぎて普通に手間取りました()
10:53 初回ビルドでの躓き
初回ビルドができたのは10:53
bcryptモジュールが正しく読み込めないとのこと。
Error: Cannot find module 'C:\Users\showo\WebSpeedHackathon\web-speed-hackathon-2025\node_modules\.pnpm\bcrypt@5.1.1\node_modules\bcrypt\lib\binding\napi-v3\bcrypt_lib.node'
ただbcrypt@5.1.1は存在していたので何かしらの原因で読み込めないらしい。
Node.jsの事前準備の段階で何かミスったのかと思い、システム環境変数からPATHを通したり再インストールを試したものの失敗。
ならpnpmの方かと思いリビルドを試すも変化なし。
bcrypt単体の再インストールも行いましたがこれもエラーでビルドが通らず。
Claude.aiくんに聞いたり調べたりすると、bcryptjsで読み込むことができるとのこと。
早速試すと無事ビルドとサーバーの起動が出来るようになりました。
この時点で11:28、既に雲行きが怪しい。
11:47 最適化への第一歩
11:47にbcryptを変更して初回デプロイ(PR作成)を実行。
どうやらReviewAppがうまく動作してないっぽい?ただデプロイした環境は開けていたので多分いけてる…のか?
一旦デプロイ通っているっぽいので12時頃にWebpackの設定に着手。
フロントエンドのチューニングでバンドルサイズを見て削減する必要があるらしいとのことなので、webpack-bundle-analyzerを導入。
webpack.config.mjsに分析ツールの設定を追加しました。
pnpm add -D webpack-bundle-analyzer --filter client
pnpm add -D @types/webpack-bundle-analyzer
13:00頃 ビルド最適化
13:00頃には以下の最適化を行いました。
- ブラウザのターゲットを最新版のみに設定
workspaces/client/package.json
"webpack-dev-server": "5.1.0",
"wireit": "0.14.9"
},
+ "browserslist": [
+ "last 1 version"
+ ],
"wireit": {
"build": {
"command": "webpack",
- Production Build設定に変更
workspaces/client/webpack.config.mjs
},
extensions: ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.tsx', '.jsx'],
},
+ target: 'browserslist', // 追加
};
export default config;
この時点でのビルド時間は以下の通り。
「webpack 5.96.1 compiled with 4 warnings in 128231 ms」
この辺でtsxファイルを眺めていたところ、スタイルが直接指定されていたので当初スタイルをSCSSに置き換えようかなーとか思っていた計画が破綻しました。
時間があったらやるかくらい優先度になりましたが結局手を付けられなかったのが残念ですね…
(まあこれで何か変わるかと言われると…)
14:00頃 画像最適化
14:00頃から画像の最適化に取り組みました。
timetableディレクトリにあるウサギのPNG画像をSquooshを使ってAVIF形式に変換し、NewTimetableFeatureDialog.tsx
のパスを更新しました。
declarations.d.ts
+ declare module '*.avif' {
+ const value: string;
+ export default value;
+ }
NewTimetableFeatureDialog.tsx
- import FeatureExplainImageUrl from '@wsh-2025/client/assets/timetable/feature-explain.png';
+ import FeatureExplainImageUrl from '@wsh-2025/client/assets/timetable/feature-explain.avif';
import { Dialog } from '@wsh-2025/client/src/features/dialog/components/Dialog';
import { useCloseNewFeatureDialog } from '@wsh-2025/client/src/pages/timetable/hooks/useCloseNewFeatureDialog';
画像をavifに置き換えたことで設定の調整が必要だったので、Webpackの設定も併せて変更しました。
workspaces/client/webpack.config.mjs
test: /\.png$/,
type: 'asset/inline',
},
+ {
+ test: /\.avif$/, // 追加: AVIF ファイルの処理
+ type: 'asset/resource',
+ },
{
resourceQuery: /raw/,
type: 'asset/source',
15:00頃 SQLiteとDrizzle ORMの調査
この辺で一度採点結果を確認。
合計 66.25 / 1200.00
(暫定 48 位)
66点、普通だな!
画像についてはSVGなどもありましたが、ISUCONでやった時みたいにDBのチューニング辺りの方が手っ取り早くやれそうかと思ったので、DBの方に手を付けました。
特にホーム画面と番組表の表示が重いことが判明したため、SQLクエリの最適化とインデックス追加を試すことにしました。
:::mesage
ちなみにSQLiteはこの時初めて触りました、おもれ~
:::
16:15頃 インデックス追加(失敗)
ISUCONでインデックスを追加して高速化出来たことから、SQLiteでもいけるんじゃないかと思いインデックスを追加しようとしていました。
まずdrizzle-kitをインストール。
pnpm add -D drizzle-kit
するとマイグレーションでエラーが発生。
どうにかしてインストールしようとするも上手くいかなかったため、コマンドラインから直接インデックスを貼ることに。
.gitignoreの修正をしてコミットしようとしたところ、容量が大きすぎてpush出来ず。結局約1時間半ほど作業が停滞し、最終的には全コミットを取り消ししました。無常。
マイグレーションまでは以下の段階までは出来ていました。
解説聞いていて、インデックス追加しようとするとサイズでpush出来なくなるのも意図的なんじゃないかってちょっと思いました。分からないですが。
pnpm exec drizzle-kit generate --dialect sqlite --schema ./src/database/schema.ts
インデックスを追加したテーブル
- channel: 3カラム、1インデックス、0外部キー
- episode: 8カラム、0インデックス、2外部キー
- program: 8カラム、1インデックス、2外部キー
- recommendedItem: 5カラム、3インデックス、3外部キー
- recommendedModule: 5カラム、1インデックス、0外部キー
- series: 4カラム、0インデックス、0外部キー
- stream: 2カラム、0インデックス、0外部キー
- user: 3カラム、1インデックス、0外部キー
マイグレーション成功
pnpm exec drizzle-kit push --dialect sqlite --schema ./src/database/schema.ts --url ../server/database.sqlite
[✓] Pulling schema from database...
[✓] Changes applied
18:00頃 GIFの圧縮
18:00からはGIF画像の圧縮に取り組み、さらにSSRの最適化としてssr.tsx
に関数を追加しました。
この後19:30の時点で、スコアは「67.00 / 1200.00(暫定46位)」でした。
あまりに不動だったので「これ本当にデプロイ出来ているのか…?」という疑問が湧きつつも、その解決だけで全ての時間を潰しそうだったのと、スコアが67になっているので反映はされているんじゃないか?と思ったので引き続き修正を進めることに。
(デプロイを成功するだけで時間を潰すのが嫌だったという気持ちもあります、まあ動かなきゃ何も意味ないですが…)
19:50頃 フォームの最適化
19:50からはフォームを最適化するためにEメールとパスワードの正規表現をより簡潔なものに変更しました。
21:30頃 SVGの圧縮(失敗)
21:30からはSVGの圧縮処理に取り組み、seed.ts
にSVGO最適化処理を追加しました。
しかし、この変更後に画面が真っ白になる問題が発生し、一時的に修正を取り消すことになりました。
この辺で打つ手がなくなってきておりかなり焦っています。
2日目に向けて
この辺で1日目は終了。
2日目にやろうと思ったのは以下の通り。
- SVGファイルの処理方法(チャンネル画像をAVIFに変換するか検討)
-
seed.ts
のwhile文の条件改善 - 潜在的なセキュリティ脆弱性(パスインジェクション)の修正
-
ssr.tsx
、seed.ts
、main.tsx
の最適化 - CSSの最適化(量が多いため部分的に実施予定)
WebSpeedHackathon2025参加記録 2日目
2日目の開始
無事に起床できたので2日目の作業を早速開始。
11:00頃 SVG圧縮と遅延読み込みの実装
番組表の処理に遅延読み込みを実装。
ぱっと見問題なく動作してそう。
11:30頃 依存ライブラリの最適化
動作に影響が出る可能性はあるものの、ライブラリ関連でいくつかの修正を入れました。
バンドルサイズの削減とロード時間の短縮に繋がると良いなーと願いつつ最適化。
- lodashが重いと判断し、使用している部分を標準のJavaScript機能で代替
- moment-timezoneは使用されていないことを確認して削除
バンドルサイズは減ってそう。ロード時間はあまり変わってなさそう…?
12:00頃 HTTP/2対応
サーバー側の設定を変更して、HTTP/2で通信できるようにしました。
なおスコアを測定したところ変わらなかった模様。
13:00頃 クロスプラットフォーム対応
Windows環境特有の問題として、パス区切り文字がバックスラッシュ(\
)になってしまう事象があるらしいので対応。
13:40頃 不要なAPIコールの削減
チャンネルのサービスで無駄なフェッチがありそうだったので削減する修正をCopilotに聞きながら修正。
14:00頃 デプロイ確認と採点
変更の効果を確認するためにデプロイ環境のチェックと採点を試みましたが、デプロイがなかなか完了せず。
(この辺でスコア動いていなかったので内心お通夜でした)
14:50頃 デプロイ問題の対応
この辺でいよいよデプロイログの確認を依頼し、デプロイが通るように対応し続けました。
16:00頃 休憩
ほぼ心は折れていましたが、競技時間が延長されたので食事休憩。
16:30頃 クライアントビルドスクリプトの修正
package.jsonやwebpackの設定を修正し、クライアント側のビルドスクリプトを改善。
競技時間の終了が近づいていたため、この時点で大きな修正は終了することにしました。
16:45頃 ビルド問題の発見
この辺でローカルでビルドできなくなる。
設定をミスってそうだったのでwebpackを修正しましたが最終的には間に合わず。
18:30 競技終了
色々ありながら競技終了。お疲れ様でした。
振り返り
19:00からの解説も聞いてより深い知見を得ることができました。
以下、2日間を通しての個人的な教訓です。
- Copilotなどのツールに頼りすぎず、自分の理解と判断で最適化を進める
- 設定ファイルの正確な理解がビルド問題を防ぐ鍵になる
- データベース最適化(特にインデックス)だけではなく、HTTPプロトコルのバージョンアップなどもフロントエンドのチューニングに繋がる
- 不要なライブラリや処理は定期的に整理する
- 練習しよう!(当たり前)
最終的にはビルドの問題で終わってしまうというかなり残念な結果になってしまいましたが、多くの学びを得られた貴重な経験となりました。
次回は事前準備をより念入りに行い、知識を入れたうえでより効果的な最適化に挑戦したいと思います。
協議中のプルリクは以下
感想
TypeScript知識0 + フロントエンドのチューニング知識0という無茶すぎる状態で挑み悲惨な結果でしたが結構楽しかったです。
ISUCONに挑んだ時はチームだったので、個人でどれだけやれるかを測れる良い機会でした。
というか手も足も出なかったのマジで悔しい!!!
のでもっと力をつけていきたいですね、色々と。
というわけで、ご覧頂きありがとうございました。またいずれ。
(こちらの記事、競技中のメモをClaude.aiに投げて記事として出力してもらったのですが推敲の段階で無味無臭の面白みのない文章になっていた+そもそも事実と異なる記述になっていたので7割くらい直しました)
(Claude.ai、船降りろ)
Discussion