Nitroサーバーエンジン
CharlieはMegaShopにキャッシュ層を追加する必要があります - 数百万商品のページでの頻繁なデータベースクエリが遅すぎます。Bobはデプロイ環境 (Node, Docker, Vercel)ごとにストレージ手法が異なることを発見しました。Nitroエンジンは統一されたストレージ抽象レイヤーとキャッシュAPIを提供し, 単一のコードベースですべての環境で動作させることができます。
1. 学ぶ内容
- Nitroアーキテクチャ:Rollupビルド + H3ルーティング + ランタイム分離
- ストレージ層:useStorage() + KV, Redis, FileSystem, OSSのマルチドライバ対応
- キャッシュAPI:cachedEventHandler / defineCachedFunction
- イベントフック:nitro.hooksライフサイクル
- MegaShop商品キャッシュ + 在庫更新 + 複数プリセットでのデプロイ
2. アーキテクトのリアルストーリー
(1) ペインポイント:数百万商品のクエリがデータベースを圧迫
MegaShopの商品詳細ページは毎秒2,000回のデータベースクエリを実行し, 応答時間は500msです。BobはRedisキャッシュを追加しましたが, コードはデプロイ環境と密結合しています - ローカルではファイルベースのキャッシュ, 本番ではRedisを使用しており, 2つの別々のコード変更が必要です。
(2) Nitroストレージ抽象レイヤーのソリューション
NitroのuseStorage()統一ストレージインターフェースにより, コードを変更せずにドライバを切り替えられます:
TYPESCRIPT
// 同じコード, プリセットに応じてドライバが切り替わる
const storage = useStorage('products')
await storage.setItem('product:123', productData)
(3) 利点:統一インターフェース + 5msの応答時間
ストレージ層のコードが標準化されます:ローカル開発ではmemory/fsを使用し, 本番ではRedis/KVを使用します。商品キャッシュがヒットした場合, 応答時間は5msとなり, データベースクエリが95%削減されます。
3. Nitroアーキテクチャ
(1) Nitroアーキテクチャの概要
graph TB
A[Nitroエンジン] --> B[Rollupビルド]
A --> C[H3 HTTPフレームワーク]
A --> D[ストレージ抽象]
A --> E[キャッシュシステム]
A --> F[フックシステム]
A --> G[マルチプリセットデプロイ]
B --> B1[サーバーバンドル]
B --> B2[未使用コードのツリーシェイキング]
C --> C1[defineEventHandler]
C --> C2[ルーター / ミドルウェア]
D --> D1[メモリドライバ]
D --> D2[ファイルシステムドライバ]
D --> D3[Redisドライバ]
D --> D4[クラウドKVドライバ]
E --> E1[cachedEventHandler]
E --> E2[defineCachedFunction]
G --> G1[Nodeサーバー]
G --> G2[Vercel / Cloudflare]
G --> G3[Docker / Lambda]
(2) Nitroプリセットの比較
| プリセット | ランタイム環境 | ストレージドライバ | デプロイ方法 |
|---|---|---|---|
| node-server | Node.js | fs/memory/redis | PM2/Docker |
| Vercel | Vercel Serverless | Vercel KV | git push |
| vercel-edge | Vercel Edge | Edge KV | git push |
| cloudflare-pages | Cloudflare Workers | KV/R2 | wrangler deploy |
| netlify | Netlify Functions | Netlify Blobs | git push |
| bun | Bunランタイム | fs/memory | bun run |
(1) ▶サンプル:Nitroプリセットの設定
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
compressPublicAssets: true,
storage: {
// 開発:メモリドライバ
cache: { driver: 'memory' },
// 本番:環境変数で設定
products: { driver: process.env.STORAGE_DRIVER || 'memory' }
}
}
})
出力:
TEXT
// 実行成功
4. ストレージ抽象レイヤー
(1) useStorage API
| メソッド | 説明 | 例 |
|---|---|---|
| setItem(key, value) | 書き込み | await storage.setItem('product:1', data) |
| getItem(key) | 読み取り | await storage.getItem('product:1') |
| removeItem(key) | 削除 | await storage.removeItem('product:1') |
| getKeys(base) | キー一覧 | await storage.getKeys('product:') |
| hasItem(key) | 存在確認 | await storage.hasItem('product:1') |
| clear(base) | クリア | await storage.clear('product:') |
(1) ▶サンプル:商品キャッシュストレージ
TYPESCRIPT
// server/api/products/[id].get.ts - ストレージキャッシュ付き
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const storage = useStorage('products')
// まずキャッシュを試す
const cached = await storage.getItem(`product:${id}`)
if (cached) return cached
// キャッシュミス:データベースから取得
const product = await fetchProductFromDB(Number(id))
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
// TTL付きでキャッシュに保存 (1時間)
await storage.setItem(`product:${id}`, product, { ttl: 3600 })
return product
})
出力:
TEXT
// 実行成功
(2) ▶サンプル:Redisストレージ設定
TYPESCRIPT
// nuxt.config.ts - 本番用Redisストレージ
export default defineNuxtConfig({
nitro: {
storage: {
products: {
driver: 'redis',
url: process.env.REDIS_URL || 'redis://localhost:6379',
prefix: 'megashop:products:'
},
cache: {
driver: 'redis',
url: process.env.REDIS_URL || 'redis://localhost:6379',
prefix: 'megashop:cache:'
}
}
}
})
出力:
TEXT
// 実行成功
(2) ストレージドライバの比較
| ドライバ | 速度 | 永続性 | 分散対応 | ユースケース |
|---|---|---|---|---|
| memory | ⚡⚡⚡ | ❌ 再起動で消失 | ❌ | 開発/テスト |
| fs | ⚡⚡ | ✅ ローカル | ❌ | スタンドアロンデプロイ |
| redis | ⚡⚡ | ✅ | ✅ | 本番クラスタ |
| cloudflare-kv | ⚡ | ✅ | ✅ | Cloudflare |
| vercel-kv | ⚡ | ✅ | ✅ | Vercel |
5. キャッシュAPI
(1) ▶サンプル:cachedEventHandler
TYPESCRIPT
// server/api/products/featured.get.ts
export default cachedEventHandler(
async () => {
// このハンドラーの結果はキャッシュされる
const products = await $fetch('/api/internal/products/featured')
return products
},
{
maxAge: 60 * 60, // 1時間キャッシュ
swr: true, // 古いキャッシュを提供しながら再検証
staleMaxAge: 60 * 60 * 4, // 古いキャッシュは4時間有効
getKey: () => 'featured-products',
varies: ['Accept-Language'] // 言語ごとのキャッシュ
}
)
出力:
TEXT
// 実行成功
(2) ▶サンプル:defineCachedFunction
TYPESCRIPT
// server/utils/cachedProduct.ts
export const getCachedProduct = defineCachedFunction(
async (id: number) => {
return await fetchProductFromDB(id)
},
{
maxAge: 60 * 60,
swr: true,
getKey: (id) => `product:${id}`,
name: 'cachedProduct'
}
)
// APIハンドラーでの使用
// server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
return await getCachedProduct(id)
})
出力:
TEXT
// 実行成功
6. イベントフック
(1) Nitroライフサイクルフック
| フック | トリガー条件 | 目的 |
|---|---|---|
| close | サービス停止 | リソース解放 |
| error | 未処理エラー | エラー報告 |
| request | リクエスト開始 | ログ/認証 |
| response | レスポンス送信 | パフォーマンス統計 |
| beforeResponse | レスポンス前 | レスポンス変更 |
(1) ▶サンプル:在庫更新イベントフック
TYPESCRIPT
// server/plugins/stock.ts
export default defineNitroPlugin((nitroApp) => {
// 在庫更新イベントをリッスン
nitroApp.hooks.hook('request', async (event) => {
const url = getRequestURL(event)
if (url.pathname === '/api/orders' && getMethod(event) === 'POST') {
// 注文前:在庫の確認
const body = await readBody(event)
const outOfStock = await checkStockAvailability(body.items)
if (outOfStock.length > 0) {
throw createError({
statusCode: 400,
message: `Items out of stock: ${outOfStock.join(', ')}`
})
}
}
})
// 注文後:商品キャッシュを無効化
nitroApp.hooks.hook('afterResponse', async (event) => {
const url = getRequestURL(event)
if (url.pathname === '/api/orders' && getMethod(event) === 'POST') {
const body = await readBody(event)
const storage = useStorage('products')
// キャッシュされた商品を無効化
for (const item of body.items) {
await storage.removeItem(`product:${item.productId}`)
}
}
})
})
出力:
TEXT
// 実行成功
7. 総合例:MegaShop商品キャッシュシステム
TYPESCRIPT
// nuxt.config.ts - 本番Nitro設定
export default defineNuxtConfig({
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
compressPublicAssets: true,
storage: {
products: {
driver: process.env.NODE_ENV === 'production' ? 'redis' : 'memory',
url: process.env.REDIS_URL
}
},
cache: {
pages: ['/products/', '/categories/']
}
}
})
TYPESCRIPT
// server/api/products/[id].get.ts - 完全なキャッシュ戦略
export default cachedEventHandler(
async (event) => {
const id = Number(getRouterParam(event, 'id'))
const product = await fetchProductFromDB(id)
if (!product) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
return product
},
{
maxAge: 60 * 60, // 1時間キャッシュ
swr: true, // 古いキャッシュを提供しながら再検証
staleMaxAge: 60 * 60 * 24, // 古いキャッシュは24時間有効
getKey: (event) => `product:${getRouterParam(event, 'id')}`,
varies: ['Accept-Language']
}
)
TYPESCRIPT
// server/api/admin/invalidate-cache.post.ts - 手動キャッシュ無効化
export default defineEventHandler(async (event) => {
const { productId, scope } = await readBody(event)
const storage = useStorage('products')
if (scope === 'all') {
// すべての商品キャッシュを無効化
const keys = await storage.getKeys()
for (const key of keys) {
await storage.removeItem(key)
}
return { message: 'All caches invalidated' }
}
if (productId) {
await storage.removeItem(`product:${productId}`)
return { message: `Cache invalidated for product ${productId}` }
}
throw createError({ statusCode: 400, message: 'Specify productId or scope=all' }
})
❓よくある質問
Q NitroとNuxtの関係は何ですか?
A NitroはNuxt 3のサーバーサイドエンジンであり, ビルド, ルーティング, ストレージ, キャッシュ, デプロイを担当します。Nuxt 3 = Vue 3 + Vite (フロントエンド)+ Nitro (バックエンド)。
Q useStorageのメモリドライバを使用すると, 再起動後にデータは失われますか?
A はい。メモリドライバは開発環境で便利ですが, 本番環境ではRedisやFSなどの永続化ドライバを使用する必要があります。ドライバの設定は
nuxt.config.tsを変更するだけで済み, コードの変更は不要です。Q cachedEventHandlerとrouteRulesのSWRの違いは何ですか?
A
routeRulesのSWRはページ全体のHTMLをキャッシュしますが, cachedEventHandlerはAPIレスポンスデータをキャッシュします。ページレベルではrouteRules, APIレベルではcachedEventHandlerを使用してください。Q NitroプラグインとNuxtプラグインの違いは何ですか?
A Nitroプラグイン (server/plugins/)はサーバー上で実行され, ストレージ, フック, データベースと連携します。Nuxtプラグイン (
plugins/に配置)はVueアプリケーション内で実行され, コンポーネント, Composables, サードパーティSDKを処理します。Q Nitroのキャッシュヒット率を監視するにはどうすればよいですか?
A request/responseフックでキャッシュのヒットとミスをログに記録してください。本番ではAPMツール (Sentry/Datadog)を使用して監視するか, 独自のメトリクスエンドポイントを設定してください。
Q Cloudflare KVとRedisのどちらを選ぶべきですか?
A CloudflareデプロイではKVを使用し (ネイティブ統合, グローバル配信), Node.js/VercelデプロイではRedisを使用します (低レイテンシ, 豊富なデータ構造)。MegaShopは
useStorageを使用して複数環境で統一された抽象化を提供しています。📖まとめ
- NitroはNuxt 3のサーバーサイドエンジン:ビルド + H3ルーティング + ストレージ + キャッシュ + マルチデプロイプリセット
- useStorage()は統一ストレージインターフェースを提供;開発ではmemory, 本番ではRedis/KVを使用し, コードの変更は不要
cachedEventHandlerはAPIレスポンスをキャッシュ;defineCachedFunctionは関数の結果をキャッシュ- Nitroフックはrequest/responseライフサイクルを監視し, 認証, ログ, キャッシュ無効化を実装
- MegaShopはNitroストレージで商品キャッシュ + イベントフックで在庫更新時にキャッシュを自動無効化
📝練習問題
- 基本問題 (難易度:⭐):useStorageメモリドライバを設定し, 商品データのキャッシュに対するシンプルな読み書き操作を実装してください
- 応用問題 (難易度:⭐⭐):
cachedEventHandlerを使用して商品一覧APIのキャッシュを実装し, キャッシュヒット時にレスポンスが速くなることを確認してください - チャレンジ (難易度:⭐⭐⭐):完全なキャッシュ無効化メカニズムを実装してください - 注文作成時に関連商品のキャッシュを自動的に無効化し, 手動APIで商品IDによるキャッシュクリアまたは全キャッシュクリアを可能にしてください
---|



