Zademy

私の新しいお気に入りスタック Nuxt 4 Nuxt UI MongoDB と Quarkus

Development
Nuxt; MongoDB; Quarkus
words 単語

最近、ウェブアプリを作るスタックを完全に変えた。Nuxt 4、Nuxt UI、MongoDB、Quarkusの組み合わせだ。ここ数ヶ月、個人プロジェクトから仕事用のプロトタイプまでこの構成で回しているが、開発スピード、本番のパフォーマンス、メンテナンス性のバランスが今のところ一番良い。

なぜこのスタックが私の最愛なのか

理由を箇条書きにするとこうなる。プロトタイプが数日ではなく数時間で立つ。本番環境のパフォーマンスが速い。各技術のドキュメントがしっかりしている。エコシステムが成熟している。そして、シンプルなAPIから複雑なシステムまでカバーできる。

Nuxt 4:ウェブ開発を再定義する Vue フレームワーク

2025年7月15日に正式リリースされた Nuxt 4 は、Vue エコシステムの地味だが重要な進化だ。派手な新機能より、安定性と日々の開発で感じる改善に振っている。

最適化されたプロジェクト構成

新しいディレクトリ構造は、アプリケーションコードをすべて app/ に置く。node_modules/ や .git/ と明確に分離される。これは見た目の問題ではない。Windows と Linux でファイルウォッチャーが速くなり、IDE がオートコンプリートにより良いコンテキストを渡せる。

my-app/
 ├─ app/
 │  ├─ assets/
 │  ├─ components/
 │  ├─ pages/
 │  └─ app.vue
 ├─ shared/
 ├─ server/
 └─ nuxt.config.ts

感じ取れるパフォーマンス

パフォーマンス向上は最初の瞬間からわかる。Turbo Mode でビルドが最大10倍速い。サーバーのコールドスタートは Nuxt 3 と比べて52%短くなった(約2.3sから約1.1s)。CLI と Vite 開発サーバーがネットワークポートではなく内部ソケットで通信するようになり、オーバーヘッドが減った。ネイティブの Edge Server も使えるので、ユーザーの近くにデプロイしてレイテンシを最小化できる。

インテリジェントなデータ取得

useAsyncData と useFetch が賢くなった。同じキーを使うコンポーネント間でデータを自動的に共有する。コンポーネントがアンマウントされると自動でクリーンアップする。キャッシュの制御も細かくなった。

// 最適化されたデータ取得の例
const { data: products } = await useAsyncData(
  'products',
  () => $fetch('/api/products'),
  {
    server: true,
    default: () => [],
    transform: (data) => data.map(p => ({...p, formattedPrice: formatCurrency(p.price)}))
  }
)

Nuxt UI v4:無料でプロフェッショナルなデザイン

2025年9月にリリースされた Nuxt UI v4 は、以前は Nuxt UI と Nuxt UI Pro として別々だったものを、MIT ライセンスの単一ライブラリに統合した。プロフェッショナル品質のコンポーネントが無料で手に入るようになった。これは正直、大きい。

110以上のアクセシブルなコンポーネント

フォーム(入力、選択、チェックボックス、ラジオ)、ナビゲーション(メニュー、パンくず、ページネーション)、レイアウト(カード、モーダル、サイドバー、アコーディオン)、データ(テーブル、リスト、グリッド)、フィードバック(トースト、アラート、プログレス、スケルトンローダー)。本番環境で使えるコンポーネントが110以上入っている。

モダンな基盤技術

基盤も悪くない。Tailwind CSS v4 でビルドが最大5倍速い。Reka UI が WAI-ARIA 準拠のアクセシビリティを担保する。Tailwind Variants で TypeScript の完全な型付け付きテーマが書ける。

Nuxt UI を使った実践的な例

