Next.js 15 の use cache ディレクティブについて調べてみた


スタートアップのプロダクト開発支援をしている Gaji-Labo では、モダンな開発環境として Next.js を採用する機会が多くあります。

そんな中、Next.js の最新機能のキャッチアップは欠かせないということで、v15 の canary バージョンで利用できるようになった use cache ディレクティブについて調査しました。

現時点では実験的な機能のため正式にリリースされてはいませんが、シンプルかつ高い柔軟性を持ったキャッシュロジックになっているようです!

use cache とは

ファイルレベルだけでなく、コンポーネントや関数といった粒度でキャッシュできる機能です。

Next.js の App Router ではデフォルトでキャッシュが有効になっており、必要に応じてオプトアウトする設計でリリースされました。

しかし、複雑なデフォルトのキャッシュ動作は学習コストが高く、開発体験が損なわれる結果となってしまったのだそうです。

そこで公開されたのが、コードに文字列リテラルとして挿入して Next.js コンパイラに特定の「境界」を定義する use cache というわけです。

use cache の導入

use cache は実験的な機能のため canary バージョンでのみ利用できます。

npx create-next-app@canary

プロジェクトを作成したら next.config.tsdynamicIO フラグを有効化します。これで Next.js の新しいキャッシュシステムが利用できます。

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    dynamicIO: true,
  },
}

export default nextConfig

データをキャッシュする方法

ファイルレベルでのキャッシュ

キャッシュの方法としては、ファイルの先頭に 'use cache' ディレクティブを宣言するだけです。

'use cache'

import { unstable_cacheLife as cacheLife } from 'next/cache'
import { Header } from 'compornent/ui/header'

export default function Layout({ children }: { children: ReactNode }) {
  const header = await fetch('/api/header') // このデータ取得がキャッシュされる
  
  return (
    <div>
      <Header data={header} /> {/* このコンポーネントはキャッシュされない */}
      {children}
    </div>
  )
}

ファイルレベルで宣言したとしても、別ファイルから import したコンポーネントはキャッシュの影響を受けず別スコープ扱いとなります。

あくまでファイル内で取得したデータや、宣言したコンポーネントに限ったキャッシュとなります。

コンポーネントレベルでのキャッシュ

コンポーネントを個別にキャッシュすることもできます。

export async function Bookings({ type = 'haircut' }: BookingsProps) {
  'use cache'

  async function getBookingsData() {
    const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
    return data
  }

  return //...
}

この場合は、コンポーネント内で実行される全てのフェッチや計算をキャッシュできます。

関数レベルでのキャッシュ

関数もコンポーネントと同じようにキャッシュできます。

export async function getData({ id }) {
  'use cache'

  const data = await fetch(`/api/data${id}`)
  return data
}

関数の依存関係が検出されてキャッシュキーに利用されるため、上記関数に同じ id を props として渡すと、キャッシュされた内容が返されます。

時間ベースのキャッシュ制御

キャッシュの保存期間を制御するには cacheLife() 関数を利用します。

'use cache'

import { unstable_cacheLife as cacheLife } from 'next/cache'

export default async function Page() {
  cacheLife({
    stale: 5 * 60 * 1000,       // 5分間:クライアントキャッシュ有効期間
    revalidate: 15 * 60 * 1000, // 15分:サーバー上でのキャッシュを更新間隔
    expire: 24 * 60 * 60 * 1000 // 24時間:キャッシュの最大保持期間
  })

  const data = await fetchData()
  return <div>{data}</div>
}

以上のように直接指定せずに、 cacheLife('hours') という形式で、デフォルトのキャッシュプロファイルから選択して使用することもできます。

また、next.config.ts ファイルにてカスタムプロファイルを作成して使い回すこともできます。

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

const nextConfig: NextConfig = {
  experimental: {
    dynamicIO: true,
    cacheLife: {
      biweekly: {
        stale: 60 * 60 * 24 * 14, // 14 days
        revalidate: 60 * 60 * 24, // 1 day
        expire: 60 * 60 * 24 * 14, // 14 days
      },
    },
  },
}

module.exports = nextConfig

// app/page.tsx
'use cache'

import { unstable_cacheLife as cacheLife } from 'next/cache'

export default async function Page() {
  cacheLife('biweekly')
  return <div>Page</div>
}

タグベースのキャッシュ制御

データの更新が不規則なパターンなど、キャッシュを明示的にクリアしたい時のために、cacheTag() 関数と revalidateTag() 関数が用意されています。

まず、キャッシュさせたい関数などに cacheTag() でタグを設置。

import { unstable_cacheTag as cacheTag } from 'next/cache'

export async function getData() {
  'use cache'
  cacheTag('my-data')

  const data = await fetch('/api/data')
  return data
}

あとは任意のタイミングで revalidateTag() でキャッシュをクリアできます。

import { revalidateTag } from 'next/cache'

export default async function submit() {
  await addPost()
  revalidateTag('my-data')
}

まとめ

以上、use cache ディレクティブについて調査してみました。

まだ実験的な機能ではありますが、公式のドキュメントに情報がまとまっていたので、困った時にもすぐ解決できそうな分かりやすい機能だなと思いました。

個人的には特に以下の点が気に入りました!

  • キャッシュの境界を明示的に定義できること
  • 時間設定とタグでの管理によって柔軟な制御ができること
  • 粒度をファイル、コンポーネント、関数レベルで選択できること

というわけで、こちらの機能が正式にリリースされることに期待しつつ、引き続き状況を見守っていこうと思います。

参考文献

Gaji-Labo では、Next.js などのフロントエンド技術を活用し、スタートアップのプロダクト開発を支援しています。

フロントエンドエンジニアをはじめ、各職種でメンバーを募集しておりますので、興味のある方はぜひご案内資料をご覧ください!

Gaji-Labo フロントエンドエンジニア向けご案内資料

Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります

フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。

「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」

フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。

オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。

Next.js, React, TypeScript の相談をする!

投稿者 Takahashi Toshiaki

フロントエンドエンジニア。ReactやNext.jsを使用したモダンな環境で開発することに関心があります。ヘッドレスCMSを利用した構築が得意です。チームの推進力となれるような働き方を模索中。映画や漫画、ゲーム、ホラー、K-POPを愛する趣味人。