Lzh on GitHub

Nuxt Lifecycle

了解 Nuxt 应用程序的生命周期可以帮助您更深入地了解框架的运作方式,尤其是在服务器端和客户端渲染方面。

本章旨在概述框架的不同部分、它们的执行顺序以及它们如何协同工作。

服务器端

在服务器端,对于应用程序的每个初始请求,都会执行以下步骤:

步骤 1:设置 Nitro 服务器和 Nitro 插件(一次)

Nuxt 由 Nitro 提供支持,这是一个现代服务器引擎。

当 Nitro 启动时,它会初始化并执行 /server/plugins 目录下的插件。这些插件可以:

  • 捕获并处理应用程序范围内的错误。
  • 注册在 Nitro 关闭时执行的钩子。
  • 注册请求生命周期事件的钩子,例如修改响应。
虽然插件本身只注册一次,但其注册的请求级钩子会在每个请求中执行。
Nitro 插件在服务器启动时仅执行一次。在无服务器环境中,服务器在每个传入请求时启动,Nitro 插件也随之启动。但是,它们不会被等待。(无服务器环境中:Nitro 插件随请求初始化但不受 await 约束,需通过 event.waitUntil() 确保关键异步操作完成。)
Read more in Docs > Guide > Directory Structure > Server#server Plugins.

步骤 2:Nitro 服务器中间件

初始化 Nitro 服务器后,将为 每个请求 执行 server/middleware/ 下的中间件。中间件可用于身份验证、日志记录或请求转换等任务。

从中间件返回值将终止请求并将返回的值作为响应发送。通常应避免此行为,以确保正确处理请求!
Read more in Docs > Guide > Directory Structure > Server#server Middleware.

步骤 3:初始化 Nuxt 并执行 Nuxt 应用程序插件

首先创建 Vue 和 Nuxt 实例。之后,Nuxt 执行其服务器插件。这包括:

  • 内置插件,例如 Vue Router 和 unhead
  • 位于 plugins/ 目录中的自定义插件,包括没有后缀的插件(例如,myPlugin.ts)和带有 .server 后缀的插件(例如,myServerPlugin.server.ts)。
  • 插件按特定顺序执行,并且可能相互依赖。有关更多详细信息,包括执行顺序和平行性,请参阅 插件文档
在此步骤之后,Nuxt 调用 app:created 钩子,该钩子可用于执行额外的逻辑。
Read more in Docs > Guide > Directory Structure > Plugins.

步骤 4:路由验证

在初始化插件和执行中间件之前,如果 definePageMeta 函数中定义了 validate 方法,Nuxt 将调用该方法。validate 方法可以是同步的也可以是异步的,通常用于验证动态路由参数。

  • 如果参数有效,validate 函数应返回 true
  • 如果验证失败,它应返回 false 或包含 statusCode 和/或 statusMessage 的对象以终止请求。
  • 有关更多信息,请参阅 路由验证文档
Read more in Docs > Getting Started > Routing#route Validation.

步骤 5:执行 Nuxt 应用程序中间件

中间件允许您在导航到特定路由之前运行代码。它通常用于身份验证、重定向或日志记录等任务。

在 Nuxt 中,有三种类型的中间件:

  • 全局路由中间件
  • 命名路由中间件
  • 匿名(或内联)路由中间件

Nuxt 会在首次进入应用程序以及每次路由导航之前自动执行全局中间件。命名和匿名中间件仅在相应页面组件中定义的页面(路由)元数据的 middleware 属性中指定的路由上执行。

有关每种类型的详细信息和示例,请参阅 中间件文档

服务器上的任何重定向都将导致向浏览器发送 Location: 标头;然后浏览器会向这个新位置发出新的请求。发生这种情况时,除非状态保存在 cookie 中,否则所有应用程序状态都将重置。

Read more in Docs > Guide > Directory Structure > Middleware.

步骤 6:设置页面和组件

在此步骤中,Nuxt 初始化页面及其组件,并使用 useFetchuseAsyncData 获取任何所需的数据。由于服务器上没有动态更新且没有 DOM 操作发生,因此在 SSR 期间 执行 Vue 生命周期钩子(如 onBeforeMountonMounted 和后续钩子)。

