VitePress SSR中如何在服务端渲染动态生成的页面内容?

Air-艺晗 阅读 20

我在用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>
我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
小玲玲
小玲玲 Lv1
你这问题出在搞混了 VitePress 的页面数据预取和客户端数据加载的时机。SSR 时 useRoute() 还没拿到参数,params.id 是 undefined,所以 fetch 的 URL 不合法,直接炸了。

别用 useRoute() 去拿路由参数再发请求,VitePress 提供了专门的钩子函数来处理动态数据预取,得用 useAsyncData 结合页面级的 data() 配置来走 SSR 渲染流程。

先把 vitepress.pages 里的 data 函数写对,确保能接收到 params:

pages: () => [
{
name: 'dynamic',
path: '/dynamic/:id',
async data({ params }) {
// 这里 params.id 在 SSR 构建时会被传入
return { content: ID: ${params.id} }
}
}
]


然后在你的页面组件里,通过 usePageData 拿到注入的数据,而不是重新 fetch:

<template>
<div>{{ content }}</div>
</template>

<script setup>
import { usePageData } from 'vitepress'

const page = usePageData()
const content = page.value.data?.content
</script>


这样 SSR 构建阶段就会根据路由参数调用 data() 函数生成内容,不会出现 undefined。你之前用 onMounted + fetch 的方式只会在客户端执行,SSR 根本不认。

记住一点:VitePress 的 SSR 动态页靠的是 pages 配置里的 data 钩子函数做预渲染,不是组件里自己去异步拉。你要真想走 API,也得配合服务端代理或者把数据打到构建产物里,别直接在组件里裸 fetch 路由参数。
点赞 5
2026-02-12 17:10
利利酱~
这个问题的关键是搞错了VitePress的SSR数据获取机制。你现在的写法混合了Nuxt的useAsyncData和路由参数处理方式,但在VitePress里这套根本不生效,尤其是在服务端渲染阶段。

首先得明确一点:VitePress的getPages返回的是静态路由定义,不是运行时动态路由。你写的path: '/dynamic/:id'这种带参数的路径,在VitePress里根本不会被解析成动态路由,SSR构建时压根不知道有哪些:id需要预渲染。

你现在遇到的空白页面是因为SSR阶段执行到组件时,params.id是undefined,导致fetch请求发出去了个错误的URL,然后await直接炸了。但为什么客户端又能显示?因为浏览器里Vue接管后,路由参数正确注入,加上客户端有完整的JavaScript环境,能重新跑一遍逻辑,看起来像是“好了”,其实是靠客户端 hydration 勉强撑住的假象。

正确的做法分三步:

第一步,放弃幻想,别用动态参数路径。VitePress不支持运行时动态路由,所有页面必须在构建时就确定下来。你需要提前知道有哪些id要生成。

比如你的内容来自某个API或文件系统,那就应该在getPages里先拉一遍列表:

export default defineConfig({
vitepress: {
themeConfig: {},
pages: async () => {
// 第一步:构建时获取所有可用的id
const ids = await fetch('https://your-api.com/ids')
.then(res => res.json())

// 第二步:为每个id生成一个静态页面
return ids.map(id => ({
name: dynamic-${id},
path: /dynamic/${id}, // 注意这里不再是 :id,而是具体的值
async data() {
// 每个页面独立请求自己的数据
const data = await fetch(https://your-api.com/content/${id})
.then(res => res.json())
return { content: ID: ${id}, Data: ${data.text} }
}
}))
}
}
})


第二步,页面组件里不要再用useRoute和fetch了。因为你已经通过data函数把数据注入进来了,可以直接用$frontmatter访问。

你的页面组件应该长这样:

<template>
<div v-html="$frontmatter.content"></div>
</template>

<script setup>
// 不需要任何异步逻辑
// 数据已经在 $frontmatter 里了
// 因为 VitePress 在 SSR 构建时已经执行过 data() 函数并挂载到了 frontmatter 上
</script>


第三步,如果你真的想做“类动态”行为,比如允许用户输入任意id跳转,那也得妥协。你可以保留一个通用模板页,但在构建时生成一堆常用id的页面,剩下的交给客户端兜底。但记住,SSR只对构建时生成的那些页面有效。

另外提醒一个坑:你在配置里写的ssr.external: false这个没意义,VitePress没有这个配置项。别乱抄Nuxt的配置。

总结一下核心原理:VitePress是静态站点生成器,不是全栈框架。它的SSR发生在构建时(build time),不是请求时(request time)。所以所有页面路径和数据都必须能在构建阶段确定。你不能指望它像Express那样处理 /dynamic/:id 这种通配路由。

现在你应该改回静态生成模式,确保getPages返回的是具体路径的数组,而不是带参数的模板路径。这样才能让SSR真正工作,页面也不会空白了。
点赞 5
2026-02-11 08:00