ご注意: 私の日本語はまだ十分ではないため、翻訳ツールを使用して記事を作成しています。表現が不自然な箇所があれば、ご容赦ください。
📑 目次
Enhanced Routing
Enhanced Routingとは?
Next.js 16は、ルーティングとナビゲーションシステムを完全に改善し、ページ遷移をより高速で軽量にします。
主な2つの改善点
1. Layout Deduplication (レイアウトの重複排除)
以前の問題:
- 50個の商品リンクがあるページ → レイアウトを50回prefetch
- 重複データを大量にダウンロード
Next.js 16の解決策:
- 共通レイアウトは1回だけダウンロード
- ネットワーク転送量を大幅に削減
実例:
// app/products/layout.tsx - このレイアウトは1回だけ読み込まれる
export default function ProductsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<ProductHeader />
<ProductSidebar />
<main>{children}</main>
</div>
);
}
// app/products/page.tsx - 50個の商品リンクがあるページ
export default function ProductsPage() {
return (
<div>
{products.map((product) => (
<Link key={product.id} href={`/products/${product.id}`}>
{product.name}
</Link>
))}
</div>
);
}
結果:
- 以前: レイアウトを50回読み込み(各リンクごとに1回)
- 現在: レイアウトは1回だけ読み込み、残り49回はキャッシュから取得
2. Incremental Prefetching (段階的なPrefetch)
スマートな仕組み:
Next.js 16は、キャッシュにないものだけをprefetchします:
- ✅ リンクがビューポートから外れたらリクエストをキャンセル
- ✅ ホバー時やリンクがビューポートに戻ったら優先的にprefetch
- ✅ データが無効化されたら再prefetch
- ✅ Cache Componentsとシームレスに連携
例:
// ユーザーがスクロールダウン → リンクが表示 → Prefetch
// ユーザーがスクロールアップ → リンクが非表示 → Prefetchをキャンセル
// ユーザーが再びスクロールダウン → リンクが表示 → 再Prefetch
export default function ProductList() {
return (
<div>
{/* このリンクは表示されている時だけprefetchされる */}
<Link href='/products/1'>Product 1</Link>
<Link href='/products/2'>Product 2</Link>
<Link href='/products/3'>Product 3</Link>
{/* ... 50 products */}
</div>
);
}
トレードオフ:
- リクエスト数は増える
- しかし転送総量は大幅に削減される
開発者へのメリット
コード変更不要 - すべて自動:
// コードはそのまま
<Link href="/dashboard">Dashboard</Link>
<Link href="/products/123">Product</Link>
// Next.js 16が自動的に最適化:
// - スマートなprefetch
// - 重複の排除
// - 不要なリクエストのキャンセル
Cache Componentsとの組み合わせ
// キャッシュされたレイアウト - 1回だけ読み込まれる
"use cache";
export default async function ProductLayout({ children }) {
const categories = await getCategories(); // キャッシュ済み
return (
<div>
<CategoryNav categories={categories} />
{children}
</div>
);
}
// 動的ページ - 段階的なprefetch
export default async function ProductPage({ params }) {
const product = await getProduct(params.id); // 最新データ
return <ProductDetails product={product} />;
}
結果:
- 超高速なナビゲーション
- データは常に最新
- 帯域幅の最適化
🔧 Improved Caching APIs
Improved Caching APIsとは?
Next.js 16は、キャッシュの動作をより明確に制御できるよう、キャッシュAPIを改善しました。
3つの主要API
1. revalidateTag() (更新)
Stale-While-Revalidate (SWR)を有効にするため、第2引数としてcacheLife profileが必要になりました:
import { revalidateTag } from "next/cache";
// ✅ 組み込みのcacheLife profileを使用(推奨: 'max')
revalidateTag("blog-posts", "max");
// または他のprofile
revalidateTag("news-feed", "hours");
revalidateTag("analytics", "days");
// またはカスタムインラインオブジェクト
revalidateTag("products", { expire: 3600 });
// ⚠️ 非推奨 - 1引数の形式
revalidateTag("blog-posts");
動作方法:
- ユーザーはキャッシュされたデータを即座に受け取る
- Next.jsはバックグラウンドで再検証
- 静的コンテンツに適し、eventual consistencyを受け入れる
実例:
// タグ付きでデータを取得
const posts = await fetch("https://api.example.com/posts", {
next: { tags: ["blog-posts"] },
});
// 新しい投稿がある場合、無効化
revalidateTag("blog-posts", "max");
// ユーザーは古いキャッシュを見る → バックグラウンドで更新 → 次回は新しいデータを見る
2. updateTag() (新規) - Read-Your-Writes
Server Actions専用のAPIで、read-your-writes semanticsを提供します:
"use server";
import { updateTag } from "next/cache";
export async function updateUserProfile(userId: string, profile: Profile) {
await db.users.update(userId, profile);
// キャッシュを無効化し、即座に更新
// ユーザーは変更を即座に確認できる
updateTag(`user-${userId}`);
}
使用タイミング:
- フォーム、ユーザー設定
- 変更を即座に確認する必要があるすべてのインタラクティブ機能
- ユーザーが「更新を即座に確認できる」ことを期待する場合
revalidateTagとの比較:
| API | 動作 | 使用ケース |
|---|---|---|
revalidateTag() |
SWR - 古いキャッシュ → バックグラウンド更新 | 静的コンテンツ、ブログ、商品 |
updateTag() |
Read-your-writes - 即座に更新 | ユーザープロフィール、設定、フォーム |
実例:
// ❌ 間違い: フォームにrevalidateTagを使用
export async function updateProfile(data: Profile) {
await db.update(data);
revalidateTag('profile', 'max'); // ユーザーは古いデータを見る!
}
// ✅ 正しい: updateTagを使用
export async function updateProfile(data: Profile) {
await db.update(data);
updateTag('profile'); // ユーザーは新しいデータを即座に確認できる!
}
3. refresh() (新規) - Uncached Data Only
Server Actions専用のAPIで、キャッシュされていないデータのみを更新します:
"use server";
import { refresh } from "next/cache";
export async function markNotificationAsRead(notificationId: string) {
// データベースを更新
await db.notifications.markAsRead(notificationId);
// 通知カウントを更新(キャッシュされていないデータ)
// キャッシュされたコンテンツには影響しない
refresh();
}
使用タイミング:
- 動的データの更新: 通知カウント、ライブメトリクス、ステータスインジケーター
- キャッシュされたコンテンツ(ページシェル、静的コンテンツ)はそのまま維持
- クライアント側の
router.refresh()と補完的
実例:
// 通知カウント付きヘッダー(キャッシュなし)
export default async function Header() {
const count = await getNotificationCount(); // キャッシュなし
return <NotificationBadge count={count} />;
}
// メニュー付きサイドバー(キャッシュ済み)
("use cache");
export default async function Sidebar() {
const menu = await getMenu(); // キャッシュ済み
return <Nav items={menu} />;
}
// Server Action
export async function clearNotification() {
await db.clear();
refresh(); // カウントのみ更新、サイドバーには影響しない
}
Next.js 15からの移行
以前 (Next.js 15):
revalidateTag("blog-posts"); // 1引数
現在 (Next.js 16):
// SWRを使用した静的コンテンツ用
revalidateTag("blog-posts", "max");
// インタラクティブ機能用
updateTag("user-profile");
// キャッシュされていないデータ用
refresh();
ベストプラクティス
1. ユースケースに適したAPIを選択
// ✅ 静的コンテンツ → revalidateTag + SWR
revalidateTag("products", "max");
// ✅ ユーザーアクション → updateTag + read-your-writes
updateTag("user-cart");
// ✅ 動的インジケーター → refresh
refresh();
2. ほとんどのケースで'max' profileを使用
// ✅ 推奨: 長期間有効なコンテンツに'max'を使用
revalidateTag("blog-posts", "max");
// または必要に応じて他のprofile
revalidateTag("stock-prices", "hours"); // より頻繁に更新
3. タグの命名規則
// ✅ 良い: 説明的なタグ
revalidateTag("blog-posts", "max");
revalidateTag(`user-${userId}`, "max");
revalidateTag("product-list", "days");
// ❌ 悪い: 汎用的なタグ
revalidateTag("data", "max");
revalidateTag("cache", "max");
🎯 まとめ
Enhanced Routing
- ✅ レイアウトとセグメントによる階層的なUI構成
- ✅ 詳細なローディングとエラーバウンダリー
- ✅ UX最適化のためのPPRとの組み合わせ
- ✅ フルリロードなしのスムーズなナビゲーション
Improved Caching API
- ✅ 暗黙的から明示的なキャッシュへの移行
- ✅ 3つのレベル: Page, Component, Function
- ✅ 管理しやすいタグベースの無効化
- ✅ データ変更のためのServer Actionsとの組み合わせ
Happy Coding!
参考資料:
