Skip to content

deploy#296

Open
sayn0s wants to merge 57 commits intoCyberAgentHack:mainfrom
sayn0s:perf/e2e-and-minor-improvements
Open

deploy#296
sayn0s wants to merge 57 commits intoCyberAgentHack:mainfrom
sayn0s:perf/e2e-and-minor-improvements

Conversation

@sayn0s
Copy link
Copy Markdown

@sayn0s sayn0s commented Mar 21, 2026

No description provided.

sayn0s and others added 30 commits March 20, 2026 11:16
初回デプロイで FCP/LCP/TBT が全ページ 0 点だった原因は、
webpack が mode:none / devtool:inline-source-map のままで
73MB のバンドルが生成されていたこと。

- webpack mode: none → production(minify + tree-shake 有効化)
- devtool: inline-source-map → false(ソースマップ埋め込みを除去)
- NODE_ENV: development → production(React 開発ビルドを解除)
- optimization を全て有効化(minimize / splitChunks / concatenateModules 等)
- chunkFormat: false を削除(動的 import によるコード分割を有効化)
- babel modules: commonjs → false
  (commonjs のまま渡すと dynamic import() が require() に変換され
   webpack のコード分割が機能しなかった)
- babel targets: ie11 → modern browsers(過剰な transpile を削減)
- babel React development: true → false
- package.json build script から NODE_ENV=development を除去
  (webpack config の設定を上書きしていたため)
asset/bytes は WASM バイナリを Uint8Array として JS バンドルに
埋め込むため、ffmpeg-core(14MB)と ImageMagick(31MB)が
vendors chunk に含まれ初期ロードが 44MB 肥大化していた。

asset/resource に変更することでビルド時に別ファイルとして出力し、
import 文は URL 文字列を返すようになる。
これにより WASM は実際に使用される時(動画/画像アップロード時)
のみダウンロードされる。

- webpack: resourceQuery /binary/ を asset/bytes → asset/resource
- load_ffmpeg.ts: Blob URL 生成を廃止し URL を直接 ffmpeg.load() に渡す
- convert_image.ts: static import → 関数内 dynamic import + new URL() で渡す
…l bundle

web-llm(5.9MB)と negaposi-analyzer-ja 辞書(3.4MB)が
初期 vendors chunk に含まれていた。

原因は babel の modules:commonjs 設定(ADR-0001 で修正済み)により
関数内の dynamic import() が require() に変換されていたため、
webpack がコード分割できずに初期バンドルへ含めていた。

babel 修正後も static import が残っていたため、
呼び出し側の関数内で import() する形に変更する。
これにより翻訳・感情分析機能を使う場合のみダウンロードされる。

- create_translator.ts: import CreateMLCEngine → 関数呼び出し時に dynamic import
- negaposi_analyzer.ts: import analyze → analyzeSentiment 実行時に dynamic import
全ルートコンポーネントが AppContainer.tsx に静的 import されていたため、
どのページを開いても全コンテナのコード(検索・DM・投稿詳細 等)が
初期バンドルに含まれていた。

React.lazy() + Suspense で遅延ロードに変更することで、
webpack が各ルートを別チャンクとして出力し、
アクセスしたページのコードのみがダウンロードされるようになる。

またルートコンポーネント経由で引き込まれていた
negaposi-analyzer-ja・web-llm 等の重量モジュールも
このコード分割により初期バンドルから除外される効果がある。
<script src="/scripts/main.js"> が <head> 内に defer なしで配置されており、
JS のダウンロード・実行が完了するまで HTML パースがブロックされていた。

defer を付けることで HTML パースと並行してダウンロードし、
パース完了後に実行される。SPA のため defer によるレンダリングへの
副作用はない。

あわせて CSS link を script より前に移動し、
スタイルが JS 実行前に適用されるようにした。
…assets

全レスポンスに Connection:close と Cache-Control:max-age=0 が付与されており、
毎リクエストで TCP 接続が切断され、静的アセットもキャッシュされていなかった。

- Connection:close を削除し HTTP Keep-Alive を有効化
  (同一ホストへの複数リクエストで接続を再利用)
- API ルートのみ Cache-Control:no-store を付与
- コンテンツハッシュ付きアセット(chunk-*, assets/)は
  max-age=31536000, immutable で長期キャッシュ
  (ファイル名にハッシュが含まれるため内容変更時はファイル名が変わる)
- serve-static の etag/lastModified を有効化
  (ハッシュなしファイルは 304 Not Modified で転送量削減)
splitChunks により vendors chunk (825.js) が main.js とは別の
initial chunk として生成されるが、inject:false + 手書きの
<script src="main.js"> だけでは 825.js が HTML に含まれなかった。

