VitePress 中如何在 SSR 时正确使用浏览器 API?
我在 VitePress 的页面里用了 window 对象,结果构建时报错说 window is not defined,但本地开发是好的。是不是因为 SSR 阶段没有浏览器环境?
我试过把代码包在 onMounted 里,但还是不行。比如下面这段:
<script setup>
import { ref, onMounted } from 'vue'
const screenWidth = ref(0)
onMounted(() => {
screenWidth.value = window.innerWidth
})
</script>
<template>
<p>当前宽度:{{ screenWidth }}</p>
</template>
这写法在普通 Vue 项目没问题,但在 VitePress 构建时就挂了,有啥办法绕过 SSR 阶段的 window 访问吗?
关键点是:VitePress 的 SSR 阶段会执行整个组件的 setup 和模板渲染,而 onMounted 只在客户端 hydration 之后才触发,但模板里如果直接引用了
screenWidth,而它初始值是0,那 SSR 输出的 HTML 就是,这没问题;但真正报错通常是因为你在模板里直接用了当前宽度:0
window.innerWidth,或者在 setup 阶段就访问了window。你那段代码本身其实不会直接报错,除非你写了类似:
这才是真·报错现场。
正确做法分两种:
1. 如果只是想拿到初始宽度做 SSR 兼容(比如布局判断),用客户端专用组件包裹
VitePress 支持
组件,或者更推荐直接用definePageConfig里的clientOnly: true(VitePress 1.0+ 支持),这样整个页面跳过 SSR,直接客户端渲染:2. 如果必须 SSR,但某些逻辑只在客户端跑
用
process.client判断(VitePress 提供的全局变量):但注意:
process.client是 VitePress 注入的,等价于typeof window !== 'undefined',但更简洁。性能上建议:能避免依赖
window就避免,比如用 CSS 媒体查询处理响应式逻辑,或者用useResizeObserver之类的 VueUse 工具——它们内部已经做了 SSR 兼容,比自己手写更稳。如果还是报错,大概率是某个依赖包在 import 时就访问了
window,这种情况可以用defineAsyncComponent或dynamic import()+process.client包裹来延迟加载。最后说一句:
onMounted不是万能保险,SSR 场景下它只是“客户端挂载后执行”,但 setup 阶段已经跑完了——所以千万别在 setup 里碰浏览器 API。