Nuxt 4 Layerモジュラーアーキテクチャ:エンタープライズフロントエンドエンジニアリング完全ガイド 2026

前端开发

Nuxt 4 Layerモジュラーアーキテクチャ:エンタープライズフロントエンドエンジニアリング完全ガイド 2026

フロントエンドチームが5人から50人に拡大し、プロジェクトがモノリスから10以上のサブプロダクトマトリックスに進化すると、従来のNuxtプロジェクト構造はすぐに制御不能になります。コードの重複、設定の不整合、バージョンの断片化……これらの問題はコーディング規約だけでは解決できず、アーキテクチャレベルのソリューションが必要です。Nuxt 4のLayerメカニズムはまさにこのために存在し、ブロックを組み合わせるようにフロントエンド機能を構成し、真のエンタープライズグレードのモジュラー化を実現します。

コア概念一覧

概念 説明 適用シーン
Nuxt Layer 再利用可能なNuxtアプリケーション断片、設定/コンポーネント/composablesを含む クロスプロジェクト再利用
extends nuxt.configでLayer依存関係を宣言 Layer構成
nuxt.config.ts Layerエントリ設定ファイル Layer定義
Layer優先度 後に宣言されたLayerが先のものをオーバーライド 競合解決
App Config 実行時オーバーライド可能な設定 環境差異化
Module vs Layer Moduleはプラグイン拡張、Layerはアプリケーション断片 異なる抽象レベル
Workspace マルチパッケージ共有依存のmonorepo エンタープライズ開発
Layer公開 npmパッケージ形式でLayerを配布 クロスチーム再利用

5つの主要な課題

  1. コード重複が深刻:10のサブプロダクトで、ログインモジュール、権限システム、レイアウトコンポーネントなどが重複実装され、1つのバグ修正で10のリポジトリを変更
  2. 設定の不整合:異なるプロジェクトのESLint、TypeScript、Tailwind設定が各々異なり、新人のオンボーディングコストが高い
  3. バージョンの断片化:共有ロジックがコピー&ペーストで伝播し、統一アップグレードが不可能、技術的負債が蓄積
  4. チームコラボレーションの困難:複数チームが各々のプロジェクトを維持し、統一された開発パラダイムが不足
  5. デプロイ効率の低下:各プロジェクトが独立してビルド・デプロイし、CI/CDパイプラインが重複構築

ステップバイステップ:5つのコアパターン

パターン1:Layer基本設定

実行環境:Node.js 20+ / Nuxt 4.0+ / pnpm 9+

共有コンポーネント、composables、設定を含む基本Layerを作成:

mkdir nuxt-layer-base && cd nuxt-layer-base
pnpm init
pnpm add -D nuxt@latest
// nuxt.config.ts - Layerエントリ設定
export default defineNuxtConfig({
  compatibilityDate: '2026-06-21',

  devtools: { enabled: true },

  modules: [
    '@nuxtjs/tailwindcss',
    '@nuxtjs/color-mode',
    '@pinia/nuxt',
    '@vueuse/nuxt',
  ],

  tailwindcss: {
    configPath: '~/tailwind.config.ts',
  },

  appConfig: {
    ui: {
      primary: 'blue',
      theme: 'light',
    },
    api: {
      baseURL: process.env.API_BASE_URL || '/api',
    },
  },

  typescript: {
    strict: true,
    shim: false,
  },

  css: ['~/assets/css/main.css'],
  plugins: ['~/plugins/api.client.ts'],
  routeRules: {
    '/admin/**': { ssr: false },
  },
})
// composables/useApi.ts - 共有APIリクエストcomposable
interface UseApiOptions {
  baseUrl?: string
  headers?: Record<string, string>
  timeout?: number
}

interface ApiResponse<T> {
  data: Ref<T | null>
  error: Ref<Error | null>
  pending: Ref<boolean>
  execute: () => Promise<void>
}

export function useApi<T>(
  url: string,
  options: UseApiOptions = {}
): ApiResponse<T> {
  const config = useAppConfig()
  const baseUrl = options.baseUrl || config.api.baseURL
  const timeout = options.timeout || 30000

  const data = ref<T | null>(null) as Ref<T | null>
  const error = ref<Error | null>(null)
  const pending = ref(false)

  const execute = async () => {
    pending.value = true
    error.value = null

    try {
      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), timeout)

      const response = await fetch(`${baseUrl}${url}`, {
        headers: {
          'Content-Type': 'application/json',
          ...options.headers,
        },
        signal: controller.signal,
      })

      clearTimeout(timeoutId)

      if (!response.ok) {
        throw new Error(`API Error: ${response.status} ${response.statusText}`)
      }

      data.value = await response.json()
    } catch (e) {
      error.value = e as Error
    } finally {
      pending.value = false
    }
  }

  return { data, error, pending, execute }
}

