Nuxt Layers 层系统与 Monorepo

Nuxt Layers 是一个被严重低估的特性。它让你像"继承类"一样继承整个 Nuxt 项目——组件、页面、布局、composables、server routes、甚至 nuxt.config.ts 配置都可以继承和覆盖。配合 Monorepo,Layers 是构建多端应用(Web + Admin + Mobile)和跨项目共享 UI Kit 的终极方案。

1. Layer 核心概念

1.1 什么是 Layer

Layer 是一个"部分的 Nuxt 项目"——它可以包含 Nuxt 项目的任何组成部分,但不需要是一个完整的可运行应用:

layers/base/
├── app/
│   ├── components/     ← 共享组件
│   ├── composables/    ← 共享 composables
│   └── layouts/        ← 共享布局
├── server/
│   └── utils/          ← 共享 server 工具
├── nuxt.config.ts      ← 基础配置
└── package.json

当另一个 Nuxt 项目通过 extends 引用这个 Layer 时,Layer 中的所有内容会被"合并"到目标项目中——就像目标项目本身就包含了这些文件一样。

1.2 extends 配置

// apps/web/nuxt.config.ts
export default defineNuxtConfig({
  extends: [
    '../layers/base',        // 本地 Layer
    'github:org/ui-kit#v2',  // GitHub Layer(远程)
    'nuxt-ui-pro',           // npm Layer
  ],
})

extends 支持三种来源:

  • 本地路径:Monorepo 中的相对路径
  • GitHubgithub:owner/repo#branch 格式
  • npm 包:直接写包名

1.3 合并规则

当 Layer 和目标项目有同名文件时,目标项目优先

内容类型合并策略示例
组件同名组件,目标项目覆盖 LayerButton.vue 覆盖 Layer 的 Button.vue
页面同路径页面,目标项目覆盖pages/index.vue 覆盖 Layer 的
布局同名布局覆盖layouts/default.vue 覆盖
composables同名函数覆盖useAuth() 覆盖
nuxt.config深度合并,目标项目优先配置项逐个合并
CSS累加,不覆盖两边的 CSS 都会加载

这种"覆盖优先"的设计让 Layer 成为一个强大的默认值提供者——Layer 提供基础实现,目标项目按需覆盖。

2. Layer vs Module:何时用哪个

这是最容易混淆的选型问题。虽然 Layer 和 Module 都能"扩展"Nuxt 项目,但它们的设计目的完全不同:

维度LayerModule
本质一个(部分)Nuxt 项目一个构建阶段的配置函数
提供什么组件、页面、布局、配置——"内容"注册组件、修改配置——"能力"
覆盖机制同名文件覆盖无覆盖概念,追加注入
适用场景跨项目共享 UI Kit、多端基础层第三方功能集成(认证、SEO、ORM)
分发方式npm / GitHub / 本地路径npm / 本地路径

简单规则

  • 如果你要共享"项目片段"(组件 + 页面 + 布局 + 配置),用 Layer
  • 如果你要添加"功能"(认证、数据库、SEO 工具),用 Module

3. Monorepo 架构

3.1 为什么用 Monorepo

当项目演进到多端(Web 前台 + Admin 后台 + API 服务)时,代码共享变得关键。Monorepo 把所有子项目放在一个 Git 仓库中:

方面Monorepo多仓库
代码共享本地 import,零发布成本npm 发布,版本管理复杂
原子提交一次 PR 改多个包跨仓库协调困难
依赖管理统一版本,避免冲突各仓库版本可能不一致
CI/CD按变更范围触发,精准构建各仓库独立 CI
上手成本中等(需要学习 Monorepo 工具)

3.2 推荐目录结构

my-platform/
├── apps/
│   ├── web/                ← Nuxt4 前台应用
│   │   └── nuxt.config.ts    extends: ['../../layers/base']
│   ├── admin/              ← Nuxt4 后台应用
│   │   └── nuxt.config.ts    extends: ['../../layers/base', '../../layers/admin']
│   └── api/                ← 独立 API 服务(可选)
├── layers/
│   ├── base/               ← 基础 Layer(组件、composables、配置)
│   ├── admin/              ← 后台专用 Layer(管理组件、权限布局)
│   └── auth/               ← 认证 Layer(登录页、Session 管理)
├── packages/
│   ├── shared/             ← 纯 TypeScript 工具库(不依赖 Nuxt)
│   └── database/           ← 数据库 schema 和 migration
├── package.json            ← workspace 根配置
├── pnpm-workspace.yaml     ← pnpm workspace 声明
└── turbo.json              ← Turborepo 任务配置(可选)

3.3 pnpm Workspace 配置

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'layers/*'
  - 'packages/*'
// package.json (root)
{
  "private": true,
  "scripts": {
    "dev:web": "nuxi dev apps/web",
    "dev:admin": "nuxi dev apps/admin",
    "build:web": "nuxi build apps/web",
    "build:admin": "nuxi build apps/admin"
  }
}

3.4 Layer 间的依赖

Layer 可以 extends 其他 Layer,形成继承链:

admin app
  └── extends: admin layer
        └── extends: base layer

Admin Layer 继承 Base Layer 的所有组件和配置,再添加后台专用的管理组件和布局。Admin App 继承 Admin Layer,再添加具体的页面。

4. 共享层设计实践

4.1 Base Layer:全局基础

Base Layer 提供所有应用共用的基础设施:

