Article
第一部分:基础入门
第一章:Nuxt 4 概述与核心优势
1.1 什么是 Nuxt?与 Vue 的关系
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| Nuxt.js | 基于 Vue.js 的全栈框架,提供开箱即用的 SSR(服务端渲染)、静态站点生成(SSG)、自动路由、状态管理等能力,简化 Vue 应用的工程化开发。 | Nuxt 不是 Vue 的替代品,而是其上层封装,依赖 Vue 核心库运行。 |
| Vue.js | 渐进式 JavaScript 框架,用于构建用户界面。核心关注视图层,支持组件化开发。 | Vue 提供响应式系统和组件模型,但不包含路由、SSR、构建工具等生产级功能。 |
| 关系定位 | Nuxt = Vue + 路由 + 状态管理 + 构建工具 + 服务端引擎 + 最佳实践约定。开发者可专注于业务逻辑,无需手动配置 Webpack、Vite、Vue Router 等。 | 使用 Nuxt 时仍需掌握 Vue 基础(如 ref、reactive、生命周期),但无需显式安装 vue-router 或 pinia(除非自定义)。 |
1.2 Nuxt 4 的核心特性(SSR、自动导入、文件路由等)
| 特性名称 | 说明 | 注意事项 |
|---|---|---|
| 服务端渲染(SSR) | 页面在服务器端预渲染为 HTML,提升首屏加载速度与 SEO。Nuxt 4 默认启用混合渲染(Hybrid Rendering),可按路由选择 SSG/SSR/CSR。 | 需确保服务端环境支持 Node.js;敏感数据不应在 SSR 中直接暴露。 |
| 自动导入(Auto Imports) | 自动扫描 composables/、components/、utils/ 等目录,无需手动 import 即可在任意组件或页面中使用。 | 仅限项目内符合命名规范的函数/组件;第三方库需通过插件或 nuxt.config 显式配置。 |
| 文件路由系统 | 基于 app/pages/ 目录结构自动生成 Vue Router 路由配置,支持动态路由([id].vue)、嵌套路由(目录嵌套)等。 | 文件名必须以 .vue 结尾;非页面组件应放在 components/ 或使用下划线前缀(如 _Card.vue)避免被识别为路由。 |
| Nitro 服务端引擎 | Nuxt 4 内置新一代服务端引擎 Nitro,统一 API 路由、中间件、部署目标(支持 Serverless、Node.js、Deno 等)。 | 所有服务端逻辑(API、中间件)必须放在 server/ 目录下;Nitro 在构建时自动打包为独立服务。 |
| 组合式 API 优先 | 全面采用 Vue 3 的 Composition API(如 setup()、ref、computed),并提供 Nuxt 专属组合函数(如 useAsyncData)。 | Options API 仍可用,但官方推荐使用组合式风格以获得更好的类型推导和逻辑复用。 |
1.3 约定优于配置:Nuxt 的设计哲学
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 约定优于配置(Convention over Configuration) | Nuxt 通过预设目录结构和命名规则(如 pages/ 表示路由、layouts/ 表示布局)减少开发者配置负担,提升开发效率。 | 开发者应优先遵循默认约定;若需自定义(如修改 pages 目录),需在 nuxt.config.ts 中显式声明。 |
| 零配置启动 | 新项目无需配置 Webpack、Babel、TypeScript 编译器等即可运行,内置 Vite 构建工具。 | 高级需求(如自定义 loader)仍可通过 nuxt.config.ts 扩展,但应尽量避免破坏默认约定。 |
| 自动化工程能力 | 包括热更新、错误提示、类型检查、代码分割、资源优化等均由框架自动处理。 | 这些能力依赖于 Nuxt 的内部工具链(如 unplugin-vue-components、nitropack),升级时需注意兼容性。 |
| 可扩展但不强制 | 虽然强调约定,但 Nuxt 允许通过模块(Modules)、插件(Plugins)、钩子(Hooks)进行深度定制。 | 过度自定义可能导致项目难以维护或升级;建议优先使用官方模块(如 @nuxtjs/tailwindcss)。 |
1.4 Nuxt 4 相比 Nuxt 3 的关键改进
| 改进项 | 说明 | 注意事项 |
|---|---|---|
| 更快的冷启动与 HMR | 基于 Vite 5 和 Nitro 2,开发服务器启动速度提升 30%+,热更新更稳定。 | 需 Node.js ≥ 18;旧版 Node 可能导致兼容问题。 |
| 更严格的类型安全 | 完整 TypeScript 支持,包括 API 路由、composables、组件 props 的自动类型推导。 | 推荐使用 definePageMeta、defineProps 等宏以获得最佳类型体验。 |
| 改进的混合渲染(Hybrid Rendering) | 支持在同一项目中为不同路由指定渲染模式(SSG / SSR / CSR),通过 routeRules 配置。 | 静态路由(SSG)需在构建时确定;动态路由建议使用 SSR 或 CSR。 |
| 更小的客户端包体积 | 通过 Tree-shaking 和自动代码分割,减少未使用代码的打包体积。 | 自动导入机制已优化,避免全局污染;但仍需注意第三方库的副作用引入。 |
| 更好的模块生态系统 | 官方模块全面适配 Nuxt 4,社区模块迁移加速;支持 ESM 优先的现代包格式。 | 使用第三方模块前应确认其是否标注支持 Nuxt 4(查看 package.json 中的 nuxt 字段)。 |
| 开发者体验增强 | 内置 Nuxt DevTools(可视化组件树、路由、状态)、更好的错误堆栈、VS Code 插件集成。 | DevTools 默认仅在开发环境启用;生产环境自动移除,不影响性能。 |
第二章:项目初始化与开发环境搭建
2.1 环境要求(Node.js、编辑器、WSL 建议)
| 要求项 | 说明 | 注意事项 |
|---|---|---|
| Node.js 版本 | Nuxt 4 要求 Node.js ≥ 18.0.0(推荐 18.x 或 20.x LTS)。Nitro 引擎依赖现代 ES 模块和 Web APIs。 | 使用 node -v 验证版本;低于 18 将导致构建失败或运行时错误。 |
| 包管理器 | 支持 npm、yarn、pnpm、bun。官方推荐 pnpm(因高效依赖解析和磁盘节省)。 | 若使用 pnpm,需确保全局安装:npm install -g pnpm。 |
| 代码编辑器 | 推荐 VS Code,配合官方扩展 Nuxt VS Code Extension(提供语法高亮、组件自动补全、DevTools 集成)。 | 安装后重启编辑器以激活 Nuxt 专属功能(如 <NuxtLayout> 智能提示)。 |
| 操作系统 | Windows、macOS、Linux 均支持。Windows 用户建议启用 WSL2(Windows Subsystem for Linux)以获得类 Unix 开发体验。 | WSL2 可避免路径分隔符、文件监听等兼容性问题;Docker 开发也更顺畅。 |
| 网络环境 | 需能访问 npm registry(或配置镜像源如 https://registry.npmmirror.com)。 | 国内用户可设置 .npmrc 或使用 --registry 参数加速依赖安装。 |
2.2 使用 nuxi 创建 Nuxt 4 项目
| 操作步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 安装 nuxi(可选) | nuxi 是 Nuxt 官方 CLI 工具,通常无需全局安装,可通过 npm create nuxt@latest 直接调用。 | 全局安装命令为 npm install -g nuxi,但不推荐,因项目应锁定 nuxi 版本。 |
| 创建新项目 | 在终端执行:npm create nuxt@latest my-nuxt-app,项目名 my-nuxt-app 可自定义;确保目标目录不存在或为空。 | 按交互提示选择 TypeScript、ESLint、Tailwind CSS 等选项。 |
| 选择模板特性 | 交互式选项包括:TypeScript(默认开启)、ESLint / Prettier、Tailwind CSS、Nuxt DevTools、SSR/SSG 渲染模式。 | 建议初学者启用所有开发辅助工具;生产项目按需选择。 |
| 安装依赖 | 创建完成后,进入目录并自动安装依赖(若未自动安装,手动运行 npm install 或对应包管理器命令)。 | 若使用 pnpm,命令为 pnpm install;bun 用户用 bun install。 |
| 验证项目结构 | 成功创建后,目录应包含 app/、server/、nuxt.config.ts、package.json 等核心文件。 | 若缺少 app/ 目录,可能误选了旧版模板(确保使用 nuxt@latest)。 |
2.3 项目目录结构详解(app/、server/、public/ 等)
| 目录/文件 | 用途说明 | 注意事项 |
|---|---|---|
| app/ | 应用主目录,包含页面、布局、根组件等前端逻辑。 | 所有 Vue 页面必须放在 app/pages/ 下;app.vue 是应用根组件。 |
| app/pages/ | 基于文件的路由系统目录。每个 .vue 文件对应一个路由(如 index.vue → /)。 | 动态路由使用 [param].vue;嵌套路由通过子目录实现。 |
| app/layouts/ | 布局组件目录。默认布局为 default.vue,可通过 definePageMeta({ layout: 'custom' }) 切换。 | 布局组件必须包含 <slot /> 以渲染页面内容。 |
| app/components/ | 全局组件目录。所有 .vue 组件自动注册,可在任意页面直接使用(无需 import)。 | 非页面组件建议放在此处;避免与 pages/ 混淆。 |
| server/ | 服务端逻辑目录,由 Nitro 引擎处理。包含 API 路由、中间件、工具函数等。 | 此目录代码仅在服务端运行,不可引用 Vue 组件或浏览器 API。 |
| server/api/ | API 路由目录。每个文件暴露一个事件处理函数(如 hello.get.ts → GET /api/hello)。 | 文件名决定路由路径;支持 .ts、.js、.mjs。 |
| public/ | 静态资源目录。文件直接复制到构建输出根目录(如 public/favicon.ico → /favicon.ico)。 | 不经过构建处理;适合图标、robots.txt、验证文件等。 |
| assets/ | 需要构建处理的静态资源(如图片、字体、CSS)。通过 ~/assets/ 引用。 | 支持 SCSS、TypeScript 等预处理器;体积过大建议移至 CDN。 |
| nuxt.config.ts | 项目配置文件。用于自定义构建、模块、运行时配置等。 | 使用 defineNuxtConfig() 包裹配置对象以获得类型提示。 |
| composables/ | 组合函数目录。所有函数自动导入,命名需符合驼峰规范(如 useCounter.ts → useCounter())。 | 仅限组合式逻辑;避免副作用或全局状态污染。 |
2.4 启动开发服务器与使用 Nuxt DevTools
| 操作步骤名称 | 操作细节 | 注意事项 |
|---|---|---|
| 启动开发服务器 | 在项目根目录运行:npm run dev(或对应包管理器命令,如 pnpm dev),默认监听 http://localhost:3000;端口冲突时自动递增(3001、3002…)。 | — |
| 访问应用 | 浏览器打开 http://localhost:3000 查看首页(通常为 app/pages/index.vue 内容)。 | 首次启动可能较慢(需编译依赖);后续热更新极快。 |
| 启用 Nuxt DevTools | 确保创建项目时启用了 DevTools(或手动安装 @nuxt/devtools 模块)。开发时按 Ctrl+Shift+D(Windows/Linux)或 Cmd+Shift+D(macOS)呼出面板。 | DevTools 仅在开发环境可用;生产构建自动剥离。 |
| DevTools 核心功能 | 组件树查看与 props 检查、路由列表与跳转测试、状态管理(useState)监控、网络请求(useFetch)日志。 | 需使用 Composition API 编写组件才能完整显示响应式数据。 |
| 自定义 DevTools 行为 | 在 nuxt.config.ts 中配置:devtools: { enabled: true, vscode: {} }。 | 可关闭 DevTools(设 enabled: false)或配置 VS Code 集成。 |
| 热模块替换(HMR) | 修改 .vue、.ts 文件后,浏览器自动更新,状态保留(如表单输入、计数器值)。 | 若 HMR 失效,检查是否修改了 nuxt.config.ts(需重启服务器)。 |
第三章:应用入口与页面系统
3.1 app.vue:应用根组件的作用与写法
| 概念/方法 | 说明 | 注意事项 |
|---|---|---|
| app.vue 文件位置 | 位于项目根目录下(与 nuxt.config.ts 同级),是整个 Nuxt 应用的根组件。 | 必须存在;若缺失,Nuxt 会自动生成一个空 <NuxtPage /> 组件。 |
| 核心作用 | 包裹所有页面内容,可定义全局布局结构(如 Header/Footer)、全局状态、错误边界等。 | 不应包含业务逻辑;复杂布局应委托给 layouts/。 |
| 基本写法示例 | <template><div class="app"><header>Global Header</header><NuxtPage /><footer>Global Footer</footer></div></template> | 必须包含 <NuxtPage /> 组件,否则页面内容不会渲染。 |
| 使用组合式 API | 可在 <script setup> 中使用 useState、useAsyncData 等 Nuxt 组合函数。 | 避免在此处发起高成本数据请求;会影响所有页面加载性能。 |
| 全局样式注入 | 可在 <style> 标签中定义全局 CSS,或通过 @import 引入外部样式文件。 | 推荐将全局样式放在 assets/css/global.css 并在 nuxt.config.ts 中注册。 |
3.2 基于文件的路由系统(app/pages/)
| 概念/规则 | 说明 | 注意事项 |
|---|---|---|
| 路由映射规则 | app/pages/index.vue → /;app/pages/about.vue → /about;app/pages/products/list.vue → /products/list | 文件名决定路由路径;目录结构即路由层级。 |
| 页面组件要求 | 必须为 .vue 单文件组件,且导出默认 Vue 组件对象(可通过 <script setup> 或 export default)。 | 非 .vue 文件(如 .ts)不会被识别为页面。 |
| 自动路由生成 | Nuxt 在构建时扫描 app/pages/ 目录,自动生成 Vue Router 配置,无需手动编写 createRouter。 | 修改页面文件后,开发服务器自动更新路由(HMR 支持)。 |
| 路由优先级 | 同级目录下,静态路径优先于动态路径(如 user.vue 优先于 [id].vue)。 | 避免命名冲突;明确区分静态页与参数页。 |
| 首页约定 | app/pages/index.vue 是默认首页,对应根路径 /。 | 若删除此文件,访问 / 将返回 404(除非配置 fallback)。 |
3.3 动态路由与嵌套路由
| 路由类型 | 文件命名方式 | 对应路径示例 | 注意事项 |
|---|---|---|---|
| 动态路由(单参数) | app/pages/user/[id].vue | /user/123 | 参数通过 useRoute().params.id 获取;参数名必须与方括号内一致。 |
| 动态路由(多参数) | app/pages/post/[category]/[slug].vue | /post/news/hello-world | 多层嵌套需逐级创建目录;参数按路径顺序匹配。 |
| 可选动态参数 | app/pages/search/[[query]].vue | /search 或 /search/vue | 使用双中括号 [[ ]] 表示参数可选;未提供时值为 undefined。 |
| 嵌套路由(父-子) | 父:app/pages/settings.vue,子:app/pages/settings/profile.vue | /settings(父),/settings/profile(子) | 父页面必须包含 <NuxtPage /> 才能渲染子页面。 |
| 嵌套布局复用 | 嵌套路由自动继承父级布局(除非子页面显式指定新布局)。 | 可通过 definePageMeta({ layout: false }) 禁用布局。 | |
| catch-all 路由 | app/pages/[…slug].vue | 匹配任意深度路径(如 /a/b/c) | 用于自定义 404 或 CMS 类路由;参数为数组 ['a', 'b', 'c']。 |
3.4 页面级组件的组织与非页面化配置
| 场景 | 解决方案 | 说明 | 注意事项 |
|---|---|---|---|
| 避免组件被识别为页面 | 将非页面组件放入 app/components/ 目录 | 此目录下所有 .vue 文件自动注册为全局组件,不参与路由生成。 | 推荐命名清晰(如 UserCard.vue),避免与页面名冲突。 |
| 临时禁用页面路由 | 在 app/pages/ 中使用下划线前缀命名文件,如 _draft.vue | Nuxt 忽略以下划线 _ 开头的文件,不生成对应路由。 | 适用于草稿、废弃页面;仍可通过 import 手动使用组件。 |
| 条件性排除页面 | 使用 nuxt.config.ts 中的 ignore 选项 | ignore: ['**/pages/_*.vue'] | 适用于批量忽略;但不如下划线命名直观。 |
| 页面专属组件共存 | 在页面同级目录创建子目录存放辅助组件,如 app/pages/user/Avatar.vue | 该组件仅在 user.vue 中使用,且不会成为独立路由。 | 需手动 import 使用(因不在 components/ 目录,不自动导入)。 |
使用 <ClientOnly> 包裹 | 对含浏览器 API 的组件包裹 <ClientOnly> | 防止 SSR 报错(如 window、localStorage 访问)。 | 仅解决渲染问题;数据获取仍需在 onMounted 中处理。 |
第二部分:核心功能开发
第四章:组件系统与自动导入
4.1 全局组件自动导入机制
| 概念/机制 | 说明 | 注意事项 |
|---|---|---|
| 自动扫描目录 | Nuxt 默认扫描 app/components/ 目录下的所有 .vue、.ts、.js 文件(支持子目录)。 | 仅限项目根目录下的 app/components/;不包括 node_modules 或其他路径。 |
| 自动注册规则 | 组件文件名(PascalCase)即为组件名。例如 MyButton.vue → <MyButton />。 | 文件名必须符合 Vue 组件命名规范(首字母大写,避免 kebab-case 作为标签名)。 |
| 无需手动 import | 在任意页面、布局或组件中可直接使用已注册组件,无需 import 语句。 | 若组件未生效,检查文件扩展名是否为 .vue,以及是否位于正确目录。 |
| 支持组合函数自动导入 | composables/ 目录下的函数(如 useCounter.ts)也自动导入,调用方式为 useCounter()。 | 函数名必须以 use 开头,且导出默认函数(export default () => {})。 |
| 类型推导支持 | VS Code 配合 Volar/Nuxt 插件可自动提供组件 props 和事件的类型提示。 | 需在组件中使用 defineProps、defineEmits 宏以启用完整类型检查。 |
4.2 页面专属组件的组织方式(避免被识别为页面)
| 场景 | 推荐做法 | 说明 | 注意事项 |
|---|---|---|---|
| 页面内私有组件 | 将组件文件放在 app/pages/ 下的子目录中,如 app/pages/user/Avatar.vue | 该组件仅用于 user.vue 页面,不会生成路由。 | 必须手动 import Avatar from './Avatar.vue' 使用(因不在 components/,不自动导入)。 |
| 临时草稿组件 | 使用下划线前缀命名:_ModalDraft.vue | Nuxt 忽略以下划线 _ 开头的 .vue 文件,不注册为路由或全局组件。 | 适用于开发中未完成的组件;仍可通过相对路径 import 使用。 |
| 共享但非全局组件 | 放入 app/components/ 子目录(如 app/components/ui/Button.vue) | 仍自动导入,组件名为 UiButton(目录名+文件名转 PascalCase)。 | 避免扁平化命名冲突;适合按功能模块组织组件。 |
| 条件性排除 | 在 nuxt.config.ts 中配置 components: { dirs: [...] } 显式指定扫描路径 | 可完全跳过 pages/ 目录中的组件扫描。 | 一般不推荐;优先使用目录结构或命名约定控制。 |
使用 <script setup> 局部注册 | 在页面组件内部直接定义子组件(较少用) | <script setup>import LocalCard from './LocalCard.vue'</script> | 仅适用于极简复用;破坏自动导入优势,不推荐常规使用。 |
4.3 自定义自动导入目录配置(components/ 扩展)
| 配置项 | 语法示例 | 用途 | 注意事项 |
|---|---|---|---|
| 扩展 components 目录 | export default defineNuxtConfig({ components: [ '~/components', '~/lib/custom-ui' ] }) | 将 ~/lib/custom-ui 中的组件也纳入自动导入范围。 | 路径需以 ~ 或相对路径开头;支持多个目录。 |
| 自定义组件前缀 | components: [{ path: '~/components/base', prefix: 'Base' }] | base/Button.vue → <BaseButton /> | 避免命名冲突;适合设计系统组件库。 |
| 禁用自动导入 | components: false | 完全关闭自动导入功能,需手动 import 所有组件。 | 仅适用于特殊迁移或调试场景;大幅降低开发效率。 |
| 控制扫描深度 | components: [{ path: '~/components', extensions: ['vue', 'ts'] }] | 限制仅扫描 .vue 和 .ts 文件。 | 默认已包含常见扩展;显式声明可提升构建性能。 |
| 全局 vs 局部作用域 | 所有通过 components 配置的目录均为全局作用域组件 | 无法实现”仅在某布局下可用”的局部自动导入。 | 如需作用域限制,应手动 import 或使用插槽传递。 |
4.4 组件命名规范与类型提示支持
| 规范项 | 推荐规则 | 说明 | 注意事项 |
|---|---|---|---|
| 文件命名 | 使用 PascalCase(如 UserProfile.vue) | 与 Vue 官方风格指南一致;确保组件标签 <UserProfile /> 符合 HTML 自定义元素规范。 | 避免 kebab-case(如 user-profile.vue),虽可工作但标签需转为 <UserProfile />。 |
| 组件标签使用 | 在模板中始终使用 PascalCase 标签 | <template><UserProfile /></template> | Vue 3 支持 kebab-case 标签(<user-profile />),但 Nuxt 自动导入仅保证 PascalCase 可靠。 |
| Props 类型定义 | 使用 defineProps<{ name: string }>()(TypeScript) | 启用严格的 props 类型检查和 IDE 提示。 | 避免使用 props: { name: String }(Options API 风格),不利于类型推导。 |
| Emits 类型定义 | 使用 const emit = defineEmits<{ (e: 'submit'): void }>() | 确保 $emit('submit') 被类型系统校验。 | 事件名应为字面量类型,避免字符串变量导致类型丢失。 |
| 自动类型生成 | Nuxt 在 .nuxt/ 目录下生成 components.d.ts,为所有自动导入组件提供全局类型声明。 | 无需手动编写 .d.ts 文件;重启 dev server 可刷新类型。 | 若类型未更新,删除 .nuxt/ 并重新启动开发服务器。 |
| 第三方组件集成 | 通过插件或模块注册的第三方组件(如 VueFinalModal)需手动声明类型或使用 globalComponents 配置。 | 自动导入仅适用于本地项目组件;外部库需额外配置。 | 可在 nuxt.config.ts 中添加 typescript: { shim: false } 精简类型。 |
第五章:布局与插槽系统
5.1 app/layouts/ 目录与默认布局
| 概念/文件 | 说明 | 注意事项 |
|---|---|---|
| app/layouts/ 目录 | 存放所有布局组件(.vue 文件),每个文件对应一种页面布局结构。 | 必须位于 app/ 下;不在此目录的组件不会被识别为布局。 |
| 默认布局文件 | app/layouts/default.vue 是 Nuxt 自动应用的默认布局(若未指定其他布局)。 | 若该文件不存在,Nuxt 会隐式使用 <NuxtLayout><NuxtPage /></NuxtLayout> 作为默认。 |
| 布局基本结构 | <template><div class="layout"><header>App Header</header><slot /><footer>App Footer</footer></div></template> | 必须包含 <slot />,用于渲染当前页面内容;可嵌套多个具名插槽。 |
| 全局样式/逻辑 | 可在布局中引入全局 CSS、状态管理(如 useState)或错误边界(<NuxtErrorBoundary>)。 | 避免在布局中执行高成本数据获取(会影响所有使用该布局的页面)。 |
| 自动导入 | 布局组件无需手动注册;Nuxt 自动将其纳入可用布局池。 | 布局名由文件名决定(PascalCase 转 kebab-case,如 dashboard.vue → 名称 “dashboard”)。 |
5.2 多布局切换(definePageMeta 配置)
| 方法/配置 | 语法 | 用途 | 注意事项 |
|---|---|---|---|
| definePageMeta 宏 | definePageMeta({ layout: 'dashboard' }) | 在页面组件顶部声明使用指定布局(如 dashboard.vue)。 | 必须在 <script setup> 顶层调用;不能在条件语句或函数内使用。 |
| 布局名称匹配 | 布局名对应 app/layouts/ 下的文件名(不带扩展名),支持 kebab-case 或 PascalCase。 | layout: ‘auth’ → 匹配 auth.vue;layout: ‘AuthLayout’ → 匹配 AuthLayout.vue。 | — |
| 禁用布局 | definePageMeta({ layout: false }) | 页面不使用任何布局,直接渲染自身内容。 | 适用于登录页、全屏图表等无通用结构的页面。 |
| 默认布局省略 | 若页面未调用 definePageMeta 或未设置 layout,则自动使用 default.vue。 | 推荐显式声明以提高可读性,尤其在大型项目中。 | — |
| 类型安全支持 | VS Code 会提示可用的布局名称(基于 layouts/ 目录内容)。 | 若新增布局后未提示,重启开发服务器以刷新类型生成。 | — |
5.3 嵌套布局与插槽传递
| 场景 | 实现方式 | 说明 | 注意事项 |
|---|---|---|---|
| 布局继承 | 子布局通过 <NuxtLayout name="parent" /> 嵌套父布局 | <template><NuxtLayout name="default"><aside>Admin Sidebar</aside><slot /></NuxtLayout></template>(app/layouts/admin.vue) | 子布局仍需包含 <slot /> 以传递页面内容至父布局。 |
| 多层嵌套 | 支持任意层级(如 marketing → default → root) | 每层通过 name 属性指定上一级布局。 | 过深嵌套可能影响性能和可维护性;建议不超过 2–3 层。 |
| 插槽内容传递 | 页面内容通过 <slot /> 逐层向上传递,最终注入到最外层布局的 <slot /> 位置。 | 所有中间布局必须保留 <slot />,否则页面内容丢失。 | 不支持具名插槽跨层传递;需在每层显式定义并透传。 |
| 布局间通信 | 通过 useState、provide/inject 或 props(结合 <NuxtLayout :prop="value" />)实现。 | <NuxtLayout> 支持传递 props 到目标布局组件。 | 避免过度依赖布局通信;复杂状态应提升至应用根或状态管理。 |
| 错误隔离 | 每个布局可独立包裹 <NuxtErrorBoundary>,防止局部错误影响全局。 | <NuxtErrorBoundary><slot /></NuxtErrorBoundary> | 推荐在关键布局(如 dashboard)中添加错误边界。 |
5.4 动态布局与条件渲染
| 方法 | 语法示例 | 用途 | 注意事项 |
|---|---|---|---|
| 运行时动态布局 | 不支持直接在 definePageMeta 中使用变量或函数返回布局名。 | definePageMeta 是构建时宏,仅接受静态字面量。 | 无法根据用户角色、路由参数等动态切换布局。 |
| 替代方案:条件渲染布局内容 | 在 default.vue 中根据状态渲染不同结构 | <template><div v-if="isAuth"><AuthHeader /><slot /></div><div v-else><PublicHeader /><slot /></div></template> | 通过 useRoute()、useState() 获取状态;保持单一布局文件。 |
| 基于路由规则的布局 | 在 nuxt.config.ts 中使用 routeRules 静态分配布局(构建时确定) | routeRules: { '/admin/**': { definePageMeta: { layout: 'admin' } } } | 适用于路径前缀固定的场景(如 /admin、/user)。 |
| 使用中间件切换 | 在页面级中间件中重定向到不同布局的代理页面(不推荐) | 增加路由复杂度;破坏布局语义。 | 仅作为最后手段;优先使用静态布局分配或条件渲染。 |
| 响应式布局切换(客户端) | 在布局组件内部使用 ref + watch 动态改变 UI 结构 | 仅改变视觉结构,不切换布局组件本身。 | 适用于主题切换、响应式侧边栏等场景。 |
第六章:数据获取与状态管理
6.1 useAsyncData:服务端/客户端统一数据获取
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| useAsyncData | const { data, pending, error, refresh } = useAsyncData(key, handler, options?) | 在服务端(SSR)或客户端(CSR)执行异步数据获取,并自动序列化结果到页面。 | const { data } = useAsyncData('posts', () => $fetch('/api/posts')) | key 必须唯一,用于缓存和刷新;handler 在服务端执行时无浏览器 API(如 window);返回值必须可 JSON 序列化 |
| key 参数 | 字符串或响应式引用(如 ref('user-1')) | 标识数据请求,用于缓存和 refresh() 触发。 | const userId = ref('123'); useAsyncData(userId, () => fetchUser(userId.value)) | 若使用响应式 key,变更时会自动重新请求(需配合 watch 选项)。 |
| options.lazy | 布尔值,默认 false | 若为 true,组件挂载时不阻塞渲染(pending 初始为 false)。 | useAsyncData('data', fetchData, { lazy: true }) | 适用于非关键数据(如推荐内容),提升首屏速度。 |
| options.server | 布尔值,默认 true | 控制是否在服务端执行 handler。设为 false 则仅在客户端运行。 | useAsyncData('geo', () => getGeoLocation(), { server: false }) | 用于访问浏览器专属 API(如定位、localStorage)。 |
| refresh() 方法 | 无参数函数 | 手动重新执行 handler 并更新 data。 | const { refresh } = useAsyncData('list', fetchList); refresh() | 可用于”重新加载”按钮;支持 await refresh() 等待完成。 |
6.2 useFetch:简化 API 请求
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| useFetch | const { data, error, pending } = useFetch(url, options?) | 基于 $fetch 的封装,自动处理 API 请求,支持 SSR/CSR 统一调用。 | const { data } = useFetch('/api/profile') | 内部调用 $fetch,继承其所有特性;自动推断 URL 类型(字符串或请求对象) |
| baseURL 配置 | 在 nuxt.config.ts 中设置 | 全局 API 基础路径,避免硬编码。 | runtimeConfig: { public: { apiBase: '/api' } } 配合 useFetch('/user', { baseURL: useRuntimeConfig().public.apiBase }) | 推荐通过 runtimeConfig.public 管理公共 API 地址。 |
| 请求头/方法定制 | 通过 options 传入 | 支持自定义 method、headers、body 等。 | useFetch('/login', { method: 'POST', body: { email, password } }) | 敏感头(如 Authorization)应在服务端注入,避免暴露在客户端。 |
| 类型安全响应 | 泛型支持 | 显式指定返回数据类型。 | interface User { id: number; name: string }; const { data } = useFetch<User>('/api/user') | 配合 TypeScript 可获得完整 IDE 提示和编译检查。 |
| 错误处理 | 通过 error 响应式引用 | 捕获请求失败信息。 | const { error } = useFetch('/api/data'); if (error.value) { console.error(error.value.message) } | error 为 null 表示成功;非空时包含 message 和原始错误对象。 |
6.3 响应式依赖监听(watch 选项)
| 选项名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| watch(在 useAsyncData/useFetch 中) | useAsyncData(key, handler, { watch: [ref1, ref2] }) | 当指定的响应式源变更时,自动重新执行数据获取。 | const page = ref(1); useAsyncData('items', () => fetchItems(page.value), { watch: [page] }) | 仅监听浅层变化;不适用于复杂对象(需用 deep: true 手动 watch) |
| 监听路由参数 | 结合 useRoute() | 当 URL 参数变化时刷新数据。 | const route = useRoute(); useAsyncData('post', () => fetchPost(route.params.id), { watch: [() => route.params.id] }) | 推荐使用计算属性包装参数以提高可读性。 |
| 多依赖监听 | 数组形式传入多个 ref/computed | 同时监听多个状态变化。 | useFetch('/search', { query: { q: searchTerm.value, category: selectedCat.value }, watch: [searchTerm, selectedCat] }) | 避免监听无关状态,防止不必要的请求。 |
| 手动 watch 替代 | 使用 Vue 的 watch() | 更精细控制(如防抖、节流)。 | watch(debounceSearch, (newVal) => { if (newVal) fetchData(newVal) }) | useAsyncData 的 watch 无防抖;高频变更场景建议手动控制。 |
6.4 缓存策略(cache.maxAge、swr)
| 缓存选项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| getCachedData / 内部缓存 | 自动基于 key 缓存 | 同一 key 在同一次请求周期内复用数据,避免重复请求。 | 两次调用相同 key,第二次直接返回缓存 | 仅限当前请求上下文(SSR)或组件实例(CSR)有效。 |
| options.cache(Nitro 缓存) | useAsyncData('data', handler, { cache: { maxAge: 60 } }) | 在 Nitro 服务端启用持久化缓存(需部署环境支持)。 | useAsyncData('news', () => $fetch('/external-api'), { cache: { maxAge: 300 } }) | 仅在服务端生效;需 Nitro 缓存驱动(如 Redis、memory)配置 |
| SWR(Stale-While-Revalidate) | useAsyncData('data', handler, { getCachedData: () => cached, immediate: true }) | 先返回旧数据(stale),后台静默更新(revalidate)。 | Nuxt 4 尚未内置 SWR 选项,需结合 useState + onMounted 手动实现。 | 官方计划未来支持;当前可通过组合 useAsyncData + 定时 refresh 模拟。 |
| 客户端缓存控制 | 使用 useState 存储结果 | 避免组件切换时重复请求。 | const cached = useState('posts'); if (!cached.value) { const { data } = await useAsyncData('posts', fetchPosts); cached.value = data.value } | 适用于静态或低频更新数据;注意内存泄漏风险。 |
6.5 useState:跨组件状态共享与持久化
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| useState | const state = useState(key, init?) | 创建可在整个应用中共享的响应式状态(类似 Pinia 的轻量替代)。 | const counter = useState('counter', () => 0); counter.value++ | key 必须全局唯一;状态在 SSR/CSR 间自动同步(通过 payload 序列化) |
| 跨组件共享 | 多个组件使用相同 key | 所有调用 useState('user') 的组件引用同一状态对象。 | ComponentA.vue 和 ComponentB.vue 中使用 useState('user') 获取同一引用 | 修改任一处,其他组件自动响应更新。 |
| 初始化函数 | init 仅在状态不存在时执行 | 避免重复初始化(尤其在 SSR 中)。 | useState('config', () => ({ theme: 'dark' })) | init 必须是纯函数;不可包含副作用或异步逻辑。 |
| 服务端持久化 | 状态在 SSR 期间生成,并序列化到 HTML payload | 客户端激活(hydration)时恢复状态,避免闪烁。 | export default defineEventHandler(() => { useState('serverTime', () => Date.now()) }) | 仅限可序列化数据(对象、数组、基本类型);函数、Symbol 会被忽略。 |
| 重置状态 | 直接赋值 null 或新值 | 清除或更新共享状态。 | useState('cart').value = [] | 无内置 reset() 方法;需手动管理初始值。 |
第七章:路由与导航控制
7.1 navigateTo:程序化导航(支持外部链接)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| navigateTo | navigateTo(to, options?) | 在组件、composable 或服务端逻辑中执行页面跳转。 | navigateTo('/dashboard') | 在服务端调用会触发 302 重定向;在客户端调用等效于 Vue Router 的 router.push() |
| 内部路由跳转 | 字符串路径或路由对象 | 导航至应用内页面。 | navigateTo({ path: '/user', query: { id: '123' } }) | 路径必须以 / 开头;支持 query、hash 等 Vue Router 选项。 |
| 外部链接跳转 | 完整 URL(含协议) | 跳转到站外地址(如 OAuth 登录页)。 | navigateTo('https://example.com/login', { external: true }) | 必须显式设置 external: true,否则会被视为无效内部路由。 |
| 替换当前历史记录 | options.replace: true | 使用 router.replace() 而非 push(),不留下返回记录。 | navigateTo('/success', { replace: true }) | 适用于登录后跳转首页,避免用户返回登录页。 |
| 服务端重定向状态码 | options.redirectCode | 自定义 HTTP 重定向状态码(默认 302)。 | navigateTo('/new-path', { redirectCode: 301 }) | 仅在服务端生效;301 有利于 SEO,302 为临时跳转。 |
| 错误处理 | 抛出错误中断导航 | 若导航被中间件阻止,可捕获错误。 | try { await navigateTo('/protected') } catch (e) { console.error('Navigation blocked') } | 实际开发中较少手动 try-catch;通常由中间件统一处理。 |
7.2 路由中间件(middleware/)
| 概念/类型 | 说明 | 文件位置与命名 | 注意事项 |
|---|---|---|---|
| 全局中间件 | 对所有路由生效 | middleware/global.global.ts(必须含 .global 后缀) | 按字母顺序执行;可用于日志、全局加载状态等。 |
| 页面级中间件 | 仅对特定页面生效 | middleware/auth.ts,并在页面中通过 definePageMeta({ middleware: 'auth' }) 引用 | 支持数组:middleware: ['auth', 'logger'] |
| 中间件函数签名 | export default defineNuxtRouteMiddleware((to, from) => { ... }) | 接收 to(目标路由)、from(来源路由)上下文 | 可返回 navigateTo()、abortNavigation() 或 undefined(继续) |
| 阻止导航 | return abortNavigation() 或抛出错误 | if (!isAuthenticated) { return navigateTo('/login') } | abortNavigation() 会取消跳转并保留当前页面;通常配合重定向使用 |
| 异步中间件 | 支持 await | const user = await fetchUser(); if (!user.roles.includes('admin')) { return navigateTo('/unauthorized') } | 需确保异步操作快速完成,避免阻塞首屏渲染 |
| 中间件执行时机 | SSR 时在服务端执行,CSR 时在客户端执行 | 同一份中间件代码在两端运行 | 避免使用浏览器专属 API(如 localStorage);敏感逻辑应在服务端验证 |
7.3 页面元信息(definePageMeta)
| 元信息字段 | 类型 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| layout | string | false | 指定页面使用的布局 | definePageMeta({ layout: 'dashboard' }) | 布局名对应 app/layouts/ 下的文件名(不含扩展名) |
| middleware | string | string[] | 关联一个或多个中间件 | definePageMeta({ middleware: ['auth', 'permission'] }) | 中间件按数组顺序执行;需提前在 middleware/ 中定义 |
| name | string | 为路由指定唯一名称(用于 router.push({ name: '...' })) | definePageMeta({ name: 'UserProfile' }) | 默认路由名由路径生成(如 /user/[id] → user-id) |
| alias | string | string[] | 设置路由别名 | definePageMeta({ alias: ['/profile'] }) | 访问 /profile 会渲染当前页面内容 |
| redirect | string | 访问此页面时自动重定向 | definePageMeta({ redirect: '/new-page' }) | 构建时静态重定向;适用于废弃页面迁移 |
| title / meta | 不直接支持 | 页面标题和 meta 标签应通过 <head> 或 useHead() 控制 | useHead({ title: 'My Page' }) | definePageMeta 不处理 SEO 相关元数据 |
7.4 路由守卫与权限控制
| 场景 | 实现方式 | 说明 | 注意事项 |
|---|---|---|---|
| 用户认证守卫 | 在 auth 中间件中检查登录状态 | const token = useCookie('auth_token'); if (!token.value && to.path !== '/login') { return navigateTo('/login') } | Cookie 在 SSR/CSR 均可读取;比 localStorage 更安全(可设 HttpOnly) |
| 角色权限控制 | 结合用户角色与页面所需权限 | const requiredRole = to.meta.requiredRole; if (user.role !== requiredRole) { return navigateTo('/forbidden') } | 权限信息应从服务端获取(如 /api/me),避免客户端伪造 |
| 动态权限路由 | 在中间件中调用 API 获取用户权限 | const { data: permissions } = await useAsyncData('perms', () => $fetch('/api/permissions')); if (!permissions.value.includes(to.name)) { return abortNavigation() } | 增加请求延迟;建议缓存权限数据(如 useState('userPermissions')) |
| 游客/会员分流 | 根据认证状态重定向不同首页 | if (to.path === '/') { if (isLoggedIn) navigateTo('/dashboard'); else navigateTo('/welcome') } | 避免无限重定向循环;确保目标页面不触发相同逻辑 |
| 全局错误拦截 | 在中间件末尾添加兜底处理 | if (error) { return navigateTo('/error') } | 需配合 onErrorCaptured 或 Nitro 错误处理机制 |
第三部分:全栈能力与服务端开发
第八章:Nitro 服务端引擎入门
8.1 server/ 目录结构与执行上下文
| 概念/目录 | 说明 | 注意事项 |
|---|---|---|
| server/ 根目录 | 所有服务端逻辑的入口目录,由 Nitro 引擎在构建时自动扫描并打包为独立服务。 | 此目录代码仅在服务端运行,不可引用 Vue 组件、浏览器 API(如 window、localStorage)。 |
| 执行上下文(Context) | 每个 API 路由或中间件函数接收一个 event 对象(类型为 H3Event),包含请求/响应信息。 | 通过 event.context 可传递自定义数据(如用户身份)到下游中间件或 API。 |
| 环境隔离 | 开发环境使用本地 Node.js 服务器;生产环境可部署至 Serverless(Vercel、Netlify)、Docker 或传统 Node 服务器。 | 构建产物位于 .output/,包含独立可运行的服务端包。 |
| 自动热重载 | 修改 server/ 下文件后,开发服务器自动重启服务端逻辑(比前端 HMR 慢)。 | 避免在服务端引入大型依赖,以免延长重启时间。 |
| 类型支持 | Nitro 基于 H3(HTTP 框架),提供完整的 TypeScript 类型推导(如 getRequestURL, getCookie)。 | 推荐使用 defineEventHandler、defineRoute 等宏以获得最佳类型体验。 |
8.2 API 路由(server/api/)
路由命名规则:
| 文件命名规则 | 对应 HTTP 方法 | 路由路径示例 | 说明 | 注意事项 |
|---|---|---|---|---|
| hello.ts | GET | /api/hello | 默认处理 GET 请求 | 若需支持多方法,需显式判断 event.method |
| user.post.ts | POST | /api/user | 仅处理 POST 请求 | 文件名中的 .post 指定 HTTP 方法 |
| item/[id].ts | GET | /api/item/123 | 支持动态参数(通过 event.context.params.id 获取) | 参数名必须与方括号内一致 |
| upload.post.ts | POST | /api/upload | 可处理表单、JSON、文件上传 | 使用 readFormData()、readBody() 解析请求体 |
| *.ts(通配符) | 任意 | 匹配任意子路径 | 如 proxy/[…].ts → /api/proxy/any/path | 适用于反向代理、CMS 路由等场景 |
API 处理函数工具:
| 方法/工具 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| defineEventHandler | export default defineEventHandler((event) => { ... }) | 定义 API 处理函数,自动注入 event 上下文 | export default defineEventHandler((event) => { return { message: 'Hello from API' } }) | 返回值自动序列化为 JSON;支持 Promise |
| getRouterParams | getRouterParams(event) | 获取动态路由参数 | const { id } = getRouterParams(event) | 替代 event.context.params,类型更安全 |
| readBody | await readBody(event) | 读取 JSON 或表单请求体 | const body = await readBody(event) | 仅可读取一次;大文件建议用流式处理 |
| setResponseStatus | setResponseStatus(event, 404) | 设置 HTTP 状态码 | if (!user) { setResponseStatus(event, 404); return { error: 'Not found' } } | 默认状态码为 200;错误场景需显式设置 |
| useRuntimeConfig | useRuntimeConfig(event) | 在服务端读取私有配置(如数据库密码) | const config = useRuntimeConfig(event); const dbUrl = config.dbUrl | 私有配置不会暴露给客户端;仅 public 字段会同步到前端 |
8.3 服务器中间件(server/middleware/)
| 文件命名 | 执行顺序 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| logger.ts | 按字母顺序(a → z) | 全局日志记录、性能监控 | export default defineEventHandler((event) => { console.log('Request:', event.path) }) | 中间件无返回值时继续执行下一个;返回值将终止链并作为响应 |
| auth.ts | 在 logger.ts 之后(若命名靠后) | 身份验证、权限检查 | export default defineEventHandler((event) => { const token = getHeader(event, 'authorization'); if (!verifyToken(token)) { setResponseStatus(event, 401); return { error: 'Unauthorized' } } }) | 返回响应对象会中断后续中间件和 API 执行 |
| cors.ts | 通常命名靠前(如 00.cors.ts) | 设置 CORS 头 | export default defineEventHandler((event) => { setHeaders(event, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST' }) }) | 可通过前缀数字控制执行优先级 |
| 中间件链机制 | 串行执行 | 类似 Express/Koa 的中间件栈 | 所有中间件共享 event.context | 可在中间件中设置 event.context.user = user 供 API 使用 |
| 错误处理中间件 | 无特殊命名;靠逻辑判断 | 捕获上游抛出的错误 | export default defineEventHandler((event) => { try { ... } catch (e) { return { error: e.message } } }) | Nitro 不支持 next(err);需手动 try-catch 或使用 onError 钩子 |
8.4 服务器工具函数(server/utils/)
| 工具类型 | 存放位置 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 通用函数 | server/utils/hash.ts | 封装加密、格式化等逻辑 | export function sha256(str: string) { ... } | 可被 API、中间件、其他工具函数 import 使用 |
| 数据库连接 | server/utils/db.ts | 初始化并导出数据库客户端 | import { createClient } from 'redis'; const client = createClient(); await client.connect(); export default client | 避免在每次请求中新建连接;应复用全局实例 |
| 第三方 SDK 封装 | server/utils/qrcode.ts | 封装外部服务(如生成二维码) | import QRCode from 'qrcode'; export async function generateQR(text: string) { return await QRCode.toDataURL(text) } | 第三方库需支持 Node.js 环境(无 DOM 依赖) |
| 类型安全调用 | 在 API 中 import 使用 | 保持服务端逻辑模块化 | import { generateQR } from '~/server/utils/qrcode'; export default defineEventHandler(async (event) => { const url = getQuery(event).url as string; return await generateQR(url) }) | 路径别名 ~ 在服务端同样有效 |
| 热重载限制 | 修改工具函数后需重启服务端(因被缓存) | 无直接解决方案 | 开发时可将高频调试逻辑临时移至 API 文件内 | — |
第九章:前后端一体化开发实践
9.1 前端调用后端 API($fetch 与类型安全)
| 方法/机制 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| $fetch(Nuxt 封装) | const data = await $fetch('/api/user') | 在组件、composable 或服务端中统一调用 API,自动处理 baseURL、序列化等。 | const { data } = await useAsyncData('user', () => $fetch('/api/profile')) | 内部基于 ofetch;自动继承 runtimeConfig.app.baseURL |
| 类型安全响应 | 泛型指定返回结构 | 避免运行时类型错误,提升 IDE 体验。 | interface UserProfile { id: number; name: string }; const user = await $fetch<UserProfile>('/api/profile') | 推荐在 types/ 目录定义共享接口,前后端共用。 |
| 错误处理 | 捕获 FetchError | 处理网络错误、4xx/5xx 响应。 | try { await $fetch('/api/data') } catch (error) { if (error.statusCode === 401) navigateTo('/login') } | $fetch 抛出的错误包含 statusCode、statusMessage 等字段。 |
| 请求头自动注入 | 通过插件或拦截器 | 如自动携带认证 Token。 | export default defineNuxtPlugin((nuxtApp) => { nuxtApp.$fetch.onRequest(({ options }) => { const token = useCookie('auth_token').value; if (token) options.headers = { ...options.headers, Authorization: 'Bearer ' + token } }) }) | 插件需注册为 plugins/fetch.client.ts(仅客户端)或通用插件。 |
| 服务端调用优势 | 在 useAsyncData 中调用 | 自动走本地 Nitro 服务,避免跨域和公网请求。 | useAsyncData('list', () => $fetch('/api/items')) | 生产部署后,Nitro 会内联 API 调用(zero-overhead)。 |
9.2 文件上传与解析(如二维码工具案例)
| 场景 | 实现方式 | 说明 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 前端文件选择 | <input type="file"> + ref | 获取用户选择的文件对象。 | <template><input type="file" @change="handleFile" /></template><script setup>const handleFile = (e) => { const file = e.target.files[0]; ... }</script> | 支持多文件、accept 限制(如 accept=“image/*“) |
| 客户端上传 API | 使用 FormData + $fetch | 将文件作为 multipart/form-data 发送。 | const formData = new FormData(); formData.append('image', file); await $fetch('/api/upload', { method: 'POST', body: formData }) | 浏览器自动设置 Content-Type: multipart/form-data; boundary=… |
| 服务端接收文件 | readFormData() 解析 | 在 server/api/upload.post.ts 中读取上传内容。 | export default defineEventHandler(async (event) => { const formData = await readFormData(event); const file = formData.get('image') as File; ... }) | File 对象包含 name、size、type 和 arrayBuffer() 方法 |
| 二维码生成案例 | 服务端调用 qrcode 库 | 上传图片后返回二维码 URL。 | import QRCode from 'qrcode'; export async function generateQR(text: string) { return await QRCode.toDataURL(text) }; export default defineEventHandler(async (event) => { const { url } = await readBody(event); return { qr: await generateQR(url) } }) | 第三方库需支持 Node.js;避免在客户端使用(体积大) |
| 文件安全校验 | 服务端验证类型/大小 | 防止恶意文件上传。 | if (file.type !== 'image/png') throw createError({ statusCode: 400, message: 'Only PNG allowed' }); if (file.size > 5 * 1024 * 1024) throw createError({ statusCode: 400, message: 'File too large' }) | 客户端校验可被绕过;服务端必须二次验证 |
9.3 环境变量与私有配置(runtimeConfig)
| 配置类型 | 存放位置 | 可见性 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 公共配置(客户端可见) | nuxt.config.ts → runtimeConfig.public | 前端可通过 useRuntimeConfig().public.xxx 访问 | // nuxt.config.ts; runtimeConfig: { public: { apiBase: process.env.NUXT_PUBLIC_API_BASE } } | } |
| 私有配置(仅服务端) | nuxt.config.ts → runtimeConfig 根级 | 仅在 server/ 中通过 useRuntimeConfig(event) 访问 | // .env; DB_PASSWORD=secret123; // nuxt.config.ts; runtimeConfig: { dbPassword: process.env.DB_PASSWORD } | 敏感信息(密码、密钥)必须放在此处;不会暴露给前端 |
| 环境变量加载 | .env、.env.local 等 | 启动时自动注入 process.env | # .env.local; NUXT_PUBLIC_API_BASE=https://prod.api.com; DB_PASSWORD=prod_secret | .env.local 通常加入 .gitignore;不同环境使用不同文件 |
| 配置优先级 | 运行时 > 构建时 | NUXT_ 前缀变量可覆盖配置 | NUXT_DB_PASSWORD=override npm run dev | 适用于 CI/CD 动态注入密钥 |
| 类型安全访问 | 自动生成类型 | VS Code 提示可用配置项 | const config = useRuntimeConfig(); console.log(config.public.apiBase) | 若类型未更新,重启 dev server 刷新 .nuxt/types/ |
9.4 错误处理与日志记录
| 机制 | 用途 | 实现方式 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 服务端错误抛出 | 统一错误格式 | 使用 createError() | if (!user) { throw createError({ statusCode: 404, message: 'User not found' }) } | 自动设置状态码、序列化错误信息;客户端可通过 error.data 获取 |
| 客户端错误捕获 | 在 useAsyncData/useFetch 中 | 通过 error 响应式引用 | const { error } = useFetch('/api/data'); if (error.value) { showError(error.value) } | showError() 会触发全局错误页面(error.vue) |
| 全局错误页面 | app/error.vue | 自定义错误展示 UI | <template><div>Error {{ error?.statusCode }}: {{ error?.message }}</div></template><script setup>defineProps(['error'])</script> | 接收 error prop(类型 NuxtError);支持重试按钮 |
| 服务端日志记录 | 在中间件或 API 中 | console.log 或集成日志库 | export default defineEventHandler((event) => { console.log('[' + new Date().toISOString() + '] ' + event.method + ' ' + event.path) }) | 生产环境建议使用 pino、winston 等结构化日志库 |
| 客户端日志上报 | 错误边界 + 上报 API | 捕获未处理异常 | onErrorCaptured((err) => { $fetch('/api/log-error', { method: 'POST', body: { message: err.message } }); return false }) | 避免无限上报;添加采样率控制 |
第四部分:工程化与部署
第十章:TypeScript 与类型安全
10.1 Nuxt 4 的 TypeScript 项目结构
| 文件/目录 | 作用 | 说明 | 注意事项 |
|---|---|---|---|
| nuxt.config.ts | 项目主配置文件 | 使用 defineNuxtConfig() 提供类型安全的配置选项。 | 必须以 .ts 扩展名创建;Nuxt 自动识别并启用 TS 支持。 |
| tsconfig.json | 根级 TypeScript 配置 | 由 Nuxt 自动生成(若不存在),包含基础编译选项。 | 开发者通常无需修改;高级需求可扩展(见 10.3 节)。 |
| types/(推荐) | 共享类型定义目录 | 存放接口、枚举等(如 User.ts、ApiResponse.ts)。 | 可被前端组件和服务端 API 共同引用,实现前后端类型对齐。 |
| app/**/*.vue | Vue 单文件组件 | 支持 <script setup lang="ts"> 编写组合式 API。 | 组件 props 应使用 defineProps<{ ... }>() 启用严格类型检查。 |
| server/**/*.ts | 服务端逻辑 | 包括 API、中间件、工具函数,运行于 Node.js 环境。 | 可使用 Node.js 内置模块(如 fs、path),但不可引用浏览器 API。 |
| .nuxt/ | 自动生成目录 | 包含类型声明(types/)、构建产物、路由映射等。 | 不应提交到 Git;类型文件在此目录下实时更新。 |
| composables/ | 组合函数 | 自动导入的 TS 函数(如 useAuth.ts),返回响应式状态或方法。 | 必须导出默认函数;命名以 use 开头以符合约定。 |
10.2 自动类型生成(组件、API、composables)
| 生成目标 | 触发时机 | 生成位置 | 说明 | 注意事项 |
|---|---|---|---|---|
| 全局组件类型 | 开发服务器启动或组件变更 | .nuxt/types/components.d.ts | 为 app/components/ 下所有组件生成全局类型声明。 | 组件必须使用 defineProps 定义 props,否则类型为 any。 |
| Composables 类型 | 文件保存时 | .nuxt/types/composables.d.ts | 为 composables/ 下函数生成自动导入类型(如 useCounter(): { count: Ref<number> })。 | 函数必须 export default;参数和返回值需显式标注类型。 |
| API 路由类型 | 构建或开发启动 | .nuxt/types/nitro.d.ts | 为 server/api/ 生成请求/响应类型的 TypeScript 接口(实验性,需配合插件)。 | 目前 Nuxt 4 不自动推断 API 类型;推荐手动定义共享接口。 |
| 页面元信息类型 | 使用 definePageMeta 时 | 内联类型推导 | VS Code 自动提示可用布局、中间件名称。 | 布局/中间件文件变更后需重启 dev server 刷新类型。 |
| 运行时配置类型 | nuxt.config.ts 修改 | .nuxt/types/runtime-config.d.ts | 为 runtimeConfig 和 publicRuntimeConfig 生成类型。 | 确保在 nuxt.config.ts 中显式声明所有配置字段。 |
| 替代方案:手动共享 API 类型 | 开发者维护 | types/api.ts | // types/api.ts; export interface UserProfile { id: number; name: string }; // server/api/profile.get.ts; import { UserProfile } from '~/types/api'; export default defineEventHandler((): UserProfile => ({ id: 1, name: 'John' })); // pages/profile.vue; import { UserProfile } from '~/types/api'; const { data } = useFetch<UserProfile>('/api/profile') | 当前最可靠的方式;确保前后端使用同一接口定义。 |
10.3 多上下文 tsconfig 配置(应用 vs 服务端)
| 上下文 | 配置文件 | 用途 | 关键配置项 | 注意事项 |
|---|---|---|---|---|
| 应用(客户端 + SSR 渲染) | tsconfig.app.json(自动生成) | 编译 Vue 组件、composables、页面等 | "module": "ESNext", "target": "ES2020", "lib": ["ESNext", "DOM"], "include": ["app", "composables", "types"] | 包含 DOM 库(因 CSR 需要);不包含 Node.js 模块 |
| 服务端(Nitro 引擎) | tsconfig.server.json(自动生成) | 编译 server/ 目录下的代码 | "module": "NodeNext", "target": "ES2022", "lib": ["ES2022"], "types": ["node"], "include": ["server", "types"] | 包含 @types/node;无 DOM 库(避免误用浏览器 API) |
| 根配置(共享基础) | tsconfig.json | 定义公共编译选项 | "strict": true, "esModuleInterop": true, "skipLibCheck": true, "paths": { "~/*": ["./*"] } | 启用 strict 模式提升类型安全;paths 支持 ~ 别名 |
| 自定义扩展 | 手动创建/修改 | 覆盖默认配置 | "jsx": "preserve", "baseUrl": ".", "types": ["vite/client"] | 修改后需重启 dev server;避免破坏 Nuxt 自动生成的子配置 |
| 类型隔离验证 | 开发时 | 分别校验两端代码 | 客户端代码误用 fs 会报错;服务端代码使用 window 会报错 | 多上下文配置有效防止跨环境 API 误用,提升代码健壮性 |
第十一章:构建、部署与性能优化
11.1 构建命令与输出分析
| 操作/文件 | 说明 | 命令或路径 | 注意事项 |
|---|---|---|---|
| 构建命令 | 生成生产环境可部署产物 | npm run build(等价于 nuxi build) | 首次构建较慢(需编译依赖);后续增量构建更快 |
| 构建输出目录 | 包含前端资源和服务端包 | .output/ | .output/public/:静态资源(CSS、JS、图片);.output/server/:Nitro 服务端 bundle(Node.js 可执行) |
| 客户端资源 | 由 Vite 生成的优化资产 | .output/public/_nuxt/ | 文件名含哈希(如 entry.abc123.js),支持长期缓存 |
| 服务端入口 | Nitro 生成的统一入口 | .output/server/index.mjs | 可直接通过 node .output/server/index.mjs 启动服务 |
| 预渲染页面(SSG) | 若启用 SSG,生成 HTML 文件 | .output/public/ 下对应路径(如 about/index.html) | 仅对 prerender 路由生效(见 11.3 节) |
| 构建分析工具 | 可视化 bundle 体积 | 安装 @nuxt/analyze 模块:modules: ['@nuxt/analyze'] | 用于识别过大依赖(如 lodash 全量引入);构建后自动打开分析报告 |
11.2 部署目标(Vercel、Netlify、Node.js 服务器)
| 部署平台 | 配置方式 | 优势 | 注意事项 |
|---|---|---|---|
| Vercel | 无需配置;推送 Git 仓库自动部署 | 原生支持 Nuxt 4;自动识别 SSR/SSG;免费 Serverless 函数 | 确保项目根目录有 package.json;Vercel 会自动运行 build |
| Netlify | 创建 netlify.toml 或使用 UI 配置 | 支持 Edge Functions(实验性 Nitro 集成) | 默认部署为静态站点;若需 SSR,需启用 Netlify Functions 并配置 functions 目录 |
| Node.js 服务器 | 手动部署 .output/ 目录 | 完全控制运行环境(如 PM2、Docker) | 启动命令:node .output/server/index.mjs;需开放端口(默认 3000) |
| Docker | 编写 Dockerfile | 便于容器化部署(K8s、云厂商) | 示例:FROM node:20-alpine; COPY .output ./app; WORKDIR /app; EXPOSE 3000; CMD ["node", "server/index.mjs"] |
| 静态托管(纯 SSG) | 仅部署 .output/public/ | 适用于无服务端逻辑的博客、文档站 | 通过 nuxi generate 生成完整静态站点(所有路由预渲染) |
11.3 静态站点生成(SSG)与混合渲染
| 渲染模式 | 配置方式 | 适用场景 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 纯 SSG | 在 nuxt.config.ts 中设置 | 内容不变或低频更新的站点(如文档、营销页) | export default defineNuxtConfig({ ssr: true, nitro: { prerender: { routes: ['/', '/about', '/posts/1'] } } }) | 未列入 prerender.routes 的动态路由仍为 SSR |
| 混合渲染(Hybrid) | 使用 routeRules 按路由指定 | 同一项目中部分页面 SSG、部分 SSR/CSR | routeRules: { '/blog/**': { ssr: true }, '/docs/**': { prerender: true }, '/dashboard/**': { ssr: false } } | 构建时自动分类处理;部署后各路由独立响应 |
| 客户端渲染(CSR) | ssr: false 或 routeRules 设置 | 高交互性应用(如管理后台),SEO 不敏感 | definePageMeta({ ssr: false }) | 首屏加载较慢;需确保关键内容不依赖 CSR |
| 增量静态再生(ISR) | Nitro 实验性支持 | SSG 页面在访问时后台更新(类似 Next.js ISR) | routeRules: { '/news/**': { isr: true } } | 需部署平台支持缓存刷新(如 Vercel);Nuxt 4 尚未完全稳定 |
| 预渲染动态路由 | 在 nitro.prerender.crawlLinks 或手动指定 | 从首页链接自动发现并预渲染 | nitro: { prerender: { crawlLinks: true, routes: ['/'] } } | 适用于博客文章列表页自动抓取子页面 |
11.4 SEO 优化(meta 标签、多语言结构)
| 优化项 | 实现方式 | 说明 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 动态 meta 标签 | 使用 useHead() | 在组件中设置 title、description、og:image 等 | const { data } = useAsyncData('post', fetchPost); useHead({ title: data.value?.title, meta: [{ name: 'description', content: data.value?.excerpt }] }) | useHead 支持响应式数据;SSR 时正确注入到 HTML <head> |
| 结构化数据(JSON-LD) | 在 useHead 中添加 script | 提升搜索引擎对内容的理解 | useHead({ script: [{ type: 'application/ld+json', children: JSON.stringify({ "@context": "https://schema.org", "@type": "Article", "headline": data.value.title }) }] }) | 避免内联大段 JSON;确保符合 schema.org 规范 |
| 多语言 SEO | 设置 hreflang 和 canonical | 告知搜索引擎多语言版本关系 | useHead({ link: [{ rel: 'canonical', href: 'https://site.com' + route.path }, { rel: 'alternate', hreflang: 'en', href: 'https://site.com/en/about' }, { rel: 'alternate', hreflang: 'zh', href: 'https://site.com/zh/about' }] }) | hreflang 必须成对出现;包含 x-default 默认版本更佳 |
| Open Graph / Twitter Cards | 在 meta 中定义 | 优化社交分享卡片显示 | useHead({ meta: [{ property: 'og:title', content: title }, { property: 'og:image', content: imageUrl }, { name: 'twitter:card', content: 'summary_large_image' }] }) | 图片建议尺寸 ≥ 1200×630px;使用绝对 URL |
| robots.txt 与 sitemap | 放入 public/ 目录 | 控制爬虫行为和索引范围 | # public/robots.txt; User-agent: *; Allow: /; Sitemap: https://site.com/sitemap.xml | 动态站点可使用 @nuxtjs/sitemap 模块自动生成 sitemap |
第十二章:模块与插件系统
12.1 官方模块集成(如 @nuxtjs/tailwindcss)
| 模块名称 | 安装命令 | 配置方式 | 功能说明 | 注意事项 |
|---|---|---|---|---|
| @nuxtjs/tailwindcss | npm install -D @nuxtjs/tailwindcss | modules: ['@nuxtjs/tailwindcss'] | 自动配置 Tailwind CSS,支持 JIT 模式、PurgeCSS、主题定制。 | 无需手动创建 tailwind.config.js;默认启用 content 扫描 app/ 目录。 |
| @nuxtjs/eslint-module | npm install -D @nuxtjs/eslint-module | modules: ['@nuxtjs/eslint-module'] | 在开发服务器中集成 ESLint 实时检查。 | 需提前安装 eslint 及配置文件(如 .eslintrc)。 |
| @nuxt/image | npm install @nuxt/image | modules: ['@nuxt/image'] | 提供 <NuxtImg> 组件,支持自动优化、懒加载、CDN 集成。 | 默认使用内置 IPX 引擎;生产环境建议配置外部 provider(如 Cloudinary)。 |
| @nuxt/devtools | 创建项目时可选,或手动安装 | modules: ['@nuxt/devtools'] | 启用可视化 DevTools(组件树、路由、状态监控)。 | 仅在开发环境加载;生产构建自动剥离。 |
| @nuxtjs/color-mode | npm install @nuxtjs/color-mode | modules: ['@nuxtjs/color-mode'] | 支持深色/浅色主题切换,自动持久化用户偏好。 | 提供 $colorMode.preference 响应式状态和 class 注入(如 dark)。 |
| 模块配置扩展 | 在 nuxt.config.ts 中传入选项 | modules: [['@nuxtjs/tailwindcss', { exposeConfig: true }]] | 大多数模块支持传入配置对象以覆盖默认行为。 | 查阅模块文档获取可用选项;类型提示由模块提供。 |
12.2 自定义插件开发(生命周期钩子)
| 插件类型 | 文件位置 | 注册方式 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|---|
| 通用插件 | plugins/myPlugin.ts | 自动注册(无需手动引入) | 扩展 Vue 应用、注入全局属性、监听生命周期。 | export default defineNuxtPlugin((nuxtApp) => { nuxtApp.provide('hello', (name: string) => 'Hello ' + name + '!') }) | 插件名必须以 .client、.server 或无后缀结尾;无后缀表示两端运行。 |
| 客户端插件 | plugins/ga.client.ts | 仅在客户端执行 | 集成浏览器专属库(如 Google Analytics、埋点 SDK)。 | export default defineNuxtPlugin(() => { if (process.client) { initGA() } }) | 文件名必须含 .client 后缀,否则会在 SSR 中报错。 |
| 服务端插件 | plugins/db.server.ts | 仅在服务端执行 | 初始化数据库连接、缓存客户端等。 | export default defineNuxtPlugin(async () => { const db = await createConnection(); return { provide: { db } } }) | 文件名必须含 .server 后缀;返回值可通过 useNuxtApp().$db 访问。 |
| 注入全局方法 | 在插件中使用 provide | 组件中通过 $xxx 调用 | 类似 Vue 2 的 Vue.prototype | nuxtApp.provide('formatDate', (d) => new Date(d).toLocaleDateString()); const formatted = useNuxtApp().$formatDate(date) | 推荐优先使用 composables/;插件适用于跨上下文共享逻辑。 |
| 生命周期钩子 | 在插件或 nuxt.config.ts 中 | 监听构建、渲染等阶段事件 | 如 builder:prepared、vue:setup | hooks: { 'pages:extend'(pages) { pages.push({ name: 'custom', path: '/custom', file: '~/pages/custom.vue' }) } } | 钩子在 plugins/ 中通过 nuxtApp.hooks.hook() 使用;配置文件中通过 hooks 对象注册。 |
12.3 第三方库集成最佳实践
| 场景 | 推荐方式 | 说明 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 纯客户端库(如地图、图表) | 使用 <ClientOnly> + 动态导入 | 避免 SSR 报错(因依赖 window) | <template><ClientOnly><MyChart v-if="loaded" /><template #fallback>Loading...</template></ClientOnly></template><script setup>const loaded = ref(false); onMounted(async () => { await import('chart.js'); loaded.value = true })</script> | 不要直接在顶层 import;防止服务端执行。 |
| 需初始化的库(如 i18n) | 通过插件注入 | 统一管理实例生命周期 | export default defineNuxtPlugin(({ vueApp }) => { const i18n = createI18n({ ... }); vueApp.use(i18n) }) | 确保库支持 Vue 3;避免重复安装。 |
| 大型库按需引入 | 使用插件 + tree-shaking | 减少客户端包体积 | import { debounce } from 'lodash-es'; export default defineNuxtPlugin((nuxtApp) => { nuxtApp.provide('debounce', debounce) }) | 优先选择 ES 模块版本(如 lodash-es);避免全量引入。 |
| CSS 库(如 Bootstrap) | 在 nuxt.config.ts 中全局引入 | 确保样式在所有页面生效 | css: ['bootstrap/dist/css/bootstrap.min.css'] | 若仅局部使用,应在组件 <style> 中 @import。 |
| TypeScript 类型支持 | 安装 @types/xxx | 获得 IDE 提示和编译检查 | npm install -D @types/lodash | 检查库是否自带类型(查看 package.json 的 types 字段)。 |
| 避免污染全局作用域 | 封装为 composable 或插件 | 保持代码可测试性和模块化 | export const useMap = () => { const map = shallowRef(null); onMounted(() => { map.value = new MapLib() }); return { map } } | 优于直接挂载到 window 或 Vue 原型。 |