您应该避免在 <script setup> 的根作用域中编写产生需要清理的副作用的代码。此类副作用的一个示例是使用 setInterval 设置定时器。在仅客户端的代码中,我们可能会设置一个定时器,然后在 onBeforeUnmountonUnmounted 中将其清除。但是,由于在服务器端 SSR 期间永远不会调用卸载钩子,因此定时器将永远存在服务器中。为避免这种情况,请将您的副作用代码移到 onMounted 中。这样仅在客户端上执行。

在 Nuxt 3 的 SSR(服务端渲染)模式下,Vue 组件的 <script setup> 根作用域代码会在服务器端和客户端都执行,但两者行为有显著差异。以下是关键机制和应对策略:

执行机制详解:
  1. 双端执行流程
  • 服务器端(SSR)
    组件代码完整执行一次,生成静态 HTML 发送给浏览器。
    <script setup>
    // 服务器执行
    console.log('根作用域代码 - 服务器执行') // 输出在 Node.js 终端
    </script>
    
  • 客户端(CSR)
    浏览器接收到 HTML 后,重新执行 <script setup> 代码,激活交互性(hydration):
    <script setup>
    // 客户端执行
    console.log('根作用域代码 - 客户端执行') // 输出在浏览器控制台
    </script>
    
  1. 潜在风险场景
代码类型风险案例
全局副作用操作服务器创建的资源(定时器、监听器)无法销毁 → 内存泄漏setInterval(() => {...}, 1000)
浏览器 API 访问服务器无 window/document运行时崩溃const width = window.innerWidth
敏感逻辑暴露服务器日志可能泄露密钥或内部逻辑 → 安全风险console.log(process.env.API_KEY)

正确编码模式:
  1. 副作用操作隔离到生命周期钩子
    <script setup>
    import { onMounted, onUnmounted } from 'vue'
    
    // ✅ 安全:定时器仅在客户端设置和清理
    let timerId
    onMounted(() => {
      timerId = setInterval(updateData, 5000)
    })
    onUnmounted(() => {
      clearInterval(timerId)
    })
    </script>
    
  2. 环境检测执行分支
    <script setup>
    // ✅ 按环境分流逻辑
    if (process.client) {
      // 仅客户端逻辑(如访问 localStorage)
      const token = localStorage.getItem('token')
    } else if (process.server) {
      // 仅服务器逻辑(如请求后端 API)
      const { data } = await useFetch('/api/init')
    }
    </script>
    
  3. 数据获取使用 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跨端数据获取(自动水合)

:客户端仅接收注入数据,不重复执行请求逻辑


总结
  1. 双端执行:Nuxt SSR 下 <script setup> 根作用域代码服务器与客户端均会执行
  2. 规避风险
    • 副作用操作(定时器、事件监听)必须移至 onMounted + onUnmounted
    • 浏览器 API 调用需用 process.client 防护。
  3. 数据获取:优先使用 useFetch/useAsyncData,自动处理水合与重复请求。

步骤 7:渲染并生成 HTML 输出

在所有组件初始化并获取数据后,Nuxt 将组件与 unhead 中的设置相结合,生成完整的 HTML 文档。此 HTML 以及相关数据将发送回客户端以完成 SSR 过程。

将 Vue 应用程序渲染为 HTML 后,Nuxt 会调用 app:rendered 钩子。
在最终确定并发送 HTML 之前,Nitro 将调用 render:html 钩子。此钩子允许您操作生成的 HTML,例如注入额外的脚本或修改 meta 标签。

SSR 请求生命周期(页面级请求)

  1. 初始化阶段
    • Nitro 服务器启动:仅应用启动时初始化一次,加载全局插件及中间件并执行(位于 /server/middleware/ 的中间件在每个请求到达 Nitro 时优先执行。)。
    • 路由匹配:根据请求路径匹配对应的页面组件(如 pages/product/[id].vue)。
  2. 中间件与验证
    • 全局中间件 → 布局中间件 → 页面中间件:按顺序执行,可修改请求或重定向。
    • 路由验证:通过 definePageMeta 中的 validate 方法校验参数(如用户权限)。
  3. 数据获取与渲染
    • 服务端数据预取:调用 useFetchuseAsyncData 在服务端获取数据,结果注入 HTML
    • HTML 生成:Vue 组件在服务端渲染为完整 HTML,包含初始数据。
  4. 客户端激活(Hydration)
    • 复用 DOM:浏览器接收 HTML 后,Vue 将静态 DOM 绑定事件及响应式状态,转为交互式应用。

