SSR 兼容性
VitePress 在构建时使用服务端渲染(SSR),某些第三方库或组件可能存在兼容性问题。本文档介绍常见问题及解决方案。
什么是 SSR
SSR(Server-Side Rendering)是在服务器端生成 HTML 内容的技术。VitePress 使用 SSR 来:
- 生成静态 HTML 文件
- 提供更好的 SEO
- 加快首屏加载速度
常见兼容性问题
1. 访问浏览器 API
在 SSR 阶段,window、document、navigator 等浏览器 API 不存在。
错误示例:
vue
<script setup>
// 错误:SSR 阶段 window 不存在
const width = window.innerWidth
</script>解决方案:
vue
<script setup>
import { ref, onMounted } from 'vue'
const width = ref(0)
onMounted(() => {
// 正确:仅在客户端执行
width.value = window.innerWidth
})
</script>2. 使用 ClientOnly 组件
对于需要浏览器环境的组件,使用 ClientOnly 包裹:
vue
<ClientOnly>
<BrowserDependentComponent />
</ClientOnly>3. 条件导入
动态导入仅客户端使用的模块:
vue
<script setup>
import { onMounted, ref } from 'vue'
const Component = ref(null)
onMounted(async () => {
// 仅在客户端导入
const module = await import('some-client-library')
Component.value = module.default
})
</script>
<template>
<component :is="Component" v-if="Component" />
</template>检测 SSR 环境
使用 import.meta
ts
// 检测是否在服务端
if (import.meta.env.SSR) {
// 服务端代码
} else {
// 客户端代码
}使用 onMounted 钩子
vue
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// 这里的代码只在客户端执行
console.log('Client only')
})
</script>常见库的处理
1. 图表库(如 ECharts)
vue
<script setup>
import { ref, onMounted } from 'vue'
const chartRef = ref(null)
let echarts = null
onMounted(async () => {
// 动态导入
echarts = await import('echarts')
const chart = echarts.init(chartRef.value)
chart.setOption({
// 配置项
})
})
</script>
<template>
<div ref="chartRef" style="height: 400px"></div>
</template>2. 代码高亮(如 Prism)
vue
<script setup>
import { onMounted, ref } from 'vue'
const codeRef = ref(null)
onMounted(async () => {
const Prism = await import('prismjs')
Prism.highlightElement(codeRef.value)
})
</script>
<template>
<pre><code ref="codeRef">const hello = 'world'</code></pre>
</template>3. 地图库(如 Leaflet)
vue
<script setup>
import { onMounted, ref } from 'vue'
import 'leaflet/dist/leaflet.css'
const mapRef = ref(null)
onMounted(async () => {
const L = await import('leaflet')
const map = L.map(mapRef.value).setView([51.505, -0.09], 13)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map)
})
</script>
<template>
<div ref="mapRef" style="height: 400px"></div>
</template>vitepress 配置选项
ssr.external
排除不兼容 SSR 的依赖:
ts
// docs/.vitepress/config.mts
export default defineConfig({
vite: {
ssr: {
external: ['some-library', 'another-library']
}
}
})ssr.noExternal
强制打包某些依赖:
ts
export default defineConfig({
vite: {
ssr: {
noExternal: ['element-plus']
}
}
})调试 SSR 问题
1. 使用 vitepress dev --ssr
在开发模式下测试 SSR:
bash
npx vitepress dev docs --ssr2. 查看构建错误
构建时会显示具体的 SSR 错误:
bash
npm run build3. 添加错误边界
vue
<script setup>
import { onErrorCaptured, ref } from 'vue'
const error = ref(null)
onErrorCaptured((e) => {
error.value = e.message
return false
})
</script>
<template>
<div v-if="error" class="error">
渲染错误: {{ error }}
</div>
<slot v-else />
</template>最佳实践
1. 延迟加载
对于大型库,使用延迟加载:
vue
<script setup>
import { defineAsyncComponent } from 'vue'
const HeavyComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>
<template>
<Suspense>
<template #default>
<HeavyComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>2. 使用 provide/inject
避免在组件初始化时访问浏览器 API:
ts
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
// 在客户端提供全局状态
if (!import.meta.env.SSR) {
app.provide('browserInfo', {
width: window.innerWidth,
height: window.innerHeight
})
}
}
}3. 使用 CSS 替代 JS
尽可能使用 CSS 实现效果:
css
/* 使用 CSS 变量 */
:root {
--vh: 1vh;
}
/* 移动端适配 */
.full-height {
height: 100vh;
height: calc(var(--vh, 1vh) * 100);
}常见错误及解决
ReferenceError: window is not defined
原因:在 SSR 阶段访问了 window 对象
解决:
vue
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
// 在这里使用 window
console.log(window.innerWidth)
})
</script>document is not defined
原因:在 SSR 阶段访问了 document 对象
解决:
ts
if (!import.meta.env.SSR) {
document.getElementById('app')
}navigator is not defined
原因:在 SSR 阶段访问了 navigator 对象
解决:
ts
const isMobile = !import.meta.env.SSR && navigator.userAgent.includes('Mobile')相关资源
下一步
了解 MPA 模式 作为 SSR 的替代方案。