404 Not Found

404 Not Found


nginx

総合演習 — 入門編

第1フェーズの5つのレッスンが完了しましたので, すべてを統合する時が来ました。Charlieはチームに動作するMegaShopの基本版の構築を依頼しました。ホームページ, カテゴリページ, 商品詳細ページ, SSR事前取得, 適切なレイアウト切り替えを含みます。Aliceが顧客としてユーザー体験をテストします。

1. 学ぶ内容


2. チームの実話

(1) ペインポイント:断片的な知識を統合できない

Aliceはルーティング, コンポーネント, データ取得を学びましたが, 完全なプロジェクトにまとめる方法がわかりません。Bobが書いたページにはレイアウトがなく, 商品データが読み込まれた後にページがちらつきます。Charlieは投資家にデモするためのMVPが必要です。

(2) 実践的課題の総合的解決策

第1フェーズのすべての概念を1つのプロジェクトに統合します:ファイルルーティング+レイアウトシステム+useFetch SSR事前取得+自動コンポーネントインポートで, ゼロから動作するECプラットフォームを構築します。

(3) 効果:部品から製品へ

この章の終わりまでに, Aliceは動作するMegaShopの基本版を完成させます。4つのページ, 2つのレイアウト, SSR事前取得付きの商品データ, SEOに優しいHTML出力を備えています。


3. プロジェクト計画

(1) MegaShop基本版アーキテクチャ

100%
flowchart TB
    A[MegaShop フェーズ1] --> B[ページ]
    A --> C[レイアウト]
    A --> D[コンポーネント]
    A --> E[APIルート]

    B --> B1[/ → ホームページ]
    B --> B2[/products → 商品一覧]
    B --> B3[/products/[id] → 商品詳細]
    B --> B4[/about → Aboutページ]

    C --> C1[default → ヘッダー+フッター]
    C --> C2[sidebar → 管理サイドバー]

    D --> D1[AppHeader]
    D --> D2[AppFooter]
    D --> D3[ProductCard]

    E --> E1[GET /api/products]
    E --> E2[GET /api/products/:id]

(2) ページとルーティング設計

URL ページファイル レイアウト 機能
/ pages/index.vue default ホーム:注目商品
/products pages/products/index.vue default 商品一覧 (SSR事前取得)
/products/[id] pages/products/[id].vue default 商品詳細 (SSR事前取得)
/about pages/about.vue default Aboutページ
/admin pages/admin/index.vue sidebar 管理ダッシュボード
404 ページなし default カスタム404

4. ページ構造の構築

(1) ▶ サンプル:app.vueルートコンポーネント

VUE
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

出力:

TEXT
// 実行成功

(2) ▶ サンプル:デフォルトレイアウト

VUE
<!-- layouts/default.vue -->
<template>
  <div class="layout-default">
    <AppHeader />
    <main class="container">
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

出力:

TEXT
// 実行成功

(3) ▶ サンプル:サイドバーレイアウト

VUE
<!-- layouts/sidebar.vue -->
<template>
  <div class="layout-sidebar">
    <AppHeader />
    <div class="main-wrapper">
      <aside class="sidebar">
        <h3>管理パネル</h3>
        <nav>
          <NuxtLink to="/admin">ダッシュボード</NuxtLink>
          <NuxtLink to="/admin/products">商品</NuxtLink>
          <NuxtLink to="/admin/orders">注文</NuxtLink>
        </nav>
      </aside>
      <main class="content">
        <slot />
      </main>
    </div>
  </div>
</template>

出力:

TEXT
// 実行成功

5. コアAPIルーティング

(1) ▶ サンプル:商品一覧API

TYPESCRIPT
// server/api/products/index.get.ts
interface Product {
  id: number; name: string; price: number
  image: string; category: string; inStock: boolean
}

const mockProducts: Product[] = Array.from({ length: 100 }, (_, i) => ({
  id: i + 1,
  name: `Product ${i + 1}`,
  price: Math.round(Math.random() * 500 * 100) / 100,
  image: `/images/product-${i + 1}.webp`,
  category: ['electronics', 'clothing', 'home'][i % 3],
  inStock: Math.random() > 0.2
}))

export default defineEventHandler((event) => {
  const query = getQuery(event)
  const page = Number(query.page) || 1
  const limit = Number(query.limit) || 12
  const category = query.category as string

  let filtered = mockProducts
  if (category) filtered = filtered.filter(p => p.category === category)

  const start = (page - 1) * limit
  return {
    items: filtered.slice(start, start + limit),
    total: filtered.length,
    page,
    limit
  }
})

出力:

TEXT
// 実行成功

(2) ▶ サンプル:商品詳細API

TYPESCRIPT
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
  const id = Number(getRouterParam(event, 'id'))
  const product = {
    id,
    name: `Premium Product ${id}`,
    price: 299.99,
    image: `/images/product-${id}.webp`,
    category: 'electronics',
    inStock: true,
    description: `High-quality product #${id} with premium features.`,
    reviewCount: Math.floor(Math.random() * 5 * 1000),
    rating: 4.5
  }

  if (id > 100) {
    throw createError({ statusCode: 404, message: 'Product not found' })
  }

  return product
})

出力:

TEXT
// 実行成功

6. ページの実装

