VitePress SSR中如何在服务端渲染动态生成的页面内容?
我在用VitePress搭建文档站点时遇到了SSR问题。通过getPages动态生成的路由页面,在服务端渲染时内容显示为空白,但客户端刷新又能正常显示。我尝试在组件setup里用onMounted获取数据,却发现SSR时数据是undefined。控制台报错说Cannot read properties of undefined,这是哪里出问题了?
配置文件用了这样的路由生成逻辑:
export default defineConfig({
vitepress: {
themeConfig: {},
ssr: {
external: false
},
pages: () => {
return [{
name: 'dynamic',
path: '/dynamic/:id',
async data({ params }) {
// 模拟API请求
return { content: <code>ID: ${params.id}</code> }
}
}]
}
}
})
在对应的页面组件里这样写:
<template>
<div>{{ content }}</div>
</template>
<script setup>
const { params } = useRoute()
const { data: content } = await useAsyncData('dynamic', () =>
fetch(<code>/api/${params.id}</code>).then(res => res.text())
)
</script>
useRoute()还没拿到参数,params.id是 undefined,所以 fetch 的 URL 不合法,直接炸了。别用
useRoute()去拿路由参数再发请求,VitePress 提供了专门的钩子函数来处理动态数据预取,得用useAsyncData结合页面级的data()配置来走 SSR 渲染流程。先把
vitepress.pages里的 data 函数写对,确保能接收到 params:然后在你的页面组件里,通过
usePageData拿到注入的数据,而不是重新 fetch:这样 SSR 构建阶段就会根据路由参数调用 data() 函数生成内容,不会出现 undefined。你之前用 onMounted + fetch 的方式只会在客户端执行,SSR 根本不认。
记住一点:VitePress 的 SSR 动态页靠的是 pages 配置里的 data 钩子函数做预渲染,不是组件里自己去异步拉。你要真想走 API,也得配合服务端代理或者把数据打到构建产物里,别直接在组件里裸 fetch 路由参数。
首先得明确一点:VitePress的getPages返回的是静态路由定义,不是运行时动态路由。你写的path: '/dynamic/:id'这种带参数的路径,在VitePress里根本不会被解析成动态路由,SSR构建时压根不知道有哪些:id需要预渲染。
你现在遇到的空白页面是因为SSR阶段执行到组件时,params.id是undefined,导致fetch请求发出去了个错误的URL,然后await直接炸了。但为什么客户端又能显示?因为浏览器里Vue接管后,路由参数正确注入,加上客户端有完整的JavaScript环境,能重新跑一遍逻辑,看起来像是“好了”,其实是靠客户端 hydration 勉强撑住的假象。
正确的做法分三步:
第一步,放弃幻想,别用动态参数路径。VitePress不支持运行时动态路由,所有页面必须在构建时就确定下来。你需要提前知道有哪些id要生成。
比如你的内容来自某个API或文件系统,那就应该在getPages里先拉一遍列表:
第二步,页面组件里不要再用useRoute和fetch了。因为你已经通过data函数把数据注入进来了,可以直接用$frontmatter访问。
你的页面组件应该长这样:
第三步,如果你真的想做“类动态”行为,比如允许用户输入任意id跳转,那也得妥协。你可以保留一个通用模板页,但在构建时生成一堆常用id的页面,剩下的交给客户端兜底。但记住,SSR只对构建时生成的那些页面有效。
另外提醒一个坑:你在配置里写的ssr.external: false这个没意义,VitePress没有这个配置项。别乱抄Nuxt的配置。
总结一下核心原理:VitePress是静态站点生成器,不是全栈框架。它的SSR发生在构建时(build time),不是请求时(request time)。所以所有页面路径和数据都必须能在构建阶段确定。你不能指望它像Express那样处理 /dynamic/:id 这种通配路由。
现在你应该改回静态生成模式,确保getPages返回的是具体路径的数组,而不是带参数的模板路径。这样才能让SSR真正工作,页面也不会空白了。