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 中的相对路径
- GitHub:
github:owner/repo#branch格式 - npm 包:直接写包名
1.3 合并规则
当 Layer 和目标项目有同名文件时,目标项目优先:
| 内容类型 | 合并策略 | 示例 |
|---|---|---|
| 组件 | 同名组件,目标项目覆盖 Layer | Button.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 项目,但它们的设计目的完全不同:
| 维度 | Layer | Module |
|---|---|---|
| 本质 | 一个(部分)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 影响构建时间