第78回ブログ|Next.js 16.3 徹底解説 — 「Instant Navigations」でSPA並みの体感速度を取り戻す

Next.js 16.3 Instant Navigations

はじめて読む方へ: この記事では、Next.js 16.3 の変更点を「速くなったらしい」で終わらせず、どこが自動で効く改善で、どこからが開発者の設計判断を必要とする改善なのかを分けて整理します。まずは アップグレードするだけで効く改善 と、Instant Navigations を有効化して画面遷移を速くする改善 の2階建てだと理解してください。

この記事について

2026年8月3日にリリースされた Next.js 16.3 は、公式が「16.0(2025年11月)以来もっとも大きなアップデート」と表現しているリリースです。目玉は Instant Navigations(インスタント・ナビゲーション) という、リンククリックの体感速度をSPA並みに引き上げるための機能群です。

ただし16.3の中身はそれだけではありません。何も書き換えなくてもアップグレードするだけで効く改善(開発時メモリ最大90%削減、ビルドキャッシュ、SSRスループット +22% など)と、設定フラグで明示的にオプトインする新しいナビゲーションモデルの2階建てになっています。

本記事はこの2階建てを分けて整理し、それぞれ「何が変わるのか」「自分のプロジェクトで今すぐ入れるべきか」を判断できるようにまとめたものです。

情報源は Vercel 公式ブログの以下2本です。数値ベンチマークはすべて Vercel 自身が自社プロダクト(vercel.com、nextjs.org、v0)で計測したものであり、第三者検証ではない点は割り引いて読んでください。

- Next.js 16.3 リリースノート: https://nextjs.org/blog/next-16-3

- Next.js 16.3: Instant Navigations: https://nextjs.org/blog/next-16-3-instant-navigations


0. 先に用語の整理

本題に入る前に、この記事で頻出する用語を先に定義しておきます。すでにご存じの方は読み飛ばしてください。

用語意味
App RouterNext.js 13 以降の新しいルーティング方式。app/ ディレクトリ配下にページを置く。従来の pages/ ベースは Pages Router と呼ばれる
Server Components(サーバーコンポーネント / RSC)サーバー側だけで実行され、実行結果(描画済みのUI)だけをクライアントに送るReactコンポーネント。ブラウザに送るJavaScriptを減らせる一方、描画にサーバーとの往復が必要になる
SPA(Single Page Application)最初にアプリ全体のJavaScriptを読み込んでしまい、以降の画面遷移をブラウザ内部の状態変更だけで行う方式。遷移が「一瞬」に感じられるのが最大の利点
prefetch(プリフェッチ)ユーザーがまだクリックしていないリンク先を、事前に裏で取得しておくこと。クリック時点で手元にデータがあるので遷移が速くなる
shell(ローディングシェル)ページの「骨格」だけのUI。ヘッダー・サイドバー・見出し・スケルトン表示など、データが揃っていなくても描画できる部分を指す。中身は後から流し込まれる
<Suspense>Reactの機能。子コンポーネントのデータ取得が終わるまで、代わりのUI(fallback)を表示しておく境界(バウンダリ)を作る
'use cache'Next.js 16 で導入されたキャッシュ用ディレクティブ。関数やコンポーネントの先頭に書くと、その結果がキャッシュ対象になる。従来の暗黙的なキャッシュを廃し、「キャッシュしたいものを明示的に宣言する」方式
Cache Components'use cache' を土台にした新しいキャッシュ/レンダリングモデル全体の呼称。cacheComponents: true で有効化する
TurbopackVercel が Rust で開発している Next.js のバンドラ(webpack の後継)。開発サーバーとビルドの高速化を担う
ISR(Incremental Static Regeneration)静的生成したページを、デプロイし直さずにバックグラウンドで再生成して差し替える仕組み
エラーバウンダリ配下のコンポーネントで発生した例外を捕捉し、アプリ全体を落とさずに代替UIを表示するReactの仕組み

1. アップグレードするだけで効く改善

まずはコードを1行も変えずに恩恵が得られる部分から。公式も「全アプリに16.3へのアップグレードを推奨」というスタンスです。

npm install next@latest

1-1. 開発サーバーのメモリ使用量が最大90%削減

next dev を長時間動かしっぱなしにするとメモリが際限なく膨らんでいく——これは多くの人が経験してきた問題だと思います。16.3 では Turbopack に ディスクキャッシュ(16.1で先行導入)と メモリエビクション(不要になったデータをメモリから追い出す仕組み) の2つがデフォルト有効になりました。

