next-cache-components-optimizer について
本番ビルドの Playwright テストで、Next.js ルートのシェル表示を最適化するエージェントスキルです。リクエスト時のコンテンツより先に意味のある UI が表示されることを検証し、テストを回帰防止に残します。
主な機能
- 01instant() で遷移目標を記述
- 02シェルと遅延コンテンツを検証
- 03ハード遷移とクライアント遷移に対応
- 04本番テスト環境を構築
活用シーン
- 01データ読み込み前にルート UI を表示
- 02遷移最適化の回帰テストを追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel/ next.js --skill next-cache-components-optimizerインストール後、Claude Code で/next-cache-components-optimizerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
rig-template.mdtest-template.mdreference/red-test-robustness.md詳細
next-cache-components-optimizer
「即時ではない」Next.js ルートを「即時」にし、その状態を維持するエージェント型の最適化ループを構築します。このループはテスト駆動です。目標を失敗する @next/playwright の instant() テストとして記述し、成功するまで改善して、そのテストをリグレッションガードとして出荷します。対象ルートとエントリーポイントごとに一度実行します。フェーズ P → G の順に進め、各フェーズの最後にゲートがあります。
ルートを変更する前に、同梱の 静的シェルの最適化 ガイドを
node_modules/next/dist/docs/01-app/02-guides/optimizing-the-static-shell.md
で読んでください。利用できない場合は、オンラインガイドを参照してください。フレームワークの動作と実装パターンは、このガイドに従ってください。このスキルが担うのは、ナビゲーションの契約、本番環境に近いテスト環境、信頼できる RED から GREEN へのループ、同等性チェック、差分、およびレポートです。
不変の要素と、自由に決められる要素
ここで固定されているものは一つだけです。それ以外は自由に決めてください。以下のコマンド、プラットフォーム、環境変数を必須要件として扱う前に、これを読んでください。
- 不変の要素:検証ループ。 シェルを最大化しても、それを証明できなければ意味がありません。その証明となるのは自動チェックです。動的データをゲートするロックがかかった状態でも、静的シェルがコミットされることを確認します。RED で問題を示し、GREEN で解消を確認し、そのテストをリグレッションガードとして出荷します。これは本番環境に近いビルドで実行され、形だけ通過することがあってはなりません。ループを一度構築すれば、その後の最適化はすべて、仕組み上検証可能になります。成果物はループであり、特定のルートではありません。
- 仕組み:
@next/playwrightのinstant()。 このスキルでは、ストップウォッチではなく、ものさしとしてinstant()(フェーズ A)を使います。これは@next/playwright(@playwright/testとともにインストールされ、nextと同じリリース系列)から提供されるため、特定のホストには依存しません。これを使い続けてください。ナビゲーションを手動で計測する方法は不安定で信頼できず、このスキルが防ごうとする失敗の原因になります。 - 自由に決められる要素:テスト環境。 ビルド、デプロイ、認証、Playwright の設定、ループの実行方法は、スキルではなくスタックに依存します。ローカルの
next build && next start、CI/ステージング用コンテナー、プッシュごとのプレビュー環境は、いずれも有効なテスト環境です。判定はビルドに基づき、プラットフォームには依存しません。フェーズ 0 では、不変の要素をリポジトリに適用する方法を整理します。以下にあるプラットフォーム名、環境変数の表記、コマンドはすべて、必須要件ではなく置き換え可能な例として読んでください。
2 つのエントリーポイント、2 つの即時 UI 契約
ルートは 2 つの方法でユーザーに表示されます。
- **初期読み込み(ハードナビゲーション)**では、事前レンダリングされたルートの静的シェルがコミットされます。遅延部分は、ローディングスケルトン(Suspense フォールバック、
loading.tsx)の背後でストリーミングされます。 - **クライアント側ナビゲーション(ソフトナビゲーション)**では、共有レイアウトが再利用され、変化する宛先セグメントで利用可能な App Shell がコミットされます。
同じ実装パターンで、どちらのエントリーポイントも即時にできます。テストで異なるのは、ナビゲーションの実行方法(下記「テストでナビゲーションを実行する方法」)だけです。また、ソフトナビゲーションでは、遷移元と遷移先の分岐点より上位のレイアウトが再利用されるため、利用可能な UI が異なることがあります。ユーザーが指定したエントリーポイントを対象にし、両方が重要な場合は両方を保護してください。「即時」の意味を参照してください。
したがって、このスキルの対象は、直接読み込み時の静的シェルを増やすことだけではありません。再利用可能な処理をキャッシュする、またはリクエスト時の処理を適切に絞った境界の背後に移すことで、宛先の利用可能な App Shell をすぐにコミットできれば、即時ではないクライアントナビゲーションも改善できます。App Shell がすでに即時であり、クリック前に URL 固有のコンテンツを追加で取得することが目標である場合は、next-partial-prefetching-optimizer に引き継いでください。
目標
利用可能な App Shell UI の中で最も意味のある変更をすぐにコミットし、その後は実行時に本当に必要な処理だけをストリーミングします。付属のテストでは、表示される ∧ すぐに表示されることを決定論的に検証します。空のシェルでも instant() の検証は通るため、空でないことは、ワークフローが判断に基づいて追加で求める条件です。
instant() はストップウォッチではなく物差しです。ロック下でシェルが表示されることをアサートし、表示時間は計測しません。信頼できる判定には、本番ビルド(フェーズ A)が必要です。
ロック下での GREEN が決定論的な判定であり、各ゲートがその信頼性を保ちます。
ユーザーへの報告
このループは無人での実行を想定しているため、ステップごとに質問して止まることはありません。ユーザーが指定したナビゲーションに取り組み、完了させてから終了します。重要なのは、割り込む頻度ではなく、結果の言い方と見せ方です。以下の仕組み――リグ、RED、GREEN、ゲート――は作業を支えるものであり、ユーザーにこれらの用語を聞かせる必要はありません。
- ユーザーの言葉で説明します。 RED/GREEN、ロック、フェーズ記号ではなく、ユーザーに見える内容で問題と結果を説明します。「ダッシュボードに移動すると、何も表示される前にチャートのクエリを待っていました。今はレイアウトとスケルトンがすぐに表示され、チャートのデータがストリーミングされます」のように伝えます。
- 説明するのではなく、見せます。 ルートを報告するときは、ブラウザーを操作する(または修正前後のスクリーンショットを添付する)ことで、主張を読むだけでなく、シェルがすぐに表示され、データがストリーミングされる様子をユーザーに見てもらいます。修正前後で同じなら、修正は何もしていません。元に戻してください。
- 実行結果は、ユーザーがクリックして確認できる結果の一覧として提示します。 ナビゲーションごとに1行で、ルート、すぐに表示されるもの、ストリーミングされるものを記載します。ループの記録にはしません。
- 質問を提示するのは、本当に判断が分かれる場合だけです。 動作を変える修正、セキュリティ上慎重な読み取り、または設計上動的なルート(シェルを大きくするのではなく、リンクごとのプリフェッチ候補となるルート)が該当します。すぐに適用できる修正は判断の分かれ目ではありません。そのまま続けてください。質問できる相手がいない無人実行では、作業を止めず、安全な既定の選択を行って、その前提を記録します。キャッシュの鮮度を選ぶ場合は、
cacheLifeを推測するのではなく、読み取りを<Suspense>の後ろに置きます(常に最新のデータを保ちながら、表示はすぐに行います)。
ワークフロー
- [ ] P PREREQS Next.js 16.3+ with cacheComponents: true; upgrade first → below
- [ ] 0 SETUP once per repo: discover + write instant-nav.rig.md → rig-template.md
- [ ] A RIG production build with the testing API exposed → below
- [ ] B BASELINE unlocked: the marker renders for the test user → test-template.md
- [ ] C RED locked instant(): the shell does not commit → test-template.md
- [ ] C-gate VERIFY-RED: stop until the RED is trustworthy → reference/red-test-robustness.md
- [ ] D FIX apply the matching documented pattern → guide links below
- [ ] D1 reuse existing loading UI; keep the shell meaningful
- [ ] D2 preserve layout and responsive behavior
- [ ] E PARITY the refactor changed only whether the route is instant
- [ ] F DIFFERENTIAL revert only the fix → RED; re-apply → GREEN → reference/red-test-robustness.md
- [ ] G REVIEW PR checklist (below)
フェーズ B と C でテストを作成し、リリースするのは C のロック下で実行したテストだけです。
P. 前提条件:Cache Components を使用する現行の Next.js
このワークフローは、現行の Next.js で利用できるフレームワーク機能に依存します。
- Next.js 16.3 以降で、
next.config.tsのcacheComponents: trueが有効であること。 Cache Components がなければ、最適化できる静的シェルはありません。 - プロジェクトで使用している
nextと同じリリース系列の@next/playwright。instant()を提供します。npm ls next @next/playwright(またはプロジェクトのパッケージマネージャー)で確認し、バージョンが異なる場合は揃えてください。対応するテスト API はnextのランタイムに含まれており、experimental.exposeTestingApiInProductionBuild設定フラグで有効にします(フェーズ A)。
プロジェクトがこれらの条件を満たしていない場合は、まずアップグレードしてください(npx @next/codemod upgrade でほとんどの作業を自動化できます)。次に、next.config.ts で Cache Components を有効にします。
export default { cacheComponents: true }
このフラグを有効にすると、まず解決すべきブロッキングルートが明らかになります。移行には
next-cache-components-adoption
スキルを使用してください。アプリが Cache Components を有効にした状態でビルドできるようになってから、このオプティマイザーを使います。
このゲートは意図的に設けられています。このスキルは現行の Next.js を対象としており、以下の判定はどれも旧バージョンでは意味をなしません。
0. セットアップ:このリポジトリで一度だけ、プロジェクトの構成を把握する
このスキルの原則は固定されていますが、それを実行するインフラはリポジトリごとに異なります。リポジトリで初めて使用するときは、プロジェクトのビルド、デプロイ、認証、テストの方法を把握してください(まずリポジトリを調べ、答えが得られないことだけをユーザーに尋ねます)。そのうえで、回答をコミット対象の instant-nav.rig.md に記録します。以降の実行では、再調査せずにこのファイルを読みます。必要なビルド、テストコンテキスト、ナビゲーションコントラクト、反復ループ、ファイルテンプレートについては rig-template.md を参照してください。
リポジトリに Playwright の e2e ハーネスがまだない場合は、最小限のもの(@next/playwright、baseURL を指定した設定、認証済みパスを1つ)を用意することも、この手順の一部です。このループでは、既存のテストスイートがあることを前提としません。
A. RIG:テスト API を公開した本番ビルド
instant-nav.rig.md に記載された rig を用意します。どのプラットフォームでも、次の2つの不変条件が成り立ちます。
-
next devでは決して測定しないでください。 プリフェッチが行われず、ブロッキングルートに対するロックの動作も信頼できないため、devでのinstant()の結果は有効な RED でも GREEN でもありません。 -
測定対象のビルドでは、テスト API を公開する必要があります。 そうでないと
instant()は何もせずに処理を終了し、テストは実質的に何も確認せず成功してしまいます(reference/red-test-robustness.mdを参照)。ロックが有効になることの証明となるのは、フェーズ C の RED 自体です。修正前の対象ルートは既知のブロッキングルートであり、ロックが適用された状態で RED になることによって、このビルドでロックが有効になることが示されます(C ゲート)。test-template.mdの自己検証型のバリエーションは、インバンド保証となります。experimental.exposeTestingApiInProductionBuildは、測定するすべてのビルドで true になり、本番環境では決して true にならない条件に接続してください。experimental: { // Use the condition your platform provides, and record it in the rig file: // local: an explicit opt-in, as below // generic CI: process.env.DEPLOY_ENV === 'staging' // Vercel: process.env.VERCEL_ENV === 'preview' exposeTestingApiInProductionBuild: process.env.EXPOSE_TESTING_API === '1', }
rig には、テスト API を公開する本番相当のビルドであれば何でも使えます。ローカルでの next build && next start、CI/ステージングコンテナ、プレビュー環境へのデプロイはいずれも有効です。判定はプラットフォームではなく、ビルドに基づきます。セットアップ要件については rig-template.md を参照してください。
デプロイ済みまたはリモートのビルドでは、判定を信頼する前に rig の LIVENESS プローブをポーリングし、アーティファクトに HEAD が含まれていることを確認してください(古いデプロイでは RED または GREEN が誤って表示されます)。ローカルの next build && next start では必要ありません。プローブの仕組みについては rig-template.md を参照してください。
B. ベースライン(ロックなし):開発用の足場。出荷しないこと
instant() ロックを適用せずに実際のナビゲーションを実行し、テストユーザーとして遷移先の SHELL_MARKER がレンダリングされることをアサートします。ここでいうテストユーザーとは、e2e スイートが認証するアカウントのことで、CI では CI アカウント、ローカルでは e2e ログインフィクスチャを指します。また、そのユーザーのフラグ、プラン、ロール、データも使います。これにより、マーカーが実在し、到達可能であることを確認できます。つまり、フラグによる制御対象ではなく、別の場所へリダイレクトされず、推測で選んだセレクターでもありません。スイートは作成者のセッションではなく、テストアカウントとして実行されます。この環境の差異(rig の DRIFT リスト)は、信頼できない RED のよくある原因です。足場の作成と実行コマンドについては test-template.md を参照してください。
PR を出す前に、このベースラインを削除してください。
C. RED(ロックあり)+ VERIFY-RED ゲート
同じナビゲーションを instant() でラップし、ロック中にシェルがコミットされることをアサートします。ここでの RED がギャップです。これがリリースするテストです
(test-template.md)。
意図した契約全体をアサートします。意味のあるシェルマーカーと既存の読み込み状態が表示され、ロック中はリクエスト時の UI が表示されず、ロック解除後に完了した UI がレンダリングされることを確認します。テストを容易にするために、既存のデータソース、本番用セレクター、必須のルートバリアントを変更しないでください。必須の契約を省略したり、弱めたりしてはいけません。
ルートに遅延コンテンツがある場合は、自己検証型のバリアントを優先してください。ブロック中にルートをビルドできない場合、または Cookie/セッションの読み取りが GREEN のままの場合は、reference/red-test-robustness.md の RED レシピを使用してください。
C ゲート:RED の信頼性が確認できるまで、最適化を始めないでください。 間違った理由で RED になっていると、本来は壊れていないルートの最適化を進めることになります。
判断を決める質問は次のとおりです。テストユーザーとして、ロックなしで SHELL_MARKER はレンダリングされますか? リリースするテストにアサーションを追加するのではなく、テストユーザーとしてフェーズ B を再実行して答えてください。2 つの分岐による解決方法(いいえ → マーカーまたは環境の問題、はい → 実際のギャップなので D に進む)、信頼できない RED の完全な分類、チェックリスト、具体例は、reference/red-test-robustness.md にあります。今すぐ読んでください。
D. 修正:ドキュメントに記載されたパターンを適用する
最初に読んだ 静的シェルの最適化ガイドを使用します。ここでフレームワークのガイダンスを重複させるのではなく、問題に一致するセクションに従ってください。
| 問題 | ガイドのパターン |
|---|---|
| より広い読み込み状態によって安定した UI が隠れている | 静的 UI をシェルに残す |
| レイアウトまたはページがツリーの上位でリクエスト時データを待機している | データアクセスを下位に移動する |
| 新規または移動した境界に、役立つ安定したフォールバックが必要 | 読み込み状態を設計する |
| 結果をリクエスト間で安全に再利用できる | 再利用可能な処理をキャッシュする |
このガイドの例では、一般的な実装パターンを取り上げています。
ルートの既存のデータソース、鮮度、認可、完了済みの動作を維持してください。静的に見せるために、変更可能な読み取りをビルド時のインポートに置き換えてはいけません。既存の読み取りを再利用できる場合はキャッシュしてください。リクエストごとに計算する必要がある場合はストリーミングしてください。
リファクタリング中に、開発環境またはビルドで別のインスタントナビゲーションに関する Insight が見つかった場合は、リファクタリング中の検証に従ってから、そこに示される正規の Insight に従ってください。これは、メタデータ、ビューポート、非決定的な値など、API 固有の問題で特に重要です。
既存の読み込み UI を再利用し、サポートされているすべてのブレークポイントで、生成されるシェルが意味のあるものになるようにしてください。解決後のコンポーネントにも視覚的な占有領域がない場合に限り、空のフォールバックが有効です。
最適化によってキャッシュ境界を追加または拡張する場合は、再検証に従ってください。書き込み処理によってキャッシュ済みデータが変更される可能性がある場合は、キャッシュにデータを格納し、変更を実行してから、次の読み取りで更新された値が返されることを確認してください。instant() が証明するのは準備状態であり、変更後の鮮度ではありません。
最適化のために export const instant = false を使用したり、コントラクトを弱めたり、空のドキュメントシェルをリリースしたりしないでください。
D ゲート:フェーズ D の完了条件は、フェーズ C のロック済みテストが本番ビルド用リグでロック下において GREEN になることです。コードがコンパイルできた時点ではありません。この GREEN を修正ループの決定的な終了条件として、フェーズ E に進んでください。
不足しているインスタント UI が URL 固有のコンテンツだけの場合は、URL 固有のコンテンツをインスタント UI に含めるで終了し、next-partial-prefetching-optimizer に引き継いでください。
E. 同等性:リファクタリングで変わったのはルートが即時表示されるかどうかだけです
この押し下げは機械的な変換であり、再設計ではありません。変換後も、ルートは以前と同じツリー、データ、順序、空状態とエラー状態、リダイレクト、インタラクションを描画する必要があります。観測可能な違いは、シェルが即座にコミットされるようになったことだけです。以下を確認してください。
- 描画結果が同じであること。 移動した
awaitが同じ値を計算して返し、ストリーム後にルートがテストユーザーに対してベースブランチと同じコンテンツを表示することを確認します。 - リクエスト入力が引き続き機能すること。 ルートまたはリグで指定された認証、Cookie、パラメーター、検索パラメーター、データの各バリエーションをテストします。
- 副作用が引き続き実行されること。 遅延された
redirect()またはnotFound()がリクエスト時に引き続き発生することを確認します。未認証時とレコードが見つからない場合の動作を確認してください。ルートで HTTP ステータスを維持する必要がある場合は、ストリーミング時のステータスコードの動作を考慮してください。 - サポート対象のビューポートで、ストリーム後に実際の UI が表示されること。
- クライアントの状態が維持されること。 レイアウト UI は解決時に差し替えられるのではなく、安定したシェルに引き上げられるため、開いているメニュー、スクロール位置、フォーカス、入力状態はストリーム中も維持されます。UI 状態の維持を参照してください。
- 既存の失敗と分けて扱うこと。 変更後にルートでエラーが発生した場合は、ベースブランチでも再現するか確認します。そこで同じ失敗が発生するなら、オプティマイザーのリグレッションではなく、環境またはデータの問題です。
ルートが即時表示されるかどうか以外に何かが変わった場合は、リファクタリングの範囲を縮小してください。
F. 差分検証
修正だけを取り消す → RED。再適用する → GREEN。両方の実行結果をリンクします(reference/red-test-robustness.md)。最適化を判別することを意図したすべての契約が、取り消し後に RED、再適用後に GREEN になる必要があります。デプロイ済みのリグでは、色を信頼する前に、各実行が稼働中であること(LIVENESS、フェーズ A)を確認してください。
最後に再適用した後、リグから対象範囲のコマンド全体を実行します。フィルターされた一部、記述しただけのコマンド、または必須テストをスキップしたスイートは、最終 GREEN ではありません。
G. レビュー(PR チェックリスト)
RED が信頼できるものでなかったなら、最終状態が GREEN でも意味がありません。テストの信頼性に関する項目は、堅牢性チェックリスト(reference/red-test-robustness.md)です。それらを確認したうえで、PR 固有の以下の項目を確認してください。
- 差分を提示:修正なしで RED、修正ありで GREEN。実行結果をリンク済み。
- 同等性を確認(E):コンテンツ、リダイレクト、状態が同じ。
- 該当する場合はミューテーションを検証:更新可能なデータを含むキャッシュに値を格納した後、ミューテーションテストで次の読み取りが期待どおりのデータを返すことを確認済み。
- 鮮度を維持:新しいキャッシュスコープが、データの既存の有効期間、または明示的に選択した有効期間に従っている。
- 既存のローディング UI を再利用(D1):ページを模倣する新しいスケルトンを追加していない。
- シェルがサポート対象のビューポートで実際の描画と一致(D2)。
- ベースラインを削除:C のロック済みテストだけが残っている。
ワークフロー全体の停止条件: C のロック済みテストがリグで GREEN であり、差分検証(F)が成立し、上記のすべての項目にチェックが入っていること。この 3 つがすべて満たされるまでは完了ではありません。
テストでのナビゲーション操作
- ソフトナビゲーション → 実際の
<Link>のクリックを操作します。初回読み込み →baseURLオプションを指定してinstant()内でpage.goto()を使用します。ソフトナビゲーションの判定にgotoを代用しないでください。2 つの契約は異なる場合があります(test-template.md)。 - パラレルルートでは、ソフトナビゲーション時に再レンダリングされるのは変更されたスロットだけです。クライアント側で描画されるナビゲーション UI は一切再レンダリングされません。ナビゲーションが変更しないスロットを追わないでください。パラレルルートでのローディング UI とエラー UIを参照してください。
ファイル
rig-template.md:フェーズ 0 の本番ビルド、テストコンテキスト、ナビゲーション コントラクト、および無人ループの検出。test-template.md:2 種類のナビゲーションに対応する、同梱のinstant()仕様 (フェーズ C)と、PR 前に削除するベースラインのスキャフォールド(フェーズ B)。reference/red-test-robustness.md:C ゲートとフェーズ F。信頼できない RED の 分類、チェックリスト、差分レシピ、空虚な成功という失敗モード、および実例。
最適化の後
対象ルートが instant になったら、アプリですでに Partial Prefetching
(partialPrefetching: true、または段階的なロールアウト中に該当の遷移先が
引き続き prefetch = 'partial' を使用)を採用しているか確認します。
この確認は機械的に行ってください。
rg -n "partialPrefetching|prefetch\s*=\s*['\"]partial['\"]" --glob 'next.config.*' --glob 'app/**' --glob 'src/app/**'
設定に partialPrefetching: true があれば、アプリは全体で採用済みです。
prefetch = 'partial' だけが一致する場合は、段階的なロールアウト中にそれらの
遷移先セグメントが採用済みと判断し、その他の対象ルートも引き続き確認します。
- すでに採用済み: 要求された instant UI が URL 固有で、遷移先の App Shell も
すでに instant である場合は、
next-partial-prefetching-optimizerに進みます。 リンクごとの値とコストの判断は、このスキルが担います。 - まだ採用していない:
next-partial-prefetching-adoptionを推奨します。このスキルにより、アプリはより優れたプリフェッチモデルに移行します。 App Shell はデフォルトで共有プリフェッチされ、表示中のリンクに対する全体の プリフェッチリクエストの重複が減り、既存の<Link prefetch={true}>の使用状況を リンク監査し、URL 固有のコンテンツに追加のサーバー処理をかける価値がある場合に 限って、リンクごとのプリフェッチを任意で行えるようになります。