Communityコーディング&開発github.com

to110i/perceived-performance

スケルトンスクリーン、楽観的UI、ローディング状態、ナビゲーションフィードバックを使用して、Web UIを瞬時に感じさせます。

perceived-performance とは?

このスキルは、実際の処理時間に関わらず、ユーザーに「速い」と感じさせるUIを構築するための包括的なガイドです。主にNext.js、React、およびそのエコシステムを念頭に置いて設計されており、スケルトンスクリーン、楽観的UI、Suspenseストリーミング、SWRによるキャッシュ、`startTransition`による遅延レンダリングなど、具体的なテクニックを提供します。スキルは、コード分析、問題特定、実装というワークフローを提供し、開発者がユーザーが感じる遅さの根本原因(例:逐次的な`await`チェーン、過剰なデータフェッチ)を特定し、適切な手法を選定して適用できるよう支援します。また、各テクニックを実装するための詳細なコード例を参照できるように、referenceドキュメントへのリンクも含まれています。

対応~Claude Code~Codex CLI~Cursor
npx skills add to110i/perceived-performance

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

プログレッシブ Web UI

体感パフォーマンスとは、UI を「実際に速くすること」だけでなく「速く感じさせること」です。2秒で読み込まれても、すぐにスケルトンを表示するページは、1.5秒で読み込まれるがずっと真っ白な画面を表示するページよりも速く感じられます。

ユーザーがコードを共有したとき:

  1. コードを読み、ユーザーが読み込み中や待機中に現在何を見ているかを理解する
  2. 以下の判断ガイドを使用して、該当するテクニックを特定する
  3. 何を変更するか、なぜそれが体感速度を向上させるか、そして具体的なコードを使ってどのように実装するかを説明する

中核原則

1. 「最初の表示」とそれ以外を分離する

常に以下を区別する:

  • 最初の描画に必要なもの
  • シェルが表示された後にロードできるもの
  • ユーザーの操作まで待つことができるもの

すべてのデータを同じ緊急度で扱わない。

2. データより先に構造を

ページシェル(レイアウト、ナビスロット、ヘッダーフレーム)は、データフェッチが完了する前にブラウザに送信されるべき。ユーザーは最初の100~200ミリ秒で速度の印象を形成する。実際の読み込み時間が同じでも、構造をすぐに表示することで待ち時間の感じ方が軽減される。

3. クリックを無視させない

すべての操作(クリック、タップ、ナビゲーション)は、最初のフレームで目に見える応答を生成しなければならない。実際の処理に時間がかかる場合は、すぐに中間状態を表示する。

4. ローディングUIだけでなく構造を修正する

loading.tsx やスケルトンは見た目を改善するが、過剰なフェッチや逐次的なデータ依存関係は修正しない。常に根本原因に対処する:

  • 逐次的な await チェーン → Promise.all で並列化
  • 単一の大きなフェッチ → 最初の表示用と遅延用に分割
  • ナビ内での完全なプロファイルフェッチ → 軽量なナビ用IDヘルパーを使用

5. ナビゲーションも速く感じさせなければならない

体感速度は初期読み込みだけの話ではない。ページ遷移、再訪問、操作も同様に速く感じさせる必要がある:

  • ユーザーがクリックする前にルートをプリフェッチ
  • ナビゲーション開始時に即座に loading.tsx を表示
  • SWR/キャッシュを使用して再訪問時は瞬時に表示

判断ガイド

