TECHSCORE BLOG

マーケティングSaaS を提供するシナジーマーケティングのエンジニアブログです。

Next.js 16を理解する - 見えるキャッシュ、速い開発、自由な選択

はじめに

2025年10月21日、Next.js 16が正式にリリースされました。

これまでのNext.jsは便利だけど、裏で何が起きているかわかりにくいフレームワークでした。特にキャッシュ周りは「なぜこのデータが古いのか?」「どうすればクリアできるのか?」といった疑問に答えるのが難しい状態でした。

Next.js 16は、この状況を大きく変えます。3つのテーマで理解しましょう。

見えるキャッシュ

Cache Componentsの導入により、キャッシュが明示的に制御できるようになりました。"use cache"ディレクティブを書いた場所だけがキャッシュされる、完全オプトイン方式です。デフォルトで毎回実行されるため、予測しやすくデバッグも容易です。

速い開発

Turbopackが正式版になり、デフォルトで使われます。本番ビルドは2-5倍、開発中のリロードは最大10倍高速化。加えてReact 19.2の新機能(View Transitions、useEffectEvent等)が使えるようになり、開発体験が大幅に向上しています。

自由な選択

Next.jsアプリの約70%は、Vercel以外で動作しています。Vercelはフレームワークに対してコードを書く設計を推進し、どこでも動くNext.jsを目指しています。Build Adapters APIの導入により、各プラットフォームが平等にNext.jsと統合できます。

この記事の構成

本記事では、Next.js 16の主要な変更点を実際のコード例を交えて解説します。第1部でキャッシュ、第2部でパフォーマンス、第3部で開発体験、第4部でアップグレード方法を扱います。

それでは、具体的な変更内容を見ていきましょう。


第1部 - 見えるキャッシュ - 透明性の向上

1. Cache Components - キャッシュが「見える化」された

これまでの問題 - 暗黙的なキャッシング

Next.js 15までのApp Routerには、「便利だけど謎」という側面がありました。

// Next.js 15 - いつキャッシュされるか予測困難
export default async function Page() {
  const data = await fetch('https://api.example.com/data');
  // このfetchはキャッシュされる? されない? わからない...
  return <div>{data.title}</div>;
}

特にキャッシュ周りは以下の問題がありました。

  • fetch()が勝手にキャッシュされる
  • いつキャッシュが効いているのかわからない
  • 「なぜ古いデータが表示されるのか」の追跡が困難

開発者としては、「便利だけど信頼しきれない」という状態でした。

"use cache"ディレクティブの登場

Next.js 16では、Cache Componentsという新しいモデルが導入されました。中心となるのが"use cache"ディレクティブです。

まず設定ファイルで有効化します。

// next.config.ts
const nextConfig = {
  cacheComponents: true,
};
export default nextConfig;

そして、キャッシュしたい場所に"use cache"と書きます。

// app/blog/[slug]/page.tsx
"use cache";