layers/base/
├── app/
│   ├── components/
│   │   ├── AppHeader.vue
│   │   ├── AppFooter.vue
│   │   ├── ui/              ← 基础 UI 组件
│   │   │   ├── UButton.vue
│   │   │   ├── UInput.vue
│   │   │   └── UModal.vue
│   │   └── shared/          ← 业务共享组件
│   │       ├── VideoCard.vue
│   │       └── UserAvatar.vue
│   ├── composables/
│   │   ├── useAuth.ts
│   │   ├── useApi.ts
│   │   └── useToast.ts
│   ├── layouts/
│   │   └── default.vue
│   └── utils/
│       ├── format.ts
│       └── validation.ts
├── server/
│   └── utils/
│       ├── db.ts
│       └── auth.ts
└── nuxt.config.ts

Base Layer 的 nuxt.config.ts 包含所有应用共用的配置:

// layers/base/nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxt/ui',
    '@nuxt/image',
    '@nuxt/fonts',
    '@pinia/nuxt',
  ],
  runtimeConfig: {
    databaseUrl: '',
    public: {
      appName: 'AI Video Platform',
    },
  },
})

4.2 Admin Layer:后台专用

layers/admin/
├── app/
│   ├── components/
│   │   ├── AdminSidebar.vue
│   │   ├── DataTable.vue
│   │   └── StatCard.vue
│   ├── layouts/
│   │   └── admin.vue         ← 带侧边栏的管理后台布局
│   ├── middleware/
│   │   └── admin-only.ts     ← 权限中间件
│   └── composables/
│       └── useAdminApi.ts
└── nuxt.config.ts

4.3 覆盖与定制

Web App 可以覆盖 Base Layer 中的任何组件。比如 Base 提供了通用的 AppHeader.vue,Web App 可以在自己的 components/AppHeader.vue 中提供定制版本——Nuxt 会优先使用 App 自己的组件。

这种模式像极了面向对象编程中的"继承 + 重写":

Base Layer    →  提供 AppHeader(通用版)
Web App       →  覆盖 AppHeader(带搜索栏的版本)
Admin App     →  不覆盖,直接用 Base 的 AppHeader

5. 实际工程问题

5.1 TypeScript 类型共享

Layer 中的类型定义需要被引用它的应用识别。在 Layer 的 nuxt.config.ts 中确保类型导出:

// layers/base/nuxt.config.ts
export default defineNuxtConfig({
  typescript: {
    includeWorkspace: true,  // 包含 workspace 中的类型
  },
})

共享的业务类型放在 packages/shared/ 中,各 Layer 和 App 通过 workspace 引用:

// layers/base/package.json
{
  "dependencies": {
    "@my-platform/shared": "workspace:*"
  }
}

5.2 环境变量隔离

不同应用可能需要不同的环境变量。推荐方式:

apps/web/.env        ← Web 前台的环境变量
apps/admin/.env      ← Admin 后台的环境变量
.env                 ← 全局共享的环境变量

Nuxt 会自动合并应用目录和根目录的 .env 文件,应用级优先。

5.3 构建与部署

Monorepo 中每个应用独立构建和部署:

# 只构建 web 应用
pnpm --filter web build
 
# 只构建 admin 应用
pnpm --filter admin build

配合 Turborepo 或 Nx,可以实现增量构建——只有 Layer 代码变化时才重新构建引用它的应用。

5.4 CI 优化

# .github/workflows/build.yml
- name: Detect changes
  id: changes
  uses: dorny/paths-filter@v3
  with:
    filters: |
      web:
        - 'apps/web/**'
        - 'layers/base/**'
      admin:
        - 'apps/admin/**'
        - 'layers/base/**'
        - 'layers/admin/**'
 
- name: Build web
  if: steps.changes.outputs.web == 'true'
  run: pnpm --filter web build

只有当 App 或其依赖的 Layer 有变化时才触发构建,节省 CI 时间。

6. Layer 的局限性

6.1 不适合的场景

  • 微前端:Layer 是构建时合并,不是运行时组合。如果需要独立部署、独立版本、运行时加载,需要微前端方案(Module Federation)
  • 非 Nuxt 项目:Layer 只能被 Nuxt 项目引用。如果共享代码需要在 React 或纯 Node.js 项目中使用,应该放在 packages/ 中作为普通 npm 包
  • 大规模共享:如果 Layer 包含 50+ 组件和 20+ 页面,合并后的构建时间会显著增加。此时考虑拆分为多个细粒度 Layer

6.2 调试挑战

Layer 中的代码报错时,堆栈信息可能指向 .nuxt/ 中的合并后文件,定位原始文件需要一些经验。Nuxt DevTools 的 Components 面板会标注组件来自哪个 Layer,有助于调试。

本章小结

  • Layer 是"可继承的 Nuxt 项目":组件、页面、布局、配置都可以继承,同名文件按"目标项目优先"覆盖
  • Layer vs Module:共享项目片段用 Layer,添加功能用 Module
  • Monorepo 架构apps/(应用)+ layers/(共享层)+ packages/(工具库),pnpm workspace 管理依赖
  • 继承链设计:Base Layer → Admin Layer → Admin App,逐层叠加,按需覆盖
  • 工程实践:TypeScript includeWorkspace 解决类型共享,.env 应用级隔离,CI paths-filter 增量构建
  • 局限:构建时合并(非运行时),只适用于 Nuxt 项目,大规模 Layer 影响构建时间