Web Speed Hackathon 2026 参加記

participation logcompetitive programmingweb speed hackathon

作成日 / 最終更新日


今年もこの季節がやってきました。
2023 から参加しているので、今年で 4 回目。

試合中にログを残しただけの参加記です。 殴り書きです。

Day -2 まで

何もしてない。よくない。
しかも最近は積極的に Web 触っているわけではないので、さらに良くない。

今年はみんな AI 丸投げになるんだろうな~と勝手に思っている。
競プロ脳なので AI 丸投げとかマジでおもんないと思ってますからね、もし上位が AI 丸投げしてたら来年以降もう出ません。 (出ます)

Day -1 (3/19)

環境構築

ずいぶん前に運営のうちの 1 人 Sor4chi に「Windows で動作確認してね!」と言ったものの、どうせ 「Windows 使いとかいう開発者の少数派が悪いんや!」とか言ってきそうだし実際それに近しいこと言われたので、 GCE の c4d-standard-4 (vCPUs: 4, RAM: 15 GiB) で Ubuntu 24.04 LTS を立てて環境構築をする。
料金計算ツールだと 48h, ディスク 100 GB でだいたい 2,000 円。
買い替えるより安いので許容。

まあ macOS のほうがいいんだろうが、 AWS で Dedicated Host 立てて... とかめんどくさいし高いので妥協。
立てたとしても macOS に慣れてないので使いづらくて無理すぎる

SSH でアクセスし、リモートデスクトップ環境を整える。
使用するのは Chrome リモートデスクトップ (remotedesktop.google.com)。
楽にセットアップできて良い。

sudo apt install ubuntu-desktop wget https://dl.google.com/linux/direct/chrome-remote-desktop_current_amd64.deb sudo apt install ./chrome-remote-desktop_current_amd64.deb DISPLAY= /opt/google/chrome-remote-desktop/start-host --code="REDACTED" --redirect-url="https://remotedesktop.google.com/_/oauthredirect" --name=$(hostname) sudo timedatectl set-timezone Asia/Tokyo sudo passwd $USER sudo passwd ubuntu sudo apt install fonts-noto-cjk

日本語入力ができないので、 Google Fonts から Noto (Sans|Serif) Japanese をダウンロードしインストール、 mozc も設定から入れて再起動。
keymap も更新し準備 OK。

  • Google Chrome
  • VS Code
  • fnm
  • gh
  • Node.js v24.14.0 (LTS)
    • npm
    • pnpm
    • @google/gemini-cli
  • sqlite3

も入れておいたので、たぶん一通り大丈夫かな
Git の設定もした。

試しに speed.cloudflare.comで速度計測してみたら、 Download で >1 Gbps 出て大爆笑 w
さすが Google のデータセンターなだけある。
WebGL サポートしてないらしいが、今回のお題は SNS サイトということでどうせ使わなさそうだしいいか。
困ったらローカルからアクセスしてやることにする。

スピードテストの結果
スピードテストの結果

対策リスト?

去年までやることリスト作っておいたけど、別にそんなに使わなかったので最低限のまとめ:

ざっとコード読んだら設定周りは GitHub Copilot Agents に丸投げしてもいい気がするな

競プロ脳なので AI 丸投げとかマジでおもんないと思ってますからね

🤔

ひとまず Private で wsh2026-workrepo を作成しておいて、 Issue で「Bundler を Vite にする」「ReDoS 修正」を作成しておいた。
(現在は wsh2026-workrepo を rename して a01sa01to/web-speed-hackathon-2026 になりました)

Day 1 (3/20) - 開始前

以下のメッセージが来ていたことに気づく。

Sor4chi (3/19 22:50)
Windowsの動作確認したぞ

ありがてぇ~けどごめんねぇ...

10:02 ごろ、 GitHub Status に Copilot Coding Agent Sessions が落ちてそうな通知が流れていた。 いい流れ...?
10:28 "We are rolling out our mitigation and are seeing recovery." あちゃー。 (??)

ところでなんかネットワークが不安定なんですが、困ります

Day 1 - 競技

CyberAgentHack/web-speed-hackathon-2026 を全力でリロード。

