上一章跑通了项目,本节把镜头拉近到目录层:Nuxt 的每个保留目录都对应一类自动行为,理解这套映射,就理解了"约定优于配置"的实现方式。本节是第 4、5、6 章的公共地基——composables、middleware、server 三个目录的深度用法都从这里的规则出发。
先上全景图,再逐个说细节。

几个容易混淆的点单独强调:
pages 是路由的总开关。项目里没有 pages 目录时,Nuxt 不启用文件路由,app.vue 就是整个应用;一旦创建 pages 目录并放入第一个页面,路由系统自动激活。这个"从无到有"的切换常让新手困惑:明明加了页面组件,访问路径却 404——通常是目录名拼错或文件没放对位置。
components 的命名规则。components/base/Button.vue 注册为 BaseButton(目录并入前缀);若目录下有 base/Button.vue 与 base/BaseButton.vue,默认后者优先后缀拼接会得到 BaseButton——遇到重名,用配置里的 pathPrefix 显式控制,别靠猜。
plugins 的执行时机。文件在应用启动时自动运行一次,适合给整个应用注入能力(比如注册一个全局指令、初始化一个图表库)。插件本质是"带 nuxtApp 上下文的安装函数",第 7 章写模块时会看到同样的机制。
自动导入(Auto Imports)是上面所有"自动"的底层机制:Nuxt 在构建期扫描约定目录,把导出项注册进一个虚拟模块,代码里直接使用即可。作用域分三层:
| 层级 | 覆盖范围 | 典型成员 |
|---|---|---|
| Vue 与 Nuxt 内置 | 全项目,服务端与客户端都可用 | ref、computed、watch、useRoute、useFetch |
| composables 导出 | 全项目自动导入 | 自写的 useXxx 函数 |
| components 导出 | 全项目模板中自动注册 | 任意 .vue 组件 |
自写一个组合函数验证。在 composables 目录新建文件:
// composables/useNow.ts // 约定目录内的导出,无需任何注册即可全局使用 export const useNow = () => useState<Date>('now', () => new Date())
随后任意组件中直接调用 useNow(),不需要 import。这里埋个伏笔:useState 是 Nuxt 提供的跨端状态容器,它能保证服务端设置的值在客户端水合时不丢——这正是第 4 章的主题。
自动导入虽好,边界要清楚:
// nuxt.config.ts export default defineNuxtConfig({ imports: { autoImport: false }, // 关闭 API 自动导入 components: { dirs: [] }, // 关闭组件自动注册 })
八个目录拼起来,一个中型电商项目的目录树长这样。拿到陌生项目时,照这张表逐目录扫描,五分钟内能判断出它的功能边界:
relay-shop/ ├── app.vue # 全局壳:NuxtLayout 包 NuxtPage ├── nuxt.config.ts # 五层配置(2.2) ├── error.vue # 兜底错误页(2.3) ├── assets/css/main.css # 参与构建的全局样式 ├── public/favicon.ico # 原样拷贝的固定路径资源 ├── components/ │ ├── ProductCard.vue # 全局注册为 ProductCard │ └── base/Button.vue # 注册为 BaseButton ├── composables/ │ ├── useProducts.ts # 商品数据层(4.3) │ └── useAuth.ts # 会话三件套(5.2) ├── layouts/ │ ├── default.vue # 导航加页脚 │ └── bare.vue # 结账极简布局 ├── middleware/ │ └── auth.global.ts # 全站登录守卫 ├── pages/ │ ├── index.vue # / │ └── products/[id].vue # /products/:id ├── plugins/ │ └── error-observer.client.ts # 生产错误哨位(9.2) └── server/ ├── api/products/[id].get.ts # GET /api/products/:id └── middleware/1.logger.ts # 请求日志(6.2)
读树的三个锚点:先看 pages 的层级(路由结构即信息架构),再看 server/api 的文件名(自带哪些后端能力),最后扫 middleware 与 plugins(行为约束与全局注入)。这三个锚点定下来,其余目录都是支撑角色。
自动导入用久了会冒出新问题:"这个函数是哪来的?" newcomers 对满屏无 import 的代码束手无策,IDE 的跳转有时也失灵。三个治理手段:
手段一:统一命名前缀。自写的组合函数强制 use 业务前缀(useOrderFetch 而非 useFetch2),看到前缀就知道来自 composables 目录;组件同理用目录前缀分层。
手段二:入口文档化。在项目 README 或约定文件里列一份"自动可用清单"——内置 API 常用项、自写组合函数、组件命名规则。新人入职第一份读物就是它。
手段三:局部显式。关键文件(比如被多处复用的工具函数)保留显式 import,用注释说明"此处显式引入是为可读性"。自动导入是权限不是义务,团队觉得显式更清晰的区域可以自主收紧。
这三种手段的共同思想:自动导入降低的是打字成本,不该抬高理解成本。约定目录的"物理位置即语义"本身就是文档——文件在 composables 里,读者自然知道它是可复用逻辑;这比注释"此函数可复用"可靠得多。
⚠️ 常见坑:命名冲突。自写的
useFetch会与内置同名 API 竞争,行为取决于扫描顺序。组合函数一律用业务前缀命名(如 useOrderFetch),从源头避免。
💡 关键直觉:自动导入省掉的是注册而非声明——文件还是要建、还是要导出,只是"在哪里登记"这一步框架代劳了。理解这点,魔法就还原成了机制。