- Next.js の今後の方向性が興味深い
- Server Actions には課題があったが、React 19 の
useOptimistic、useFormStatus によって改善の可能性が見えている
- Remix の
useFetcher 方式も優れた DX を提供する
- Next.js の PPR(Partial Pre-rendering)と新しい granular なキャッシュシステムが特に際立っている
- 全体として非常にポジティブな印象を受けた
The Big Picture
next.config.js で新しいキャッシュシステムを実験的に有効化できる
- キャッシュプロファイルを定義して、さまざまな有効期限や再検証周期を設定できる
// next.config.js
const config = {
experimental: {
// 新しいキャッシュシステムを有効化。これでコード内で `use cache` を使える
dynamicIO: true,
// 任意: キャッシュプロファイル設定
cacheLife: {
blog: {
stale: 3600, // クライアントキャッシュ維持: 1時間
revalidate: 900, // サーバーで再更新: 15分
expire: 86400, // 最大寿命: 1日
},
},
},
};
use cache の基本的な使い方
- ファイル、コンポーネント、関数レベルで
"use cache" を宣言してキャッシュできる
- コード例のように
use cache を追加するだけで簡単にキャッシュを適用できる
cacheTag、revalidateTag などを活用して任意のタイミングでキャッシュを無効化できる
// 1. ファイル単位のキャッシュ
"use cache";
export default function Page() {
return <div>Cached Page</div>;
}
// 2. コンポーネント単位のキャッシュ
export async function PriceDisplay() {
"use cache";
const price = await fetchPrice();
return <div>${price}</div>;
}
// 3. 関数単位のキャッシュ
export async function getData() {
"use cache";
return await db.query();
}
タグベースのキャッシュ
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache';
// 特定のデータグループをキャッシュ
export async function ProductList() {
'use cache';
cacheTag('products');
const products = await fetchProducts();
return <div>{products}</div>;
}
// データ変更時にキャッシュを無効化
export async function addProduct() {
'use server';
await db.products.add(...);
revalidateTag('products');
}
カスタム Cache プロファイル
unstable_cacheLife を使って next.config.js で定義したキャッシュプロファイルを読み込める
- コード内で宣言したプロファイル名(例:
"blog")を使ってキャッシュポリシーを適用する
import { unstable_cacheLife as cacheLife } from "next/cache";
export async function BlogPosts() {
"use cache";
cacheLife("blog"); // 事前定義したブログ用キャッシュプロファイルを使用
return await fetchPosts();
}
重要だが見落としやすい点
キャッシュキーの自動生成
- コンポーネントの
props と arguments は自動的にキャッシュキーに含まれる
- シリアライズ不可能な値(関数など)は「変更不能な参照」の形で処理される
export async function UserCard({ id, onDelete }) {
"use cache";
// id はキャッシュキーに含まれる
// onDelete は渡されるが、キャッシュには影響しない
const user = await fetchUser(id);
return <div onClick={onDelete}>{user.name}</div>;
}
動的コンテンツとキャッシュ済みコンテンツの混在
- キャッシュされたコンテンツの内部に動的コンテンツを子要素として渡し、混在させて使える
cacheTag の配列を指定して、複数のタグを同時に適用・無効化できる
export async function CachedWrapper({ children }) {
"use cache";
const header = await fetchHeader();
return (
<div>
<h1>{header}</h1>
{children} {/* 動的コンテンツはそのまま維持 */}
</div>
);
}
export async function ProductPage({ id }) {
"use cache";
cacheTag(["products", `product-${id}`, "featured"]);
// これらのタグのどれを使っても無効化できる
}
キャッシュの階層構造
- 最上位レベルで
"use cache" を宣言すると、その領域全体がキャッシュされる
- 特定の部分(例: Suspense を使った動的セクション)はキャッシュ領域から除外できる
"use cache";
export default async function Page() {
return (
<div>
<CachedHeader />
<div>
<Suspense fallback={<Loading />}>
<DynamicFeed /> {/* 動的コンテンツ */}
</Suspense>
</div>
</div>
);
}
型安全性
- キャッシュキーやキャッシュプロファイルなどの文字列を定数で管理し、マジックストリングの使用を減らせる
- React Query のパターンのようにタグ生成方式を使うと便利
// 定数でキャッシュプロファイルキーを管理
export const CACHE_LIFE_KEYS = {
blog: "blog",
} as const;
const config = {
experimental: {
cacheLife: {
[CACHE_LIFE_KEYS.blog]: {
stale: 3600,
revalidate: 900,
expire: 86400,
},
},
},
};
キャッシュタグを効率的に管理する方法
- React Query スタイルのタグファクトリパターンを適用
export const CACHE_TAGS = {
blog: {
all: ["blog"] as const,
list: () => [...CACHE_TAGS.blog.all, "list"] as const,
post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,
comments: (postId: string) =>
[...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,
},
} as const;
// キャッシュタグを設定
function tagCache(tags: string[]) {
cacheTag(...tags);
}
// 使用例
export async function BlogList() {
"use cache";
tagCache(CACHE_TAGS.blog.list());
}
3件のコメント
SEO が重要で SSR が必要な状況でのみ、Next.js や Remix のような framework を使うのがよいと思います。
特に B2B ビジネス製品や back office のように SEO が重要でないサービスに Next.js を導入するのは、慎重であるべきだと思います。Next.js が強制するインターフェースや複雑さが、開発生産性を下げる可能性があるためです。
個人的には、SEO が不要な場合は Vite + React のほうが開発生産性や柔軟性の面でずっと良いと思います。
Next.js は 13 以降かなり実用的になりましたが、最近は本当にとても気に入っています。フルスタックWeb開発の技術スタックにおける事実上の標準になりそうです。