3.1 SSR执行流程与生命周期钩子


3.1 SSR 执行流程与生命周期钩子

本节进入第一棒内部:一次请求从浏览器敲下回车到 HTML 返回,Nuxt 服务器里依次发生什么。它是第 4 章(水合)、第 5 章(路由中间件)、第 6 章(服务端中间件)的时序基准——后面所有"什么时候执行"的问题,都对照本节的管线图回答。

把一次请求拆开看

图 3-1:SSR 请求管线全景

图 3-1:SSR 请求管线全景

对着图里标号走一遍细节:

② 服务端中间件。Nitro 层的请求拦截(server/middleware 目录),在 Vue 应用创建之前运行,能改请求上下文、设置响应头,适合日志、鉴权预检、CORS 处理——细节第 6 章展开。注意它与"路由中间件"(④,middleware 目录)是两个物种:前者拦一切请求含 API,后者只拦页面导航。

③ 插件执行。plugins 目录的文件在应用创建后按文件名顺序执行,负责注入全局能力。插件拿到 nuxtApp 实例,是注册生命周期钩子的标准场所:

// plugins/trace.ts:登记钩子观察管线 export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('app:created', () => { console.log('Vue 应用已创建') }) nuxtApp.hook('page:finish', () => { console.log('页面渲染完成') }) })

④⑤ 组件与数据。路由中间件跑完后,页面组件的 setup 在服务端执行;其中的 useAsyncData 发起取数,Nuxt 会等待所有 Promise 落定后再进入渲染——这是"首屏 HTML 里就有数据"的机制保证。

⑥⑦ 渲染与交付。组件树被序列化为 HTML 字符串;同时,异步数据被序列化成 payload,以脚本形式嵌入页面。浏览器水合时直接读 payload,不重复请求——第 4 章的交接物资在这里装车。

生命周期钩子清单

按阶段整理常用钩子:

钩子 触发时机 典型用途
app:created Vue 应用实例创建后 初始化全局状态
page:start 页面组件开始渲染 开启加载指示
page:finish 页面渲染完成 关闭加载指示
app:rendered SSR 渲染完成(仅服务端) 抓取渲染结果做审计
render:response 响应发送前(仅服务端) 修改最终 HTML/头
app:suspense:resolve 水合 suspense 解除 客户端首屏后动作
page:loading:end 页面加载状态结束 隐藏全局进度条

钩子解决的是"想在框架流程里插一脚"的需求。比如给所有页面响应统一加安全响应头,render:response 是正解;而想统计服务端渲染耗时,app:rendered 里读时间戳即可。

setup 的两端执行与判端技巧

同一份 setup 代码,服务端渲染时执行一次,客户端水合时再执行一次。写跨端代码的三板斧:

<script setup> // 判端三板斧:编译期常量、修饰符文件、生命周期 import { ClientOnly } from '#build/components' // 概念示意 if (import.meta.server) { // 仅服务端执行:安全地读服务端环境变量 } onMounted(() => { // 仅客户端执行:访问 window、初始化图表 }) // 想按端拆分组件:同名 .server.vue 与 .client.vue 成对出现 // Nuxt 自动在对应端加载对应实现 </script>

最常见的翻车是服务端执行到 windowdocument:SSR 管线里没有浏览器对象,直接抛错让整页 500。防御写法是把这类代码挪进 onMounted,或用 ClientOnly 组件包裹模板片段。

用钩子解决三个真实需求

钩子清单背不住很正常,需求驱动地过一遍用法比背表格有效。三个高频需求:

需求一:给所有响应加安全头。在 render:response 里改最终响应:

// plugins/security-headers.ts(服务端插件) export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('render:response', (response) => { response.headers = response.headers || {} response.headers['X-Frame-Options'] = 'DENY' response.headers['X-Content-Type-Options'] = 'nosniff' }) })

注意这处理的是页面响应;API 响应的头要在 Nitro 层加(6.2 的服务端中间件或 9.3 的路由规则),两层各管各的出口。

需求二:服务端渲染耗时打点。app:rendered 触发时请求已渲染完,配合请求开始时间算总耗时:

export default defineNuxtPlugin((nuxtApp) => { if (import.meta.server) { nuxtApp.hook('app:created', () => { // 上下文里记起点 nuxtApp.ssrContext.startTime = Date.now() }) nuxtApp.hook('app:rendered', () => { const cost = Date.now() - (nuxtApp.ssrContext.startTime || 0) console.log(`[ssr] 渲染耗时 ${cost}ms`) }) } })

这类打点数据积累起来,就是服务器容量的规划依据——P95 渲染耗时升高的那周,通常也是该加实例的那周。

需求三:页面切换的全局加载条。page:start 开、page:finish 关,三行代码还原一个经典 UI:

export default defineNuxtPlugin((nuxtApp) => { const loading = useState('page-loading', () => false) nuxtApp.hook('page:start', () => { loading.value = true }) nuxtApp.hook('page:finish', () => { loading.value = false }) })

三个需求的共同模式:钩子是管线上的标准插座,插座的形状(参数与时机)由框架定义,插什么由你决定。找到"我想在哪个步骤之后做事",钩子名就是那个步骤的名字。

⚠️ 常见坑:在 setup 顶层放定时器、事件监听却不清理。服务端执行时创建的 interval 会挂在渲染进程上泄漏,请求一多进程就肿。服务端只做"渲染需要的事",副作用一律留给客户端生命周期。

💡 关键直觉:把 SSR 管线记成"中间件拦路、插件装弹、setup 取数、渲染出货、payload 同车"五步,任何执行时机问题先对号入座再查文档。

本节要点回顾

  • 管线八步:请求进来,服务端中间件 → 插件 → 路由中间件 → setup 等数据 → 渲染 HTML → payload 注入 → 响应返回;
  • 两类中间件是两个物种:server/middleware 拦一切请求,middleware 目录只拦页面导航;
  • 异步数据阻塞渲染:useAsyncData 未落定前不产 HTML,这是首屏带数据的保证;
  • setup 两端各跑一次:判端用 import.meta.server 与 .server/.client 文件对,window 访问挪进 onMounted;
  • 钩子登记在插件里:app:created 到 render:response,管线上每个关键节点都有挂点。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U