Nuxt Layers 全指南:编写可复用 Nuxt Layer(配置扩展、优先级、发布与模块集成)
Layers(层)是 Nuxt 提供的一整套应用扩展体系:它允许你通过 extends 或 ~~/layers 目录复用另一个"近乎完整"的 Nuxt 应用——包括页面、组件、composables、layout、server 端点乃至整个 nuxt.config。本文将围绕仓库中的官方 Layer 编写指南 docs/3.guide/6.going-further/7.layers.md,并结合 Nuxt 源码与测试,系统地讲解如何编写、发布、排序并深度定制一个 Layer,让读者能够用 Layer 在 monorepo、npm 包与远程 Git 仓库之间共享与复用 Nuxt 应用片段。
阅读前可先了解 Layer 的消费侧用法(快速上手)与 layers/ 目录约定(目录结构),本文聚焦"作者(Authoring)"视角,即如何写出一个高质量的 Layer。
什么是 Nuxt Layer,以及它为什么与标准 Nuxt 应用几乎一致
Nuxt Layers 是一种强大的机制,用于在 monorepo 内部、Git 仓库或 npm 包之间分享和复用部分 Nuxt 应用。它的最大设计特点是:Layer 的目录结构几乎与一个标准 Nuxt 应用完全一致,因此编写与维护的成本极低——你不需要学习一套新的项目格式,只需把平时组织应用的方式照搬到 Layer 目录里。
官方文档明确给出一个最小 Layer 的形态:一个 Layer 目录只需包含一个 nuxt.config.ts 文件即被 Nuxt 视为合法 Layer:
export default defineNuxtConfig({})
这个"空配置"文件的作用是向 Nuxt 标记这是一个 Layer。这一点也体现在目录结构文档中:"每个 Layer 都必须有一个 nuxt.config.ts 才会被识别为合法 Layer,即使它是空的"。Nuxt 加载层时正是通过 configFile 的存在与否来做过滤的(见下文源码说明)。
除了 nuxt.config.ts,Layer 目录中的下列文件会被 Nuxt 自动扫描,并合并进引用该 Layer 的项目:
app/components/*:扩展默认组件app/composables/*:扩展默认 composablesapp/layouts/*:扩展默认布局app/middleware/*:扩展默认路由中间件app/pages/*:扩展默认页面app/plugins/*:扩展默认插件app/utils/*:扩展默认工具函数app/app.config.ts:扩展默认应用配置server/*:扩展默认服务端端点与中间件nuxt.config.ts:扩展默认 Nuxt 配置
从类型定义上也能印证这一点:在 packages/kit/src/layers.ts 中,LayerDirectories 接口列出了 Nuxt 会为每个 Layer 解析的目录集合,包括 root、server、modules、shared、public、app(srcDir)以及 appLayouts、appMiddleware、appPages、appPlugins 等,均为各自带默认路径(如 /server、/app、/public)的约定目录。而 getLayerDirectories() 在计算时还会读取 Layer 自身配置中的 dir.layouts、dir.pages 等字段,说明这些目录的位置在 Layer 内部是可通过配置定制的。
值得注意,server、app 等目录会同时把 Layer 的服务端能力纳入项目:例如 Layer 中的 server/api/* 会被 Nitro 注册为可访问的 API 端点。
基础示例:用 extends 组合本地 Layer
把 Layer 接入项目的入口是 nuxt.config 中的 extends 选项。一个最小示例目录如下(与官方文档一致):
export default defineNuxtConfig({
extends: [
'./base',
],
})
<template>
<BaseComponent />
</template>
export default defineNuxtConfig({
// 这里的配置会与主项目配置合并!
app: {
head: {
title: 'Extending Configs is Fun!',
meta: [
{ name: 'description', content: 'I am using the extends feature in Nuxt!' },
],
},
},
})
<template>
<h1>Extending Components is Fun!</h1>
</template>
在这个示例中:
app/app.vue中直接使用了<BaseComponent />,而这个组件定义在 Layer 的base/app/components/中——它会被自动扫描进项目的组件体系,并支持全局自动导入;base/nuxt.config.ts中设置页面的title与meta描述,会被合并到最终配置中。
配置合并的底层机制值得展开:Nuxt 在加载配置时依赖 unjs/c12 解析各 Layer 的配置文件,用 unjs/defu 完成递归合并,远程 Git 源则由 unjs/giget 支持(这些库不属于本仓库,更多选项请查阅各自文档)。在 Nuxt 源码 packages/kit/src/loader/config.ts 中可以看到 Layer 配置的处理流程:它会为每个 Layer 解析 rootDir、srcDir,只处理一次(通过 processedLayers 去重),并在得到最终合并的配置列表后,从后向前让"项目本身"成为最高优先级的那一层。
extends 的值既可以是一个字符串路径,也可以写成 [source, options] 的元组形式,用于为单个 Layer 传入附加选项,例如 ['github:username/repoName', { install: true }] 或携带 auth 令牌。
Layer 优先级(Layer Priority):谁覆盖谁
当项目同时扩展多个 Layer 时,理解覆盖顺序至关重要。多个 Layer 定义了同名文件或组件时,优先级更高的 Layer 会覆盖优先级较低的。从高到低的完整顺序如下:
- 你的项目文件 —— 永远拥有最高优先级;
- 从
~~/layers目录自动扫描到的 Layer —— 按字母序排列(Z 的优先级高于 A); extends配置中列出的 Layer —— 排在最前面的条目优先级更高(第一个条目最高)。
也就是说,任何属于项目自身的文件都会压过 Layer 中的同名文件;而若仅存在 Layer,则自动扫描层排在 extends 之前。
两种引用方式的适用场景
extends:适合外部依赖(npm 包、远程仓库),或位于项目目录之外的 Layer;~~/layers目录:适合作为项目一部分的本地 Layer。
:::tip
需要手动控制自动扫描层顺序时,可用数字前缀命名目录:如 ~/layers/1.z-layer、~/layers/2.a-layer。这样 2.a-layer 的优先级会高于 1.z-layer。
:::
与之相呼应,目录结构文档 也给出了常见实践:在同一项目内做"基础默认值 → 特性模块 → 管理后台"这类渐进式覆盖时,用 1.base/、2.features/、3.admin/ 的数字前缀是最直白的排序手段。若不想重命名目录,还可以直接在 nuxt.config 的 extends 中以 ~~/layers/admin、~~/layers/features 的写法列出它们,使其遵循普通 extends 的排序规则(第一个优先级最高);未在 extends 中列出的本地层会保持字母序自动扫描顺序,排在被列出的层之下。
Nuxt 核心的初始化逻辑也印证了这种层次关系:在 packages/nuxt/src/core/nuxt.ts 中,initNuxt 会以逆序遍历 nuxt.options._layers 并注册各层配置里声明的 hooks(逆序保证 extends 先加载、项目自身最后加载),同时监听根目录下 layers 目录的结构变化——当新增或删除 Layer 目录时触发 restart,让自动扫描的 Layer 变更即时生效。
一个完整的优先级示例
export default defineNuxtConfig({
extends: [
// 项目目录之外的本地 Layer
'../base',
// NPM 包
'@my-themes/awesome',
// 远程仓库
'github:my-themes/awesome#v1',
],
})
如果项目中同时存在 ~~/layers/custom,那么最终优先级从高到低为:
- 你的项目文件(最高)
~~/layers/custom../base@my-themes/awesomegithub:my-themes/awesome#v1(最低)
这意味着项目文件会覆盖任何 Layer,而 ~~/layers/custom 会覆盖 extends 中的一切。
从源码看,本地层会被自动赋予与其所在目录同名的基础元数据:在 packages/kit/src/loader/config.ts 中,当检测到某 Layer 位于项目的 layers/ 相对路径下且尚未声明 meta.name 时,会以 basename(layer.cwd)(即目录名)作为其名字。
用官方模板快速起步
如果不想从零搭一个 Layer,可以使用官方提供的 layer starter 模板初始化:
npm create nuxt -- --template layer nuxt-layer
该命令会基于 Nuxt 官方 layer 模板在当前目录生成名为 nuxt-layer 的基础结构,随后按照生成项目 README 中的说明继续构建即可。它生成的结构本身就是一个最小可用的 Layer,可直接在上面叠加 app/ 与 server/ 目录。
发布与共享 Layer
Layer 可以通过两种主要渠道共享:远程 Git 仓库或 npm 包。
通过 Git 仓库共享
extends 支持远程 Git 源,写法是在字符串中使用 provider:username/repoName 形式的"giget"源。官方示例包括:
export default defineNuxtConfig({
extends: [
// GitHub 远程源
'github:username/repoName',
// 指向仓库内 /base 子目录
'github:username/repoName/base',
// 指定 dev 分支
'github:username/repoName#dev',
// 指定 v1.0.0 标签
'github:username/repoName#v1.0.0',
// GitLab 远程源示例
'gitlab:username/repoName',
// Bitbucket 远程源示例
'bitbucket:username/repoName',
],
})
几条与远程源相关的实践经验值得记住:
:::note 官方建议:尽量把 Layer 内容发布为 npm 包(公开或私有均可),而不是依赖远程 Layer。 :::
:::tip
扩展私有远程源时,需要提供环境变量 GIGET_AUTH=<token>。
:::
:::tip
扩展自托管的 GitHub/GitLab 实例时,需要设置 GIGET_GITHUB_URL=<url> 或 GIGET_GITLAB_URL=<url> 环境变量,或在 nuxt.config 中直接配置相应的 auth 选项。
:::
:::warning
从远程源作为 Layer 扩展时,你无法在 Nuxt 之外访问它的依赖。例如远程 Layer 依赖某个 eslint 插件,该插件不能在你的 eslint 配置中直接使用——因为这些依赖被安装在特殊目录(node_modules/.c12/layer_name/node_modules/),你的包管理器无法访问它。
:::
:::note
使用 Git 远程源时,如果 Layer 带 npm 依赖并希望安装它们,可以在 Layer 选项中指定 install: true:
:::
export default defineNuxtConfig({
extends: [
['github:username/repoName', { install: true }],
],
})
补充说明:当远程源不指定分支时,默认克隆 main 分支;若扩展私有 GitHub 仓库需要鉴权,也可以在扩展项中传入 token,如 ['github:my-themes/private-awesome', { auth: process.env.GITHUB_TOKEN }]。
通过 npm 包发布
另一种更推荐的方式是把 Layer 发布为 npm 包,包内包含你想要扩展的文件与依赖,供他人、多项目或私有使用。
要扩展 npm 包形式的 Layer,需要确保该包已发布并作为 devDependency 安装到用户项目中,然后直接用包名扩展当前配置:
export default defineNuxtConfig({
extends: [
// 带 scope 的 Node 模块
'@scope/moduleName',
// 或仅模块名
'moduleName',
],
})
发布前请确认 package.json 中的关键字段填写正确,以保证发布时包含所需的全部文件:
{
"name": "my-theme",
"version": "1.0.0",
"type": "module",
"main": "./nuxt.config.ts",
"dependencies": {},
"devDependencies": {
"nuxt": "^3.0.0"
}
}
:octicons-alert-24: 重要:Layer 中任何被 import 的依赖都必须显式加入 dependencies。nuxt 本身,以及任何仅用于发布前测试 Layer 的依赖,应保留在 devDependencies 中。
之后即可将包发布到 npm(公开或私有)。发布为私有 npm 包时,务必先登录并完成 npm 鉴权,才能在其他项目里下载该模块。
Tips:让 Layer 更好用的进阶技巧
命名 Layer 别名(Named Layer Aliases)
自动扫描的 Layer(来自 ~~/layers 目录)会自动生成别名。例如 ~~/layers/test 层可通过 #layers/test 访问。
若想为其它 Layer 创建命名别名,可在该 Layer 的配置中指定 $meta.name:
export default defineNuxtConfig({
$meta: {
name: 'example',
},
})
这会产生一个指向该 Layer 根目录的 #layers/example 别名。
源码实现与这一行为完全对应:在 packages/kit/src/loader/config.ts 中,只要 Layer 的 meta.name 存在,Nuxt 就会向 nuxtConfig.alias 写入 #layers/<name> 别名(指向该层带尾斜杠的 rootDir),并且只在别名尚未被占用时才写入(||=),从而保证项目自身可以覆盖默认别名。同样地,获取层目录的函数 只关心目录解析,而别名的生成集中在配置加载器这一处。类型层面,#layers/<name> 这样的别名会随 tsconfig 的 paths 一起生成,供编辑器与类型检查直接使用。
在快速上手文档中还可以看到这类别名的常见用法,例如 import { useAdmin } from '#layers/admin/composables/useAdmin'——利用别名可以直接从项目代码中访问 Layer 内部文件,而不必关心它的物理位置。
相对路径与别名注意事项
在 Layer 的组件与 composables 中使用全局别名(如 ~/ 与 @/)时要特别注意:这些别名是相对于"用户项目路径"解析的,而不是相对于 Layer 本身。官方给出的两个可行替代方案是:
- 使用相对路径进行导入;
- 使用上文介绍的命名 Layer 别名(
#layers/<name>)。
此外,Layer 的 nuxt.config 文件里若使用相对路径(嵌套 extends 除外),它们同样相对于用户项目解析,而不是 Layer 目录。官方给出的解决方法是使用 Node 的 fileURLToPath 等 API 计算出完全解析后的路径:
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
const currentDir = dirname(fileURLToPath(import.meta.url))
export default defineNuxtConfig({
css: [
join(currentDir, './app/assets/main.css'),
],
})
这种"基于 import.meta.url 计算当前目录再 join"的模式,能确保 Layer 的配置文件始终指向自身目录内的资源,是 Layer 作者处理路径问题的标准姿势。
禁用来自 Layer 的模块 :v4.3
扩展 Layer 时,你可能想禁用其中包含的某些模块。方式很简单:在 Nuxt 配置中把该模块的 configKey 设为 false:
export default defineNuxtConfig({
extends: ['./base-layer'],
// 通过把 config key 设为 false 来禁用 Layer 带来的模块
image: false, // 禁用 @nuxt/image
pinia: false, // 禁用 @pinia/nuxt
})
:::note
config key 由每个模块自行定义。常见示例有 @nuxt/image 对应 image、@pinia/nuxt 对应 pinia、@nuxt/content 对应 content。请查阅对应模块文档确认其 configKey。
:::
这个能力在以下场景特别有用:
- Layer 包含了你项目不需要的模块;
- 你想使用与 Layer 提供的不同的实现;
- 需要在特定环境中禁用埋点/分析类模块。
:::tip
这套机制同样适用于你自己的项目,而不只是 Layer 带来的模块。将某模块的 config key 设为 false 会阻止其 setup 函数运行,同时仍会为该模块生成类型。
:::
换言之,configKey: false 是一种"软卸载":模块的类型仍被保留以便代码可编译,但运行时不会执行其安装逻辑。这也解释了为什么文档示例中能安全地以 image、pinia 这类与模块强相关的键来关停对应能力。
多 Layer 支持:让 Nuxt 模块感知每个 Layer
如果你在编写 Nuxt 模块,并希望它对项目中的多个 Layer 分别处理,可以使用 Nuxt Kit 提供的 getLayerDirectories 工具。它返回按优先级排序的、每个 Layer 已解析目录的数组:
import { defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
export default defineNuxtModule({
setup (_options, nuxt) {
const layerDirs = getLayerDirectories()
for (const [index, layer] of layerDirs.entries()) {
console.log(`Layer ${index}:`)
console.log(` Root: ${layer.root}`)
console.log(` App: ${layer.app}`)
console.log(` Server: ${layer.server}`)
console.log(` Pages: ${layer.appPages}`)
// ... 其他目录
}
},
})
注意点:
- 数组靠前的条目优先级更高,会覆盖靠后的条目;
- 用户项目总是数组的第一项。
实现细节印证了这两点。在 packages/kit/src/layers.ts 中,getLayerDirectories() 直接基于 nuxt.options._layers 映射每个 Layer 到一组已解析目录;其 JSDoc 明确说明"第一个是用户/项目层(最高优先级),靠前的层会覆盖靠后的层"。同时,函数会对 rootDir 与项目 rootDir 相同的"根层"特殊处理(isRoot 分支),对根层直接采用最终合并后的 nuxt.options,而对其它层则读取各自的 layer.config——这保证了用户项目与派生 Layer 在读取自定义目录配置(如 dir.layouts、dir.pages)时语义一致。返回值还通过 WeakMap 做了缓存,避免同一 Layer 被重复解析路径。
此外,模块还可以通过 nuxt.options._layers 直接拿到每个 Layer 的原始配置(含 configFile、cwd、meta 等),Nuxt 内部正是据此在 initNuxt 时以逆序注册各层 hooks、完成各层模板的合并。如果你的模块需要感知"我处于哪个 Layer""这个文件来自哪个层",上述两种方式都能拿到所需信息。
深入阅读(Going Deeper)
配置加载与 extends 支持由 unjs 生态的三个库协作完成:
- unjs/c12:负责加载与解析 Layer 配置(含远程源支持);
- unjs/defu:负责将各层配置递归合并;
- unjs/giget:负责从 Git 远程源拉取 Layer。
想验证 Layer 行为是否与本文描述一致,可以直接在本仓库中进行三类探索:
- 配置加载与别名生成:阅读 packages/kit/src/loader/config.ts,重点关注对每个 Layer 去重处理、为本地层回填
meta.name、为meta.name写入#layers/*别名,以及对自动扫描本地层按extends顺序重排的完整逻辑; - 层目录解析:阅读 packages/kit/src/layers.ts,掌握
getLayerDirectories()对root/server/modules/shared/public/app/appLayouts/appMiddleware/appPages/appPlugins等目录的解析规则(每个目录都可通过对应 Layer 配置项的dir.*与serverDir调整); - 核心运行时集成:阅读 packages/nuxt/src/core/nuxt.ts,观察 Layer 列表(
nuxt.options._layers)如何驱动 hooks 注册、目录监听与重启等初始化行为。
相关参考文档同样值得对照阅读:Nuxt 分层入门(含消费侧完整示例与优先级演示)、layers/ 目录结构与自动注册规则,以及 Kit 层的 API 文档。把这些材料与本文的编写侧要点相互印证,即可系统地掌握 Nuxt Layer 从"消费"到"生产"的完整闭环。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00