パターン2:マルチLayer構成

エンタープライズプロジェクトでは、通常複数のLayerを構成する必要があります:

// nuxt.config.ts - メインアプリ設定、複数Layerを構成
export default defineNuxtConfig({
  extends: [
    '@company/nuxt-layer-base',
    '@company/nuxt-layer-auth',
    '@company/nuxt-layer-admin',
    './layers/custom',
  ],
})
// layers/auth/nuxt.config.ts - 認証Layer
export default defineNuxtConfig({
  extends: ['@company/nuxt-layer-base'],

  modules: ['@sidebase/nuxt-auth'],

  auth: {
    provider: {
      type: 'local',
      endpoints: {
        signIn: { path: '/auth/login', method: 'post' },
        signOut: { path: '/auth/logout', method: 'post' },
        signUp: { path: '/auth/register', method: 'post' },
        getSession: { path: '/auth/session', method: 'get' },
      },
      token: {
        signInResponseTokenPointer: '/token/access_token',
        headerName: 'Authorization',
        type: 'Bearer',
      },
    },
    globalAppMiddleware: true,
  },

  pages: true,

  routeRules: {
    '/login': { auth: false },
    '/register': { auth: false },
  },
})
// composables/usePermission.ts - 権限チェックcomposable
interface Permission {
  code: string
  name: string
  description?: string
}

interface Role {
  id: string
  name: string
  permissions: Permission[]
}

interface UserInfo {
  id: string
  name: string
  email: string
  roles: Role[]
}

export function usePermission() {
  const { data: user } = useAuth()

  const userPermissions = computed<Set<string>>(() => {
    if (!user.value) return new Set()
    const perms = (user.value as UserInfo).roles.flatMap(
      (role: Role) => role.permissions.map((p: Permission) => p.code)
    )
    return new Set(perms)
  })

  const hasPermission = (code: string): boolean => {
    return userPermissions.value.has(code)
  }

  const hasAnyPermission = (codes: string[]): boolean => {
    return codes.some((code) => userPermissions.value.has(code))
  }

  const hasAllPermissions = (codes: string[]): boolean => {
    return codes.every((code) => userPermissions.value.has(code))
  }

  const hasRole = (roleName: string): boolean => {
    if (!user.value) return false
    return (user.value as UserInfo).roles.some((r: Role) => r.name === roleName)
  }

  return { userPermissions, hasPermission, hasAnyPermission, hasAllPermissions, hasRole }
}

パターン3:モジュール公開と再利用

Layerをnpmパッケージとして公開し、クロスチーム再利用を実現:

// package.json - Layerパッケージ設定
{
  "name": "@company/nuxt-layer-base",
  "version": "2.1.0",
  "description": "エンタープライズNuxt基本Layer - UIコンポーネント、ユーティリティ、基本設定",
  "type": "module",
  "exports": {
    ".": {
      "import": "./nuxt.config.ts",
      "require": "./nuxt.config.ts"
    },
    "./components/*": "./components/*",
    "./composables/*": "./composables/*"
  },
  "files": [
    "components", "composables", "layouts", "plugins",
    "assets", "utils", "nuxt.config.ts", "app.config.ts", "tailwind.config.ts"
  ],
  "peerDependencies": {
    "nuxt": "^4.0.0",
    "@nuxtjs/tailwindcss": "^6.0.0",
    "@pinia/nuxt": "^0.9.0"
  },
  "keywords": ["nuxt", "nuxt-layer", "ui-components", "enterprise"],
  "license": "MIT"
}
// app.config.ts - 実行時オーバーライド可能な設定
export default defineAppConfig({
  ui: {
    primary: 'blue',
    theme: 'light',
    radius: 'rounded-lg',
    animations: true,
  },
  toast: {
    duration: 3000,
    position: 'top-right' as const,
  },
  table: {
    pageSize: 20,
    pageSizeOptions: [10, 20, 50, 100],
  },
  api: {
    baseURL: '/api',
    timeout: 30000,
    retryCount: 3,
    retryDelay: 1000,
  },
})

パターン4:エンタープライズLayerテンプレート

標準化されたエンタープライズLayerテンプレートを作成し、統一開発パラダイムを実現:

// nuxt.config.ts - Layerテンプレートの標準設定
export default defineNuxtConfig({
  compatibilityDate: '2026-06-21',

  meta: {
    name: '@company/nuxt-layer-payment',
    version: '1.0.0',
    description: '決済モジュールLayer',
  },

  extends: ['@company/nuxt-layer-base'],

  typescript: { strict: true },
  pages: true,

  runtimeConfig: {
    paymentSecretKey: '',
    paymentWebhookSecret: '',
    public: {
      paymentProvider: 'stripe',
      paymentCurrency: 'CNY',
      paymentMethods: ['alipay', 'wechat', 'card'],
    },
  },
})
// composables/usePayment.ts - 決済コアcomposable
interface PaymentOptions {
  amount: number
  currency?: string
  description?: string
  metadata?: Record<string, string>
}

interface PaymentResult {
  id: string
  status: 'pending' | 'completed' | 'failed' | 'refunded'
  paymentUrl?: string
}

export function usePayment() {
  const config = useRuntimeConfig()
  const { $api } = useNuxtApp()

  const createPayment = async (options: PaymentOptions): Promise<PaymentResult> => {
    const response = await $api('/payments/create', {
      method: 'POST',
      body: {
        amount: options.amount,
        currency: options.currency || config.public.paymentCurrency,
        description: options.description,
        metadata: options.metadata,
      },
    })
    return response as PaymentResult
  }

  const verifyPayment = async (paymentId: string): Promise<PaymentResult> => {
    const response = await $api(`/payments/${paymentId}/verify`, { method: 'GET' })
    return response as PaymentResult
  }

  const refundPayment = async (paymentId: string, reason?: string): Promise<PaymentResult> => {
    const response = await $api(`/payments/${paymentId}/refund`, {
      method: 'POST',
      body: { reason },
    })
    return response as PaymentResult
  }

  return { createPayment, verifyPayment, refundPayment }
}

パターン5:本番レベルLayerガバナンス

Layerのバージョン管理、品質保証、公開フローを確立:

# .github/workflows/layer-release.yml
name: Layer Release

on:
  push:
    tags:
      - 'layer-*'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test
      - run: pnpm lint
      - run: pnpm typecheck

  publish:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
          registry-url: https://registry.npmjs.org
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - run: pnpm publish --no-git-checks --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

よくある落とし穴

落とし穴1:Layerの循環依存

// ❌ 間違い:Layer A extends B、Layer B extends A
// ✅ 正しい:明確なLayer依存階層を確立
// base → auth → admin(一方向依存)
export default defineNuxtConfig({
  extends: ['@company/nuxt-layer-base'],
})

落とし穴2:Layer内のサーバー側コード漏洩

// ❌ 間違い:composableでサーバーモジュールを直接使用
export function useDatabase() {
  const { connect } = require('mongoose') // クライアントでクラッシュ!
}

// ✅ 正しい:serverディレクトリでサーバー側コードを分離
export function useApi() {
  const config = useRuntimeConfig()
  return { baseUrl: config.public.apiBaseURL }
}

落とし穴3:App ConfigとRuntime Configの混同

// ❌ 間違い:app.configに機密情報を配置
export default defineAppConfig({
  apiSecretKey: 'sk-xxx', // クライアントに露出!
})

// ✅ 正しい:機密情報はruntimeConfigを使用
export default defineNuxtConfig({
  runtimeConfig: {
    apiSecretKey: '',
    public: { apiBaseURL: '/api' },
  },
})

落とし穴4:Layerコンポーネントの命名衝突

<!-- ❌ 間違い:複数のLayerに同名コンポーネント -->
<!-- ✅ 正しい:名前空間プレフィックスを使用 -->
<!-- layers/base/components/BaseButton.vue -->
<!-- layers/admin/components/AdminButton.vue -->

落とし穴5:LayerのTree-shakingを無視

// ❌ 間違い:Layerの全モジュールを一括インポート
export default defineNuxtConfig({
  modules: ['@company/nuxt-layer-everything'],
})

// ✅ 正しい:必要なLayerのみを構成
export default defineNuxtConfig({
  extends: ['@company/nuxt-layer-base', '@company/nuxt-layer-auth'],
})

エラートラブルシューティング表