export default async function BlogPost({ params }) {
  const { slug } = await params;
  const post = await fetchPost(slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}

この"use cache"により以下の挙動となり、前述の問題が解決されました。

  • このページ全体がキャッシュされる
  • キャッシュキーはコンパイラが自動生成
  • 明示的で予測しやすい

デフォルト動作の変更 - 完全オプトイン

これが最も大きな変更点です。

Next.js 15まで Next.js 16から
デフォルト キャッシュされる 毎回サーバー実行
キャッシュ制御 no-storeで無効化 "use cache"で有効化
予測性 低い 高い

何も書かなければ毎回最新のデータを取得します。これは一般的なフルスタックフレームワークの挙動に近く、予測しやすくなりました。

Partial Prerendering(PPR)との統合

Cache ComponentsはPartial Prerendering(PPR)を完成させるピースでもあります。

PPRとは、1つのページ内で「静的な部分」と「動的な部分」を混在させる技術です。

// app/products/[id]/page.tsx
"use cache"; // ページ全体はキャッシュ対象

export default async function ProductPage({ params }) {
  const { id } = await params;
  const product = await fetchProduct(id); // キャッシュされる

  return (
    <div>
      {/* 商品情報は静的 */}
      <ProductInfo product={product} />

      {/* 在庫数は動的 */}
      <Suspense fallback={<LoadingStock />}>
        <StockStatus productId={id} />
      </Suspense>
    </div>
  );
}

// このコンポーネントはキャッシュしない
async function StockStatus({ productId }: { productId: string }) {
  const stock = await fetchRealTimeStock(productId); // 毎回取得
  return <p>在庫: {stock}個</p>;
}

動作の流れ

  1. ユーザーがページにアクセス
  2. 商品情報(キャッシュ済み)が即座に表示
  3. 在庫数(動的)はSuspenseで後から読み込み
  4. ユーザーは待たされない
sequenceDiagram
    autonumber
    actor User as ユーザー
    participant Browser as  ブラウザ
    participant Server as サーバー (Next.js)

    User->>Browser: 商品ページへアクセス

    rect rgb(235, 255, 235)
        Note over Server, Browser: 【フェーズ1:瞬時の表示 (静的)】
        Browser->>Server: 1. リクエスト送信

        Note right of Server: キャッシュ済みの<br/>「ページの骨組み」を取得<br/>(ヘッダー・本文・画像)
        Server-->>Browser: 2. 静的HTMLを即座に返却<br/>(商品情報 + ローディング表示)

        Browser->>User: ✅ 商品情報が表示される<br/>(在庫部分は🌀ぐるぐる)
        Note left of User: ユーザーはこの時点で<br/>コンテンツを読める!
    end

    rect rgb(255, 253, 230)
        Note over Server, Browser: 【フェーズ2:後から埋め込み (動的)】
        Server->>Server: 3. 在庫データをDBから取得...

        Server-->>Browser: 4. 在庫データをストリーミング送信

        Browser->>User: ✅ 🌀が「在庫: 3個」に置換される
    end

実際の使いどころ

Cache Componentsが特に有効なのは以下のような機能です。

1. ECサイト

  • 商品説明、画像 → キャッシュ
  • 価格、在庫、カート → 動的

2. ダッシュボード

  • レイアウト、グラフの骨組み → キャッシュ
  • 最新データ、ユーザー固有情報 → 動的

3. ブログ

  • 記事本文、画像 → キャッシュ
  • 閲覧数、コメント、いいね → 動的

4. SNS

  • タイムラインの構造 → キャッシュ
  • 投稿内容、通知 → 動的

「変わりにくい部分」と「変わりやすい部分」を明確に分離できるのが、Cache Componentsの最大の価値です。

2. 改良されたキャッシングAPI - より細かい制御が可能に

Cache Componentsと合わせて、キャッシュを操作するAPIも改良されました。3つの新しい/更新されたAPIを理解しましょう。

revalidateTag()

revalidateTag()は、特定のタグをつけたキャッシュを再検証(更新)する関数です。Next.js 16では、第2引数としてcacheLifeプロファイルの指定が必須になりました。

import { revalidateTag } from 'next/cache';

// ✅ 推奨: 'max'プロファイルを使用
revalidateTag('blog-posts', 'max');

// 用途に応じて選べるビルトインプロファイル
revalidateTag('news-feed', 'hours');  // 1時間ごと
revalidateTag('analytics', 'days');   // 1日ごと

// カスタムで秒数指定も可能
revalidateTag('products', { expire: 3600 });

// ⚠️ これは非推奨(動くけど警告が出る)
revalidateTag('blog-posts');

この変更により、Stale-While-Revalidate(SWR)動作が明示的になりました。

SWRの仕組みは、

  1. ユーザーには古いキャッシュをすぐに返す(待たせない)
  2. その裏で新しいデータを取得
  3. 次回のアクセス時には新しいデータを表示

つまり、ユーザーは待たされることなく、データが徐々に新鮮なものに更新されます。

revalidateTag() を呼ぶと、そのタグのキャッシュが「stale(古い)」とマークされ、次のリクエストでこのSWR動作が発動します。第2引数の cacheLife プロファイルは、再検証後のキャッシュ寿命を指定するもので、「再検証したデータをどのくらいの期間有効とするか」を制御します。

使いどころ

'use server';
import { revalidateTag } from 'next/cache';

// ブログ記事を公開
export async function publishPost(post: Post) {
  await db.posts.create(post);

  // 'max'なら長時間キャッシュ、裏で更新
  revalidateTag('blog-posts', 'max');

  // ユーザーはすぐにページを見られる
  // 新しい記事は次のアクセスで表示される
}

updateTag() API

updateTag()は、Next.js 16で新しく追加されたServer Actions専用のAPIです。

'use server';
import { updateTag } from 'next/cache';

export async function updateUserProfile(userId: string, profile: Profile) {
  // 1. データベースを更新
  await db.users.update(userId, profile);

  // 2. キャッシュを即座に無効化
  updateTag(`user-${userId}`);

  // 3. ユーザーには更新後のデータがすぐ表示される
}

revalidateTag()との違い

revalidateTag() updateTag()
使える場所 Server Actions / Route Handlers Server Actionsのみ
動作 SWR(裏で更新) 即座に無効化
ユーザー体験 次回から新しいデータ すぐに新しいデータ
待ち時間 なし(古いデータを即表示) あり(新しいデータを待つ)
用途 定期的な更新 ユーザー操作後の即時反映

使い分けの例

// ❌ Route HandlerでupdateTagは使えない
export async function POST() {
  updateTag('posts'); // エラー!

  // ⭕ revalidateTagを使う
  revalidateTag('posts', 'max');
}

// ⭕ Server ActionならupdateTagが使える
export async function createPost(formData: FormData) {
  'use server';

  await db.posts.create({
    title: formData.get('title'),
    content: formData.get('content'),
  });

  // ユーザーは自分の投稿をすぐ見たい
  updateTag('posts');
}

refresh() API

refresh()は、キャッシュされていないデータだけを更新するAPIです。 このAPIもNext.js 16で新しく追加され、Server Actions専用となります。

'use server';
import { refresh } from 'next/cache';

export async function markNotificationAsRead(notificationId: string) {
  // 通知を既読にする
  await db.notifications.markAsRead(notificationId);

  // キャッシュには触らず、動的データだけ更新
  refresh();
}

特徴

  • ページの骨組み(キャッシュ済み)はそのまま
  • 通知数やステータス(動的)だけ更新
  • 無駄なキャッシュ削除を避けられる

実際の使い分け

プロジェクトでは、こんな風に使い分けます。

// 1. ブログ記事の公開 → revalidateTag()
// 理由: 裏で更新すればOK、急ぎではない
export async function publishBlogPost(post: Post) {
  await db.posts.create(post);
  revalidateTag('blog-posts', 'max');
}

// 2. プロフィール編集 → updateTag()
// 理由: ユーザーはすぐに変更を確認したい
export async function updateProfile(userId: string, data: ProfileData) {
  await db.users.update(userId, data);
  updateTag(`user-${userId}`);
}

// 3. 通知の既読 → refresh()
// 理由: ページ全体は変えず、通知バッジだけ更新
export async function readNotification(notificationId: string) {
  await db.notifications.markAsRead(notificationId);
  refresh();
}

// 4. Webhook経由の更新 → revalidateTag()
// 理由: Route Handlerで使える、即時性は不要
export async function POST(request: Request) {
  const { tag } = await request.json();

  // 外部CMSからの更新通知など
  revalidateTag(tag, 'max');

  return Response.json({ revalidated: true });
}

迷ったら

  • ユーザーの操作直後に反映したいupdateTag()
  • バックグラウンドで更新すればOKrevalidateTag()
  • 動的データだけ更新したいrefresh()

この3つのAPIを適切に使い分けることで、ユーザー体験とパフォーマンスを両立できます。


第2部 - 速い開発 - パフォーマンスの飛躍

4. Turbopack正式版 - 開発速度が爆上がり

正式版になった背景

Turbopackは、Rustで書き直された新しいバンドラーです。長らくベータ版として提供されてきましたが、Next.js 16でついに正式版となり、デフォルトのバンドラーになりました。

Next.js 15.3以降の実際のデータを見てみましょう。

  • 開発セッションの50%以上がTurbopackを使用
  • 本番ビルドの20%以上がTurbopackで実行されている

つまり、すでに多くの開発者が実戦投入していて、安定性が証明されているのです。

具体的なパフォーマンス向上

公式発表では、以下のパフォーマンス改善が報告されています。

  • 本番ビルドが2〜5倍高速化
  • Fast Refresh(開発中のリロード)が最大10倍高速化

この「体感できる速さ」が、Turbopackの最大の魅力です。コードを書いて、保存して、ブラウザで確認する。この一連の流れが劇的に速くなります。

設定不要でデフォルト化

Next.js 16では、何もしなければTurbopackが使われます。

以前のpackage.json

{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack"
  }
}