API 请求生命周期(数据级请求)

无论部署环境如何,单个请求的处理均复用已初始化的 Nitro 资源:

  1. 中间件执行:
    • 每个请求触发 /server/middleware/ 中的中间件(如身份验证、日志)。
    • 中间件按文件名排序执行(例如 1.auth.ts → 2.logger.ts)。
  2. 路由匹配与处理:
    • 根据 URL 匹配 /server/api//server/routes/ 中的事件处理器(Event Handlers)。
    • 动态参数(如 [id].ts)通过 getRouterParam 提取。
  3. 数据注入与响应:
    • 通过 render:response 钩子修改响应头或内容(例如注入缓存标头)。

客户端(浏览器)

无论您选择哪种 Nuxt 模式,生命周期的这一部分都完全在浏览器中执行。

步骤 1:初始化 Nuxt 并执行 Nuxt 应用程序插件

此步骤类似于服务器端执行,包括内置插件和自定义插件。

plugins/ 目录中的自定义插件(例如,没有后缀的 myPlugin.ts 和带有 .client 后缀的 myClientPlugin.client.ts)在客户端执行。

在此步骤之后,Nuxt 调用 app:created 钩子,该钩子可用于执行额外的逻辑。
Read more in Docs > Guide > Directory Structure > Plugins.

步骤 2:路由验证

此步骤与服务器端执行相同,如果 definePageMeta 函数中定义了 validate 方法,则包括该方法。

步骤 3:执行 Nuxt 应用程序中间件

Nuxt 中间件在服务器和客户端上都运行。如果您希望某些代码在特定环境中运行,请考虑使用 import.meta.client(用于客户端)和 import.meta.server(用于服务器)进行拆分。

Read more in Docs > Guide > Directory Structure > Middleware#when Middleware Runs.

步骤 4:挂载 Vue 应用程序和水合(hydration)

调用 app.mount('#__nuxt') 将 Vue 应用程序挂载到 DOM。如果应用程序使用 SSR 或 SSG 模式,Vue 会执行水合步骤以使客户端应用程序具有交互性。在水合期间,Vue 会重新创建应用程序(在客户端创建一个全新的 Vue 应用实例)(不包括 服务器组件),将每个组件与其对应的 DOM 节点匹配,并附加 DOM 事件监听器。

服务器组件处理: 服务器端组件不会在客户端重建:
  • 仅存在于服务器端执行
  • 结果被序列化并嵌入 HTML 中
  • 客户端仅使用生成的静态内容
服务器端组件的后缀是 .server.vue 。客户端组件的后缀是 .client.vue

为确保正确的水合,重要的是保持服务器和客户端数据的一致性。对于 API 请求,建议使用 useAsyncDatauseFetch 或其他 SSR 友好的组合式函数。这些方法确保在服务器端获取的数据在水合期间被重用,避免重复请求。任何新请求都应仅在水合后触发,以防止水合错误。

在挂载 Vue 应用程序之前,Nuxt 会调用 app:beforeMount 钩子。
在挂载 Vue 应用程序之后,Nuxt 会调用 app:mounted 钩子。

步骤 5:Vue 生命周期

与服务器端不同,浏览器执行完整的 Vue 生命周期

Nuxt 生命周期

Nuxt 生命周期是指整个应用从启动到运行的完整过程,主要分为 构建时运行时 两个阶段。

构建时生命周期

构建时生命周期通过一系列钩子函数来管理,这些钩子定义在 hooks.ts:84-177 。主要包括:

  • modules:beforemodules:done: 模块安装前后
  • app:resolve: 应用实例解析完成
  • app:templates: 模板生成阶段
  • build:beforebuild:done: 构建前后

这些钩子可以在 nuxt.config.ts 中使用,如 2.hooks.md:14-22

运行时生命周期

运行时生命周期钩子定义在 nuxt.ts:36-58 ,包括:

  • app:created: Vue 应用实例创建
  • app:beforeMountapp:mounted: 应用挂载前后
  • app:error: 错误处理
  • page:startpage:finish: 页面渲染开始和完成

这些钩子可以在插件中使用,如 use-nuxt-app.md:55-66