エラーメッセージ 原因 解決策
Could not resolve "@company/nuxt-layer-xxx" Layerパッケージ未インストールまたはパスエラー pnpm add -D @company/nuxt-layer-xxx
Nuxt module should be a function Layerのnuxt.configエクスポート形式エラー defineNuxtConfig()エクスポートを確認
Circular dependency detected Layer間の循環参照 extendsチェーンを確認、一方向依存を確保
Hydration mismatch サーバー/クライアントレンダリングの不一致 <ClientOnly>ラッピングを確認
Unknown component <XxxButton> Layerコンポーネントが自動インポートされていない コンポーネント命名とファイル位置を確認
Cannot read property of undefined composable使用時にNuxtインスタンス未準備 callOnceまたは遅延呼び出しを使用
Tailwind classes not applied LayerのTailwind設定がメインプロジェクトに認識されていない tailwind.config.tsの参照を確認
Type errors in Layer composables 型宣言の欠落 Layerルートにindex.d.tsを追加
Layer overrides not working extends順序の誤り 後に宣言されたLayerが高優先度
Build performance slow Layerが多すぎる、または大量の静的アセット 細粒度Layer分割を使用、画像/フォントを最適化

高度な最適化

1. Nuxt DevToolsでLayerをデバッグ

export default defineNuxtConfig({
  devtools: { enabled: true, timeline: { enabled: true } },
})
// Layer継承チェーン、コンポーネントソース、composable呼び出し関係を確認可能

2. Layerバージョン自動同期

{
  "devDependencies": {
    "@company/nuxt-layer-base": "workspace:*",
    "@company/nuxt-layer-auth": "workspace:*"
  }
}

3. Layerテスト戦略

// layers/base/__tests__/useApi.test.ts
import { describe, it, expect, vi } from 'vitest'
import { useApi } from '../composables/useApi'

vi.mock('#imports', () => ({
  useAppConfig: () => ({ api: { baseURL: '/api' } }),
  ref: vi.fn((val: unknown) => ({ value: val })),
  computed: vi.fn((fn: () => unknown) => ({ value: fn() })),
}))

describe('useApi', () => {
  it('should create API instance', () => {
    const { execute } = useApi('/users')
    expect(execute).toBeDefined()
  })
})

4. Layerドキュメント自動生成

// scripts/generate-layer-docs.ts
import { readdirSync, writeFileSync } from 'fs'
import { join } from 'path'

function generateLayerDocs(layerPath: string) {
  const name = layerPath.split('/').pop()
  let doc = `# @company/nuxt-layer-${name}\n\n`
  writeFileSync(join(layerPath, 'README.md'), doc)
}

5. Layerパフォーマンス監視

// plugins/layer-metrics.client.ts
export default defineNuxtPlugin(() => {
  if (process.dev) {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.name.includes('component')) {
          console.log(`[Layer Metrics] ${entry.name}: ${entry.duration.toFixed(2)}ms`)
        }
      }
    })
    observer.observe({ entryTypes: ['measure'] })
  }
})

比較分析

特徴 Nuxt Layer Nuxt Module Monorepo共有 Git Submodule
再利用粒度 アプリ断片 プラグイン機能 コード断片 コードリポジトリ
設定継承 ✅ 自動 ❌ 手動 ❌ 手動 ❌ 手動
コンポーネント自動インポート ❌ 設定必要 ❌ 設定必要
バージョン管理 npmバージョン npmバージョン workspace git commit
オーバーライド機構 ✅ 宣言順序
学習コスト 中程度 中程度 低い 低い
適用シーン クロスプロジェクト再利用 機能拡張 単一チーム 単純共有

まとめ

Nuxt 4のLayerメカニズムは、大規模フロントエンドプロジェクトに真のモジュラーソリューションを提供します:

  • Layer基本設定:コンポーネント、composablesからTailwind設定までの完全なカプセル化
  • マルチLayer構成extendsで依存チェーンを宣言、後の宣言が前をオーバーライド
  • モジュール公開:npmパッケージ形式で配布、セマンティックバージョニング対応
  • エンタープライズテンプレート:標準化されたLayer構造で統一開発パラダイム
  • 本番ガバナンス:CI/CD公開、品質チェック、パフォーマンス監視、ドキュメント生成

推奨:3つ以上のプロジェクトでコード共有が必要な場合、Layerを導入。チームが10人を超える場合、Layerガバナンスプロセスを確立。プライベートnpmレジストリに公開してクロスチーム再利用を実現。

オンラインツール推薦

  • /ja/json/format - JSONフォーマッター、Nuxt設定のデバッグに必須
  • /ja/dev/curl-to-code - HTTPリクエストからコード変換、Layer内のリクエストロジックを迅速生成
  • /ja/encode/hash - ハッシュ計算ツール、Layerバージョン検証に
  • /ja/text/diff - テキスト差分ツール、Layer設定のバージョン間比較に

ブラウザローカルツールを無料で試す →

#Nuxt4 Layer#模块化架构#企业级前端#Nuxt Layer#前端工程化#2026#前端开发