Next.js 16では

{
  "scripts": {
    "dev": "next dev",
    "build": "next build"
  }
}

--turbopackフラグは不要になりました。シンプルです。

webpackを使い続けたい場合

Next.js 16でもwebpackは使えます。

# 開発
next dev --webpack

# ビルド
next build --webpack

ただし注意点

  • カスタムwebpack設定がある場合、ビルドが失敗する可能性
  • Turbopackでは一部のwebpack loaderが動かない

推奨アプローチ

  1. まずはwebpackのまま動作確認
  2. 問題なければTurbopackに切り替え
  3. エラーが出たら段階的に移行

なぜこんなに速いのか??

Turbopackが速い理由

1. Rustによる実装

TurbopackはRustで書かれており、CやC++と同等のパフォーマンスを発揮しつつ、安全性も確保しています。 Rustはメモリアクセスの効率が高く、並列処理や最適化に向いているため、従来のJavaScriptベースのバンドラ(Webpackなど)と比べて高速動作が可能です。

2. インクリメンタルバンドル

変更のあったコードだけを再ビルドする「インクリメンタルバンドル」を採用します。 これにより、フルリビルドを避け、ビルド時間を最小限に抑えています。

3. キャッシング戦略

Turbopackはディスクキャッシュも活用し、前回のビルド結果や依存関係を記録します。 これにより、何度も同じ処理を行う必要がなくなるため、再起動や再ビルドでもスピードが維持されます。

