Next.js 16.3 Instant Navigations入門——Firestore画面遷移を速く見せる
Next.js 16.3 Previewで公開されたInstant Navigationsを、Next.js App RouterとFirestoreの業務画面にどう適用するかを実践形式で解説します。

はじめに
2026年6月25日、Next.js公式ブログで Next.js 16.3 Preview: Instant Navigations が発表されました。これは、Server Components中心の設計を保ったまま、SPAのように「クリック直後に次画面の骨格が出る」体験へ近づけるための機能群です。
業務システムでは、Firestoreから詳細データを読む画面で遷移が重く見えがちです。たとえば架空の受発注管理SaaS「OrderBoard」で、注文一覧から詳細へ移動するたびに、顧客情報、注文明細、監査ログ、在庫確認をすべて待ってから表示していると、ユーザーは「画面が止まった」と感じます。
本記事では、公式ブログとプレビューDocsで確認できる範囲に絞り、Next.js App Router + Firestore + TypeScriptの画面でInstant Navigationsを試す手順を解説します。なお、16.3はPreviewであり、公式にも本番ユーザーへの適用は慎重に判断するよう案内されています。
Instant Navigationsで何が変わるのか
公式Docsでは、navigationがinstantである状態を「クリックした瞬間に、静的コンテンツ、キャッシュ済みコンテンツ、fallback UIを表示でき、残りのサーバー処理はfallbackへstreamされる状態」と説明しています。つまり、すべてのデータ取得を速くする機能ではなく、待つべき場所とすぐ見せる場所を分ける設計 です。
今回押さえる要素は次の4つです。
| 要素 | 役割 | 実務での使いどころ |
|---|---|---|
cacheComponents: true | Cache Componentsを有効化 | 静的Shellや"use cache"の検証 |
partialPrefetching: true | LinkごとにShellをprefetch | 一覧から詳細への初速改善 |
<Suspense> | 未キャッシュ処理をfallbackへ逃がす | Firestoreの詳細取得や監査ログ取得 |
| Navigation Inspector | Shellに何が含まれるか可視化 | 画面遷移の見た目を検査 |
事前準備
16.3 Previewは next@preview として公開されています。npmのdist-tagでも preview が存在することを確認できます。モリソンのプロジェクト標準に合わせ、検証コマンドは yarn で書きます。
git switch -c chore/nextjs-instant-navigation
yarn add next@preview
次に next.config.ts でフラグを有効にします。公式ブログでは cacheComponents と partialPrefetching がこの機能群の前提として示されています。
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
};
export default nextConfig;
既存プロジェクトでWebpack固有設定、独自Babel設定、古いApp Routerの回避策を持っている場合は、まず yarn build が通るかだけを確認してください。Preview検証では、機能追加より先に「戻せるブランチで試す」ことが重要です。
ハンズオン1: 注文詳細をShellと動的データに分ける
OrderBoardでは、orders/{orderId} に注文概要、orders/{orderId}/items に明細、orders/{orderId}/auditLogs に監査ログがある想定にします。遷移直後に必要なのは、画面タイトル、注文番号、ステータスです。明細や監査ログは少し遅れても業務上は困りません。
ページ側では、すぐ表示したい概要と、待ってよい領域を分離します。
// src/app/orders/[id]/page.tsx
import { Suspense } from "react";
import { OrderAuditLog } from "./OrderAuditLog";
import { OrderItems } from "./OrderItems";
import { getOrderSummary } from "@/lib/orders/getOrderSummary";
type Props = PageProps<"/orders/[id]">;
export default function OrderDetailPage(props: Props) {
return (
<main className="space-y-6">
<Suspense fallback={<p>注文概要を確認しています...</p>}>
<OrderSummary params={props.params} />
</Suspense>
<Suspense fallback={<p>明細を読み込んでいます...</p>}>
<OrderItems params={props.params} />
</Suspense>
<Suspense fallback={<p>監査ログを読み込んでいます...</p>}>
<OrderAuditLog params={props.params} />
</Suspense>
</main>
);
}
async function OrderSummary({ params }: { params: Props["params"] }) {
const { id } = await params;
const order = await getOrderSummary(id);
return (
<section>
<p className="text-sm text-gray-500">注文番号: {order.code}</p>
<h2 className="text-2xl font-bold">{order.customerName}</h2>
<p>ステータス: {order.status}</p>
</section>
);
}
ポイントは、Firestore取得を全部ページ直下で await しないことです。<Suspense> の内側へ移すことで、Shellが先に表示され、明細や監査ログは後からstreamできます。
ハンズオン2: キャッシュしてよいFirestore取得を明示する
注文概要が頻繁に変わる場合はキャッシュ対象にできません。一方で、注文作成後にほぼ変わらない顧客名、注文番号、配送先ラベルなどはキャッシュ候補になります。Next.js 16系のCache Componentsでは、キャッシュしたい関数の先頭に "use cache" を置きます。
// src/lib/orders/getOrderSummary.ts
import { getFirestore } from "firebase-admin/firestore";
export type OrderSummary = {
code: string;
customerName: string;
status: "draft" | "confirmed" | "shipped" | "cancelled";
};
export async function getOrderSummary(id: string): Promise<OrderSummary> {
"use cache";
const snapshot = await getFirestore().collection("orders").doc(id).get();
if (!snapshot.exists) {
throw new Error("Order not found");
}
const data = snapshot.data() as OrderSummary;
return {
code: data.code,
customerName: data.customerName,
status: data.status,
};
}
ここで注意したいのは、キャッシュは都合の悪い遅さを隠す道具ではない という点です。ステータスが頻繁に変わる業務なら、status をキャッシュ対象から外す、再検証タイミングを設計する、またはその領域をSuspenseの動的取得に分離するほうが安全です。
ハンズオン3: 一覧のLinkでPrefetchを深くする
Partial Prefetchingを有効にすると、表示中の <Link> は宛先のApp Shellをprefetchします。さらに特定リンクでは prefetch を明示して、Shellだけでなくページコンテンツ側のprefetchも狙えます。
// src/app/orders/OrderList.tsx
import Link from "next/link";
type OrderRow = {
id: string;
code: string;
customerName: string;
};
export function OrderList({ orders }: { orders: OrderRow[] }) {
return (
<ul className="divide-y">
{orders.map((order) => (
<li key={order.id} className="py-3">
<Link href={`/orders/${order.id}`} prefetch className="block">
<span className="font-medium">{order.code}</span>
<span className="ml-3 text-gray-500">{order.customerName}</span>
</Link>
</li>
))}
</ul>
);
}
ただし、全リンクで深いprefetchを行うと、サーバー負荷やFirestore読み取り回数が増えます。業務画面では次のように優先順位を付けるのが現実的です。
| 画面 | prefetch方針 | 理由 |
|---|---|---|
| 注文一覧の上位20件 | 有効化を検討 | クリック率が高い |
| 検索結果の全ページ | 慎重に検討 | 表示件数が多く読み取りが増えやすい |
| 管理者向け監査ログ | Shellのみで十分 | 詳細確認は頻度が低い |
ハンズオン4: CIで遷移体験の退化を検知する
公式Docsでは、@next/playwright の instant() helperで、navigation直後に見えるUIだけを検査できると説明されています。重要な導線だけでもE2Eに入れておくと、「誰かがページ直下に重いFirestore取得を足して遷移が止まった」問題を検知しやすくなります。
yarn add -D @next/playwright @playwright/test
// e2e/order-navigation.test.ts
import { expect, test } from "@playwright/test";
import { instant } from "@next/playwright";
test("注文詳細の概要が遷移直後に見える", async ({ page }) => {
await page.goto("/orders");
await instant(page, async () => {
await page.click('a[href="/orders/demo-order-001"]');
await expect(page.getByText("注文番号: OD-001")).toBeVisible();
});
await expect(page.getByText("明細を読み込んでいます...")).toBeVisible();
});
GitHub Actionsでは、まずビルドを必須にします。E2Eは環境変数やFirestore Emulatorの準備が必要なため、導入する場合は別jobに分けると運用しやすいです。
# .github/workflows/web-ci.yml
name: Web CI
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn build
導入時の判断基準
Instant Navigationsは、すべてのルートに一括適用するより、まずユーザーが何度も往復する導線から試すのが向いています。OrderBoardなら「注文一覧 -> 注文詳細 -> 戻る」、CRMなら「顧客一覧 -> 顧客詳細」、在庫管理なら「商品一覧 -> 在庫詳細」です。
Preview段階では、次のチェックリストで扱う範囲を絞りましょう。
next@previewを本番に入れず、検証ブランチまたは検証環境で試す- Navigation InspectorでShellに出る情報を確認する
- Firestore読み取りがprefetchで増えすぎないか見る
- キャッシュしてよいデータと、常に最新であるべきデータを分ける
- 重要導線だけ
instant()でE2E化する
参考にした一次情報
まとめ
Next.js 16.3 PreviewのInstant Navigationsは、Firestoreを使う業務画面で特に効きやすい機能です。重要なのは、遷移を「全データ取得完了まで待つ処理」ではなく、「Shell、キャッシュ済み概要、Suspenseでstreamする詳細」に分け直すことです。
まずは注文詳細や顧客詳細のような往復頻度の高い1画面で、cacheComponents、partialPrefetching、<Suspense>、Navigation Inspectorを試してみてください。ユーザーがクリックした瞬間に意味のある画面が見えるだけで、同じFirestore読み取りでも体感速度は大きく変わります。