Nuxt Lifecycle
本章旨在概述框架的不同部分、它们的执行顺序以及它们如何协同工作。
服务器端
在服务器端,对于应用程序的每个初始请求,都会执行以下步骤:
步骤 1:设置 Nitro 服务器和 Nitro 插件(一次)
Nuxt 由 Nitro 提供支持,这是一个现代服务器引擎。
当 Nitro 启动时,它会初始化并执行 /server/plugins 目录下的插件。这些插件可以:
- 捕获并处理应用程序范围内的错误。
- 注册在 Nitro 关闭时执行的钩子。
- 注册请求生命周期事件的钩子,例如修改响应。
await 约束,需通过 event.waitUntil() 确保关键异步操作完成。)步骤 2:Nitro 服务器中间件
初始化 Nitro 服务器后,将为 每个请求 执行 server/middleware/ 下的中间件。中间件可用于身份验证、日志记录或请求转换等任务。
步骤 3:初始化 Nuxt 并执行 Nuxt 应用程序插件
首先创建 Vue 和 Nuxt 实例。之后,Nuxt 执行其服务器插件。这包括:
- 内置插件,例如 Vue Router 和
unhead。 - 位于
plugins/目录中的自定义插件,包括没有后缀的插件(例如,myPlugin.ts)和带有.server后缀的插件(例如,myServerPlugin.server.ts)。 - 插件按特定顺序执行,并且可能相互依赖。有关更多详细信息,包括执行顺序和平行性,请参阅 插件文档。
app:created 钩子,该钩子可用于执行额外的逻辑。步骤 4:路由验证
在初始化插件和执行中间件之前,如果 definePageMeta 函数中定义了 validate 方法,Nuxt 将调用该方法。validate 方法可以是同步的也可以是异步的,通常用于验证动态路由参数。
- 如果参数有效,
validate函数应返回true。 - 如果验证失败,它应返回
false或包含statusCode和/或statusMessage的对象以终止请求。 - 有关更多信息,请参阅 路由验证文档。
步骤 5:执行 Nuxt 应用程序中间件
中间件允许您在导航到特定路由之前运行代码。它通常用于身份验证、重定向或日志记录等任务。
在 Nuxt 中,有三种类型的中间件:
- 全局路由中间件
- 命名路由中间件
- 匿名(或内联)路由中间件
Nuxt 会在首次进入应用程序以及每次路由导航之前自动执行全局中间件。命名和匿名中间件仅在相应页面组件中定义的页面(路由)元数据的 middleware 属性中指定的路由上执行。
有关每种类型的详细信息和示例,请参阅 中间件文档。
服务器上的任何重定向都将导致向浏览器发送 Location: 标头;然后浏览器会向这个新位置发出新的请求。发生这种情况时,除非状态保存在 cookie 中,否则所有应用程序状态都将重置。
步骤 6:设置页面和组件
在此步骤中,Nuxt 初始化页面及其组件,并使用 useFetch 和 useAsyncData 获取任何所需的数据。由于服务器上没有动态更新且没有 DOM 操作发生,因此在 SSR 期间 不 执行 Vue 生命周期钩子(如 onBeforeMount、onMounted 和后续钩子)。
您应该避免在 <script setup> 的根作用域中编写产生需要清理的副作用的代码。此类副作用的一个示例是使用 setInterval 设置定时器。在仅客户端的代码中,我们可能会设置一个定时器,然后在 onBeforeUnmount 或 onUnmounted 中将其清除。但是,由于在服务器端 SSR 期间永远不会调用卸载钩子,因此定时器将永远存在服务器中。为避免这种情况,请将您的副作用代码移到 onMounted 中。这样仅在客户端上执行。
<script setup> 根作用域代码会在服务器端和客户端都执行,但两者行为有显著差异。以下是关键机制和应对策略:
执行机制详解:
- 双端执行流程
- 服务器端(SSR)
组件代码完整执行一次,生成静态 HTML 发送给浏览器。<script setup> // 服务器执行 console.log('根作用域代码 - 服务器执行') // 输出在 Node.js 终端 </script> - 客户端(CSR)
浏览器接收到 HTML 后,重新执行<script setup>代码,激活交互性(hydration):<script setup> // 客户端执行 console.log('根作用域代码 - 客户端执行') // 输出在浏览器控制台 </script>
- 潜在风险场景
| 代码类型 | 风险 | 案例 |
|---|---|---|
| 全局副作用操作 | 服务器创建的资源(定时器、监听器)无法销毁 → 内存泄漏 | setInterval(() => {...}, 1000) |
| 浏览器 API 访问 | 服务器无 window/document → 运行时崩溃 | const width = window.innerWidth |
| 敏感逻辑暴露 | 服务器日志可能泄露密钥或内部逻辑 → 安全风险 | console.log(process.env.API_KEY) |
正确编码模式:
- 副作用操作隔离到生命周期钩子
<script setup> import { onMounted, onUnmounted } from 'vue' // ✅ 安全:定时器仅在客户端设置和清理 let timerId onMounted(() => { timerId = setInterval(updateData, 5000) }) onUnmounted(() => { clearInterval(timerId) }) </script> - 环境检测执行分支
<script setup> // ✅ 按环境分流逻辑 if (process.client) { // 仅客户端逻辑(如访问 localStorage) const token = localStorage.getItem('token') } else if (process.server) { // 仅服务器逻辑(如请求后端 API) const { data } = await useFetch('/api/init') } </script> - 数据获取使用 Nuxt 专属 API
<script setup> // ✅ Nuxt 优化:避免重复请求 const { data } = await useAsyncData('key', () => $fetch('/api/data')) </script>
useAsyncData/useFetch 自动处理 SSR 数据转发,服务器结果注入客户端 payload,避免二次请求。必须避免的根作用域操作:
<!-- ❌ 危险示例 -->
<script setup>
// 1. 未隔离的定时器 → 服务器内存泄漏
setInterval(() => { ... }, 1000)
// 2. 直接访问浏览器 API → 服务器崩溃
const userAgent = navigator.userAgent
// 3. 客户端专属初始化 → 服务器报错
localStorage.setItem('key', 'value')
</script>
执行位置对比表
| 代码位置 | 服务器执行 | 客户端执行 | 推荐场景 |
|---|---|---|---|
<script setup> 根作用域 | ✅ | ✅ | 纯逻辑计算(无副作用) |
onMounted 钩子 | ❌ | ✅ | DOM 操作/定时器/浏览器 API |
onServerPrefetch 钩子 | ✅ | ❌ | 服务器专属数据预取 |
useAsyncData | ✅ | ❌注 | 跨端数据获取(自动水合) |
注:客户端仅接收注入数据,不重复执行请求逻辑
总结
- 双端执行:Nuxt SSR 下
<script setup>根作用域代码服务器与客户端均会执行。 - 规避风险:
- 副作用操作(定时器、事件监听)必须移至
onMounted+onUnmounted。 - 浏览器 API 调用需用
process.client防护。
- 副作用操作(定时器、事件监听)必须移至
- 数据获取:优先使用
useFetch/useAsyncData,自动处理水合与重复请求。
步骤 7:渲染并生成 HTML 输出
在所有组件初始化并获取数据后,Nuxt 将组件与 unhead 中的设置相结合,生成完整的 HTML 文档。此 HTML 以及相关数据将发送回客户端以完成 SSR 过程。
app:rendered 钩子。render:html 钩子。此钩子允许您操作生成的 HTML,例如注入额外的脚本或修改 meta 标签。SSR 请求生命周期(页面级请求):
- 初始化阶段
- Nitro 服务器启动:仅应用启动时初始化一次,加载全局插件及中间件并执行(位于
/server/middleware/的中间件在每个请求到达 Nitro 时优先执行。)。 - 路由匹配:根据请求路径匹配对应的页面组件(如
pages/product/[id].vue)。
- Nitro 服务器启动:仅应用启动时初始化一次,加载全局插件及中间件并执行(位于
- 中间件与验证
- 全局中间件 → 布局中间件 → 页面中间件:按顺序执行,可修改请求或重定向。
- 路由验证:通过
definePageMeta中的validate方法校验参数(如用户权限)。
- 数据获取与渲染
- 服务端数据预取:调用
useFetch或useAsyncData在服务端获取数据,结果注入HTML。 - HTML 生成:Vue 组件在服务端渲染为完整 HTML,包含初始数据。
- 服务端数据预取:调用
- 客户端激活(Hydration)
- 复用 DOM:浏览器接收 HTML 后,Vue 将静态 DOM 绑定事件及响应式状态,转为交互式应用。
API 请求生命周期(数据级请求):
无论部署环境如何,单个请求的处理均复用已初始化的 Nitro 资源:
- 中间件执行:
- 每个请求触发
/server/middleware/中的中间件(如身份验证、日志)。 - 中间件按文件名排序执行(例如 1.auth.ts → 2.logger.ts)。
- 每个请求触发
- 路由匹配与处理:
- 根据 URL 匹配
/server/api/或/server/routes/中的事件处理器(Event Handlers)。 - 动态参数(如
[id].ts)通过getRouterParam提取。
- 根据 URL 匹配
- 数据注入与响应:
- 通过
render:response钩子修改响应头或内容(例如注入缓存标头)。
- 通过
客户端(浏览器)
无论您选择哪种 Nuxt 模式,生命周期的这一部分都完全在浏览器中执行。
步骤 1:初始化 Nuxt 并执行 Nuxt 应用程序插件
此步骤类似于服务器端执行,包括内置插件和自定义插件。
plugins/ 目录中的自定义插件(例如,没有后缀的 myPlugin.ts 和带有 .client 后缀的 myClientPlugin.client.ts)在客户端执行。
app:created 钩子,该钩子可用于执行额外的逻辑。步骤 2:路由验证
此步骤与服务器端执行相同,如果 definePageMeta 函数中定义了 validate 方法,则包括该方法。
步骤 3:执行 Nuxt 应用程序中间件
Nuxt 中间件在服务器和客户端上都运行。如果您希望某些代码在特定环境中运行,请考虑使用 import.meta.client(用于客户端)和 import.meta.server(用于服务器)进行拆分。
步骤 4:挂载 Vue 应用程序和水合(hydration)
调用 app.mount('#__nuxt') 将 Vue 应用程序挂载到 DOM。如果应用程序使用 SSR 或 SSG 模式,Vue 会执行水合步骤以使客户端应用程序具有交互性。在水合期间,Vue 会重新创建应用程序(在客户端创建一个全新的 Vue 应用实例)(不包括 服务器组件),将每个组件与其对应的 DOM 节点匹配,并附加 DOM 事件监听器。
- 仅存在于服务器端执行
- 结果被序列化并嵌入 HTML 中
- 客户端仅使用生成的静态内容
.server.vue 。客户端组件的后缀是 .client.vue 。为确保正确的水合,重要的是保持服务器和客户端数据的一致性。对于 API 请求,建议使用 useAsyncData、useFetch 或其他 SSR 友好的组合式函数。这些方法确保在服务器端获取的数据在水合期间被重用,避免重复请求。任何新请求都应仅在水合后触发,以防止水合错误。
app:beforeMount 钩子。app:mounted 钩子。步骤 5:Vue 生命周期
与服务器端不同,浏览器执行完整的 Vue 生命周期。
Nuxt 生命周期
Nuxt 生命周期是指整个应用从启动到运行的完整过程,主要分为 构建时 和 运行时 两个阶段。
构建时生命周期
构建时生命周期通过一系列钩子函数来管理,这些钩子定义在 hooks.ts:84-177 。主要包括:
modules:before和modules:done: 模块安装前后app:resolve: 应用实例解析完成app:templates: 模板生成阶段build:before和build:done: 构建前后
这些钩子可以在 nuxt.config.ts 中使用,如 2.hooks.md:14-22 。
运行时生命周期
运行时生命周期钩子定义在 nuxt.ts:36-58 ,包括:
app:created: Vue 应用实例创建app:beforeMount和app:mounted: 应用挂载前后app:error: 错误处理page:start和page:finish: 页面渲染开始和完成
这些钩子可以在插件中使用,如 use-nuxt-app.md:55-66 。