<template>
  <UContainer class="py-8">
    <UCard>
      <template #header>
        <h1 class="text-2xl font-bold">ユーザー管理</h1>
      </template>
      
      <UTable :rows="users" :columns="columns">
        <template #actions-data="{ row }">
          <UButton
            icon="i-heroicons-pencil"
            size="sm"
            color="primary"
            variant="ghost"
            @click="edit(row)"
          />
        </template>
      </UTable>
      
      <template #footer>
        <div class="flex justify-between items-center">
          <span class="text-sm text-gray-500">
            {{ users.length }} users found
          </span>
          <UPagination
            v-model="page"
            :total="total"
            :page-count="10"
          />
        </div>
      </template>
    </UCard>
  </UContainer>
</template>

<script setup lang="ts">
const users = ref([])
const page = ref(1)
const total = ref(100)

const columns = [
  { key: 'name', label: 'Name' },
  { key: 'email', label: 'Email' },
  { key: 'role', label: 'Role' },
  { key: 'actions', label: 'Actions' }
]

async function loadUsers() {
  const { data } = await useFetch('/api/users', {
    query: { page: page.value }
  })
  users.value = data.value
}
</script>

MongoDB:モダン開発のための柔軟なデータベース

MongoDB は、スキーマの柔軟性と開発スピードが要る仕事での私のデフォルトになった。JSON ドキュメントモデルが JavaScript や TypeScript と自然に噛み合う。

スキーマの柔軟性

リレーショナルデータベースと違って、MongoDB は複雑なマイグレーションなしでスキーマを進化させられる。要件が頻繁に変わるプロトタイピング、半構造化データを扱うアプリケーション、異なるドキュメントタイプが混在するシステム、イテレーションが激しいアジャイル開発に向いている。

スタックとの自然な統合

Nuxt と Quarkus、どちらとの統合もシームレスだ。

// Nuxt での Mongoose 接続
import mongoose from 'mongoose'

export default defineNitroPlugin(async (nitroApp) => {
  if (!mongoose.connection.readyState) {
    await mongoose.connect(process.env.MONGODB_URI)
  }
})

// 例のスキーマ
const userSchema = new mongoose.Schema({
  name: String,
  email: { type: String, unique: true },
  role: { type: String, enum: ['admin', 'user', 'editor'] },
  preferences: {
    theme: String,
    notifications: Boolean
  },
  createdAt: { type: Date, default: Date.now }
})

// 最適化インデックス
userSchema.index({ email: 1 })
userSchema.index({ createdAt: -1 })

パフォーマンスとスケーラビリティ

複合インデックスで複数フィールドのクエリが速い。集計パイプラインで強力なデータ変換ができる。全文検索が組み込まれている。Change Streams でリアルタイム更新が取れる。シャーディングで水平スケーリングもできる。

Quarkus:クラウド向けに強化された Java

Quarkus は、Java をクラウドネイティブ向けに再構築したフレームワークだ。起動が速く、メモリを食わない。マイクロサービスとサーバーレスにちょうどいい。

印象的なパフォーマンス

2025年のベンチマークで、ネイティブモードのコールドスタートは約50ms、RSS メモリは約12MB、P99 レイテンシは約95ms、使用ヒープは約3.2MBだった。サーバーレス関数と Kubernetes コンテナに理想的な数字だ。

JVM とネイティブ開発モード

両方の世界を使える。JVM モードでホットリロードしながら開発し、本番向けにはネイティブ実行可能ファイルにコンパイルする。

// Quarkus での REST API の例
@Path("/api/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductResource {
    
    @Inject
    ProductService service;
    
    @GET
    @Operation(summary = "製品一覧", description = "すべての製品を返却")
    public Response list(
            @QueryParam("page") @DefaultValue("0") int page,
            @QueryParam("size") @DefaultValue("20") int size) {
        List<Product> products = service.list(page, size);
        return Response.ok(products).build();
    }
    
    @POST
    @Operation(summary = "製品作成")
    public Response create(ProductDTO dto) {
        Product product = service.create(dto);
        return Response.status(Status.CREATED)
                      .entity(product)
                      .build();
    }
}

MongoDB 統合

Quarkus には MongoDB Panache がある。データアクセスがかなりシンプルになる。

// Panache を使ったエンティティ
@Entity
public class Product extends PanacheMongoEntity {
    public String name;
    public String description;
    public BigDecimal price;
    public Integer stock;
    public String category;
    public LocalDateTime createdAt;
    