開発体験への影響は大きいです。「保存したのにまだ反映されない...」というストレスが軽減されるでしょう。

5. React 19.2の新機能 - より使いやすく、より速く

Next.js 16はReact 19.2をサポートしています。React 19.2では、開発者体験を向上させる新機能がいくつか追加されました。ここでは特に実用的な2つの機能を紹介します。

View Transitions - ページ遷移をスムーズに

View Transitionsは、要素の変更時にスムーズなアニメーションを追加できる機能です。つまり、要素が表示・非表示になる時や、位置が変わる時に、突然パッと変わるのではなく、フェードインやスライドのような滑らかな動きをつけられるということです。

React 19.2では、<ViewTransition>コンポーネントを使うことで、この機能を簡単に実装できます。アニメーションさせたい要素を<ViewTransition>で囲むだけです。

コード例

'use client';

import { ViewTransition, useState, startTransition } from 'react';

function Item() {
  return (
    <ViewTransition>
      <div className="card">
        <h2>商品情報</h2>
        <p>ここに商品の詳細が表示されます</p>
      </div>
    </ViewTransition>
  );
}

export default function ProductPage() {
  const [showItem, setShowItem] = useState(false);

  return (
    <>
      <button onClick={() => {
        startTransition(() => {
          setShowItem(!showItem);
        });
      }}>
        {showItem ? '閉じる' : '商品を見る'}
      </button>
      {showItem && <Item />}
    </>
  );
}

重要なのは、状態の変更をstartTransitionで囲むことです。これにより、Reactが自動的にView Transitionを起動し、要素の表示・非表示がアニメーションされます。デフォルトでは滑らかなクロスフェード(フェードイン・フェードアウト)が適用されます。

useEffectEvent() - イベント処理がシンプルに

useEffectEvent()は、Effect内で非リアクティブなロジックを扱うための新しいフックです。「最新のpropsやstateを参照したいけど、それが変わってもEffectを再実行したくない」という長年の課題を解決します。

これまで、Effect内で最新のstateにアクセスしたい場合、そのstateを依存配列に含める必要がありました。しかし、そうするとstateが変わるたびにEffectが再実行されてしまいます。特に、WebSocket接続やイベントリスナーの設定など、頻繁に再実行したくない処理で問題になっていました。

コード例

'use client';

import { useState, useEffect, useEffectEvent } from 'react';

export default function ChatRoom({ roomId }: { roomId: string }) {
  const [message, setMessage] = useState('');

  // useEffectEventで定義すると、関数は再生成されないが最新のmessageを参照できる
  const onConnected = useEffectEvent(() => {
    console.log(`Connected to ${roomId}`);
    console.log(`Latest message: ${message}`); // 最新のmessageにアクセス可能
  });

  // useEffectの依存配列にonConnectedを含める必要がない
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.on('connected', onConnected);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // messageが変わってもエフェクトは再実行されない

  return (
    <div>
      <input value={message} onChange={(e) => setMessage(e.target.value)} />
    </div>
  );
}

従来はuseCallbackuseRefを組み合わせるなど、複雑な回避策が必要でした。 useEffectEvent()を使うことで、依存配列の管理から解放され、コードの意図が明確になります。つまり、Effect Eventは「Effectの中で呼べるイベントハンドラー」のような存在です。常に最新のpropsとstateを参照しますが、それ自体はリアクティブではありません。

Activity - 状態を保持したまま非表示にする

<Activity>コンポーネントは、UIの一部を非表示にしながらも、その内部状態を保持できる機能です。つまり、コンポーネントをアンマウント(削除)せずに、display: noneで非表示にするだけなので、再度表示した時に以前の状態が復元されます。

これまで、コンポーネントを条件付きでレンダリングする場合、表示・非表示を切り替えると内部状態が失われていました。例えば、タブを切り替えるとフォームの入力内容が消えてしまう、といった問題です。

コード例

'use client';

import { Activity, useState } from 'react';

export default function TabExample() {
  const [activeTab, setActiveTab] = useState('home');

  return (
    <>
      <button onClick={() => setActiveTab('home')}>Home</button>
      <button onClick={() => setActiveTab('contact')}>Contact</button>

      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <Home />
      </Activity>
      <Activity mode={activeTab === 'contact' ? 'visible' : 'hidden'}>
        <Contact />
      </Activity>
    </>
  );
}

function Contact() {
  return (
    <div>
      <p>メッセージを送信してください</p>
      <textarea placeholder="ここに入力..." />
    </div>
  );
}