公式が示す実測値(50ルートをコンパイルした後のメモリ使用量):

プロジェクトBeforeAfter削減率
vercel.com(ダッシュボード)21.5 GB2 GB約90%減
nextjs.org4,600 MB840 MB約82%減

21.5GB というのがそもそも凄まじいのですが、大規模アプリでは「開発機のメモリが足りずに next dev が落ちる」という話が実際にあったわけで、それが2GBまで落ちるインパクトは大きいです。

設定の詳細は turbopackMemoryEviction のドキュメントを参照してください。

1-2. ビルドが速くなる(ディスクキャッシュのビルド対応)

これまで開発時にしか効かなかった Turbopack のディスクキャッシュが、next build でもデフォルト有効になりました。変更されていない成果物はキャッシュから読み出されるため、2回目以降のビルドが速くなります。

公式のビルド時間比較:

プロジェクトCold(キャッシュなし)Cached改善
nextjs.org21秒9.2秒約2.3倍
vercel.com/home66秒46秒約1.4倍
vercel.com/geist30秒5.5秒約5.5倍

注意点として、この効果はCI環境で「前回ビルドのキャッシュディレクトリが引き継がれること」が前提です。GitHub Actions などでコンテナが毎回まっさらになる構成だと、キャッシュの永続化(actions/cache などでのキャッシュディレクトリ保存)を自分で設定しない限り、常に Cold のままです。ここは導入時に必ず確認すべきポイントです。

1-3. TypeScript 7 での型チェックに対応

TypeScript 7(TypeScriptコンパイラをGoでネイティブ移植したバージョン。従来比で約10倍高速とされる)がリリースされたことを受け、next build 時の型チェックに TypeScript 7 を使えるようになりました。プロジェクトの依存を上げるだけです。

pnpm add -D typescript@^7

大規模プロジェクトほど型チェックがビルド時間の支配項になりがちなので、ここは効く可能性が高いです。

1-4. SSRのスループットが最大22%向上

App Router のレンダリング層で使っていた Web Streams(ブラウザ標準のストリームAPI)を、Node.js ネイティブのストリームに置き換えたという変更です。サーバーサイドレンダリング(SSR)中に両者を相互変換していたオーバーヘッドが消えた、というのが理由です。

結果として、負荷時に最大22%多くのリクエストを捌けるとのこと。アプリ側のコード変更は不要です。地味ですが、SSRを本番で回している環境にとってはインフラコストに直結する改善です。

1-5. AIコーディングエージェント向けの「バージョン一致ドキュメント」

個人的にはこれが隠れた目玉だと思っています。

Claude Code や Cursor のようなAIコーディングエージェントは、学習データが古いと 存在しないAPIや旧バージョンの書き方を平気で提案してくる という問題を抱えています。Next.js は特にバージョン間の差分が大きいフレームワークなので、この被害が大きい領域でした。

16.3 では、next dev を実行すると AGENTS.md(プロジェクト直下に置く、AIエージェント向けの指示書ファイル。多くのコーディングエージェントが自動で読み込む慣習になっている)に、プロジェクトが実際に使っているバージョンのドキュメントへのパスを書き込み、維持してくれるようになりました。参照先はローカルの node_modules にバンドルされたドキュメントです。

つまり、エージェントは「インターネット上の最新記事」でも「学習時点の古い知識」でもなく、手元のバージョンと完全に一致したドキュメントを読むようになります。この仕組みが入ったことで、Vercel は同じ目的で提供していた従来の Skills(vercel-labs/next-skills)を役目終了として引退させています。

1-6. prefetchリクエストの本数を削減(prefetch inlining)

一定のサイズ以下のprefetchは自動的にまとめて1リクエストに束ねられるようになりました。ただし、複数ルートで再利用できる大きめの共有セグメントは、再利用性を保つために引き続き別リクエストのままです。

1-7. 静的アセットをデプロイ間で再利用

内容が変わらない(immutable な)静的アセットを、デプロイをまたいで再利用できるようになりました。内容が不変である以上、いわゆるスキュー(新旧デプロイが混在して、古いHTMLが新しいアセットを参照して壊れる問題)が起きないため安全、という理屈です。

