写在开头
Hello吖,各位UU们好!👏
今是2026年03月14日,下午,幽静、无人打扰,刚刷了会手机,但有点看腻了。
然后,今天上午,小编将自己的上一台电脑叫了一个转转来上门回收,2021年款,联想小新R7,本来在APP上预估是能卖两千二左右的,结果线下验机后说只能卖1700了,就没卖,想着再找一个爱回收看看价格,🤔不知道能不能涨点。
还有个事,昨天听朋友说,他网恋成功了,说是在Soul上找的对象,已经线下面基过。唉,这年头...这也能成功?🥶 你们说小编要不要也去试试?🤔
好了,回到正题,今天要来分享的是小编上周工作中排查的一个问题,其实也是比较基础的概念问题,只是小编太久没用了,这次也写出来记录一下,请诸君按需食用哈~
需求背景 💡
最近小编正在做一个 SSR 项目,作为一名 Vue 老玩家,自然就选择 Nuxt 来搞,上次用 Nuxt 还是在上次,时间略久了!😗
整体项目开发进展还算顺利,也就是部署稍微麻烦一丢丢。然而,这天测试同学给我提了个问题:
"页面加载出来后,有时按钮点了没啥反应,总要多点几次或者要等一会才能点。"
小编一开始也按常规思路来:先看控制台有没有报错 ------ 结果没有明显的红字错误(因为并不是水合错位报错,只是水合还没执行到那块,事件还没绑上)。于是怀疑是事件没绑好或者代码写错了,又查了一圈事件和逻辑,代码确实没问题!🤔
最后才反应过来:原来是 水合(Hydration) 还没完成,那部分组件还没绑上事件,所以有时候才能点。
什么是水合?
上面说了,按钮点不了是因为水合还没完成。那水合到底是什么?🤔
简单说:服务端先返回 HTML,客户端 JS 加载完后,把事件绑上去,让页面能点、能交互------这个过程就叫水合。
下面简单用 CSR 和 SSR 对比一下,帮你建立直觉。
传统 CSR(客户端渲染)
普通的 Vue SPA 应用是这样的:
js
用户访问页面
↓
加载空白 HTML + JS
↓
JS 执行,渲染页面
↓
用户看到内容,可以交互
缺点:首屏白屏时间长,SEO 也不友好。
SSR(服务端渲染)
SSR 是这样的:
js
用户访问页面
↓
服务端直接返回完整 HTML
↓
用户立刻看到内容(快!)
↓
加载 JS,执行"水合"
↓
页面变得可交互
优点:首屏快,SEO 友好。
很明显,CSR 和 SSR 是两种不同的取舍,没有谁一定更好,咱们得根据业务场景来选,不要一刀切。❌
问题来了
SSR 有个尴尬的地方:HTML 先出来了,但 JS 还没加载完,事件还没绑定上。
js
用户看到页面了
↓
想点按钮 → 点不了 ❌(JS 还没准备好)
↓
等 1-2 秒...
↓
终于能点了
这就是测试同学遇到的问题!页面出来了,但还处于"僵尸"状态,看得见摸不着。😅
懒加载水合是什么?
既然问题是「要等一会儿才能点」,那有没有办法让首屏更快可交互?小编查了一下 Nuxt 的文档,发现有个功能叫 懒加载水合(Lazy Hydration),专门解决这类问题!
懒加载水合 :它还是「水合」------还是把事件绑到服务端 HTML 上,只是不再一次性水合整页,而是按需、分优先级地水合。如,首屏先水合,下面的等需要时再水合。
所以呢,用词上要分清:水合 是整个过程,懒加载水合 是水合的一种策略(延迟一部分组件的水合时机)。
在 Vue 3.5 / Nuxt 里,这个策略常和 异步组件 一起用:异步组件负责延迟加载 组件 JS(减包体),懒加载水合负责延迟 该组件的水合时机(让首屏先可交互),两个搭配着用。
核心思想:不用一次性把所有组件都水合,按需水合!
比如:
- 首屏可见的组件 → 立刻水合
- 非首屏的组件 → 用户滚到那里再水合
- 低优先级的组件 → 浏览器空闲时再水合
这样,首屏的 JS 体积就小了,水合速度就快了,用户点按钮就不会"卡壳"啦!🎯
Nuxt 中怎么用?
Nuxt 已经内置了懒加载水合的支持,用起来非常简单的!🏃
第1️⃣步:认识 Lazy 组件
在 Nuxt 中,所有放在 components/ 目录下的组件都会被自动导入。如果在组件名前加上 Lazy 前缀,就可以延迟加载:
js
<template>
<!-- 普通组件 -->
<MyComponent />
<!-- 懒加载组件 -->
<LazyMyComponent />
</template>
但这只是 懒加载 ,还不是 懒加载水合!区别在于:
- 懒加载:延迟加载 JS 代码
- 懒加载水合:延迟执行水合(JS 可能已经加载了,但不急着绑定事件)
第2️⃣步:添加水合策略
Nuxt 提供了多种水合策略,咱们来看几个常用的:
hydrate-on-visible(可见时水合)
组件进入视口时才水合,适合非首屏内容:
js
<template>
<div>
<h1>首屏内容</h1>
<!-- 下面的组件要用户滚到这里才会水合 -->
<LazyComments hydrate-on-visible />
</div>
</template>
🍊 为什么这么做❓
非首屏的组件,用户不一定马上会看到,何必急着水合呢?等用户滚到那里再说,这样首屏更快。
hydrate-on-interaction(交互时水合)
用户点击/悬停组件时才水合:
js
<template>
<!-- 用户点击这个区域时才水合 -->
<LazyExpensiveComponent hydrate-on-interaction="click" />
<!-- 或者鼠标悬停时水合 -->
<LazyChart hydrate-on-interaction="mouseover" />
</template>
hydrate-after(延迟水合)
指定毫秒数后自动水合:
js
<template>
<!-- 2 秒后水合 -->
<LazySidebar :hydrate-after="2000" />
</template>
hydrate-on-media-query(媒体查询水合)
匹配特定媒体查询时水合:
js
<template>
<!-- 只在移动端水合 -->
<LazyMobileMenu hydrate-on-media-query="(max-width: 768px)" />
</template>
hydrate-when(条件水合)
根据条件决定是否水合:
js
<script setup>
const isReady = ref(false)
// 某个条件触发后
function triggerHydration() {
isReady.value = true
}
</script>
<template>
<LazyHeavyComponent :hydrate-when="isReady" />
</template>
第3️⃣步:监听水合完成事件
所有懒加载水合组件都会触发 @hydrated 事件:
js
<template>
<LazyComments
hydrate-on-visible
@hydrated="onHydrated"
/>
</template>
<script setup>
function onHydrated() {
console.log('组件水合完成!')
}
</script>
第4️⃣步:小编的实际应用
回到咱们的场景,测试反馈按钮点不了,小编的解决方案是这样的:
js
<template>
<div>
<!-- 首屏重要内容,正常水合 -->
<Header />
<MainContent />
<!-- 非首屏的评论区,懒加载水合 -->
<LazyComments hydrate-on-visible />
<!-- 底部推荐,用户悬停时才水合 -->
<LazyRecommendations hydrate-on-interaction="mouseover" />
</div>
</template>
这样首屏的 JS 体积就小了,水合速度变快,按钮响应更及时!🎉
💡 小贴士:
- 首屏核心交互内容不要用懒加载水合,会影响用户体验。
- 适合用在非首屏、低优先级的组件上。
- 如果组件本身就用了
v-if="false",那就不需要懒加载水合了。
Vue 3.5 原生用法
如果你用的不是 Nuxt,而是纯 Vue 3.5 + 自己搭的 SSR,其实也可以用原生的懒加载水合。
底层原理其实是 Vue 3.5 提供的水合策略,Nuxt 只是在上面封装了一层更易用的 API。
官方文档:传送门。
第1️⃣步:导入水合策略
js
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
第2️⃣步:定义异步组件
js
const LazyComments = defineAsyncComponent({
loader: () => import('./Comments.vue'),
hydrate: hydrateOnVisible()
})
可用的水合策略
| 策略 | 说明 |
|---|---|
hydrateOnIdle() |
浏览器空闲时水合 |
hydrateOnVisible() |
进入视口时水合 |
hydrateOnInteraction('click') |
点击时水合 |
hydrateOnMediaQuery('(max-width:768px)') |
媒体查询匹配时水合 |
用法都差不多,小编就不一一列举了,大家看文档就好~😋
至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。