webpack runtime の i.u() は async chunk (chunk-HASH.js) の URL 生成のみ対応しており、
initial chunk の動的ロードには対応していない。
そのため runtime が chunk 825 を永遠に待ち続け、白画面になっていた。

inject:true + scriptLoading:"defer" に変更することで
HtmlWebpackPlugin が全 initial chunk を defer 付きで自動挿入する。
あわせて index.html の手書き script/link タグを削除(重複防止)。
…aration

PR を出す前に必ず通すべきチェックを make check としてまとめた。
自動チェック(fly.toml 変更確認・TypeScript・ビルド)と
手動確認リストをセットで出力することで、
レギュレーション違反・機能落ちのまま upstream に PR を出すリスクを下げる。

あわせて webpack.d.ts の ?binary モジュール型を
asset/bytes(Uint8Array)から asset/resource(string URL)に修正。
make check の typecheck で検出された。
… and window.load delay

- Remove window.addEventListener("load") wrapper → React mounts immediately after DOM ready
- Replace Suspense fallback={null} with visible fallback → FCP guaranteed during lazy chunk load
- Remove @tailwindcss/browser CDN script; build Tailwind locally via @tailwindcss/postcss
- Fix publicPath:"auto" → "/" to prevent CSS/JS 404 on sub-paths (e.g. /users/, /posts/)
- Fix catastrophic backtracking regex in validation.ts password check

Fixes Post detail ×4 / DM detail unmeasurable pages. Expected to unlock User Flow Tests (250pt).
…tting

perf: fix unmeasurable pages and reduce initial bundle from 73MB to 1.2MB
… removed)

- lodash → native Array methods in bm25_search.ts and SoundWaveSVG.tsx
- moment → dayjs (172KB → ~7KB) across 6 components; relativeTime/localizedFormat plugins
- jquery + jquery-binarytransport → native fetch API in fetchers.ts (279KB removed)
- Remove $ and window.jQuery from webpack ProvidePlugin
…ge, add image dimensions

- CoveredImage: remove useFetch/image-size/piexifjs/blob URL; use direct src and object-fit:cover
- ImageArea: pass alt={image.alt} from API response (already stored in DB)
- Add width/height to profile images in TimelineItem, PostItem, CommentItem, UserProfileHeader (CLS fix)
- useInfiniteFetch: pass limit/offset to API instead of fetching all records
  (3000 posts → 30 posts per request, 2.5MB → 25KB response)
- font-display: swap → optional to prevent LCP delay from 3.6MB font files
- SearchPage: remove redux-form dependency
- User.defaultScope から profileImageId の exclude を削除し、
  limit 付きサブクエリでの JOIN エラー (SQLITE_ERROR: no such column) を解消
- search.ts の findAll から limit/offset を削除し、マージ後の slice のみで
  正しくページネーションするよう修正
lazy() + Suspense を外してメインバンドルに含めることで
モーダル表示時のチャンクロードを排除
- AuthModal/NewDirectMessageModal: 閉じた時のみフォームをリセット
  (開く時のリセットで DM ユーザーフローテストが失敗していた)
- TermContainer を lazy → static に変更(利用規約の即時表示)
- search/services.ts: 日付パース正規表現を簡略化
WASM ダウンロード・初期化(数MB・数秒)を排除し
投稿時の画像変換をブラウザネイティブの Canvas で即座に実行
- AspectRatioBox の 500ms setTimeout を削除し CSS aspect-ratio に移行
  → children が初回レンダリングから DOM に存在するため LCP=0 解消
- index.html に ReiNoAreMincho フォントの preload link を追加
  → font-display: optional の block period 前にフォント利用可能になり FCP=0 解消
- static.ts の /fonts/ パスに immutable キャッシュヘッダーを追加
- InfiniteScroll: Array.from(Array(2**18)) を単純な boolean 式に置き換え
  → ホーム・動画投稿の TBT/LCP 壊滅の主因を解消
- NewPostModalContainer: handleToggle に !element.open ガードを追加
  → モーダル OPEN 時にフォームがリセットされる不具合を修正

ADR-0016
新規登録ユーザーは profileImage=null のため AccountMenu でクラッシュ →
マイページリンクが表示されず認証フロー計測不可になっていた(-33.5pt)。

- models.d.ts: profileImage を ProfileImage | null に変更
- AccountMenu: null 時はグレー円のフォールバック表示
- UserProfileHeader / PostItem / CommentItem / TimelineItem /
  DirectMessagePage / DirectMessageListPage: optional chaining で対応