1-8. カスタムエラーバウンダリ(catchError

従来のNext.jsでは、Reactのエラーバウンダリが notFound()redirect() を呼ぶアプリケーションコードと干渉するという問題がありました。notFound() は内部的に例外を投げて実現されているため、自前のエラーバウンダリがそれを「エラー」として拾ってしまう、というものです。

さらに、リセットできるのはクライアント側の状態だけで、レンダリング中に失敗したServer Componentを再取得する手段がありませんでした

16.3 では next/errorcatchError を使って、これらを解決したカスタムエラーバウンダリを定義できます。

'use client';
import { catchError, type ErrorInfo } from 'next/error';

function Fallback(props: { title: string }, { error, retry }: ErrorInfo) {
  return (
    <div>
      <h2>{props.title}</h2>
      <p>{error.message}</p>
      {/* retry() で配下を再フェッチ = Server Component の再レンダリングも含む */}
      <button onClick={() => retry()}>再試行</button>
    </div>
  );
}

export default catchError(Fallback);

ポイントは第2引数で渡ってくる retry()。これを呼ぶと、そのバウンダリ配下の子を再フェッチします。Server Componentの再レンダリングも含まれるため、「一時的なDBエラーで落ちた画面を、ユーザーのボタン1つで復帰させる」といった実装が素直に書けるようになりました。

1-9. import.meta.glob(globインポート)

Turbopack が Vite 互換の import.meta.glob API に対応しました。ファイルシステム上の複数モジュールをまとめて読み込めます。

import matter from 'gray-matter';

export default function Page() {
  // 第2引数 eager: true で即時読み込み
  const posts = import.meta.glob('./posts/*.md', { eager: true });

  return (
    <ul>
      {Object.entries(posts).map(([path, mod]) => (
        <li key={path}>{matter(mod.default).data.title}</li>
      ))}
    </ul>
  );
}

ローカルのMarkdownを読んでブログを構成する、といったよくあるパターンで、HMR(ホットモジュールリロード:ファイルを保存すると画面が即座に更新される仕組み)が効くようになるのが実利です。従来 fs.readdir で書いていた箇所は、ファイル追加時に手動リロードが必要でした。

なお .md を読むには next.config.js 側でローダー登録が必要です。

1-10. Root params(next/root-params

これまで動的ルートのparams(URL中の可変部分)にアクセスするには、ページからpropsとして下位コンポーネントに渡していくしかありませんでした。app/[lang]/... のようなルートレイアウトより上位で定義されたparamsは事実上グローバルな値なのに、コンポーネントツリーの深い場所までprops のバケツリレー(prop drilling)を強いられていたわけです。

16.3 では、こうした「root params」を任意のServer Componentから直接読めます。

import { lang } from 'next/root-params';

export default async function PostPage(props: PageProps<'/[lang]/posts/[slug]'>) {
  const { slug } = await props.params;
  const language = await lang(); // props経由でなくても取得できる

  return <article>{language} / {slug}</article>;
}

i18n(多言語対応) のように、共通ユーティリティや深い階層のコンポーネントが現在の言語を必要とするケースで効きます。'use cache' スコープの内側でも動作します。

現時点でのサポートはServer Componentのみで、Route HandlerとServer Actionへの対応は今後のリリース予定とされています。


2. 本題:Instant Navigations

ここからがオプトイン(明示的に有効化する)機能です。

2-1. 何が問題だったのか

サーバー駆動のアプリでは、画面遷移は基本的にネットワーク往復を伴います。

  1. リンクをクリックする
  2. 何も起きない
  3. サーバーが応答して、次のページが表示される

ニュースサイトやブログのようなコンテンツ主体のサイトなら、これでも問題ありません。しかし「アプリらしさ」を求められるプロダクトでは、この2の空白が致命的にもっさり感じられます。

一方、SPAはこうです。

  1. リンクをクリックする
  2. 即座に次ページのシェルが出る(データはまだロード中)
  3. サーバーが応答して、ページが完全に表示される

Server Componentsは「送るJavaScriptを減らす」「ネットワークのウォーターフォール(データ取得が数珠つなぎになって遅くなる現象)を避ける」という点で成功しましたが、代わりに遷移が遅く感じられるという副作用を生みました。16.3 はここに正面から取り組んだリリースです。

2-2. 有効化する2つのフラグ

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
};

export default nextConfig;

この2つは将来のメジャーバージョンでデフォルトになる予定と明言されています。つまり今オプトインするのは「実験的な寄り道」ではなく「先行移行」です。

2-3. 開発者に迫られる選択:Stream / Cache / Block

cacheComponents を有効にすると、ルートがサーバー側で何かを await している場合、開発時に明示的な選択を迫られるようになります。

前2者を選べば遷移はSPAのように「即座」になります。3つ目は明示的なオプトアウトで、例えばブログ記事のように「中途半端なシェルを見せるくらいなら完全な内容が揃うまで待たせたい」というケース向けです。ページやレイアウトに1行書けば警告は消えます。

重要なのは、フレームワークが勝手に決めるのではなく、開発者が意思決定する構図になっている点です。従来の暗黙的キャッシュへの反省が反映されています。

2-4. Instant Insights — 遅い遷移を自動で炙り出すDevTool

とはいえ「全ルートについて漏れなく判断する」のは人間には無理があります。従来も loading.tsx を各ルートに置けば同じことはできましたが、どこか1つ置き忘れて遅い遷移が残るのが常でした。

そこで追加されたのが Instant Insights です。Next.js DevTools のパネルとして、即座でない遷移を開発時にエラーとして自動的に表示します。

さらに各インサイトには、そのまま貼り付けられるAIエージェント向けプロンプトが添付されます。「この警告を消すにはどう直すか」をエージェントに教える形になっているわけで、前述のAGENTS.md対応と合わせて、Vercelが本気でエージェント前提の開発体験を設計しにきているのが分かります。

既知の問題として、Safari では Instant Insights の挙動に不具合があるとアナウンスされています。開発時は Chrome か Firefox の利用が推奨されています。

2-5. Partial Prefetching — prefetchの考え方を根本から変える

ここが個人的に一番面白い変更点です。

従来の問題: Next.js は、ビューポートに入ったリンクごとにprefetchリクエストを飛ばしていました。本番モードでNetworkタブを開いてスクロールすると、リクエストが滝のように流れていくアレです。公式も「ridiculous(ばかげている)に見えるという指摘はもっともだ」と認めています。

例えばサイドバーにチャットのリンクが20本あれば、20本のprefetchリクエストが飛んでいました。リンク先はすべて /chat/[id] という同じルートなのにです。

16.3のアプローチ: SPAのやり方を借りました。SPAが遷移を即座にこなせるのは、次の画面を描画するのに必要なコードをあらかじめバンドルして持っているからです。データはまだ足りないかもしれませんが、シェルを描くには十分です。

そこで Next.js も、リンクごとではなくルートごとに、再利用可能なシェルを1回だけprefetchする方式に変えました。取得したシェルはクライアント側にキャッシュされ、セッション中は使い回されます。

概念的には、SPAにおけるルート単位のコード分割とほぼ同じ発想です。

それでも「シェル以上」が欲しいとき は、リンク側で <Link prefetch={true}> を指定します。ただしこの場合でも、Next.js はルート全体を最深部までレンダリングしようとはしません。同期的に取得できるもの/URLから判明するもの(paramsやsearchParams)/'use cache' が付いているもの、ここまでを描画対象とします。

つまり、従来の「全部prefetchするか、しないか」というオール・オア・ナッシングの選択が解消されたわけです。シェルがベースラインとしてあり、その上に必要なリンクだけ厚みを足せます。

2-6. ISRの改善 — 未生成ページの初回訪問者も待たせない

generateStaticParams(ビルド時に静的生成する動的ルートのパラメータ一覧を返す関数)で一部のページだけを事前生成している場合、残りのページには従来トレードオフがありました。

16.3 では両取りできます。事前生成していないページは、初回訪問時に即座にローディングシェルを返し、裏で完全な事前生成へアップグレードします。2人目以降の訪問者はキャッシュから完成品を受け取ります。

トラフィックに応じてアップグレード頻度を制御するAPIも検討中とのことです。

2-7. Navigation Inspector — シェルを目で確認する

Next.js は開発時にはprefetchを無効化しているため、「ユーザーが実際に見るローディングの流れ」を開発中に確認するのが困難でした。

Navigation Inspector は、ページロードやクライアント遷移をシェルの状態で一時停止できるDevToolです。「Resume」を押すと完成状態に進みます。これにより「このルートのどこまでが即座に出て、どこからがネットワーク待ちなのか」を視覚的に切り分けられます。

2-8. Playwright用 instant() ヘルパー — デグレを防ぐ

もう1つのよくある失敗パターンが、今日は即座に遷移するページが、明日には遅くなっているというものです。原因は地味です。

いずれも「見た目は動いているのでレビューでは気づかない」種類の劣化です。

そこで @next/playwrightinstant() ヘルパーが追加されました。遷移の瞬間に何が見えているべきかをテストとして固定できます。

import { expect, test } from '@playwright/test';
import { instant } from '@next/playwright';

test('商品タイトルは即座に表示される', async ({ page }) => {
  await page.goto('/products/shoes');

  // ネットワーク待ちをせずに、この時点で見えるべきものを検証する
  await instant(page, async () => {
    await page.click('a[href="/products/hats"]');
    await expect(page.locator('h1')).toContainText('Baseball Cap');
    await expect(page.getByText('Checking inventory...')).toBeVisible();
  });

  // ここから先はデータ到着後
  await expect(page.getByText('12 in stock')).toBeVisible();
});

パフォーマンスを「感覚」ではなくE2Eテストの成否として扱えるようにした点が本質で、CIに組み込めば体感速度のデグレをPR単位で止められます。

2-9. 移行について

既存プロジェクトを移行する場合、公式は Cache Components 移行ガイドと、AIエージェントに移行作業を任せるための Skill を用意しています。移行時には dynamic / revalidate / dynamicParams といった、Cache Components 下では使えなくなるセグメント設定の除去が必要になります。


3. 実験的機能

3-1. Rust製 React Compiler

React Compiler は、useMemouseCallback による手作業のメモ化を不要にするため、ビルド時にコンポーネントを自動最適化するツールです。

これまでは Babel(JavaScriptのトランスパイラ)経由でNode.js上で動かす必要がありました。実験的なRust移植版は Turbopackの内部で直接動作するため、コードを生成して再パースする往復が消えます。

// next.config.ts
const nextConfig: NextConfig = {
  reactCompiler: true,
  experimental: {
    turbopackRustReactCompiler: true,
  },
};

v0 のような大規模アプリでの計測では、next dev からページが表示可能になるまでの時間がコールドビルドで34%、ウォームビルドで46%短縮されたとのこと。

ただしこの数値はBabelを完全に外していることが前提です。他のトランスフォームでBabelを併用し続けている場合、効果は小さくなります。ここは自分の環境で条件を確認しないと期待外れになりやすい部分です。

3-2. ネットワーク耐性(useOffline

通常、ネットワークが切れるとソフトナビゲーション・データフェッチ・Server Action は例外を投げます。experimental.useOffline を有効にすると、Next.js はそれらをエラーにせず保留状態のまま維持し、接続が戻ったタイミングで再試行します。

'use client';
import { useOffline } from 'next/offline';

export function OfflineBanner() {
  const isOffline = useOffline();
  if (!isOffline) return null;
  return <div>オフラインです。再接続後に自動で再試行します。</div>;
}

前述の Partial Prefetching がルートのシェルをクライアントにキャッシュしているため、prefetch済みのルートはオフラインでもシェルまでは描画され、再接続時にデータが流し込まれます。モバイル回線で使われる業務アプリなどでは、かなり実用的な組み合わせになりそうです。


4. 実務での導入判断

ここからは公式の記述ではなく、筆者の整理です。

すぐ上げてよい部分(Part 1): 開発メモリ・ビルドキャッシュ・Node.jsストリーム・TypeScript 7 は、いずれもコード変更を伴わない性能改善です。16.x 系にいるなら上げない理由が薄い。ただしビルドキャッシュはCI側の設定が伴わないと効果が出ない点に注意。

慎重に評価すべき部分(Part 2): Instant Navigations は「速くなる魔法」ではありません。実態は、各ルートについて Stream / Cache / Block のどれかを開発者が選ぶことを強制する仕組みです。裏を返せば、レイアウトとデータ取得がきれいに分離されていないコードベースでは、移行時に大量の設計判断が発生します。効果が出るのも、そもそも遷移でサーバー待ちが発生しているアプリに限られます。全ページが完全に静的でCDN配信されているサイトでは、体感差はほぼ出ません。

したがって現実的な進め方は、いきなり全面適用ではなく、トラフィックの多い2〜3ルートで試し、Navigation Inspector で実際のシェルを確認し、instant() テストで固定する——という順序になると思います。

また測定は、開発機のノートPCではなくスロットリングした回線・中位のAndroid端末で行うべきです。ナビゲーション系の機能は、高速な開発環境では常に素晴らしく見えます。


まとめ

参考リンク