パフォーマンス最適化
MegaShopは公開されましたが, Lighthouseスコアはわずか45です。初回画面の読み込みに4.5秒かかり, 画像は最適化されておらず, JSバンドルが大きすぎます。CharlieはLighthouseスコア90+と初回画面のLCP 2秒未満を達成する必要があります。
1. 学ぶ内容
- コード分割とTree Shaking:Viteビルド最適化とバンドル分析
- 画像最適化:@nuxt/image + WebP + 遅延読み込み + レスポンシブsrcset
- コンポーネント遅延読み込みと非同期処理:"Lazy"プレフィックス + Suspense
- キャッシング戦略:HTTPキャッシュヘッダー + Nitro KV + CDN
- MegaShop Lighthouse 90+最適化の実践
2. アーキテクトのある本当の話
(1) 課題:Lighthouseスコア45, 初回コンテンツ描画4.5秒
AliceがスマホでMegaShopを開くと, 商品が表示されるまで4.5秒かかりました。CharlieがLighthouse監査を実行すると, JSバンドルが2.8 MB, すべての画像が未圧縮のPNG, キャッシュヘッダーがなく, 50コンポーネントのコードが初回画面で読み込まれていました。
(2) Nuxt 3パフォーマンス最適化の解決策
Nuxt 3にはさまざまな組み込みパフォーマンス最適化機能が含まれています:
TYPESCRIPT
// @nuxt/imageによる画像最適化
<NuxtImg src="/products/123.webp" width="400" height="400" loading="lazy" />
(3) 結果:Lighthouseスコア92, LCP 1.3秒
最適化後, JSバンドルサイズは180 KB (gzip)に削減され, 画像は自動的にWebPで配信されレスポンシブ対応, 初回画面のLCPは1.3秒, Lighthouseスコアは92になりました。
3. コード分割とTree Shaking
(1) パフォーマンス最適化判定ツリー
flowchart TB
A[パフォーマンス問題] --> B{問題の種類は?}
B -->|初回描画が遅い| C[コード分割]
B -->|画像が大きい| D[画像最適化]
B -->|後続読み込みが遅い| E[キャッシング戦略]
B -->|JSが大きすぎる| F[Tree Shaking]
C --> C1[Lazyコンポーネント]
C --> C2[動的インポート]
D --> D1["@nuxt/imageモジュール"]
D --> D2[WebP変換]
E --> E1[HTTPキャッシュヘッダー]
E --> E2[Nitro KVキャッシュ]
F --> F1[rollup-plugin-visualizerでバンドル確認]
(2) バンドル分析
BASH
npm install -D rollup-plugin-visualizer
TYPESCRIPT
// nuxt.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineNuxtConfig({
vite: {
plugins: [
visualizer({ filename: 'bundle-stats.html', open: true })
],
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'vue-router'],
'pinia': ['pinia']
}
}
}
}
}
})
(1) ▶ サンプル:Lazyコンポーネントによるコード分割
VUE
<template>
<div>
<!-- コア:即座に読み込み -->
<AppHeader />
<ProductHero :product="featured" />
<!-- フォールド下:遅延読み込み -->
<LazyProductGrid :products="all" />
<LazyProductReviews :product-id="id" />
<!-- 条件付き:表示時のみ読み込み -->
<LazyNewsletterSignup v-if="showNewsletter" />
</div>
</template>
出力:
TEXT
// 実行成功
(3) 最適化前後の比較
| 指標 | 最適化前 | 最適化後 | 改善 |
|---|---|---|---|
| JSバンドル (gzip) | 2.8 MB | 180 KB | -94% |
| 初回画面のコンポーネント数 | 50 | 8 | -84% |
| 初回画面のJSダウンロード | 2.8 MB | 85 KB | -97% |
| LCP | 4.5s | 1.3s | -71% |
4. 画像最適化
(1) ▶ サンプル:@nuxt/imageのインストール
BASH
npm install @nuxt/image
出力:
TEXT
# コマンド実行成功
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
quality: 80,
format: ['webp', 'avif'],
screens: {
xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536
}
}
})
(2) ▶ サンプル:NuxtImgレスポンシブ画像
VUE
<template>
<div>
<!-- 自動WebP/AVIF + レスポンシブsrcset -->
<NuxtImg
src="/images/product-123.jpg"
width="800"
height="600"
format="webp"
loading="lazy"
sizes="sm:100vw md:50vw lg:33vw"
:modifiers="{ quality: 80 }"
alt="Premium Headphones"
/>
<!-- ブラー付きプレースホルダー -->
<NuxtImg
src="/images/hero.jpg"
placeholder
width="1920"
height="1080"
format="webp"
alt="MegaShop Hero"
/>
</div>
</template>
出力:
TEXT
// 実行成功
(3) ▶ サンプル:NuxtPictureマルチフォーマット
VUE
<template>
<!-- NuxtPicture:ブラウザごとに最適フォーマットを自動選択 -->
<NuxtPicture
src="/images/banner.jpg"
width="1200"
height="400"
format="avif,webp"
loading="lazy"
alt="Sale Banner"
/>
</template>
出力:
TEXT
// 実行成功
(1) 画像フォーマットの比較
| フォーマット | 圧縮率 | ブラウザサポート | ユースケース |
|---|---|---|---|
| AVIF | 🟢 最高 (JPG比50%) | Chrome/Edge/Firefox | モダンブラウザ |
| WebP | 🟢 高 (JPG比30%) | 主要ブラウザ全般 | 汎用推奨フォーマット |
| JPEG | 🟡 ベースライン | 全ブラウザ | 互換性フォールバック |
| PNG | 🔴 大きい | 全ブラウザ | 透過画像 |
5. キャッシング戦略
(1) ▶ サンプル:HTTPキャッシュヘッダー
TYPESCRIPT
// nuxt.config.ts - ルートごとのキャッシュヘッダー
export default defineNuxtConfig({
routeRules: {
// 静的アセット:1年間のimmutableキャッシュ
'/_nuxt/**': {
headers: { 'cache-control': 'public, max-age=31536000, immutable' }
},
// 画像:1日キャッシュ
'/images/**': {
headers: { 'cache-control': 'public, max-age=86400, stale-while-revalidate=604800' }
},
// 商品ページ:ISRキャッシュ
'/products/**': {
swr: 86400,
headers: { 'cache-control': 'public, s-maxage=86400, stale-while-revalidate=3600' }
}
}
})
出力:
TEXT
// 実行成功
(2) ▶ サンプル:Nitro KVキャッシュAPI
TYPESCRIPT
// server/api/products/[id].get.ts - キャッシュ付きハンドラー
export default cachedEventHandler(
async (event) => {
const id = getRouterParam(event, 'id')
const product = await fetchProductFromDB(Number(id))
return product
},
{
maxAge: 60 * 60, // 1時間キャッシュ
swr: true, // キャッシュ期限後も古いデータを返しつつ再検証
getKey: (event) => `product:${getRouterParam(event, 'id')}`,
varies: ['Accept-Language'] // 言語ごとに異なるキャッシュ
}
)
出力:
TEXT
// 実行成功
(1) キャッシュ階層の比較
| レベル | 場所 | 速度 | 制御方法 |
|---|---|---|---|
| ブラウザキャッシュ | ユーザーデバイス | ⚡⚡⚡ 最速 | Cache-Controlヘッダー |
| CDNエッジキャッシュ | 最寄りのノード | ⚡⚡ 高速 | CDN設定 + Vary |
| Nitro KVキャッシュ | サーバーメモリ | ⚡ 高速 | cachedEventHandler |
| ISRページキャッシュ | Nitroストレージ | ⚡ 高速 | routeRules swr |
6. 総合例:MegaShopパフォーマンス最適化設定
TYPESCRIPT
// nuxt.config.ts - 完全パフォーマンス最適化
export default defineNuxtConfig({
ssr: true,
modules: ['@nuxt/image', '@nuxtjs/tailwindcss'],
// 画像最適化
image: {
quality: 80,
format: ['webp', 'avif'],
screens: { xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280 }
},
// Viteビルド最適化
vite: {
build: {
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('pinia')) return 'vendor-pinia'
if (id.includes('vue')) return 'vendor-vue'
return 'vendor-libs'
}
}
}
}
}
},
// ルートレベルのキャッシング戦略
routeRules: {
'/': { prerender: true },
'/products': { swr: 3600 },
'/products/**': { swr: 86400 },
'/admin/**': { ssr: false },
'/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
'/images/**': { headers: { 'cache-control': 'public, max-age=86400' } }
},
// アプリパフォーマンスヒント
experimental: {
payloadExtraction: true // ペイロードサイズを削減
}
})
VUE
<!-- pages/index.vue - 最適化されたホームページ -->
<template>
<div>
<AppHeader />
<!-- フォールド上:即座に読み込み -->
<section class="hero">
<NuxtImg src="/images/hero.jpg" width="1200" height="400" format="webp"
priority alt="MegaShop" />
<h1>MegaShop</h1>
</section>
<!-- フォールド下:遅延読み込み -->
<LazyProductGrid :products="featured" />
<!-- 条件付き:ビューポート内のみ読み込み -->
<LazyNewsletterSignup v-if="showNewsletter" />
</div>
</template>
<script setup lang="ts">
const { data: featured } = await useFetch('/api/products/featured')
const showNewsletter = ref(false)
</script>
❓ よくある質問
Q Lighthouseスコアが低い場合はどうすればよいですか?
A 優先度順に対処してください:1)画像最適化 (WebP/圧縮/遅延読み込み)→ 2)JSバンドル分析 (Lazyコンポーネント/Tree Shaking)→ 3)キャッシング戦略 (HTTPヘッダー/ISR)→ 4)フォント最適化。
Q @nuxt/imageは外部画像URLをサポートしていますか?
A はい。
image.domainsで外部ドメインを追加します:image: { domains: ['cdn.example.com'] }。NuxtImgは外部画像も自動的に最適化します。Q NuxtImgと通常の
imgタグの違いは何ですか?A NuxtImgは自動的に
srcsetを生成し, 画像をWebP/AVIFに変換し, デバイスサイズに合わせてクロップし, 遅延読み込みを有効にします。通常のimgタグでは, これらすべての最適化を手動で行う必要があります。Q ISRキャッシングとHTTPキャッシングはどちらが優先されますか?
A ISRはサーバー側キャッシュ (Nitroに保存), HTTPキャッシングはブラウザ/CDN層で発生します。リクエストフロー:ブラウザキャッシュ → CDNキャッシュ → Nitro ISR → 動的レンダリング。各層は独立して動作します。
Q
manualChunksの設定で注意すべき落とし穴はありますか?A クライアントチャンクにサーバー側コードを含めないでください。Nuxt 3はサーバーとクライアントのコード分離を自動的に処理します。
manualChunksはサードパーティライブラリの分割にのみ使用してください。Q Lighthouseスコア90+を達成するのは難しいですか?
A SSR, 画像最適化, キャッシング戦略で90+に到達できます。最も難しいのはLCPを2.5秒未満に保つことで, クリティカルレンダリングパスを合理化し, 初回画面では必須リソースのみを読み込む必要があります。
📖 まとめ
- コード分割:Lazyコンポーネント + 動的インポート + manualChunks。初回画面では必要なコードのみ読み込み
- 画像最適化:@nuxt/image — 自動WebP/AVIF, レスポンシブsrcset, 遅延読み込み
- キャッシング戦略:ブラウザ → CDN → Nitro ISR → 動的レンダリング, 多層キャッシュ
rollup-plugin-visualizerでバンドル分析し, サイズのボトルネックを特定- 最適化後のMegaShop:JS 180 KB (gzip)+ LCP 1.3秒 + Lighthouseスコア92
📝 練習問題
- 基本問題 (難易度:⭐):@nuxt/imageをインストールし, すべての
imgタグをNuxtImgに置き換え, 自動WebP変換を確認してください。 - 応用問題 (難易度:⭐⭐):
rollup-plugin-visualizerでバンドルを分析し, 最大の依存関係を特定してmanualChunksで分割を設定してください。 - チャレンジ (難易度:⭐⭐⭐):Lighthouseスコア90+を達成してください:画像最適化 + コード分割 + キャッシング戦略, Lighthouse CIで自動監査
---|



