You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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阶段的内容差异:

  1. 服务端虽然无法获取视口精确宽度,但可以通过请求头的User-Agent判断设备类型,给响应式变量设置贴合真实设备的初始值,保证服务端直出内容和客户端首次渲染内容一致
  2. 不要等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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:09:39