    // 静的クエリ
    public static List<Product> findByCategory(String category) {
        return list("category", category);
    }
    
    public static List<Product> findActive() {
        return list("stock > 0");
    }
    
    public static long countByCategory(String category) {
        return count("category", category);
    }
}

// リポジトリ
@ApplicationScoped
public class ProductRepository implements PanacheMongoRepository<Product> {
    
    public List<Product> searchWithStock(int page, int size) {
        return find("stock > 0")
               .page(page, size)
               .list();
    }
    
    public Optional<Product> searchByCode(String code) {
        return find("code", code).firstResultOptional();
    }
}

完全なスタック統合

このスタックの真価は、部品が揃ったときに出る。どう繋ぐかを書く。

一般的なアーキテクチャ

┌─────────────────────────────────────────────────────────┐
│                    Nuxt 4 (Frontend)                    │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │ Nuxt UI v4  │  │  Pages/Views│  │   API Client    │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────┘
                              │
                              ▼ HTTP/REST/gRPC
┌─────────────────────────────────────────────────────────┐
│                   Quarkus (Backend)                     │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │  REST API   │  │  Services   │  │ MongoDB Panache │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────┐
│                      MongoDB                            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │  Products   │  │    Users    │  │     Orders      │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
└─────────────────────────────────────────────────────────┘

完全な実装例

Nuxt 設定:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxt/ui',
    '@nuxtjs/mdc',
    '@pinia/nuxt'
  ],
  
  runtimeConfig: {
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
    }
  },
  
  ui: {
    global: true,
    icons: ['heroicons', 'simple-icons']
  },
  
  devtools: { enabled: true }
})

中央集権化された API クライアント:

// composables/useApi.ts
export const useApi = () => {
  const config = useRuntimeConfig()
  
  const client = $fetch.create({
    baseURL: config.public.apiBase,
    headers: {
      'Content-Type': 'application/json'
    },
    timeout: 30000
  })
  
  return {
    get: <T>(url: string, options?: object) =>
      client<T>(url, { method: 'GET', ...options }),
    
    post: <T>(url: string, body: object, options?: object) =>
      client<T>(url, { method: 'POST', body, ...options }),
    
    put: <T>(url: string, body: object, options?: object) =>
      client<T>(url, { method: 'PUT', body, ...options }),
    
    delete: <T>(url: string, options?: object) =>
      client<T>(url, { method: 'DELETE', ...options })
  }
}

パフォーマンス比較

数字で見る。

フロントエンド(Nuxt 4)

First Contentful Paint が約1.2s(Nuxt 3 より33%速い)。Largest Contentful Paint が約1.9s(同34%速い)。Cumulative Layout Shift が約0.08(同47%改善)。ビルド時間が約18s(同60%速い)。

バックエンド(Quarkus ネイティブ)

コールドスタートが約50ms。メモリ RSS が約12MBでコンテナにちょうどいい。P99 レイテンシが約95msで安定。スループットが約50K req/s。

理想的なユースケース

モダンウェブアプリケーション

アクセシブルで統一感のあるコンポーネントが必要な場合、Nuxt UI が車輪の再発明なしに済ませてくれる。

高パフォーマンス API

Quarkus が低リソースで速い応答を返す。マイクロサービスとサーバーレスに向いている。

柔軟なデータを持つシステム

MongoDB が複雑なマイグレーションなしで要件の変更に追従する。スタートアップと進化するプロジェクトにちょうどいい。

グローバルアプリケーション

Nuxt Edge と Quarkus を組み合わせれば、ユーザーの近くでサービスを動かしてレイテンシを最小化できる。

結論:完璧な組み合わせ

Nuxt 4、Nuxt UI、MongoDB、Quarkus。この4つが揃うと、開発速度、パフォーマンス、型安全性、ユーザー体験、スケーラビリティのバランスが取れる。各技術が互いに補完し合い、ドキュメントもコミュニティも成熟している。学習曲線は管理可能で、長期的なメンテナンスも予測しやすい。

私の今のデフォルトだ。しばらくこの構成で回すつもり。


追加リソース: