---
title: "Next.js 16 Cache Components と Server Component 統合によるSupabaseクエリキャッシュ実装"
created: "2026-01-13"
modified: "2026-03-17"
status: "published"
i18n:
lang: "ja"
translations:
en: "20260113-113141-nextjs-16-cache-components-supabase.en.md"
platforms:
wordpress:
published: true
wp_status: "publish"
url: "https://wakatchi.dev/nextjs-16-cache-components-supabase/"
published_date: "2026-03-17T06:00:00+09:00"
post_id: 2579
slug: "nextjs-16-cache-components-supabase"
tags: ["Server Component", "サーバーキャッシュ", "パフォーマンス改善", "SSG", "CI/CD"]
---

Next.js 16の新機能「Cache Components」を使って、Supabaseからのデータ取得をサーバーサイドでキャッシュできるようにしました。
クライアントサイドで毎回フェッチしていた数百件規模のマスターデータを、サーバーサイドキャッシュに移行することで、初回表示を高速化できました。
導入自体はシンプルですが、CI環境でのビルドエラーにハマった経験も踏まえて、実装のポイントを共有します。
## こんな人におすすめ
この記事は以下のような方に参考になります。
- Next.js 16のCache Components機能を試してみたい方
- Supabaseから取得するデータをサーバーサイドでキャッシュしたい方
- CI/CD環境でのビルドエラーに悩んでいる方
- クライアントサイドフェッチからサーバーサイドキャッシュへの移行を検討している方
## 目次
- [背景](#背景)
- [実装方針](#実装方針)
- [つまづいた点](#つまづいた点)
- [CI環境でのビルドエラー](#ci環境でのビルドエラー)
- [force-dynamic との互換性](#force-dynamic-との互換性)
- [得られた効果](#得られた効果)
- [まとめ](#まとめ)
## 背景
業務用の検索アプリで、Supabaseから取得するマスターデータ(数百件規模)のパフォーマンスを改善するため、Next.js 16のCache Components機能を導入しました。
従来はクライアントサイドで毎回フェッチしていましたが、サーバーサイドキャッシュに移行することで初回表示を高速化しています。
## 要件
- Supabaseから取得するマスターデータをサーバーサイドでキャッシュする
- キャッシュライフサイクル:30分ステール、1時間再検証、1日有効期限
- 別データソースへのフォールバックとの互換性を維持
- CI環境(ダミーSupabase URL)でのビルドエラーを回避する
### キャッシュライフサイクルのイメージ
Next.js 16のキャッシュは、stale(新鮮)→ revalidate(再検証)→ expire(期限切れ)の3段階で管理されます。
```mermaid
flowchart TD
A[リクエスト] --> B{キャッシュ存在?}
B -->|なし| C[Supabaseから取得]
C --> D[キャッシュ保存]
D --> E[レスポンス返却]
B -->|あり| F{stale期限内?<br/>30分以内}
F -->|はい| G[キャッシュを即座に返却]
F -->|いいえ| H{revalidate期限内?<br/>1時間以内}
H -->|はい| I[キャッシュを返却]
I --> J[バックグラウンドで再取得]
H -->|いいえ| K{expire期限内?<br/>1日以内}
K -->|はい| L[再取得を待って返却]
K -->|いいえ| C
```
## 実装方針
### 1. serverCache.ts - キャッシュ関数の実装
```typescript
import { cacheTag, cacheLife } from "next/cache";
export async function getCachedItems(): Promise<Item[]> {
"use cache";
cacheTag("items");
cacheLife("items");
return await fetchItemsFromSupabase();
}
```
### 2. next.config.ts - キャッシュライフサイクル設定
```typescript
cacheLife: {
items: {
stale: 1800, // 30分
revalidate: 3600, // 1時間
expire: 86400, // 1日
},
},
```
### 3. app/page.tsx - Server Component統合
```typescript
export default async function Home() {
let initialItems: Item[] | null = null;
if (isSupabaseEnabled() && isValidSupabaseUrl()) {
try {
initialItems = await getCachedItems();
} catch (error) {
initialItems = null; // フォールバック
}
}
return <App initialItems={initialItems} />;
}
```
#### データ取得フローの全体像
サーバーサイドキャッシュを導入したことで、データの取得フローが以下のようになりました。
```mermaid
sequenceDiagram
participant Browser as ブラウザ
participant Server as Next.js Server
participant Cache as Next.js Cache
participant Supabase as Supabase API
Browser->>Server: ページリクエスト
Server->>Cache: キャッシュ確認
alt キャッシュヒット(stale期限内)
Cache-->>Server: キャッシュデータ返却
Server-->>Browser: HTML + 初期データ
else キャッシュミス または 期限切れ
Server->>Supabase: データ取得リクエスト
Supabase-->>Server: マスターデータ(数百件)
Server->>Cache: キャッシュ保存
Server-->>Browser: HTML + 初期データ
end
Note over Browser,Cache: 初回表示完了後、クライアントサイドでの<br/>追加フェッチは不要
```
### 4. useItemSearch.ts - 初期データ受け取り
```typescript
if (initialItems && initialItems.length > 0) {
// サーバーキャッシュからのデータを使用
actions.initSuccess(initialItems);
return initialItems;
}
// 従来のクライアントサイドフェッチにフォールバック
```
## つまづいた点
### CI環境でのビルドエラー
ここで困ったのが、CI環境でのビルドエラーです。
CIではダミーのSupabase URL(`example.supabase.co`)が設定されているため、ビルド時の静的生成でfetchが失敗してしまいます。
**解決策**: ダミーURLを検出してサーバーサイドキャッシュをスキップ
```typescript
function isValidSupabaseUrl(): boolean {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
return url.length > 0 && !url.includes("example.supabase.co");
}
```
#### 環境判定とフォールバックのフロー
```mermaid
flowchart TD
A[ページリクエスト] --> B{Supabase有効?}
B -->|無効| C[initialItems = null]
B -->|有効| D{有効なURL?}
D -->|ダミーURL| C
D -->|本番URL| E[getCachedItems呼び出し]
E --> F{キャッシュ取得成功?}
F -->|成功| G[initialItems = キャッシュデータ]
F -->|失敗| H{別データソース有効?}
H -->|有効| I[別データソースから取得]
H -->|無効| C
I --> J[initialItems = フォールバックデータ]
G --> K[App コンポーネント描画]
J --> K
C --> K
subgraph " useItemSearch.ts "
K --> L{initialItemsあり?}
L -->|あり| M[キャッシュデータを使用]
L -->|なし| N[クライアントサイドフェッチ]
end
```
### force-dynamic との互換性
`cacheComponents: true` と `export const dynamic = 'force-dynamic'` は互換性がありません。
Next.js 16のCache Components機能を使う場合は、別の方法でビルド時エラーを回避する必要があります。
### 型変換の注意点
- `supabaseService.getItems()` は `SearchResult[]` を返します
- アプリは `Item[]`(レガシー型)を使用しています
- `supabaseAdapter.fetchItems()` で変換したデータを取得しています
## 良かった点
実装して一番良かったのは、コードのシンプルさです。
`"use cache"` ディレクティブと `cacheTag`、`cacheLife` を追加するだけで、複雑なキャッシュロジックを自前で実装する必要がありませんでした。
また、フォールバック設計を残しておいたことで、Supabaseに問題が発生しても別のデータソースに自動的に切り替わる安心感があります。
## 得られた効果
キャッシュ導入後、以下の効果を確認できました。
### パフォーマンス改善の内訳
```mermaid
pie showData
title 改善効果の内訳
"初回表示高速化" : 40
"サーバー負荷軽減" : 30
"CI/CD安定化" : 20
"コード保守性向上" : 10
```
### 具体的な改善ポイント
- **初回表示の高速化**: 数百件規模のデータをサーバーサイドでキャッシュすることで、クライアントサイドのフェッチ待ち時間が削減されました
- **サーバー負荷の軽減**: 同じデータへの重複リクエストが減り、Supabaseへのアクセス頻度が低下しました
- **CI/CDの安定化**: ダミーURL検出により、CI環境でのビルドが確実に成功するようになりました
### 改善前後の比較
```mermaid
flowchart LR
subgraph " 改善前 "
A1[ブラウザ] --> A2[クライアントJS]
A2 --> A3[Supabase API]
A3 --> A4[数百件取得]
A4 --> A2
A2 --> A1
end
subgraph " 改善後 "
B1[ブラウザ] --> B2[Next.js Server]
B2 --> B3{キャッシュ?}
B3 -->|あり| B4[即座に返却]
B3 -->|なし| B5[Supabase API]
B5 --> B6[取得+キャッシュ保存]
B6 --> B4
B4 --> B1
end
style A3 fill:#ffcccc
style B5 fill:#ccffcc
```
## まとめ
Next.js 16のCache Componentsは、サーバーサイドキャッシュを非常にシンプルに実装できる強力な機能です。
ただし、CI/CD環境での考慮や `force-dynamic` との互換性など、いくつかの注意点もあります。
### 4つのポイント
1. **Next.js 16のCache Componentsは強力だが制約がある**
- `use cache` ディレクティブでシンプルにキャッシュを実装
- ただし `force-dynamic` との併用はできない
2. **CI/CD環境での考慮が必須**
- ダミー環境変数でのビルドは静的生成時に問題を起こす
- 環境検出によるスキップ処理が必要
3. **フォールバック設計の重要性**
- サーバーサイドキャッシュ失敗時はクライアントサイドフェッチで対応
- データソース切り替え(Supabase/フォールバック先)との互換性を維持
4. **テストカバレッジの確保**
- `initialItems` オプションの全パターンをテスト
- `next/cache` はJest環境でモック必須

Next.js 16の新機能「Cache Components」を使って、Supabaseからのデータ取得をサーバーサイドでキャッシュできるようにしました。
クライアントサイドで毎回フェッチしていた数百件規模のマスターデータを、サーバーサイドキャッシュに移行することで、初回表示を高速化できました。
導入自体はシンプルですが、CI環境でのビルドエラーにハマった経験も踏まえて、実装のポイントを共有します。
こんな人におすすめ
この記事は以下のような方に参考になります。
- Next.js 16のCache Components機能を試してみたい方
- Supabaseから取得するデータをサーバーサイドでキャッシュしたい方
- CI/CD環境でのビルドエラーに悩んでいる方
- クライアントサイドフェッチからサーバーサイドキャッシュへの移行を検討している方
目次
背景
業務用の検索アプリで、Supabaseから取得するマスターデータ(数百件規模)のパフォーマンスを改善するため、Next.js 16のCache Components機能を導入しました。
従来はクライアントサイドで毎回フェッチしていましたが、サーバーサイドキャッシュに移行することで初回表示を高速化しています。
要件
- Supabaseから取得するマスターデータをサーバーサイドでキャッシュする
- キャッシュライフサイクル:30分ステール、1時間再検証、1日有効期限
- 別データソースへのフォールバックとの互換性を維持
- CI環境(ダミーSupabase URL)でのビルドエラーを回避する
キャッシュライフサイクルのイメージ
Next.js 16のキャッシュは、stale(新鮮)→ revalidate(再検証)→ expire(期限切れ)の3段階で管理されます。
flowchart TD
A[リクエスト] --> B{キャッシュ存在?}
B -->|なし| C[Supabaseから取得]
C --> D[キャッシュ保存]
D --> E[レスポンス返却]
B -->|あり| F{stale期限内?<br/>30分以内}
F -->|はい| G[キャッシュを即座に返却]
F -->|いいえ| H{revalidate期限内?<br/>1時間以内}
H -->|はい| I[キャッシュを返却]
I --> J[バックグラウンドで再取得]
H -->|いいえ| K{expire期限内?<br/>1日以内}
K -->|はい| L[再取得を待って返却]
K -->|いいえ| C
実装方針
1. serverCache.ts - キャッシュ関数の実装
import { cacheTag, cacheLife } from "next/cache";
export async function getCachedItems(): Promise<Item[]> {
"use cache";
cacheTag("items");
cacheLife("items");
return await fetchItemsFromSupabase();
}
2. next.config.ts - キャッシュライフサイクル設定
cacheLife: {
items: {
stale: 1800, // 30分
revalidate: 3600, // 1時間
expire: 86400, // 1日
},
},
3. app/page.tsx - Server Component統合
export default async function Home() {
let initialItems: Item[] | null = null;
if (isSupabaseEnabled() && isValidSupabaseUrl()) {
try {
initialItems = await getCachedItems();
} catch (error) {
initialItems = null; // フォールバック
}
}
return <App initialItems={initialItems} />;
}
データ取得フローの全体像
サーバーサイドキャッシュを導入したことで、データの取得フローが以下のようになりました。
sequenceDiagram
participant Browser as ブラウザ
participant Server as Next.js Server
participant Cache as Next.js Cache
participant Supabase as Supabase API
Browser->>Server: ページリクエスト
Server->>Cache: キャッシュ確認
alt キャッシュヒット(stale期限内)
Cache-->>Server: キャッシュデータ返却
Server-->>Browser: HTML + 初期データ
else キャッシュミス または 期限切れ
Server->>Supabase: データ取得リクエスト
Supabase-->>Server: マスターデータ(数百件)
Server->>Cache: キャッシュ保存
Server-->>Browser: HTML + 初期データ
end
Note over Browser,Cache: 初回表示完了後、クライアントサイドでの<br/>追加フェッチは不要
4. useItemSearch.ts - 初期データ受け取り
if (initialItems && initialItems.length > 0) {
// サーバーキャッシュからのデータを使用
actions.initSuccess(initialItems);
return initialItems;
}
// 従来のクライアントサイドフェッチにフォールバック
つまづいた点
CI環境でのビルドエラー
ここで困ったのが、CI環境でのビルドエラーです。
CIではダミーのSupabase URL(example.supabase.co)が設定されているため、ビルド時の静的生成でfetchが失敗してしまいます。
解決策: ダミーURLを検出してサーバーサイドキャッシュをスキップ
function isValidSupabaseUrl(): boolean {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
return url.length > 0 && !url.includes("example.supabase.co");
}
環境判定とフォールバックのフロー
flowchart TD
A[ページリクエスト] --> B{Supabase有効?}
B -->|無効| C[initialItems = null]
B -->|有効| D{有効なURL?}
D -->|ダミーURL| C
D -->|本番URL| E[getCachedItems呼び出し]
E --> F{キャッシュ取得成功?}
F -->|成功| G[initialItems = キャッシュデータ]
F -->|失敗| H{別データソース有効?}
H -->|有効| I[別データソースから取得]
H -->|無効| C
I --> J[initialItems = フォールバックデータ]
G --> K[App コンポーネント描画]
J --> K
C --> K
subgraph " useItemSearch.ts "
K --> L{initialItemsあり?}
L -->|あり| M[キャッシュデータを使用]
L -->|なし| N[クライアントサイドフェッチ]
end
force-dynamic との互換性
cacheComponents: true と export const dynamic = 'force-dynamic' は互換性がありません。
Next.js 16のCache Components機能を使う場合は、別の方法でビルド時エラーを回避する必要があります。
型変換の注意点
supabaseService.getItems() は SearchResult[] を返します
- アプリは
Item[](レガシー型)を使用しています
supabaseAdapter.fetchItems() で変換したデータを取得しています
良かった点
実装して一番良かったのは、コードのシンプルさです。
"use cache" ディレクティブと cacheTag、cacheLife を追加するだけで、複雑なキャッシュロジックを自前で実装する必要がありませんでした。
また、フォールバック設計を残しておいたことで、Supabaseに問題が発生しても別のデータソースに自動的に切り替わる安心感があります。
得られた効果
キャッシュ導入後、以下の効果を確認できました。
パフォーマンス改善の内訳
pie showData
title 改善効果の内訳
"初回表示高速化" : 40
"サーバー負荷軽減" : 30
"CI/CD安定化" : 20
"コード保守性向上" : 10
具体的な改善ポイント
- 初回表示の高速化: 数百件規模のデータをサーバーサイドでキャッシュすることで、クライアントサイドのフェッチ待ち時間が削減されました
- サーバー負荷の軽減: 同じデータへの重複リクエストが減り、Supabaseへのアクセス頻度が低下しました
- CI/CDの安定化: ダミーURL検出により、CI環境でのビルドが確実に成功するようになりました
改善前後の比較
flowchart LR
subgraph " 改善前 "
A1[ブラウザ] --> A2[クライアントJS]
A2 --> A3[Supabase API]
A3 --> A4[数百件取得]
A4 --> A2
A2 --> A1
end
subgraph " 改善後 "
B1[ブラウザ] --> B2[Next.js Server]
B2 --> B3{キャッシュ?}
B3 -->|あり| B4[即座に返却]
B3 -->|なし| B5[Supabase API]
B5 --> B6[取得+キャッシュ保存]
B6 --> B4
B4 --> B1
end
style A3 fill:#ffcccc
style B5 fill:#ccffcc
まとめ
Next.js 16のCache Componentsは、サーバーサイドキャッシュを非常にシンプルに実装できる強力な機能です。
ただし、CI/CD環境での考慮や force-dynamic との互換性など、いくつかの注意点もあります。
4つのポイント
-
Next.js 16のCache Componentsは強力だが制約がある
use cache ディレクティブでシンプルにキャッシュを実装
- ただし
force-dynamic との併用はできない
-
CI/CD環境での考慮が必須
- ダミー環境変数でのビルドは静的生成時に問題を起こす
- 環境検出によるスキップ処理が必要
-
フォールバック設計の重要性
- サーバーサイドキャッシュ失敗時はクライアントサイドフェッチで対応
- データソース切り替え(Supabase/フォールバック先)との互換性を維持
-
テストカバレッジの確保
initialItems オプションの全パターンをテスト
next/cache はJest環境でモック必須