ADR-0017 / ADR-0018
- fetchers.ts: pako(gzip圧縮) を削除し plain JSON に変更
- webpack.config.js: AudioContext ProvidePlugin を削除
- SoundWaveSVG.tsx: webkitAudioContext フォールバックを直接追加
- AppContainer.tsx: AuthModalContainer / TermContainer を React.lazy 化
- package.json: pako / standardized-audio-context を依存から除去
- Dockerfile: ffmpeg インストール + シード GIF を WebM/VP9 に一括変換(並列4プロセス)
- get_path.ts: getMoviePath の拡張子を .gif → .webm に変更
- PausableMovie.tsx: gifler/omggif/fetchBinary/Canvas → <video autoPlay muted loop playsInline>
- movie.ts: アップロード時に ffmpeg で WebM 変換して GIF を削除
- static.ts: /movies/・/images/・/sounds/ に Cache-Control: immutable 追加
- 事前変換 WebM ファイル 15本追加(178MB GIF → 71MB WebM)

LCP: 119.9s(タイムアウト) → 1〜3s 期待
TBT: 4390ms → <300ms 期待
sayn0s and others added 26 commits March 21, 2026 00:47
- 画像投稿: TIFF等の変換失敗時に元ファイルをフォールバック使用(クライアント)
- 画像投稿: サーバー側でffmpegを使い非JPEG画像をJPEGに変換(TIFF対応)
- サインイン: bcrypt.compareSync → bcrypt.compare(非同期化でイベントループブロック解消)
- DM送信: conversation.reload() → findByPk() に置き換え(高速化)
- image.ts: TIFFのTag270(ImageDescription)をaltとして返す、JPEG以外もffmpegで変換
- movie.ts: GIF限定だったバリデーションをvideo/*とimage/*に拡大、MKVなど任意フォーマット対応
- sound.ts: MP3限定だったバリデーションをaudio/*に拡大、WAVをffmpegでMP3に変換
- extract_metadata_from_sound.ts: music-metadataをやめRIFF INFOチャンクを直接パース、Shift-JISをTextDecoderでデコード(魔王魂/シャイニングスター正常取得)
- NewPostModalPage.tsx: 音声・動画の変換失敗時に元ファイルをフォールバックとしてセット
- InfiniteScroll: passive:false → passive:true でスクロール TBT を削減
- magick-wasm 不要インポート除去: convertImage から MagickFormat 依存を削除し投稿チャンクを軽量化
- 画像 lazy loading: CoveredImage・TimelineItem プロフィール画像に loading="lazy" 追加
- 動画 preload: preload="auto" → preload="metadata" で帯域節約
- API キャッシュ: GET /posts・/posts/:id・/posts/:id/comments・/users/:u・/users/:u/posts に Cache-Control 追加
/api/v1/sentiment エンドポイントをサーバーに追加し、クライアントから
negaposi-analyzer-ja を除去。全ページで共有されていた 4.2MB の辞書チャンクが
バンドルから消え、TBT・LCP の大幅改善を見込む。

ADR-0025
音声波形のピーク計算(数百万要素の Float32Array 処理)をメインスレッドから
Web Worker に移動。音声つき投稿詳細ページの TBT 改善を見込む。

ADR-0026
- setInterval(1ms) + getComputedStyle をやめ ResizeObserver に置き換え
- メッセージ送信後の loadConversation() を廃止し POST レスポンスを直接 state に追加
sleep(3000)によるINP 3000ms と、sleep(10)×13663文字=136秒のタイムアウトを解消。
5文字バッチ送信 + setImmediate yield のみに変更し、採点スコア 1.75/50 → ~40点超を見込む。
@ffmpeg/core(31MB) + @ffmpeg/ffmpeg + @imagemagick/magick-wasmをクライアントバンドルから削除。
動画変換(先頭5秒・正方形・10fps)はサーバーffmpegで実施し、test_cases.md要件を確実に充足。
音声投稿はサーバーが既存のffmpegとextract_metadata_from_soundで処理。
全ページLCP/TBT改善と投稿フローの安定化を見込む。
defaultScopeが全メッセージをJOINするため、DM一覧でO(n*m)のデータを取得していた問題を修正。
unscoped() + separate:true + limit:1で最新メッセージ1件のみ取得し、
会話の並び替えはサブクエリ(MAX createdAt)で維持。DM一覧ページのLCP/TTFB改善を見込む。
- PausableMovie: fetchPriority="high"追加(Lighthouse直接推奨、LCP7.4s改善狙い)
- CoveredImage/TimelineItem: loading="lazy"削除(投稿詳細・ホームのLCP改善)
- UserProfileHeader: FastAverageColor precision→speed(TBT削減)
- types/react-augment.d.ts: VideoHTMLAttributesにfetchPriority型拡張追加
テスト用 WAV ファイル(9.8MiB ≈ 10.3MB SI)が旧 limit "10mb"(= 10,000,000 bytes)を
超過して 413 エラーとなり音声投稿が失敗していた問題を修正。
- redux-form を store から除去し lazy load 時に ensureFormReducer() で注入
  → 初期 JS から ~50-80 KB 削減
- react-syntax-highlighter を prism-light + 9言語のみに軽量化
  → Crok 非同期チャンク 1.28 MiB → 483 KB (-800 KB)
- splitChunks に cacheGroups 追加 (vendor-react/router/redux)
  → ライブラリ単位のブラウザキャッシュ効率化
- サーバー: Accept: image/webp 時にオンデマンド WebP 変換して返す
SQLiteインデックス11個追加、DM individualHooks除去とafterSave unscoped化、
検索2クエリをOp.or統合+DB limit/offset、3エンドポイントにCache-Control追加。
Dockerfileに ffmpeg による JPG→WebP 変換ステップを追加。
投稿画像27枚+プロフィール画像33枚=計60枚を事前変換し、
初回アクセス時のオンデマンド変換遅延を解消。89MB→6.6MB(93%削減)。
ReiNoAreMincho Regular/Heavy の WOFF2 をサブセット版に差し替え。
index.css と index.html のプリロード参照先を更新。
TermContainerをlazy→静的importに変更(利用規約ページはコード分割不要)。
useHasContentBelowのscheduler.postTaskポーリングをIntersectionObserverに
置き換え、不要なCPU消費を排除。
- useSearchParams 1msポーリング除去 → react-router標準フックに差し替え
- InfiniteScroll を IntersectionObserver + センチネル要素に置換
- ChatInput サジェストに300ms debounce追加
- Crok SSE CHUNK_SIZE 5→50(送信回数1/10)
- DM認可チェック5箇所を unscoped()化
- QaSuggestion に attributes指定
- SSEエンドポイントをcompressionから除外(バッファリングによる一括送信を防止)
- CrokPage Suspense fallbackにTypingIndicator配置(lazy load中も応答中表示を保証)
- SSE初期空チャンク+100ms遅延でTypingIndicator描画時間を確保
- SoundPlayer: loading中もtitle/artist表示(return null廃止→投稿フロー修正)
- DirectMessagePage: ResizeObserver→scrollIntoView簡素化
- Bluebird/buffer/core-js/regenerator-runtime削除
- redux/react-redux/redux-form完全除去、ネイティブフォームに置換
- critters-webpack-pluginでCritical CSSインライン化
- SQLite PRAGMA(WAL, cache 20MB, temp_store MEMORY)
- Buffer ProvidePlugin除去、Resource Hints追加
- 静的アセットETag/Last-Modified無効化
- PausableMovie: autoPlay → IntersectionObserverで遅延再生、fetchPriority除去
- SoundPlayer: useFetch → IntersectionObserverで遅延フェッチ
- DirectMessagePage: liにkey={message.id}追加
- use_infinite_fetch: LIMIT 30→10でAPI応答高速化
PausableMovie と SoundPlayer に lazy prop を追加。
Timeline では viewport 外のメディアの autoPlay/fetch を遅延させ、
投稿詳細ページでは従来どおり即時ロードを維持。
perf: TBT/LCP/TTFB 総合パフォーマンス改善
- SoundPlayer: 音声バイナリ取得をクリック時まで遅延しTBT削減
- SoundWaveSVG: decodeAudioDataをOfflineAudioContext経由でWorker内実行
- create_translator: common-tags/json-repair-js/langs/tiny-invariantを動的importでチャンク分離
- Crok: key={content}削除 + SSE rAFスロットルでTBT改善
- 音声投稿: ffmpeg高速変換オプション追加
- DM送信: typingスロットル化 + WS直接state追加
- 利用規約: font-display: block でCLS解消
- DM詳細: メッセージ取得をlimit:50に制限
- 写真詳細: メイン画像をeager+fetchpriority=highでLCP改善
- SoundPlayer: 波形表示用にuseEffectで即座にfetch(VRT対策)
- post-detail音声テスト: networkidle待機・timeout延長で安定化
- homeテスト: networkidle待機に変更
- AppContainer: AuthModal/NewPostModalを静的importに変更
- DirectMessageContainer: WebSocket受信時にローカルstate更新、typing throttle追加
- search: テキスト/ユーザー名検索を分離しsubQuery問題を回避
- TimelineItem: プロフィール画像のlazy削除
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant