多くのNext.js開発者が直面する問題の一つに、AIアプリケーションにおけるパフォーマンスとコストのバランスがあります。特に、Vercel上でAI機能を組み込む際、「AIの応答速度が遅い」「LLMの利用コストがかさむ」「コンテンツの更新が反映されない」といった課題にぶつかることは少なくありません。
この記事では、VercelのCDNキャッシュ機能と最新のAI連携機能を活用し、Next.jsアプリケーションのパフォーマンスを最大化しつつ、AIモデルのコストを最適化するための具体的な設定・実装手順を解説します。
Vercel CDNキャッシュ戦略の基本とAI連携のメリット
このセクションでは、VercelのCDNキャッシュがどのように機能し、AI連携においてどのようなメリットをもたらすかを解説します。VercelのCDNは、静的アセットだけでなく、動的なAPIレスポンスやページコンテンツもキャッシュすることで、ユーザー体験を劇的に向上させます。
VercelのCDNは、世界中のエッジロケーションにコンテンツをキャッシュし、ユーザーに最も近いサーバーから提供することで、レイテンシーを削減し、オリジンサーバーの負荷を軽減します。AIアプリケーションでは、LLMへのリクエスト自体に時間がかかることが多いため、AIレスポンスのキャッシュはユーザーの体感速度を向上させ、LLMの呼び出し回数を減らすことでコスト削減に直結します。
前提・環境
本記事で解説する内容は、以下の環境を前提としています。
- Next.js (App Router)
- Vercel AI SDK (v3.0以降)
- Vercel CLI (v28.9.0以降)
- Vercelプロジェクトがデプロイ済みであること
Vercel CDNによるキャッシュ設定の実装手順
このセクションでは、Cache-Control ヘッダーやvercel.json を用いたVercel CDNキャッシュの具体的な設定方法を解説します。適切なキャッシュ設定は、アプリケーションのパフォーマンスを大きく左右します。
Cache-Control ヘッダーによるAPIレスポンスのキャッシュ
Vercel Functionsからのレスポンスに Cache-Control ヘッダーを設定することで、VercelのCDNでのキャッシュ動作を細かく制御できます。特に Vercel-CDN-Cache-Control ヘッダーは、Vercel CDNに対して明示的にキャッシュ期間を指示するため、他のプロキシやブラウザキャッシュとは独立して制御したい場合に有効です。
以下の例では、APIレスポンスをVercel CDNで3600秒間(1時間)、ダウンストリームのCDNで60秒間、クライアント(ブラウザ)で10秒間キャッシュするように設定しています。
// app/api/cache-control-headers/route.ts
export async function GET() {
return new Response('Cache Control example', {
status: 200,
headers: {
'Cache-Control': 'max-age=10', // クライアントキャッシュ
'CDN-Cache-Control': 'max-age=60', // ダウンストリームCDNキャッシュ
'Vercel-CDN-Cache-Control': 'max-age=3600', // Vercel CDNキャッシュ
},
});
}
この設定により、ユーザーからのリクエストはまずVercel CDNでキャッシュの有無が確認され、キャッシュがあれば高速にレスポンスが返されます。キャッシュがない場合や有効期限が切れている場合は、オリジンサーバー(Vercel Function)にリクエストが転送されます。
vercel.json を使ったルーティングレベルでのキャッシュ設定
vercel.json ファイルを使用すると、特定のパスに対するHTTPヘッダーをグローバルに設定できます。これは、アプリケーション全体で一貫したキャッシュ戦略を適用したい場合に便利です。
以下の例では、/api/data パスに対するリクエストをVercel CDNで3600秒間キャッシュし、さらに X-Vercel-IP-Country ヘッダーによってレスポンスが変化する可能性があることを示す Vary ヘッダーを設定しています。Vary ヘッダーは、特定のHTTPヘッダーの値に応じてコンテンツが異なる場合に、CDNがそれらを別々のキャッシュエントリとして扱うために重要です。
// vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"headers": [
{
"source": "/api/data",
"headers": [
{ "key": "Vary", "value": "X-Vercel-IP-Country" },
{ "key": "Cache-Control", "value": "s-maxage=3600" }
]
}
]
}
Vercel AI SDKとAIレスポンスのキャッシュ戦略
このセクションでは、Vercel AI SDKを活用したAIアプリケーションでのキャッシュ実装方法に焦点を当てます。特に、LLMのストリーミングレスポンスを効率的にキャッシュすることで、ユーザー体験とコスト効率を両立させます。
AI SDKミドルウェアによるLLMレスポンスのキャッシュ
Vercel AI SDKは、言語モデルミドルウェアを通じてAIレスポンスのキャッシュをサポートしています。これにより、同じプロンプトに対するLLMの呼び出しを削減し、コストとレイテンシーを改善できます。ここではVercel KV(Upstash Redis)をキャッシュストアとして使用する例を紹介します。
まず、Upstash RedisをVercelプロジェクトに連携し、KV_URL と KV_TOKEN 環境変数を設定します。
次に、AI SDKのミドルウェアファイルを定義します。このミドルウェアは、wrapGenerate で非ストリーミングレスポンスを、wrapStream でストリーミングレスポンスをキャッシュします。ストリーミングレスポンスの場合、AI SDKの simulateReadableStream 関数を使用して、キャッシュされたチャンクから ReadableStream を再構築します。
// ai/middleware.ts
import { Redis } from '@upstash/redis';
import {
type LanguageModelV4,
type LanguageModelV4Middleware,
simulateReadableStream,
type LanguageModelV4StreamPart,
} from 'ai';
const redis = new Redis({
url: process.env.KV_URL!,
token: process.env.KV_TOKEN!,
});
export const cacheMiddleware: LanguageModelV4Middleware = {
// 非ストリーミングレスポンスのキャッシュ処理
wrapGenerate: async ({ doGenerate, params }) => {
const cacheKey = JSON.stringify(params);
const cached = (await redis.get(cacheKey)) as Awaited<
ReturnType<LanguageModelV4['doGenerate']>
> | null;
if (cached !== null) {
// キャッシュがあればそれを返す
return {
...cached,
response: {
...cached.response,
timestamp: cached?.response?.timestamp
? new Date(cached?.response?.timestamp)
: undefined,
},
};
}
// キャッシュがなければモデルを呼び出し、結果をキャッシュ
const result = await doGenerate();
redis.set(cacheKey, result, { ex: 3600 }); // 1時間キャッシュ
return result;
},
// ストリーミングレスポンスのキャッシュ処理
wrapStream: async ({ doStream, params }) => {
const cacheKey = JSON.stringify(params);
const cached = (await redis.get(cacheKey)) as LanguageModelV4StreamPart[] | null;
if (cached !== null) {
// キャッシュがあればsimulateReadableStreamでストリームを再現して返す
return {
stream: simulateReadableStream(cached),
// usageやfinishReasonなどもキャッシュから取得可能であれば設定
usage: (cached as any).usage,
finishReason: (cached as any).finishReason,
};
}
// キャッシュがなければモデルを呼び出し、ストリームをキャッシュしながら返す
const streamResult = await doStream();
const fullResponse: LanguageModelV4StreamPart[] = [];
const transformStream = new TransformStream({
transform(chunk, controller) {
fullResponse.push(chunk);
controller.enqueue(chunk);
},
flush(controller) {
// ストリームが終了したら、完全なレスポンスをキャッシュ
redis.set(cacheKey, fullResponse, { ex: 3600 }); // 1時間キャッシュ
},
});
return {
stream: streamResult.stream.pipeThrough(transformStream),
usage: streamResult.usage,
finishReason: streamResult.finishReason,
};
},
};
このミドルウェアをAI SDKの構成に適用することで、AIレスポンスの透過的なキャッシュが可能になります。
タグベースのキャッシュ無効化
VercelのCDNは、Vercel-Cache-Tag ヘッダーとVercel CLIを組み合わせることで、タグベースのキャッシュ無効化をサポートします。これは、特定のコンテンツが更新された際に、関連するキャッシュエントリのみを効率的にパージするのに役立ちます。
例えば、ブログ記事の更新時にその記事に関連するキャッシュだけを無効化したい場合に、記事IDなどをタグとして設定し、更新後に以下のコマンドを実行します。
vercel cache invalidate --tag my-content-tag
このコマンドにより、my-content-tag が付与されたすべてのキャッシュエントリがVercel CDNから無効化され、次回のアクセスで最新のコンテンツが取得されるようになります。
Vercel Edge ConfigとAI SDKの連携
Vercel Edge Configは、機能フラグ、A/Bテスト、リダイレクトなど、グローバルに利用できる設定データストアです。ユーザーに近いエッジロケーションでデータを読み取れるため、AIアプリケーションにおいて動的にAIモデルを切り替えたり、プロンプトを調整したりするのに非常に強力なツールとなります。
例えば、特定のユーザーグループに対して異なるAIモデル(例: コスト優先のモデルと品質優先のモデル)を適用する場合、Edge Configにその設定を保存し、Edge FunctionやAI SDKから参照することで、デプロイなしで動的なモデル切り替えが実現できます。
Vercelでは、Edge ConfigとAI SDKを連携したテンプレートを提供しています。
npx create-next-app --example https://github.com/vercel-labs/ai-sdk-flags-edge-config ai-sdk-flags-edge-config-example
このテンプレートは、Edge Configを使ってAIモデルを動的に切り替えるチャットボットの基本的な実装を示しています。
よくあるエラー・ハマりどころと回避策
VercelでのCDNキャッシュ戦略とAI連携には、いくつかの共通の落とし穴があります。ここでは、それらの問題と具体的な回避策を解説します。
古いコンテンツが配信される (Stale Content)
-
ハマりどころ:
Cache-Controlヘッダーの設定ミスやキャッシュ無効化の漏れにより、ユーザーに古いコンテンツが提供されてしまうことがあります。特に、Vercelの前に別のCDNやプロキシがある場合、キャッシュの連鎖が複雑になり、問題の特定が難しくなります。 -
回避策:
-
Cache-Controlヘッダーでs-maxageやstale-while-revalidateを活用し、キャッシュの鮮度とパフォーマンスのバランスを取ります。stale-while-revalidateは、古いコンテンツを即座に提供しつつ、バックグラウンドで新しいコンテンツを再検証するため、ユーザー体験を損なわずにコンテンツの鮮度を保てます。 - コンテンツ更新時には、Next.jsの
revalidatePath(),revalidateTag()や Vercel CLIのvercel cache invalidate --tag <tag-name>を積極的に使用し、明示的にキャッシュをクリアします。 - Vercelの前に外部CDNを使用することは、キャッシュ戦略を複雑にするため、極力避けるか、外部CDNでのキャッシュ期間を非常に短く設定することを検討してください。
-
POSTリクエストでの予期せぬキャッシュヒット
- ハマりどころ: JSON-RPCやGraphQLのようなAPIで、POSTリクエストのボディに基づいて異なるレスポンスを返す場合、同じURLへの異なるPOSTボディが同じキャッシュされたレスポンスを返してしまうことがあります。これは、VercelのISRがPOSTリクエストをURLのみでキャッシュし、リクエストボディを考慮しないために発生します。
-
回避策:
- このようなAPIでは、
Cache-Control: no-storeヘッダーを設定してCDNキャッシュを完全に無効にするのが最も確実な方法です。 - あるいは、リクエストボディの内容をハッシュ化してURLのクエリパラメータに含めるなど、キャッシュキーにボディの内容を反映させる工夫が必要です。ただし、これはAPI設計に大きな変更を伴う場合があります。
- このようなAPIでは、
AIモデルのコスト超過とレート制限
- ハマりどころ: AIモデルへのリクエストが急増すると、予期せぬコスト増加やプロバイダー側のレート制限に引っかかり、サービス停止につながる可能性があります。
-
回避策:
- AIレスポンスのキャッシュ: AI SDKミドルウェアで解説したように、AIレスポンスをキャッシュすることで、重複するLLM呼び出しを削減し、コストとレイテンシーを大幅に改善できます。
- レート制限の実装: Vercel WAFとVercel KV (Upstash Ratelimit) を組み合わせて、APIエンドポイントにレート制限を実装します。これにより、悪意のある大量リクエストや誤った実装による過剰な呼び出しから保護できます。
- Vercel AI Gatewayの活用: AI Gatewayは、プロバイダーレベルのフォールバックやプロンプトのキャッシュをサポートし、モデルの障害やレート制限時にも対応できる堅牢なシステムを構築するのに役立ちます。
-
モデルパラメータの最適化: LLMへのリクエスト時に
maxTokensやtimeoutの制限を設定し、AI呼び出しが予算を使い果たしたり、応答が遅すぎたりするのを防ぎます。
まとめ
Vercel CDNとAI連携を最大限に活用することは、高速でコスト効率の良いAIアプリケーションを構築する上で不可欠です。本記事では、以下の主要なポイントを解説しました。
-
Vercel CDNキャッシュの活用:
Cache-Controlヘッダーとvercel.jsonを用いた動的コンテンツのキャッシュ設定。 - AI SDKでのキャッシュ実装: Vercel AI SDKのミドルウェアとVercel KVを組み合わせた、LLMレスポンス(特にストリーミング)のキャッシュ戦略。
- タグベースのキャッシュ無効化: コンテンツ更新時の効率的なキャッシュパージ。
- Vercel Edge Configとの連携: 動的なAIモデル切り替えや機能フラグによる柔軟な制御。
- よくある問題と回避策: 古いコンテンツ、POSTリクエストのキャッシュ、AIコスト・レート制限への対応。
これらの技術を適切に組み合わせることで、ユーザーに最高の体験を提供しつつ、運用コストを最適化することが可能です。AIアプリケーションの構築において、ぜひこれらの Vercel の最新機能を活用してみてください。
さらに深く学ぶには、Vercel DocsのCachingとVercel AI SDK Docsを参照することをお勧めします。