Nuxt 3 SSR模式页面刷新出现双组件渲染闪烁问题
问题根因
闪烁本质是SSR hydration 内容不匹配导致的:
- 服务端渲染阶段不存在
window对象,原代码里三个响应式变量初始值全为false,因此v-if="isMobile"判断为假,服务端直出的HTML会直接渲染桌面端组件LazyMediaPageGallery - 浏览器加载到服务端返回的HTML时,会立刻把桌面端组件绘制到页面上
- 等客户端JS加载完成、组件挂载完成后,
onMounted钩子才会触发,此时才会读取真实视口宽度修改变量值,如果是移动端就会销毁桌面端组件、渲染移动端组件,这个时间差就形成了可见的跳转闪烁 - 关闭SSR后所有DOM都在客户端生成,JS执行速度远快于网络加载直出HTML的速度,用户感知不到中间的错误渲染状态,因此闪烁消失
另外原代码存在两个可优化点:
- 平板的变量名拼写错误:
isTabled应为isTablet - 没有在组件卸载时移除
resize事件监听,存在内存泄漏风险
修复方案
方案1:对齐SSR与客户端初始值,提前执行首次宽度计算
从两个层面消除hydration阶段的内容差异:
- 服务端虽然无法获取视口精确宽度,但可以通过请求头的
User-Agent判断设备类型,给响应式变量设置贴合真实设备的初始值,保证服务端直出内容和客户端首次渲染内容一致 - 不要等
onMounted才执行首次宽度计算,客户端环境下在setup阶段同步执行宽度检测,在DOM首次渲染前就拿到正确的视口值,避免渲染错误组件
修复后的组合式函数代码如下(以Nuxt环境为例,纯Vue3+Vite SSR可将环境判断逻辑替换为typeof window !== 'undefined'):
// 移动端UA匹配正则 const mobileUAreg = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i export default function useResponsive() { // 服务端初始值从UA推导,客户端初始先设为false,后续同步计算覆盖 const ssrInitialIsMobile = process.server ? mobileUAreg.test(useRequestHeader('user-agent') || '') : false const isMobile = ref(ssrInitialIsMobile) const isTablet = ref(false) const isDesktop = ref(!ssrInitialIsMobile) const updateWidth = () => { const width = window.innerWidth isMobile.value = width <= 640 isTablet.value = width > 640 && width <= 1280 isDesktop.value = width > 1280 } // 客户端环境下同步执行首次宽度计算,注册resize监听 if (process.client) { updateWidth() window.addEventListener('resize', updateWidth) } // 组件卸载时移除事件监听,避免内存泄漏 onUnmounted(() => { if (process.client) { window.removeEventListener('resize', updateWidth) } }) return { isMobile, isTablet, isDesktop } }
方案2:使用ClientOnly组件包裹视口敏感内容(备选)
如果不需要这部分内容参与SSR,可以用内置的ClientOnly组件把依赖视口判断的区块包裹起来,服务端不会渲染这部分内容,等客户端JS加载完成后再按真实视口渲染对应组件,同时可以加fallback占位内容避免布局偏移:
<div class="tw-py-0 tw-mb-6 xl:tw-py-7 xl:tw-mb-0"> <ClientOnly> <LazyMediaMobilePageGallery v-if="isMobile"/> <LazyMediaPageGallery v-else/> <!-- 占位骨架屏,高度和实际组件接近即可 --> <template #fallback> <div class="tw-h-80 tw-animate-pulse tw-bg-gray-100 tw-rounded-lg"></div> </template> </ClientOnly> </div>
这个方案实现成本更低,但缺点是这部分内容不会出现在服务端直出的HTML中,对SEO有轻微影响,适合非核心内容场景使用。
内容的提问来源于stack exchange,提问作者Aslero
相关产品推荐
相关产品推荐

