404 Not Found

404 Not Found


nginx

Nitroサーバーエンジン

CharlieはMegaShopにキャッシュ層を追加する必要があります - 数百万商品のページでの頻繁なデータベースクエリが遅すぎます。Bobはデプロイ環境 (Node, Docker, Vercel)ごとにストレージ手法が異なることを発見しました。Nitroエンジンは統一されたストレージ抽象レイヤーとキャッシュAPIを提供し, 単一のコードベースですべての環境で動作させることができます。

1. 学ぶ内容


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アーキテクチャの概要

100%
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を使用して複数環境で統一された抽象化を提供しています。

📖まとめ


📝練習問題

  1. 基本問題 (難易度:⭐):useStorageメモリドライバを設定し, 商品データのキャッシュに対するシンプルな読み書き操作を実装してください
  2. 応用問題 (難易度:⭐⭐):cachedEventHandlerを使用して商品一覧APIのキャッシュを実装し, キャッシュヒット時にレスポンスが速くなることを確認してください
  3. チャレンジ (難易度:⭐⭐⭐):完全なキャッシュ無効化メカニズムを実装してください - 注文作成時に関連商品のキャッシュを自動的に無効化し, 手動APIで商品IDによるキャッシュクリアまたは全キャッシュクリアを可能にしてください

---|

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%