Nuxt.js

Vue.jsエコシステムの標準的フルスタックフレームワーク。Composition APIとTypeScript統合により、大規模アプリケーション開発を強力にサポート。

フレームワークVue.jsフルスタックSSRSSGハイブリッドレンダリングTypeScript自動インポート

GitHub概要

nuxt/nuxt

The Intuitive Vue Framework.

ホームページ:https://nuxt.com
スター57,911
ウォッチ787
フォーク5,339
作成日:2016年10月26日
言語:TypeScript
ライセンス:MIT License

トピックス

csrframeworkfull-stackhacktoberfesthybridnodenuxtserver-renderingssgssrstatic-site-generatoruniversalvue

スター履歴

nuxt/nuxt Star History
データ取得日時: 2025/8/13 01:43

フレームワーク

Nuxt.js

概要

Nuxt.jsは、Vue.jsをベースとした無料でオープンソースのフルスタックWebフレームワークです。型安全で高性能、プロダクション対応のWebアプリケーションやウェブサイトの構築を設計されています。開発者が機能に集中できるよう、規約と自動化を提供してWeb開発を簡素化することを目的としています。

詳細

主要な特徴

  • 多様なレンダリングモード: サーバーサイドレンダリング(SSR)をデフォルトとし、静的サイト生成(SSG)、ハイブリッドレンダリング、エッジサイドレンダリングなど様々なレンダリング戦略をサポート
  • ファイルベースルーティング: pages/ディレクトリの構造に基づいて自動的にルートが生成され、ルート設定を簡素化
  • コード分割: 初期読み込み時間を短縮するため、自動的にコードを小さなチャンクに分割
  • 自動インポート: Vue composables、コンポーネント、ユーティリティを明示的にインポートすることなく使用でき、ツリーシェイキングと最適化されたJSバンドルの恩恵を受けることができる
  • データ取得ユーティリティ: SSR対応のデータ取得用composablesを提供
  • ゼロ設定TypeScriptサポート: 自動生成される型を含む組み込みTypeScriptサポート
  • 設定済みビルドツール: 開発時のホットモジュール置換(HMR)と本番向け最適化ビルドのためにViteをデフォルトで使用。Webpack、Rspackも代替バンドラーとしてサポート
  • サーバーエンジン(Nitro): フルスタック機能、APIルート、サーバーレスサポート、クロスプラットフォームデプロイメントを可能にするサーバーエンジン

アーキテクチャ

Nuxt.jsは、モジュラーアーキテクチャを採用しており、必要な機能のみを選択して使用できます。フレームワークのコアには、Vue.js、Vue Router、Vite/Webpack、h3、Nitroなどの実績のあるツールが統合されています。

メリット・デメリット

メリット

  • 優れたSEO: サーバーサイドレンダリングにより、検索エンジンがページをより適切にインデックス化可能
  • 高速な初期ページ読み込み: SSRによりブラウザに完全にレンダリングされたHTMLページが送信され、体感読み込み時間が向上
  • 優れた開発者体験: 自動インポート、ゼロ設定TypeScript、規約による設定で開発速度が向上
  • 柔軟なレンダリング: ユニバーサルレンダリング(SSR)、CSR、ハイブリッドレンダリング、エッジサイドレンダリングなど、細かい制御が可能
  • フルスタック対応: server/ディレクトリとNitroエンジンにより、単一フレームワーク内でフロントエンドとバックエンドの両方を構築可能
  • パフォーマンス: 低性能デバイスでも優れたパフォーマンスを発揮
  • アクセシビリティ: コンテンツが即座に利用可能で、支援技術を助ける

デメリット

  • 開発の制約: サーバーとブラウザの両方の環境でシームレスに動作するコードを書くことは、APIの違いにより困難な場合がある
  • 運用コスト: オンザフライレンダリング用のサーバー運用には月額費用が発生(エッジサイドレンダリングの活用により軽減可能)
  • 学習コストの上昇: Vue.jsに加えてNuxt.js固有の概念や規約を学ぶ必要がある
  • 過剰機能: シンプルなSPAには機能が多すぎる場合がある