git clone https://github.com/CyberAgentHack/web-speed-hackathon-2026.git cd web-speed-hackathon-2026 git remote set-url origin https://github.com/a01sa01to/wsh2026-workrepo.git git push -u origin main

koyeb にデプロイし、準備 OK

開始

mise!? なんですかそれは

今年は採点 web-speed-hackathon-2026-scoring-tool じゃないんだ、ひとまず参加登録一番乗りした ( Issue #2 CyberAgentHack/web-speed-hackathon-2026 )
だが採点の Actions が Skipped とのこと。 動いてますか...?

10:47 直ったとのことで retry すると動いてそう。
227.40 / 1150.00 点。 CLS がほぼ満点... まあ最初はそうだよね

コードを読む

ざっとコードを読んで、怪しいところを見つけにかかる
気づいたら Issue を立てておくことに。

ReDoS

client/src/auth/validation.ts/^(?:[^\P{Letter}&&\P{Number}]*){16,}$/ が ReDoS になってる。
「『文字ではない かつ 数字ではない』ではない が 16 回以上」なので、「文字 または 数字 の 16 回以上」と等価で、 /^[\p{Letter}\p{Number}]{16,}$/ にすれば OK (後で気づくが ([...]*){16,} なので大嘘)

client/src/search/services.ts/\b(from|until)\s*:?\s*(\d{4}-\d{2}-\d{2})\d*/gi
Gemini に投げると /\b(from|until)\s*(?::\s*)?(\d{4}-\d{2}-\d{2})\d*/gi を返してくれた。

同ファイルの /since:((\d|\d\d|\d\d\d\d-\d\d-\d\d)+)+$/ も ReDoS になってるっぽい。
/since:((?:\d{4}-\d{2}-\d{2}|\d)+)$/ で OK。

/since:.*(\d{4}-\d{2}-\d{2}).*/g ここもだめ。

なんですか? 今回 ReDoS 祭りですか?

というか server/src/utils/parse_search_query.ts にほぼ同じ処理あるじゃん。
ここ最初に見ておけばよかった...

念の為 2の18乗 回、最下部かどうかを確認する

Vite 置換 (できず)

Webpack 使ってたので Copilot Agents に丸投げしておいた。
11:00 くらいに完了したのでローカルで動いてるかテスト。

PR #3 a01sa01to/web-speed-hackathon-2026

E2E のスナップショットが撮れてなかったので、最初に回しておくべきだった。

開いてみると、壊れている。 壊れてるよと言って丸投げする。
使いすぎて Rate Limit に引っかかってしまった。そんなぁ
Gemini に投げても全然動いてくれないので後回し。

Production 化

とりあえず Webpack でやることに。もう 11:57。

webpack-bundle-analyzer を入れ、解析していく。

Webpack Bundle Analyzer の結果
Webpack Bundle Analyzer の結果

すべての optimization 設定が false になっていたので true にする。

PR #9 a01sa01to/web-speed-hackathon-2026

Aspect ratio

これは css で aspect-ratio: width/height と書き換えてやればいいですね

PR #10 a01sa01to/web-speed-hackathon-2026

CaX が重すぎて RAM を食い尽くし、リモートデスクトップが切断される事態が頻発。困るよ~

Covered Image

これは object-fit: cover でいい気がするな
いや、なんか表示がずれてる...
無駄なクラスがついていた + height 指定がなかっただけだった

PR #12 a01sa01to/web-speed-hackathon-2026

Alt を exif から読み出すとかいうよくわからんことしてるので直したいな~ Issue (#11) にあげといていったん後回し

2^18

明らかに無駄なので、 Intersection Observer 使って書き直すことに。
一番下に表示される div を作ってやる。 1px くらい追加されちゃってもいいよね...?

PR #13 a01sa01to/web-speed-hackathon-2026

重すぎてまた切断されてしまった。勘弁してよ~
swap ファイルを 8 GiB 追加したので、多少軽減されたはず...?

jQuery

jQuery 消したいよね
You Might Not Need jQuery を使って書き直し。

youmightnotneedjquery.com

$.ajax だけしか使ってなかったのでこのサイトそんなに使う必要なかった。

PR #14 a01sa01to/web-speed-hackathon-2026

Lodash

lodash もいつも消してるのでやる。

youmightnotneed.com
PR #15 a01sa01to/web-speed-hackathon-2026

Vite 置換 (できた)

さすがに bundle size がでかすぎるのでどうにかしたい。
どうにかして server に逃がせないかな~

いったんチャンク分割かな
Gemini と会話しながら修正していく

やっぱり Vite にしたい気持ちが出てきたので、やってみる。
行けた!!!!

PR #18 a01sa01to/web-speed-hackathon-2026

もう 14:10
~~もぐもぐタイム~~

lazy 化

最初に一括で重いのが読み込まれちゃうので lazy import する

ついでに createRootload 後にされていたので、 DOMContentLoaded 後にするように変更。

リンクが a タグの遷移してるな?と思ったので react-router の Link に変更。
したが、なぜか click イベントハンドラが登録されてなくて謎。
react-router v7.9.4 だと unstable_useTransitions すらまだ対応してないらしい。
上げても動かず、よくわからなかったのでいったん Link に書き換えるだけして飛ばし。

PR #20 a01sa01to/web-speed-hackathon-2026

ここでこんなメッセージが届く。

【運営からのお知らせ】
GitHub Actions の runner の並列上限に引っかかりデプロイ、及び計測の滞留が発生しております

www
Koyeb にしといてよかった。
自分で用意すると課金は必要だけど詳細設定できてうれしいし、こんな事態が (余程のことがない限り) 起きないのもいい。

Vite fix

ここでデプロイされているサイトにアクセスしてみると Cannot GET / と出ている。困った。
Rollback しながらテストしていく。
すると、 Vite のコミットで落ちてることがわかる。
出力先が違ったみたい。

Commit b100912 a01sa01to/web-speed-hackathon-2026

HTTP/2 (できず)

やっぱり HTTP/2 にしたいので、する。
Gemini に聞いたところ、 Express でやるのはめんどくさいので Fastify に移行したほうが早いかも?とのことでそうする。

歯を磨きながらなので Copilot Agents に投げる。 PR #22 a01sa01to/web-speed-hackathon-2026

PR を待っている間にひとまず Connection: close になっていたので keep-alive にしてデプロイ。

Commit 855f93a a01sa01to/web-speed-hackathon-2026

ReDoS 修正

Server と合わせるだけで簡単なのでここをやっちゃう。

PR #23 a01sa01to/web-speed-hackathon-2026

終わったところでさっきの HTTP/2 の Agent が終わったみたい。
がダメダメ。マジで使い物にならない。
いったんスキップ。

FontAwesome

なんか 1 ファイルの svg としてまとまってるんですが?
使ってるのは

solid: home, search, envelope, edit, user, sign-in-alt, balance-scale, arrow-right, arrow-down, paper-plane, exclamation-circle, pause, play, circle-notch, images, music, video regular: calendar-alt

だけなので、それ以外のやつを消してやる。

PR #24 a01sa01to/web-speed-hackathon-2026

ffmpeg, imagemagick

こいつら重すぎるので server に処理を任せたいな~と開始時から思って早 6 時間 (16:48)

動画周りの処理がよくわからないので、適当な動画をアップロードしてみる。
すると、最初の 5s しかいらないらしい。
server 側に処理任せちゃっていいな?

[17:40] 動画と音声は完全にできた。気がする。
あとは image なんだよな~
Exif から読み出すのは piexifjs が比較的軽いから現状妥協かな~~
これ imagemagick 消すだけで完結するのでは?

PR #25 a01sa01to/web-speed-hackathon-2026

CSS 修正

いったん VRT を回してみる。
なんか一部の CSS が読み込まれてないような diff が表示されている。

normalize.css が読み込まれてなさそう。
それはまあめんどくさいので normalize.css を index.css にべた書きすることで対応... (よくない)

PR #26 a01sa01to/web-speed-hackathon-2026

あれ?いつのまにか Link がちゃんと動いてる????なんだったんだ

なんか日付が Jan 1, 2026 形式になってる?そこ変えた覚えないんだけどな
意味が分からん!

Translator

これも Server に回したいよな
ただ @mlc-ai/web-llm は express でホストできないっぽいので、 Cloudflare Workers AI を立てて叩くようにしてみる。

PR #27 a01sa01to/web-speed-hackathon-2026

画像・動画の軽量化

ほかの部分で忙しくて全然できてなかったや。 max で横幅 640px なので、縮小しておく。

profile image は 128px でよさそう。
こんな感じに書いた。

import fs from "fs/promises" import path from "path" import sharp from "sharp" import { PUBLIC_PATH } from "./paths" export const tmp_sharpResize = async () => { // 画像をリストアップ const files = await fs.readdir(path.resolve(PUBLIC_PATH, "images", "profiles")); console.log(path.resolve(PUBLIC_PATH, "images")) // 画像をリサイズして保存 await Promise.all( files.map(async (file) => { if (!file.endsWith(".jpg")) return; const inputPath = path.resolve(PUBLIC_PATH, "images", "profiles", file); const outputPath = path.resolve(PUBLIC_PATH, "images", "profiles", "resized", file); // 画像をリサイズして保存 await sharp(inputPath) .resize(128, 128, { fit: 'cover' }).jpeg({ quality: 80 }) .toFile(outputPath); }) ); }

gif 変換は Gemini に投げて書いてもらった。

#!/bin/bash # 保存先の専用ディレクトリ名 OUT_DIR="resized" # カレントディレクトリに出力先のフォルダを作成 mkdir -p "$OUT_DIR" # .gif ファイルを検索。ただし作成した $OUT_DIR の中は検索から除外する find . -type f -iname "*.gif" -not -path "./${OUT_DIR}/*" -print0 | while IFS= read -r -d '' file; do # 元のパスから先頭の "./" を取り除いて整理 clean_path="${file#./}" base=$(basename "$clean_path") # 出力先に元のディレクトリ構造を再現して作成 target_dir="${OUT_DIR}/" mkdir -p "$target_dir" # 出力ファイルのパス output="${target_dir}${base}" echo "処理中: ${file} -> ${output}" # -vf "scale=640:-1" で幅を640に固定、高さは縦横比を維持して自動調整 ffmpeg -y -loglevel error -i "$file" -vf "scale=640:-1" -t 5 -r 10 -an "$output" sleep 1 done echo "すべての処理が完了しました!ファイルは ${OUT_DIR}/ 内に保存されています。"

ローカルで試してみる。
その途中、 serve-static のオプションに etag, lastmodified があったことに気づく。
すかさず true にしておく。

見てみると、 Alt が表示されてない。
Exif が消されてしまった模様。

#!/bin/bash # ディレクトリのパス設定 SOURCE_DIR="./base-public/images" TARGET_DIR="./application/public/images" # exiftoolがインストールされているか確認 if ! command -v exiftool &> /dev/null; then echo "エラー: exiftool がインストールされていません。" echo "sudo apt install libimage-exiftool-perl を実行してインストールしてください。" exit 1 fi echo "EXIFデータのコピーを開始します..." # コピー元ディレクトリ内のすべての .jpg ファイルをループ処理 for src_file in "$SOURCE_DIR"/*.jpg; do # 該当するファイルがない場合の処理をスキップ [ -e "$src_file" ] || continue # ファイル名のみを抽出(例: image1.jpg) filename=$(basename "$src_file") target_file="$TARGET_DIR/$filename" # コピー先ディレクトリに同名のファイルが存在するか確認 if [ -f "$target_file" ]; then echo "$filename のEXIFデータをコピー中..." # -TagsFromFile でコピー元を指定 # -overwrite_original でバックアップファイル(.jpg_original)の生成を抑制 exiftool -TagsFromFile "$src_file" -overwrite_original "$target_file" else echo "スキップ: コピー先に $filename が見つかりません。" fi done echo "すべての処理が完了しました!"

問題なく動作することを確認。

PR #28 a01sa01to/web-speed-hackathon-2026

そのうち CF R2 でファイルホストとかしたいな (なんならフロントは Workers からホストできそうだしワンチャン全部 CF に乗るのでは...)

フォントの Subset

全然対応してなかったや。
使ってる文字は以下だけ。しかも全部 bold。

利用規約 第1条(適用) 第2条(利用登録) 第3条(ユーザーIDおよびパスワードの管理) 第4条(利用料金および支払方法) 第5条(禁止事項) 第6条(本サービスの提供の停止等) 第7条(著作権) 第8条(利用制限および登録抹消) 第9条(退会) 第10条(保証の否認および免責事項) 第11条(サービス内容の変更等) 第12条(利用規約の変更) 第13条(個人情報の取扱い) 第14条(通知または連絡) 第15条(権利義務の譲渡の禁止) 第16条(準拠法・裁判管轄)

Ubuntu 側でのフォント変換は知らないので、 Windows ソフト (サブセットフォントメーカーWOFFコンバータ) を使って woff2 にしていく。

PR #29 a01sa01to/web-speed-hackathon-2026

ビルド終わったら計測回してもぐもぐタイム + おふろへ。 20:20。早いよ~
これまで全然計測回せてなくて 2 回目なのやばい。

21:25 帰還。
499.95 点。まずまずの結果。
入浴中にいくつか案おもいついたのでやってみる。

negaposi

negaposi のファイルが重いので server に移してやる。

PR #30 a01sa01to/web-speed-hackathon-2026

Posts の Pagination

ネットワークタブを見てみると、 Posts 取得 API で大量に読みに行ってる。
最初から全部取得しに行ってるので、それは遅いよね...
コード読んでみたら server 側はすでに Limit, Offset の対応されていてうれしい!

PR #31 a01sa01to/web-speed-hackathon-2026

音声・画像・映像の Placeholder

こいつら binary fetch で読み込んでるのでどうにかしたいが、画像は Exif 読み込む都合上できないし、音声は波形を読みださないといけないので困る。 映像も canvas に書いてるのでいったん困る。
結局何もできないやんけ!

ひとまず CLS 対策として loading の placeholder 処理だけ書いといた。

PR #32 a01sa01to/web-speed-hackathon-2026

Tailwind を Vite で入れる

Tailwind が CDN 経由になっていて遅いので、 Vite plugin で読み込むことにする。
想像以上に簡単に組み込めてしまって逆に怖いので VRT 回すことに。
でもスタイルは問題なかった。
ついでに normalize.css をもとに戻しておいた。

PR #33 a01sa01to/web-speed-hackathon-2026

DB 周りの修正

なんか E2E を回してみると、 DB が壊れていることが発覚...???????
変えた覚えありませんが?????
全然直らないので、いったんローカルのリポジトリを削除して再 Clone してみることに... が、直らず。

初期状態 (手つかずの web-speed-hackathon-2026 リポジトリ) だと動いているので、まあここまでやってきたことによる不具合なんだろうなぁと
Git bisect の出番や! 存在しか知らないので Gemini に聞きながら進めていく。
結果、 Posts を Pagination にした #31 がよくないことが判明。なぜ...?
Client 側しか変えてないのに????

あー!!! /search も infinite scroll の対象じゃん!!
え、でも初期状態でも /search に limit つけると落ちるんですが...
逆に offset つけてもいける。 なぜ...?
ひとまず limit をコメントアウトすることで対応。

Commit 7fdaa5e a01sa01to/web-speed-hackathon-2026

momentjs 消し

そういえば時刻の表示もおかしかったな... ついでに直しちゃうか
bisect すると、 #18 の Vite 移植がよくないっぽい。うーん???

もう moment やめて dayjs に移植する。
いや、ここで Temporal, Intl の出番じゃないか?
試してみると Temporal の型が出ず、困った...
dayjs でいいや...

Commit 90b29c2 a01sa01to/web-speed-hackathon-2026

Index はる

全然張ってなかったので index を張ることに。

Copilot に任せていた感じで大体よさそうなのでマージ。

PR #34 a01sa01to/web-speed-hackathon-2026

highlightjs の言語を必要なものだけにする

Crok の応答となる元の Markdown 見てみると、 json, mermaid, bash, python, ts, rust, sql, text しか使ってないが、全言語を読み込んでいそうなので絞る。
react-syntax-highlighterREADMEを見ながら直していく。

Mermaid の言語ないんですが???
よくコードを見てみると、 javascript に fallback されている?
実際にレンダリングしてみると、なんか違う。
react-syntax-highlighter とか依存先とかのコードを追っかけてみると、 highlightAuto で自動判別してるらしい。
めんどくさすぎる...

仕方がないので pnpm patch react-syntax-highlighterconsole.log を挟んで取得する。
すると ebnf だということが判明する。 ほんとか?
見比べると良さそうなので、そのまま続行する。

PR #35 a01sa01to/web-speed-hackathon-2026

Day 1 最後の採点

[25:24] 今日はひとまずここまでにして、寝る前に採点を回して結果をみておく。
[25:35] 530.30 点。 39 位。全然伸びないなぁ... おやすみなさーい

Day 2

[07:10] 目が覚めてしまう。眠い。全然疲労が取れてない感じがする。開始。

画像 Alt を DB から

やっぱり Exif から読むのやめよう!
最初から DB から空文字の Alt が読み出されているので、保存するようにしてやればよい。
ただこのままだと元からある投稿に Alt がつかないので、 initialize するときに付加してやることにする。

PR #36 a01sa01to/web-speed-hackathon-2026

波形データを static に

こっちも static にしてやりたい。
まあテキストファイルで配置してやればいいか
Nodejs だと AudioContext がないので、 web-audio-api を入れて投稿時に取り出してやる。

こっちも元の投稿に波形データがないので、public ディレクトリ内であらかじめ生成。

PR #37 a01sa01to/web-speed-hackathon-2026

デプロイで落ちる。 web-audio-api のビルドで Python が必要だと怒られる。
Commit 922a60b a01sa01to/web-speed-hackathon-2026 直した。

映像データ

これどうしようかな~と Gemini に聞くと、 mp4 に変換すれば?と言われる。
そのほうがファイルサイズも軽いし js で canvas 使わずに再生・一時停止ができる。 🦀
まあ劇的にファイルサイズ軽くなるわけでもなかったが、いろいろライブラリが消せるので OK。

PR #38 a01sa01to/web-speed-hackathon-2026

kuromoji

こうすればあとは全部 Cloudflare R2 に逃がせないかな~と考えていると、 dicts がいる。
こいつ必要なのか?とみてみると、 ChatInput の kuromoji で使っている。
Tokenizer は Server に逃がしてやりたいな~

PR #39 a01sa01to/web-speed-hackathon-2026

E2E を回すと検索結果がおかしかったので、 limit, offset を SQL クエリでは外して結果が一致するようにした。
パフォーマンス的にはよろしくないが、レギュ落ちしては元も子もないので妥協。

Commit eef617c a01sa01to/web-speed-hackathon-2026

[09:13] このへんで採点回しながらもぐもぐタイムへ。

[09:32] 帰還。606.80 点。苦しい...
ただ CLS と TBT はいい感じになっているので、 FCP, LCP, SI をどうにかしたいな~というところ。

R2 から読み出し

やっぱり R2 に移動すれば結構よくなるんじゃないか? ということで実装。
initialize がめんどくさいが、まあ uuidv4 重複しないだろ!と妥協。

OK。 フォントとかそのほかのファイルも移動させちゃう。
svg は xlinkHref が unsafe とのことだったので、それぞれファイルを切り分けて対処。...する必要なくない?
もう bundle に含めちゃえばいいのでは
Crok も Figma に投げると多少圧縮されて返ってくるので、それを使ってやる。

PR #40 a01sa01to/web-speed-hackathon-2026

これでどのくらい変わったのか計測。うーん、そこまで大きく変わってなさそう。なんなら少し下がってる。

CDN 有効活用

FCP, LCP, SI が遅い。 しかも Koyeb の CDN でキャッシュされてない。
Cache-Control 設定を変えてなかったことに気づく...
API は一応 maxage=0, no-transform にしておいて、 static だけ public, max-age=3600 にしておいた。

PR #42 a01sa01to/web-speed-hackathon-2026

これで再計測してみる。 多少 FCP, LCP, SI が上がった。
ほか少し下がったところがあった (特に Flow の Crok AI の INP がめっちゃ下がった) が、合計は 628.75 と微増。

DM 画面

いい加減 DM 詳細ページを頑張りたい。

そういえば setInterval(..., 10) があったな
これって conversation.messages.length が変わった時しか発火しなくていいよな
念のため 100 ms 待ってから scroll する
あと毎回 typing を発火しなくても 10s くらい、いや余裕をもって 5s ごとでいい気がするな

PR #43 a01sa01to/web-speed-hackathon-2026

すべてを Worker に

リクエストをよく見ると、 R2 のリクエストに HTTP/3 が使われていない。
しかも Cache-Control がセットされてない。

探してみると Worker 経由じゃないと HTTP/3 にならない?とのこと。
https://community.cloudflare.com/t/will-cloudflare-r2-support-http-3-or-not/798982

ということで、いったんすべてを Workers から提供して、 API は Proxy してみることにした。
すべてにおいて HTTP/3 が使えるようになってうれしい!
Cache-Controlpublic, max-age=0, must-revalidate になってるのはどうしようもないのか?
_headers ファイルに書くといいらしい。

PR #44 a01sa01to/web-speed-hackathon-2026

計測。
SPA 特有の 404 ハンドリングがミスってた。
直して再計測。 669.25 点。 サインインに失敗しているらしくて困る。
手元で workers.dev に接続してみると動くのになぜ...?

フォントのレンダリングブロック

PageSpeed Insights で見てみると、 CSS でレンダリングブロックされているみたい。
フォントに font-display: swap 指定し忘れてた w

あ、そういえば profile のヘッダがおかしいんだったな、修正

Commit 2657b76 a01sa01to/web-speed-hackathon-2026 Commit 612b154 a01sa01to/web-speed-hackathon-2026

redux-form → react-hook-form

検索画面で何も入力しなくてもエラーが表示されない問題があった (正確には、表示されるときとされないときがある)
これ直さないとまずいよな~と思って Gemini とやり取りしていると、「WSH やってんの? redux-form は負債として残されてる可能性が高いよ」といわれたのでそのまま移行する流れに

途中、パスワードの正規表現が違うことに気づき、危ない...
「16 文字以上で記号を含まない」じゃなくて「0 文字以上で記号を含まない」なのか

うおー!!!できた!!!!

PR #45 a01sa01to/web-speed-hackathon-2026

[14:57] ここで計測。あと 3 時間半。短い。
681.15 点。

採点落ちを直したい

「サインインに失敗しました」この計測がマジで謎。
ローカルで動作を見てみると、読み込みに時間がかかってるみたい。
ログアウト後に / に遷移して画像を一気に読み込もうとするのがダメみたい。
ログアウト後 / にリダイレクトしなくてもいいのかな?
レギュレーションには書いてないので clar を投げる。
Sor4chi が一瞬質問に「草」リアクションつけた後にスレッドが建てられ、 Sor4chi 宛に DM で質問するようにとのこと。

  • ログアウト後 / にリダイレクトされる必要はあるか
  • Crok のレスポンスは 1 文字ずつ SSE で返す必要があるか (一気に送っても問題ないか)

以上お願いします

と投げると

  1. 既存実装の通りです
  2. いいよ

と返ってくる。そんなぁ

あ、ふつうに fetch で priority 高めにすればいいのか
ついでに img に loading=lazy, decoding=async を設定。

画像投稿も見てみる。
めちゃくちゃレスポンスが遅い。
Koyeb で見るとメモリ使いすぎてカツカツだったので、 RAM 4 GB に設定してリトライ。
(ついでに Actions に近そうなアメリカリージョンを選択した)

ふつうに自分で画像を投稿してみると、 R2 に画像があるのに読み込まれない。
/images/...images/... に変換する必要があり、 R2 へのリクエストが違っていた。
あと Alt 情報がちゃんと読み込めてないっぽい。
Gemini に投げると exifr を使えと言われたので、仰せのままにやる。

PR #47 a01sa01to/web-speed-hackathon-2026

Crok のレスポンス

SSE で 1 文字ずつ送っているが、これをまとめて送ればだいぶ TBT 上がらないかな~
さっき質問して "いい" と返ってきたので、やる
100 文字くらい一気に送ろうかな
一気に全部送るのはダメだよね?

Asa
さっきの質問に関連して: さすがに Crok レスポンス一気に全部送るは許されませんよね...?

Sor4chi
SSEを守ってください

Asa
SSE 使って全部送る場合ってどうですか

Sor4chi
SSEを守ってください

Asa
😥 逆にどのくらいなら許容されますか?言及不可です?

Sor4chi
です 😭

そんなぁ
100 文字くらいにしとこ

ローカルで試してみると、全然上がらん。
よくよく見てみると、 Markdown render に key={content} がついている。
それはだめですねぇ

そのほか Gemini とやり取りして直しにかかった

Commit 6774501 a01sa01to/web-speed-hackathon-2026

JS 経由 height

すっかり忘れてた。 ${Math.min(textarea.scrollHeight, 200)}px を max-height でやるやつ。
max-height でやるとうまくいかない。
Gemini に聞くと field-sizing: content; でできるらしい。

Commit d7202da a01sa01to/web-speed-hackathon-2026

ここで計測してみる。 17:22 あと一時間や。モームリ
ただ上位勢のサイトを見てみると、レギュ落ちしてそうな雰囲気がある
685.70 点。全然上がらん。困ってしまった。

投稿時ファイルタイプチェック

やっぱり画像アップロードが遅いのが気になるよな~
そういえば「filetype が undefined じゃない」のチェックしてるけどさすがに要らなくないか?

PR #49 a01sa01to/web-speed-hackathon-2026

不要依存関係削除

regenerator-runtime いらんな

pako ってなに? gzip 圧縮してそう。これいるのか?
sendJSON でそんなに重いものを扱ってるわけではないので消しちゃお

圧縮といえば zlib とかあったな まあやらなくていいか

PR #50 a01sa01to/web-speed-hackathon-2026

あと 40 分。ここでもう一回計測してみる。
696.60 点、ギリ 700 点に届かん。

Avif 化

まあいいやと放置した結果、すっかり忘れてた。
こうなることなら最初からやっておけばよかったね

PR #52 a01sa01to/web-speed-hackathon-2026

計測を回した瞬間、 Koyeb にデプロイしてないことに気づく。
急いでデプロイしたが、結局タイムアウトになってしまった。
再計測すると 690.50 点。うーん...

迷走

もうこうなったらリモートのデスクトップを外部公開してやっちゃえばいいのでは?
いや結局通信距離が長いからローカルほど長くならんやろ
なんもわからん...

終了

690.50 点、暫定 42 位。学生 21 位。

黄~緑 色変 (690 点?) のボーダー...
マジで無理すぎる... 終わりや

やりたかった

  • SSR (というかデータの inject) とかやりたかったなぁ
  • sendFile を zlib とかで圧縮するとよかったんだろうか
  • サーバーから送るデータ削減してないや
  • もっとちゃんと対策すればよかったかねぇ

反省

  • E2E 回しすぎ! 計測もっと回そう!
  • Coding Agents は使い物にならない 丸投げする部分と自分で書く部分をちゃんと分けよう

感想部屋とか解説とかを見ての感想

  • gif アニメーション、 WebM に変換する手か、完全に忘れてた...
  • Web Translator API、そんなのあったね
  • img の srcset 完全にさぼってた...
  • Scheduler API なにそれ、使われていたことすら知らないんですが... 終了後にコード検索してみると、偽 useSearchParams が生えていた。 気づきたかった...

結果発表

今年も結果発表が長引いている。デジャヴですか??がんばって...

[20:39] 結果発表がそろそろ行われるとのこと。
気になって Koyeb ダッシュボードを見に行くと、計測されたっぽいような痕跡が。
ワンチャンあるか?

Koyeb ダッシュボードのスクリーンショット。計測された痕跡が見える
Koyeb ダッシュボードのスクリーンショット。計測された痕跡が見える

[20:48] 第 3 位が発表される。ワンチャンなかった w

700 点未満はレギュレーションチェックされてないとのことだったが、 Sor4chi 曰く「一応学生のチェックはした、たぶん大丈夫だった」とのこと。
Koyeb ダッシュボードに表示されているアクセス時間的に、自分が最後だったんだろうなぁと。
ということで、

最終順位表のスクリーンショット
最終順位表のスクリーンショット

正真正銘レギュ落ちなし (?) の学生 2 位、全体 6 位でした! やった!! (物は言いよう)
上位勢レギュ落ちしすぎや... こわい...

まとめ

レギュ落ち怖いね (他人事) 来年はできるだけ対策して頑張ります