症状テクニック
データ読み込み中に空白画面が表示されるスケルトンスクリーン / Suspense ストリーミング
ボタンクリックに即座に応答がない楽観的UI / インスタントフィードバック
ページ遷移がクリック後すぐに視覚的に開始されないloading.tsx + プリフェッチ
重いコンポーネントが初期レンダリングを遅くする動的インポート
同じデータにアクセスするたびにスピナーが表示されるSWR stale-while-revalidate
1つの遅いAPIがページ全体をブロックするSuspense ストリーミング(並列非同期Server Components)
Auth/プロファイルフェッチがナビやページシェルを遅らせるAuthレイアウト分離(references/auth-performance.md 参照)
画像がレイアウトシフトを引き起こしたり、遅く感じるnext/image と priority / blur プレースホルダー
フォントスワップによりスタイル未適用テキストがちらつくnext/font
フォーム送信に視覚的なフィードバックがないuseFormStatus / useActionState
長いリストがスクロールのカクつきやレンダリング遅延を引き起こす仮想スクロール(TanStack Virtual / react-window)
緊急でない状態更新がユーザー入力をブロックする遅延レンダリングのための startTransition

分析ワークフロー

ステップ1: ユーザーが最初に何を見るかを特定する

0~200ミリ秒で何がレンダリングされるか?それが空白かスピナーであれば、スタイル修正の前に構造変更が必要。

ステップ2: データを緊急度で分類する

  1. 最初の表示 — ページを意味のあるものにするために必須
  2. 遅延 — シェルが表示された後にストリーミングできる
  3. オンデマンド — ユーザー操作後にのみロード

ステップ3: データ依存関係を修正する

  • 逐次 await → Promise.all または個別の非同期 Server Components
  • 初期ロードでの過剰フェッチ → API分割またはページネーション
  • ナビ内の完全なプロファイル → 個別のナビ用IDヘルパー

ステップ4: プログレッシブレンダリングを適用する

必要なものに基づいて選択:

  • loading.tsx — インスタントナビゲーションスケルトン(ページ全体)
  • <Suspense> — 粒度の高いストリーミング(セクションごと)
  • useOptimistic — インスタントなミューテーションフィードバック
  • dynamic() — 重いコンポーネントのバンドルを遅延
  • SWR — キャッシュからのインスタントな再訪問

ステップ5: 体感パフォーマンスを検証する

  • 意味のあるものが即座に(<200ms)表示されるか?
  • すべてのローディング状態でユーザーに情報が伝えられているか?
  • 完全なデータが到着する前にUIは応答するか?
  • 操作が最初のフレームでフィードバックを生成するか?

出力形式

このスキルを適用する際は、以下の構造で応答する:

## 現在起きていること
- [ユーザーが現在経験していることを説明]

## 根本原因
- [コード内で遅さを感じさせている原因]

## 変更内容とその理由
- [変更1]: [これが体感速度を向上させる理由]
- [変更2]: ...

## 実装

[コード]

## 注意事項
- [注意点、エッジケース、フォローアップの改善点など]

参照

完全なコード例を含む実装の詳細については、以下を参照:

  • references/nextjs-patterns.md — スケルトンスクリーン、楽観的UI、Suspense ストリーミング、プリフェッチ、動的インポート、SWR、画像、フォント、インタラクションフィードバック、useFormStatus、仮想スクロール、startTransition
  • references/auth-performance.md — Authレイアウト分離、ナビ用IDヘルパー、リクエスト/プロセスウォームアップ、フォールバック状態設計

特定のテクニックを実装する際は、関連する参照を読むこと。


修正すべきアンチパターン

常に以下を検出し対処する:

  • トップレベルレイアウトで完全なプロファイル/authを待つ → すべてのページを遅延させる
  • 独立したデータに対する逐次的な await チェーン → 不要な直列化
  • 根本的な過剰フェッチを修正せずに loading.tsx を追加
  • 初期ロードでデータセット全体をレンダリング → ページネーションまたは仮想化
  • 操作完了までクリックに視覚的フィードバックがない → 即座に応答を
  • ナビとページで異なる認証ソース → ユーザー名の不整合
  • サーバー応答後にのみ管理されるフォーム送信状態 → インスタントなペンディング状態のために useFormStatus を使用
  • マウント時に長いリストのすべてのアイテムをレンダリング → 仮想化(表示行のみレンダリング)
  • ユーザー入力をブロックする高コストな状態更新(フィルター/ソート/検索) → startTransition でラップ

関連スキル