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