この例では、Contactタブでテキストエリアにメッセージを入力した後、Homeタブに切り替えて再度Contactタブに戻っても、入力内容が保持されています。<Activity>が内部状態を保存してくれるからです。

これらの新機能により、React 19.2ではより洗練されたユーザー体験を、シンプルなコードで実現できるようになりました。Next.js 16を使えば、これらの機能をすぐに活用できます。

6. React Compiler - 自動で最適化してくれる

Next.js 16では、React Compiler 1.0のサポートが正式版(stable)になりました。React Compilerは、ビルド時にコンポーネントを自動的に最適化するツールです。つまり、これまで手動で書いていたuseMemouseCallbackReact.memoといったメモ化のコードを、コンパイラが自動で挿入してくれるということです。

React Compilerとは

React Compilerは、Metaで本番環境で使われてきた実績のあるツールで、コンポーネントやフックを自動的にメモ化します。従来、Reactでは親コンポーネントが再レンダリングされると、子コンポーネントもすべて再レンダリングされていました。これを防ぐために、開発者はReact.memoでコンポーネントをラップしたり、useMemoで値をメモ化したりする必要がありました。

React Compilerはこのプロセスを自動化します。コンパイラがコードを解析し、安全にメモ化できる箇所を判断して、自動的に最適化コードを挿入します。手動でメモ化する必要がなくなるため、コードがシンプルになり、メンテナンスも楽になります。

有効化方法

Next.js 16では、React Compilerはデフォルトでは無効になっています。有効化するには、 next.config.tsで設定を追加します。

設定例

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  reactCompiler: true,
};

export default nextConfig;

それとReact Compiler プラグインの最新バージョンをインストールします。

npm install -D babel-plugin-react-compiler@latest

これで、React Compilerが有効になります。Next.jsは独自の最適化を施しており、JSXやReact Hooksを含むファイルだけにコンパイラを適用するため、ビルド時間への影響を最小限に抑えています。

Before/After - コードの変化

React Compilerを使うと、コードがどう変わるのでしょうか?実際には、コードを変更する必要はありません。コンパイラが裏で自動的に最適化してくれます。

従来の書き方(手動メモ化)

import { memo, useCallback, useMemo } from 'react';

const ExpensiveList = memo(function ExpensiveList({ items, onItemClick }) {
  const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);

  const handleClick = useCallback((id) => {
    onItemClick(id);
  }, [onItemClick]);

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

React Compilerを使った書き方

function ExpensiveList({ items, onItemClick }) {
  const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));

  const handleClick = (id) => {
    onItemClick(id);
  };

  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

コンパイラが有効な場合、下のシンプルなコードでも、上の手動メモ化したコードと同等のパフォーマンスが得られます。コンパイラが自動的にmemouseMemouseCallback相当の最適化を適用してくれるからです。

注意点

React Compilerは便利ですが、いくつか注意点があります。

1. ビルド時間の増加

React CompilerはBabelプラグインとして動作するため、有効にするとビルド時間が長くなります。Next.jsは最適化を施していますが、それでも若干の影響があります。公式では「開発時およびビルド時のコンパイル時間が長くなることが予想されます。」と明記されています。

2. デフォルトでは無効

Next.js 16では、React Compilerはデフォルトで無効です。Vercelチームは、さまざまなアプリケーションタイプでのビルドパフォーマンスデータを収集している段階です。安定版になったとはいえ、まだ慎重に導入を進めている状況です。

3. 効果に差がある

すべてのコンポーネントで劇的な効果が出るわけではありません。シンプルなコンポーネントでは効果が大きい一方、複雑なコンポーネントでは期待したほどの改善が得られない場合もあります。実際のプロジェクトでの効果は、コードベースの状態や、Reactのルール(Rules of React)をどれだけ守っているかに依存します。

4. 手動メモ化との併用

既存のコードにuseMemouseCallbackが残っていても問題ありません。コンパイラはそれらを尊重します。新しいコードでは基本的にコンパイラに任せ、Effect の依存配列で細かい制御が必要な場合にのみ、useMemouseCallbackを使うことが推奨されています。

React Compilerは、Reactの未来を示す重要な機能です。手動メモ化から解放され、より直感的なコードを書けるようになります。Next.js 16で正式版になったことで、本格的な導入を検討できる段階に入りました。


第3部: 開発体験の向上

7. ルーティングとナビゲーションの強化 - より速く、より賢く

Next.js 16では、ルーティングとナビゲーションシステムが完全に刷新されました。ページ遷移がより軽量で高速になり、ユーザー体験が大幅に向上しています。重要なのは、コードを変更する必要がないということです。これらの改善は自動的に適用されます。

レイアウトの重複排除

