VitePress 中如何在 SSR 时正确使用浏览器 API?

南宫春晖 阅读 14

我在 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 访问吗?

我来解答 赞 5 收藏
二维码
手机扫码查看
1 条解答
迷人的小利
你这问题我太熟悉了,VitePress 默认是 SSR 渲染的,服务端跑的时候根本没有 window,所以哪怕你代码写在 onMounted 里也没用——因为 SSR 阶段根本不会执行到 onMounted。

关键点是:VitePress 的 SSR 阶段会执行整个组件的 setup 和模板渲染,而 onMounted 只在客户端 hydration 之后才触发,但模板里如果直接引用了 screenWidth,而它初始值是 0,那 SSR 输出的 HTML 就是

当前宽度:0

,这没问题;但真正报错通常是因为你在模板里直接用了 window.innerWidth,或者在 setup 阶段就访问了 window

你那段代码本身其实不会直接报错,除非你写了类似:

const screenWidth = ref(window.innerWidth) // ❌ setup 里直接读 window


这才是真·报错现场。

正确做法分两种:

1. 如果只是想拿到初始宽度做 SSR 兼容(比如布局判断),用客户端专用组件包裹
VitePress 支持 组件,或者更推荐直接用 definePageConfig 里的 clientOnly: true(VitePress 1.0+ 支持),这样整个页面跳过 SSR,直接客户端渲染:

<script setup>
const screenWidth = ref(0)

onMounted(() => {
screenWidth.value = window.innerWidth
})
</script>

<template>
<p>当前宽度:{{ screenWidth }}</p>
</template>

<script>
export default {
definePageConfig: {
clientOnly: true
}
}
</script>


2. 如果必须 SSR,但某些逻辑只在客户端跑
process.client 判断(VitePress 提供的全局变量):

<script setup>
import { ref, onMounted } from 'vue'

const screenWidth = ref(0)

if (process.client) {
onMounted(() => {
screenWidth.value = window.innerWidth
})
}
</script>

<template>
<p>当前宽度:{{ screenWidth }}</p>
</template>


但注意:process.client 是 VitePress 注入的,等价于 typeof window !== 'undefined',但更简洁。

性能上建议:能避免依赖 window 就避免,比如用 CSS 媒体查询处理响应式逻辑,或者用 useResizeObserver 之类的 VueUse 工具——它们内部已经做了 SSR 兼容,比自己手写更稳。

如果还是报错,大概率是某个依赖包在 import 时就访问了 window,这种情况可以用 defineAsyncComponentdynamic import() + process.client 包裹来延迟加载。

最后说一句:onMounted 不是万能保险,SSR 场景下它只是“客户端挂载后执行”,但 setup 阶段已经跑完了——所以千万别在 setup 里碰浏览器 API。
点赞 3
2026-02-26 12:00