主要リンク

書き方の例

Hello World

<!-- pages/index.vue -->
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Welcome to Nuxt!</p>
  </div>
</template>

<script setup>
// Composition API with auto-import
const title = 'Hello Nuxt!'

// Meta管理
useHead({
  title: 'Home',
  meta: [
    { name: 'description', content: 'My amazing site.' }
  ]
})
</script>

ページルーティングとナビゲーション

<!-- pages/about.vue -->
<template>
  <div>
    <h1>About Page</h1>
    <NuxtLink to="/">ホームに戻る</NuxtLink>
    <NuxtLink to="/products">商品一覧</NuxtLink>
  </div>
</template>

<!-- pages/products/[id].vue - 動的ルート -->
<template>
  <div>
    <h1>Product: {{ $route.params.id }}</h1>
    <button @click="$router.back()">戻る</button>
  </div>
</template>

<script setup>
// パラメータの取得
const route = useRoute()
const productId = route.params.id
</script>

データフェッチング

<template>
  <div>
    <h1>ユーザー一覧</h1>
    <div v-if="pending">読み込み中...</div>
    <div v-else-if="error">エラー: {{ error }}</div>
    <ul v-else>
      <li v-for="user in data" :key="user.id">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>
    <button @click="refresh()">更新</button>
  </div>
</template>

<script setup>
// useFetch - SSR対応データフェッチング
const { data, pending, error, refresh } = await useFetch('/api/users')

// useLazyFetch - 非同期フェッチング
const { data: posts } = await useLazyFetch('/api/posts')

// $fetch - 手動フェッチング
const submitForm = async (formData) => {
  try {
    const result = await $fetch('/api/submit', {
      method: 'POST',
      body: formData
    })
    console.log('送信成功:', result)
  } catch (error) {
    console.error('送信エラー:', error)
  }
}
</script>

ステート管理(Pinia)

// stores/counter.js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  function reset() {
    count.value = 0
  }
  
  return { count, doubleCount, increment, reset }
})
<!-- pages/counter.vue -->
<template>
  <div>
    <h1>カウンター: {{ counter.count }}</h1>
    <p>ダブル: {{ counter.doubleCount }}</p>
    <button @click="counter.increment()">+1</button>
    <button @click="counter.reset()">リセット</button>
  </div>
</template>

<script setup>
const counter = useCounterStore()
</script>

サーバーAPI

// server/api/users.js
export default defineEventHandler(async (event) => {
  const method = getMethod(event)
  
  if (method === 'GET') {
    // ユーザー一覧の取得
    return await getUsersFromDatabase()
  }
  
  if (method === 'POST') {
    // 新規ユーザーの作成
    const body = await readBody(event)
    return await createUser(body)
  }
})

// server/api/users/[id].js
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  const method = getMethod(event)
  
  if (method === 'GET') {
    return await getUserById(id)
  }
  
  if (method === 'PUT') {
    const body = await readBody(event)
    return await updateUser(id, body)
  }
  
  if (method === 'DELETE') {
    return await deleteUser(id)
  }
})

ミドルウェアと認証

// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useAuthUser()
  
  if (!user.value) {
    return navigateTo('/login')
  }
})

// middleware/admin.global.js - グローバルミドルウェア
export default defineNuxtRouteMiddleware((to) => {
  if (to.path.startsWith('/admin')) {
    const user = useAuthUser()
    
    if (!user.value?.isAdmin) {
      throw createError({
        statusCode: 403,
        statusMessage: 'Access Denied'
      })
    }
  }
})
<!-- pages/profile.vue -->
<script setup>
// ページレベルでミドルウェアを適用
definePageMeta({
  middleware: 'auth'
})
</script>