従来、複数のリンクをプリフェッチする際、各リンクごとに共有レイアウトを別々にダウンロードしていました。例えば、50個の商品リンクがあるページでは、同じレイアウトを50回ダウンロードしていたのです。

Next.js 16では、レイアウトは1回だけダウンロードされます。50個の商品リンクがあっても、共有レイアウトは1回だけ取得され、ネットワーク転送量が劇的に削減されます。

この仕組みをフローにすると、以下のようになります。

sequenceDiagram
    autonumber
    actor User as ユーザー
    participant Browser as  ブラウザ (画面)
    participant Server as  サーバー (Next.js)

    Note over Browser: 【現在】<br/>共通レイアウト + ページAを表示中

    User->>Browser: 「ページB」のリンクをクリック!

    rect rgb(230, 245, 255)
        Note over Browser: 判定プロセス<br/>「レイアウトは同じだな...<br/>中身だけ取りに行こう」

        Browser->>Server: 1. ページBの「差分データ」だけください
        Note right of Browser: ⚠️ レイアウトのHTMLは<br/>リクエストしません!

        Server-->>Browser: 2. ページBのデータだけ返却
    end

    Browser->>Browser: 3. 画面の更新処理
    Note over Browser: ・共通レイアウト ➡️ 維持 (再レンダリングなし)<br/>・ページA ➡️ 削除<br/>・ページB ➡️ 表示

    Note over Browser: 【完了】<br/>共通レイアウト + ページBを表示中

つまり、ECサイトの商品一覧ページや、ダッシュボードのような共通レイアウトを持つアプリで、大幅な高速化が期待できます。

インクリメンタルプリフェッチ

Next.js 16では、プリフェッチの仕組みも改善されました。従来はページ全体をプリフェッチしていましたが、新しいバージョンではキャッシュにない部分だけをプリフェッチします。

さらに、以下のような賢い動作をします。

  • ビューポートから外れたリンク: プリフェッチリクエストをキャンセル
  • ホバーや再表示: リンクにカーソルを合わせたり、再度表示されたりした時に優先的にプリフェッチ
  • データの無効化: キャッシュが無効化されたら、自動的に再プリフェッチ

トレードオフ - 理解しておくべきこと

これらの改善には、わずかなトレードオフがあります。プリフェッチのリクエスト数は増える可能性がありますが、合計の転送量は大幅に減少します。公式では「ほぼすべてのアプリケーションにとって、これは正しいトレードオフだと考えています」と述べています。

具体的には、50個のリンクがある場合、従来は50回の大きなリクエストでしたが、新しいバージョンでは100回の小さなリクエストになる可能性があります。しかし、合計のデータ量は10分の1になる、といったイメージです。

体感できる効果

これらの改善により、特に以下のようなアプリで効果を実感できます。

  • ECサイト: 商品一覧から詳細ページへの遷移が高速化
  • ダッシュボード: タブやページ間の切り替えがスムーズに
  • CMSや管理画面: 大量のリンクがあるページでの操作が快適に

ルーティングの改善は、Cache Componentsとも連携して動作するため、今後さらに最適化が進む予定です。Next.js 16にアップグレードするだけで、これらの恩恵を自動的に受けられます。

8. proxy.tsとDevTools MCP - 開発体験の改善

Next.js 16では、開発体験を向上させる2つの重要な変更があります。1つはmiddleware.tsからproxy.tsへの名称変更、もう1つはDevTools MCPの導入です。

proxy.ts - 名前が変わった理由

Next.js 16では、middleware.tsproxy.tsに名称変更されました。関数名もproxyに変更が必要です。基本的なロジックはそのままですが、ランタイムがNode.jsに固定される点に注意してください(Edge Runtimeは非サポート)。

移行方法

// 従来: middleware.ts
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

// Next.js 16: proxy.ts
export function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

ファイル名をmiddleware.tsからproxy.tsに変更し、エクスポートする関数名をmiddlewareからproxyに変更するだけです。ロジックはそのまま使えます。

なぜ名前が変わったのか?

「middleware」という名前は、ExpressJSなどの従来のフレームワークと混同されやすく、誤用を招いていました。proxy.tsという名前に変更することで、この機能が「ネットワーク境界でのルーティングとリクエスト処理」に特化していることを明確にしています。つまり、nginxやCDNのエッジ関数のように、トラフィックをルーティングする役割だということです。

重要な変更点

  • ランタイムがNode.jsに固定: proxy.tsはNode.jsランタイムで動作し、設定変更はできない
  • Edgeランタイムは非対応: Edgeランタイムを使いたい場合は、従来のmiddleware.tsを引き続き使用する (※ 将来的には削除される予定)