(1) ▶ サンプル:ホームページのSSR事前取得

VUE
<!-- pages/index.vue -->
<template>
  <div class="homepage">
    <section class="hero">
      <h1>MegaShop</h1>
      <p>100万以上の商品を世界中にお届け</p>
      <NuxtLink to="/products" class="cta">今すぐショッピング</NuxtLink>
    </section>
    <section class="featured">
      <h2>注目商品</h2>
      <div class="grid">
        <ProductCard v-for="p in featured" :key="p.id" :product="p" />
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
const { data: featured } = await useFetch('/api/products', {
  query: { limit: 6 },
  transform: (data: any) => data.items
})

useHead({ title: 'MegaShop - Premium E-Commerce' })
</script>

出力:

TEXT
// 実行成功

(2) ▶ サンプル:商品詳細ページ

VUE
<!-- pages/products/[id].vue -->
<template>
  <div v-if="product" class="product-detail">
    <img :src="product.image" :alt="product.name" />
    <div class="info">
      <h1>{{ product.name }}</h1>
      <p class="price">${{ product.price }} USD</p>
      <p>{{ product.description }}</p>
      <p>{{ product.reviewCount }} レビュー · {{ product.rating }} スター</p>
      <button @click="addToCart">カートに追加</button>
    </div>
  </div>
  <div v-else class="not-found">
    <h1>商品が見つかりません</h1>
    <NuxtLink to="/products">商品一覧に戻る</NuxtLink>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)

useHead({
  title: computed(() => product.value ? `${product.value.name} - MegaShop` : 'Not Found')
})

const cart = useState<any[]>('cart', () => [])
function addToCart() {
  if (product.value) cart.value.push(product.value)
}
</script>

出力:

TEXT
// 実行成功

7. コードレビューチェックリスト

(1) SSRデータハイドレーションチェック

チェック項目 正しいアプローチ よくある間違い
useFetchの使用 await useFetch()でデータを待機 awaitを忘れるとpending状態になる
SSRソースコード表示 HTMLに商品データが含まれる <div>のみ
Payload引き渡し クライアントがリクエストを重複しない ネットワークタブに重複APIリクエスト
route.params setupの最上位で取得 onMountedで取得 (SSRではこのフックなし)

(2) 自動インポート有効性チェック

チェック項目 正しいアプローチ よくある間違い
コンポーネント参照 <ProductCard />自動インポート コンポーネントを手動インポート
コンポーザブル useFetch()を直接使用 useFetchを手動インポート
useState useState('key')を直接使用 'nuxt/app'から手動インポート
コンポーネント名 ディレクトリプレフィックスルールに基づく参照 コンポーネント名がファイルパスと不一致

8. 総合例:MegaShop基本版nuxt.config.ts

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,

  app: {
    head: {
      title: 'MegaShop',
      meta: [
        { name: 'description', content: '100万以上の商品を扱うプレミアムEC' }
      ]
    }
  },

  modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],

  components: [{ path: '~/components', pathPrefix: false }],

  routeRules: {
    '/': { prerender: true },
    '/products': { swr: 60 },
    '/products/**': { swr: 3600 },
    '/admin/**': { ssr: false }
  }
})

❓ よくある質問

Q ホームページをprerenderに設定するとデータが更新されないのはなぜですか?
A prerender: trueを設定するとビルド時に静的HTMLが生成されるため, データは自動的に更新されません。再ビルドするか, swr戦略に切り替える必要があります。
Q 商品詳細ページが時々SSRで時々そうでないのはなぜですか?
A useFetchが正しくawaitと共に使用されているか確認してください。useFetchが条件文の中にある場合, SSRはそれをスキップする可能性があります。setupの最上位で呼び出すようにしてください。
Q 404ページはどのようにトリガーされますか?
A Nuxt 3はルートがマッチしない場合に自動的に404ページを表示します。APIレベルでcreateError({ statusCode: 404 })をスローすることもできます。
Q レイアウト切り替え時にちらつきが発生する場合はどうすればよいですか?
A レイアウトは切り替え時に再レンダリングされます。<Transition>でスロットをラップしてトランジションアニメーションを追加するか, pageTransition設定を使用できます。
Q モックデータは本番環境でどのように置き換えられますか?
A フェーズ2でPrismaがモックを置き換えます。現在のAPIレスポンス形式は一貫性を保ちます。後でserver APIの実装を変更するだけで済みます。
Q SSRが動作しているかどうかを確認するにはどうすればよいですか?
A ブラウザの「ソースを表示」機能 (Ctrl+U)を使ってください。HTMLに商品名と価格が含まれていればSSRは動作しています。空のdivのみであればCSRです。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度:⭐):MegaShop基本版のすべてのページを完成させ, すべてのルートがアクセス可能であることを確認してください。
  2. 応用問題 (難易度:⭐⭐):商品カテゴリフィルター機能を追加し, 商品一覧ページでwatchuseFetchを使ってカテゴリ切り替え時に自動リフレッシュするようにしてください。
  3. チャレンジ (難易度:⭐⭐⭐):admin/productsページ (サイドバーレイアウト+CSRモード)を追加し, 商品テーブル表示を実装し, ソースコード表示時のCSRとSSRページの違いを比較してください。

---|

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%