Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6eedc0f
init
sayn0s Mar 20, 2026
c21d8c7
perf: switch webpack/babel to production mode
sayn0s Mar 20, 2026
d0d195a
perf: output WASM binaries as separate asset files instead of inlining
sayn0s Mar 20, 2026
2916b36
perf: dynamically import heavy vendor libraries to remove from initia…
sayn0s Mar 20, 2026
ffb539b
perf: split routes with React.lazy to enable per-page code splitting
sayn0s Mar 20, 2026
7571511
perf: add defer to main script to avoid blocking HTML parsing
sayn0s Mar 20, 2026
24e284c
perf: enable HTTP keep-alive and set proper cache headers for static …
sayn0s Mar 20, 2026
8d33c5d
fix: inject all initial chunks into HTML via HtmlWebpackPlugin
sayn0s Mar 20, 2026
03871f5
re-trigger deploy
sayn0s Mar 20, 2026
a724a1c
chore: add make check for pre-PR validation and fix ?binary type decl…
sayn0s Mar 20, 2026
f7306ae
perf: fix unmeasurable pages by removing render-blocking Tailwind CDN…
sayn0s Mar 20, 2026
cd7b6d7
Merge pull request #1 from sayn0s/perf/production-build-and-code-spli…
sayn0s Mar 20, 2026
175bda3
perf: replace lodash/moment/jquery with native JS/dayjs/fetch (~940KB…
sayn0s Mar 20, 2026
0191f66
perf: replace binary-fetch blob URL with direct img src in CoveredIma…
sayn0s Mar 20, 2026
f8de2ac
fix: VRT bug fixes - search validation, upload error state, ImageMagi…
sayn0s Mar 20, 2026
cb8e140
perf: add brotli/gzip pre-compression and font WOFF2 with font-displa…
sayn0s Mar 20, 2026
06787e4
perf: lazy-load kuromoji in ChatInput/negaposi_analyzer, narrow babel…
sayn0s Mar 20, 2026
b4850c7
perf: lazy-load NewPostModalContainer and AuthModalContainer to reduc…
sayn0s Mar 20, 2026
ae3395e
perf: server-side pagination, font-display:optional, redux-form removal
sayn0s Mar 20, 2026
3af266e
fix: search API の 500 エラーと二重ページネーションを修正
sayn0s Mar 20, 2026
3156db0
perf: AuthModal/NewPostModal を eager load に変更
sayn0s Mar 20, 2026
d3d346d
perf: CoveredImage に absolute inset-0 を追加して CLS 改善
sayn0s Mar 20, 2026
b918544
fix/perf: モーダルリセットバグ修正・検索クエリ正規表現簡略化・TermContainer eager load
sayn0s Mar 20, 2026
ced701e
perf: 画像変換を ImageMagick WASM → Canvas API に変更
sayn0s Mar 20, 2026
5576754
perf: AspectRatioBox CSS aspect-ratio化・フォントプリロード追加
sayn0s Mar 20, 2026
3f2ed86
fix: InfiniteScroll 2^18ループ除去・NewPostModal 閉じ時のみリセット
sayn0s Mar 20, 2026
ae64368
fix: profileImage null クラッシュ修正・認証フロー回帰解消
sayn0s Mar 20, 2026
6d2d0e4
perf: pako 除去・standardized-audio-context 除去・AuthModal lazy 化
sayn0s Mar 20, 2026
e140a14
perf: GIF→WebM サーバーサイド変換・<video>要素移行
sayn0s Mar 20, 2026
e445fe4
chore: upstream の e2e テスト修正を取り込み(タイムアウト調整・動画セレクタ安定化)
sayn0s Mar 20, 2026
960704e
fix: 計測不能フロー修正(画像投稿・サインイン・DM送信)
sayn0s Mar 20, 2026
c52c528
fix: 計測できません3フローのメディアアップロード修正
sayn0s Mar 20, 2026
b74c097
perf: TBT・LCP・TTFB 改善チューニング5点
sayn0s Mar 21, 2026
b3cc0c3
perf: negaposi-analyzer-ja をサーバーサイドに移行(4.2MB チャンク削除)
sayn0s Mar 21, 2026
d80547c
perf: SoundWaveSVG の CPU 処理を Web Worker に移行
sayn0s Mar 21, 2026
5e33849
perf: DM送信フローのタイムアウト修正(setInterval→ResizeObserver・POST最適化)
sayn0s Mar 21, 2026
26100b3
perf: Crok sleep削減でINP/TBTを大幅改善
sayn0s Mar 21, 2026
c0d688e
perf: FFmpeg/ImageMagick WASMをクライアントから除去しサーバー移行
sayn0s Mar 21, 2026
3bf0f5a
perf: DM一覧のN+1クエリを解消(会話ごと最新1件のみ取得)
sayn0s Mar 21, 2026
e762be4
perf: fetchPriority=high追加・lazy除去でLCP/TBTを改善
sayn0s Mar 21, 2026
01a207c
revert: CoveredImage/TimelineItem のlazy除去を差し戻し(LCP悪化のため)
sayn0s Mar 21, 2026
fbd6ca9
fix: bodyParser raw limit を 50mb に拡張(WAV 投稿の 413 エラー修正)
sayn0s Mar 21, 2026
7865886
perf: redux-form遅延初期化・prism-light化・splitChunks細分化・WebP変換
sayn0s Mar 21, 2026
0bce96d
perf: バックエンド クエリ最適化(インデックス・DM N+1解消・検索統合・Cache-Control)
sayn0s Mar 21, 2026
0cbd088
perf: JPG→WebP 事前変換をDockerビルドに追加(ADR-0032)
sayn0s Mar 21, 2026
a86a3c3
perf: フォントサブセット化で転送量削減
sayn0s Mar 21, 2026
bd40de0
perf: TermContainer静的import化 + IntersectionObserverでポーリング除去
sayn0s Mar 21, 2026
f9bca74
perf: クライアント・サーバー残存ボトルネック除去(ADR-0036)
sayn0s Mar 21, 2026
678dd02
fix: Crokフロー計測復活 + SSEストリーミング修正 + SoundPlayer/DM改善(ADR-0037)
sayn0s Mar 21, 2026
cc1d6ac
Merge remote-tracking branch 'fork-upstream/main' into perf/tbt-lcp-i…
sayn0s Mar 21, 2026
3bdd94f
perf: バンドル削減・Critical CSS・SQLite PRAGMA最適化(ADR-0038)
sayn0s Mar 21, 2026
5530b5d
perf: 動画・音声の遅延ロード、DMリストkey追加、ページネーション削減
sayn0s Mar 21, 2026
8a4a103
perf: Timeline の動画・音声を lazy 化し TBT 改善
sayn0s Mar 21, 2026
52e4135
Merge pull request #2 from sayn0s/perf/tbt-lcp-improvement
sayn0s Mar 21, 2026
d053441
perf: SoundPlayer遅延初期化・decodeAudioData Worker化・createTranslator動的import化
sayn0s Mar 21, 2026
4767f48
perf: Crok TBT・投稿フロー・DM・利用規約CLS・写真LCP一括改善(ADR-0039)
sayn0s Mar 21, 2026
dfebfd2
fix: E2Eテスト安定化・SoundPlayer波形即時表示・検索/DM/認証モーダル改善
sayn0s Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Web Speed Hackathon 2026 — CLAUDE.md

## プロジェクト概要

SNS アプリ「CaX」のパフォーマンス改善競技。Lighthouse スコアを最大化する。

- **採点**: 1150点満点(ページ表示 900点 + ページ操作 250点)
- **採点ツール**: Lighthouse v10 Performance Scoring
- **デプロイ先**: Fly.io(GitHub Actions 経由)

---

## ⚠️ レギュレーション違反に注意

以下は違反すると**順位対象外**になる。変更前に必ず確認すること。

### 絶対に変更してはいけないもの

| 対象 | 理由 |
|------|------|
| `fly.toml` | Fly.io デプロイ設定。変更禁止 |
| `GET /api/v1/crok` の SSE プロトコル | ストリーミング仕様変更禁止 |
| `crok-response.md` と同等画面の構成情報 | SSE 以外での伝達禁止 |
| シードデータの各種 ID | `generateSeeds.ts` が生成した ID を変更してはならない |

### 機能・デザインを壊してはいけない

- VRT(Visual Regression Tests)が失敗してはいけない
- `docs/test_cases.md` の手動テスト項目が失敗してはいけない
- Google Chrome 最新版で著しい機能落ちやデザイン差異を発生させてはいけない

### その他

- `POST /api/v1/initialize` で DB が初期値にリセットできること(採点サーバーの前提)
- 競技終了後にアプリを更新してはいけない

### デプロイ前チェック

```bash
make check # fly.toml 差分確認 + TypeScript型チェック + ビルド確認
```

---

## 技術スタック

| レイヤー | 技術 |
|---------|------|
| フロントエンド | React 19 + Redux + React Router 7 + Webpack 5 + Tailwind CSS 4 |
| バックエンド | Express 5 + Sequelize 6 + SQLite |
| 言語 | TypeScript 5.9 |
| パッケージマネージャー | pnpm 10(monorepo workspace) |
| デプロイ | Fly.io(GitHub Actions 自動デプロイ) |
| テスト | Playwright E2E + VRT |

---

## よく使うコマンド

```bash
make dev # ビルド + サーバー起動(localhost:3000)
make build # クライアントのみビルド
make start # サーバーのみ起動
make check # デプロイ前チェック(必ず実行)
make score # ローカルで Lighthouse 計測
make score-target TARGET="ホーム" # 特定ページのみ計測
make score-prod PR=133 # デプロイ済み環境を計測
make pr # upstream に "deploy" PR 作成 → Fly.io デプロイ
make open-board # スコアボードをブラウザで開く
```

---

## ディレクトリ構成

```
web-speed-hackathon-2026/
├── application/
│ ├── client/ # フロントエンド(React + Webpack)
│ │ ├── src/
│ │ ├── webpack.config.js # WASM alias・ProvidePlugin 等、複雑な設定あり
│ │ └── babel.config.js # targets: last 1 chrome
│ ├── server/ # バックエンド(Express + SQLite)
│ │ ├── src/
│ │ └── database.sqlite # 98MB のシード DB(採点用、変更注意)
│ └── e2e/ # Playwright VRT
├── docs/
│ ├── regulation.md # レギュレーション(必読)
│ ├── scoring.md # 採点方法
│ └── test_cases.md # 手動テスト項目
├── ../adr/ # 意思決定ログ(ADR-0001〜0023)
├── ../strategy/ # 戦略ドキュメント
├── fly.toml # ⚠️ 変更禁止
└── Makefile
```

---

## 採点の仕組み

### ページ表示(900点満点 = 9ページ × 最大100点)

各ページのスコア = 以下の合計:
- FCP × 10
- Speed Index × 10
- **LCP × 25**(最重要)
- **TBT × 30**(最重要)
- CLS × 25

計測対象: ホーム・投稿詳細・写真投稿詳細・動画投稿詳細・音声投稿詳細・DM一覧・DM詳細・検索・利用規約

**ページ表示 300点以上の場合のみ、ページ操作の採点が行われる。**

### ページ操作(250点満点 = 5シナリオ × 最大50点)

各シナリオのスコア = TBT × 25 + INP × 25

計測対象: 認証・DM・検索・Crok(AI チャット)・投稿

---

## 実施済みの主要最適化(ADR-0001〜0023)

| ADR | 施策 | 効果 |
|-----|------|------|
| 0001 | Webpack production mode 有効化 | minify・tree-shake |
| 0002 | React.lazy + route-based code splitting | 初期 JS 削減 |
| 0003 | 静的アセット Cache-Control: max-age=31536000 | キャッシュ活用 |
| 0006 | lodash・moment・jquery 除去 | ~940 KB 削減 |
| 0008 | core-js polyfill 除去(targets: last 1 chrome) | 大幅削減 |
| 0011 | brotli/gzip 事前圧縮 + WOFF2 + font-display | 転送量削減 |
| 0012 | サーバーサイドページネーション | TTFB 改善 |
| 0019 | GIF→WebM サーバーサイド変換 | LCP/TBT 改善 |
| 0020 | AspectRatioBox → CSS aspect-ratio | 500ms 遅延除去・CLS 修正 |
| 0021 | Terms ページ フォントプリロード | FCP 改善 |
| 0023 | InfiniteScroll passive・lazy loading・preload="metadata"・API キャッシュ | TBT/LCP/TTFB 改善 |

---

## webpack.config.js の注意点

複雑な設定が入っている。壊すと VRT が全滅するため慎重に変更すること。

- WASM alias × 4(`@ffmpeg/core`・`@imagemagick/magick-wasm` 等)
- `resourceQuery: /binary/` — バイナリファイルの取り扱い
- `ProvidePlugin` — `standardized-audio-context` 用
- `splitChunks` — vendor chunk 分割設定

---

## ADR の書き方

`../adr/` に `NNNN-施策名.md` で記録する。テンプレートは `../adr/template.md`。
1 施策 = 1 ADR = 1 PR が基本。

---

## デプロイフロー

```
make check → make pr → GitHub Actions → Fly.io デプロイ → 採点サーバー計測 → リーダーボード更新
```

PR URL: `https://pr-<NUM>-web-speed-hackathon-2026.fly.dev`
スコアボード: `https://web-speed-hackathon-scoring-board-2026.fly.dev/`
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ LABEL fly_launch_runtime="Node.js"

ENV PNPM_HOME=/pnpm

RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*

WORKDIR /app
RUN --mount=type=cache,target=/root/.npm npm install -g pnpm@${PNPM_VERSION}

Expand All @@ -23,6 +25,14 @@ COPY ./application .

RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm build

# シード GIF を WebM に変換(並列処理)
RUN find /app/public/movies -name "*.gif" | xargs -P4 -I{} sh -c \
'out="${1%.gif}.webm"; ffmpeg -y -i "$1" -c:v libvpx-vp9 -b:v 0 -crf 33 -an -deadline realtime -cpu-used 8 "$out" 2>/dev/null && echo "converted: $out"' _ {}

# シード JPG を WebP に事前変換(並列処理)
RUN find /app/public/images -name "*.jpg" | xargs -P4 -I{} sh -c \
'out="${1%.jpg}.webp"; ffmpeg -y -i "$1" -vf "scale=min(1200\,iw):-2" -c:v libwebp -quality 80 "$out" 2>/dev/null && echo "converted: $out"' _ {}

RUN --mount=type=cache,target=/pnpm/store CI=true pnpm install --frozen-lockfile --prod --filter @web-speed-hackathon-2026/server

FROM base
Expand Down
86 changes: 86 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.PHONY: dev build start pr pr-fork check score score-prod score-target open-board sync

UPSTREAM := CyberAgentHack/web-speed-hackathon-2026
APP_URL := http://localhost:3000
PR ?= 0

# ── ローカル開発 ────────────────────────────────────────────

# クライアントをビルドしてサーバーを起動
dev: build start

# クライアントのみビルド
build:
cd application && pnpm build

# サーバーのみ起動(ビルド済みの dist を使う)
start:
cd application && pnpm start

# ── デプロイ前チェック ───────────────────────────────────────

# PR を出す前に必ず実行する。自動チェック + 手動確認リストを表示。
check:
@echo "=== [1/3] fly.toml が変更されていないか ==="
@git diff fork-upstream/main -- fly.toml | grep -q '.' \
&& echo "❌ fly.toml が変更されています(レギュレーション違反)" && exit 1 \
|| echo "✅ fly.toml 変更なし"
@echo ""
@echo "=== [2/3] TypeScript 型チェック ==="
cd application && pnpm --filter @web-speed-hackathon-2026/client typecheck \
&& pnpm --filter @web-speed-hackathon-2026/server typecheck
@echo ""
@echo "=== [3/3] ビルド成功確認 ==="
cd application && pnpm build
@echo ""
@echo "=== ✅ 自動チェック完了 ==="
@echo ""
@echo "以下を手動で確認してから make pr を実行してください:"
@echo " [ ] localhost:3000 で主要ページ(ホーム・投稿詳細・DM・検索)が表示される"
@echo " [ ] 新規投稿モーダルが開く"
@echo " [ ] make score でスコアが前回より改善している"
@echo " [ ] GET /api/v1/crok の SSE が正常に動作する(crok ページで確認)"
@echo ""

# ── デプロイ ────────────────────────────────────────────────

# upstream に "deploy" PR を作成 → GitHub Actions が fly.io にデプロイ・採点
# PR がすでに存在する場合は push だけで Actions が再トリガーされる
pr:
git push origin HEAD
gh pr create \
--repo $(UPSTREAM) \
--base main \
--title "deploy" \
--body "" \
|| echo "PR already exists – push triggered re-deploy"

# 自分の fork の main に記録用 PR を作成(詳細な説明を書く用)
pr-fork:
git push origin HEAD
gh pr create --base main --title "" --body ""

# upstream の最新を取り込んで fork に反映
sync:
git fetch upstream
git merge fork-upstream/main --ff-only
git push origin main

# ── 採点 ────────────────────────────────────────────────────

# ローカルのアプリをスコアリングツールで計測
score:
cd scoring-tool && pnpm start --applicationUrl $(APP_URL)

# 特定ページのみ計測(例: make score-target TARGET="ホーム")
score-target:
cd scoring-tool && pnpm start --applicationUrl $(APP_URL) --targetName "$(TARGET)"

# デプロイ済みの fly.io アプリを計測(例: make score-prod PR=133)
score-prod:
@[ "$(PR)" != "0" ] || (echo "❌ PR番号を指定してください: make score-prod PR=<number>" && exit 1)
cd scoring-tool && pnpm start --applicationUrl https://pr-$(PR)-web-speed-hackathon-2026.fly.dev

# スコアボードをブラウザで開く
open-board:
open https://web-speed-hackathon-scoring-board-2026.fly.dev/
7 changes: 3 additions & 4 deletions application/client/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ module.exports = {
[
"@babel/preset-env",
{
targets: "ie 11",
corejs: "3",
modules: "commonjs",
targets: "last 1 chrome version",
modules: false,
useBuiltIns: false,
},
],
[
"@babel/preset-react",
{
development: true,
development: false,
runtime: "automatic",
},
],
Expand Down
29 changes: 6 additions & 23 deletions application/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,58 @@
"license": "MPL-2.0",
"author": "CyberAgent, Inc.",
"scripts": {
"build": "NODE_ENV=development webpack",
"build": "webpack",
"typecheck": "tsc"
},
"dependencies": {
"@ffmpeg/core": "0.12.10",
"@ffmpeg/ffmpeg": "0.12.15",
"@imagemagick/magick-wasm": "0.0.37",
"@mlc-ai/web-llm": "0.2.80",
"@web-speed-hackathon-2026/client": "workspace:*",
"bayesian-bm25": "0.4.0",
"bluebird": "3.7.2",
"buffer": "6.0.3",
"classnames": "2.5.1",
"common-tags": "1.8.2",
"core-js": "3.45.1",
"dayjs": "1.11.20",
"encoding-japanese": "2.2.0",
"fast-average-color": "9.5.0",
"gifler": "github:themadcreator/gifler#v0.3.0",
"image-size": "2.0.2",
"jquery": "3.7.1",
"jquery-binarytransport": "1.0.0",
"json-repair-js": "1.0.0",
"katex": "0.16.25",
"kuromoji": "0.1.2",
"langs": "2.0.0",
"lodash": "4.17.21",
"moment": "2.30.1",
"negaposi-analyzer-ja": "1.0.1",
"normalize.css": "8.0.1",
"omggif": "1.0.10",
"pako": "2.1.0",
"piexifjs": "1.0.6",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-helmet": "npm:@dr.pogodin/react-helmet@3.0.4",
"react-redux": "9.2.0",
"react-router": "7.9.4",
"react-syntax-highlighter": "16.1.0",
"redux": "5.0.1",
"redux-form": "8.3.10",
"regenerator-runtime": "0.14.1",
"rehype-katex": "7.0.1",
"remark-gfm": "4.0.1",
"remark-math": "6.0.0",
"standardized-audio-context": "25.3.77",
"tiny-invariant": "1.3.3"
},
"devDependencies": {
"@babel/core": "7.28.4",
"@babel/preset-env": "7.28.3",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1",
"@tailwindcss/postcss": "4.2.2",
"@tsconfig/strictest": "2.0.8",
"@types/bluebird": "3.5.42",
"@types/common-tags": "1.8.4",
"@types/encoding-japanese": "2.2.1",
"@types/jquery": "3.5.33",
"@types/kuromoji": "0.1.3",
"@types/langs": "2.0.5",
"@types/lodash": "4.17.20",
"@types/node": "22.18.8",
"@types/omggif": "1.0.5",
"@types/pako": "2.0.4",
"@types/piexifjs": "1.0.0",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.1",
"@types/react-syntax-highlighter": "15.5.13",
"@types/redux-form": "^8.3.11",
"babel-loader": "10.0.0",
"compression-webpack-plugin": "12.0.0",
"copy-webpack-plugin": "13.0.1",
"critters-webpack-plugin": "3.0.2",
"css-loader": "7.1.2",
"html-webpack-plugin": "5.6.4",
"mini-css-extract-plugin": "2.9.4",
Expand All @@ -83,6 +65,7 @@
"postcss-loader": "8.2.0",
"postcss-preset-env": "10.4.0",
"react-markdown": "10.1.0",
"tailwindcss": "4.2.2",
"typescript": "5.9.3",
"webpack": "5.102.1",
"webpack-cli": "6.0.1",
Expand Down
2 changes: 2 additions & 0 deletions application/client/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const tailwindcss = require("@tailwindcss/postcss");
const postcssImport = require("postcss-import");
const postcssPresetEnv = require("postcss-preset-env");

module.exports = {
plugins: [
postcssImport(),
tailwindcss(),
postcssPresetEnv({
stage: 3,
}),
Expand Down
Loading
Loading