この変更により、Next.jsはセキュリティとアーキテクチャの面で、より明確な方向性を示しています。認証やビジネスロジックは、proxy.tsではなく、Route HandlersやServer Actionsで処理することが推奨されています。

DevTools MCP - AIがNext.jsを理解する

Next.js 16では、Model Context Protocol(MCP)統合による、AI支援デバッグ機能が導入されました。MCPとは、AIエージェントやコーディングアシスタントがアプリケーションと対話するための標準化されたインターフェースです。

MCPができること

Next.js DevTools MCPは、AIエージェントに以下の情報を提供します。

  • Next.jsの知識: ルーティング、キャッシング、レンダリングの仕組みを理解
  • 統合ログ: ブラウザとサーバーのログを一元化、コンテキスト切り替えが不要
  • 自動エラーアクセス: 詳細なスタックトレースを手動でコピーせずに取得
  • ページの認識: アクティブなルートのコンテキストを理解

セットアップ方法

Next.js 16以降では、開発サーバーに組み込みのMCPエンドポイント(http://localhost:3000/_next/mcp)が自動的に有効になっています。

ClaudeやCursorなどのAIコーディングアシスタントで使うには、プロジェクトルートに.mcp.jsonファイルを作成します。

{
  "mcpServers": {
    "next-devtools": {
      "command": "npx",
      "args": ["-y", "next-devtools-mcp@latest"]
    }
  }
}

これだけで、AIエージェントがNext.jsアプリケーションの内部状態にリアルタイムでアクセスできるようになります。

実際の使い方

設定後は、自然言語でAIに指示できます。

  • 「このページのエラーを修正して」→ AIが現在のエラーを検出し、修正案を提示
  • 「Next.js 16にアップグレードして」→ Codemodを実行し、移行をサポート
  • 「Cache Componentsを有効にして」→ 設定ファイルを更新し、必要な変更を案内

AIがアプリケーションの構造、現在の設定、ルート、エラー状態を理解しているため、的確な提案ができます。

どんな時に役立つか

  • エラーのデバッグ: スタックトレースをコピーせずに、AIが直接エラーを分析
  • アップグレード作業: バージョンアップ時の移行作業を自動化
  • 学習: Next.jsの仕組みを理解しながら開発できる
  • 生産性向上: コンテキストを理解したAIの提案で、開発スピードが向上

DevTools MCPは、まだ初期段階ですが、AIツールがアプリケーションの仕組みを理解することで、単なる反応的なツールから、予測的で理解力のあるアシスタントへと進化しています。Next.js 16では、この新しい開発体験を試すことができます。

9. Build Adapters API (アルファ版) - どこでも動くNext.jsへ

インフラ選択の「見えない壁」を壊す

「Next.jsはVercel以外だとデプロイが面倒」 そう感じたことがある人は多いはずです。これまで、AWSやCloudflare、Docker環境などにデプロイするには、コミュニティが独自に開発したツール(OpenNextなど)を使ったり、ビルド成果物の .next フォルダの構造を解析して自力で調整したりする必要がありました。

Next.js 16では、ついにこの問題に対する公式の回答 Build Adapters API がアルファ版として導入されました。

公式が提供する「変換のインターフェース」

これは、Next.jsのビルドプロセスと、各インフラストラクチャをつなぐための公式APIです。インフラプロバイダーやツールの作者は、このAPIを使うことで、Next.jsのビルド出力を特定の環境(AWS Lambda, Cloudflare Workers, Dockerコンテナなど)に合わせて最適化・変換できます。

何が変わるのか?

ハック不要: Next.jsの内部構造をリバースエンジニアリングする必要がなくなります。

公式サポート: アップグレード時にデプロイパイプラインが壊れるリスクが減ります。

選択の自由: 将来的には、next.config.ts にアダプターを1行追加するだけで、あらゆるインフラに最適化されたビルドが可能になります。

// next.config.ts のイメージ
import type { NextConfig } from 'next';

const nextConfig = {
  experimental: {
    // 実際には、利用するプラットフォームが提供するアダプターのパスを指定します
    adapterPath: require.resolve('./my-adapter.js'),
  },
};
export default nextConfig;

まだアルファ段階ですが、これはVercel社が掲げる「Framework-defined infrastructure(フレームワーク定義のインフラ)」を一歩進め、「どのインフラで動かすか」の決定権を完全に開発者に委ねるという強いメッセージです。


第4部: 実践

10. アップグレード方法 - 安全に移行するために

Next.js 16へのアップグレードは、Codemodを使えば比較的スムーズに進められます。ここでは、安全に移行するための手順と、主な破壊的変更について解説します。

Codemodを使う

最も簡単な方法は、公式のCodemodを使うことです。

npx @next/codemod@canary upgrade latest

このコマンドは以下の作業を自動的に行います。

  • next.config.jsのTurbopack設定を更新
  • middleware.tsproxy.tsに移行
  • 安定化されたAPIからunstable_プレフィックスを削除
  • experimental_ppr設定を削除

手動でアップグレードする場合は、以下のコマンドで最新版をインストールします。

npm install next@latest react@latest react-dom@latest

TypeScriptを使っている場合は、型定義も更新してください。

npm install -D @types/react@latest @types/react-dom@latest

環境要件の確認

Next.js 16では、最低要件が引き上げられています。アップグレード前に必ず確認してください。

  • Node.js: 20.9.0以上(Node.js 18はサポート終了)
  • TypeScript: 5.1.0以上
  • ブラウザ: Chrome 111+、Edge 111+、Firefox 111+、Safari 16.4+

Node.jsのバージョンは以下のコマンドで確認できます。

node -v

v18.x.xと表示された場合は、Node.js 20以上にアップグレードが必要です。

主な破壊的変更

1. 非同期params/searchParams(重要)

Next.js 15で導入された非同期Request APIが、Next.js 16では完全に必須になりました。 paramssearchParamsに同期的にアクセスすることはできません。

変更前

export default function Page({ params, searchParams }) {
  const { slug } = params; // エラー
  const query = searchParams; // エラー
}

変更後

export default async function Page({ params, searchParams }) {
  const { slug } = await params; // 正しい
  const query = await searchParams; // 正しい
}

Async Dynamic API への移行には、codemod をご利用ください。

2. middleware.ts → proxy.ts

middleware.tsproxy.tsに名称変更されました。ファイル名と関数名を変更します。

# ファイル名を変更
mv middleware.ts proxy.ts
// 関数名も変更
export function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

3. 削除された機能

以下の機能は完全に削除されました。

  • AMPサポート: useAmpフック、amp設定など
  • next lintコマンド: Biome またはESLint CLIを直接使用
  • serverRuntimeConfig/publicRuntimeConfig: 環境変数を使用

特にnext lintが削除されたため、package.jsonscriptsを更新する必要があります。

{
  "scripts": {
    "lint": "eslint ." // next lintではなく、eslintを直接実行
  }
}

段階的移行のステップ

大規模なプロジェクトでは、以下の手順で段階的に移行することをおすすめします。

ステップ1: Codemodの実行

Codemodを実行して、機械的に変更できる部分を処理します。

npx @next/codemod@canary upgrade latest

ステップ2: 手動での修正

Codemodでカバーされない部分を手動で修正します。特に以下をチェックしてください。

  • 非同期params/searchParamsの対応
  • カスタムwebpack設定の確認(Turbopackとの互換性)
  • 画像最適化の設定確認

ステップ3: テストの実行

すべてのテストを実行し、問題がないか確認します。

npm run test
npm run build
npm run start

AIアシスタントを活用する

Next.js 16では、DevTools MCPを使ってAIアシスタントにアップグレードを手伝ってもらうこともできます。

.mcp.jsonを作成し、以下の設定を追加します。

{
  "mcpServers": {
    "next-devtools": {
      "command": "npx",
      "args": ["-y", "next-devtools-mcp@latest"]
    }
  }
}

設定後、自然言語で指示できます。

Next Devtools, help me upgrade my Next.js app to version 16

AIがCodemodを実行し、手動での対応が必要な箇所を案内してくれます。

Next.js 16へのアップグレードは、自動ツールを活用することで比較的スムーズに進められます。環境要件と破壊的変更を理解し、段階的に進めることで、安全に最新版に移行できます。

11. まとめ

Next.js 16は、パフォーマンス、透明性、自由度という3つの軸で大きく進化しました。

Next.jsの方向性

Next.js 16が示す3つの方向性は、今後のWeb開発のトレンドそのものです。

透明性の重視: 暗黙的なキャッシュから、明示的な制御へ。Cache Componentsにより、開発者がコードを見れば何が起きているか一目で分かる設計へと回帰しました。

ベンダーロックインからの解放: Build Adapters APIの導入により、Vercel以外のプラットフォームでも最適に動作する道が公式に開かれました。Next.jsアプリの約70%はすでにVercel以外で稼働しているという現実に対し、フレームワーク側が正式に歩み寄った形です。

AIとの統合: DevTools MCPの搭載は、AIが単なるコード生成ツールを超え、開発者のデバッグや運用を深く理解し支援する未来を提示しています。

Next.js 16は、単なる機能追加にとどまらず、これからの10年を見据えた「開発のあり方」を再定義するリリースと言えるでしょう。

参考資料

hiraokuのプロフィール画像
hiraoku

暇があったらクライミングしているフロントエンドエンジニアです。

シナジーマーケティング株式会社では一緒に働く仲間を募集しています。