跳至内容

SSR 兼容性

VitePress 在生产构建期间使用 Vue 的服务器端渲染 (SSR) 功能在 Node.js 中预渲染应用程序。这意味着主题组件中的所有自定义代码都必须符合 SSR 兼容性。

官方 Vue 文档中的 SSR 部分 提供了更多关于 SSR 是什么、SSR/SSG 之间的关系以及编写 SSR 友好代码的常见注意事项的背景信息。经验法则是,仅在 Vue 组件的 beforeMountmounted 钩子中访问浏览器/DOM API。

<ClientOnly>

如果您正在使用或演示不符合 SSR 兼容性的组件(例如,包含自定义指令),则可以将它们包装在内置的 <ClientOnly> 组件中

md
<ClientOnly>
  <NonSSRFriendlyComponent />
</ClientOnly>

导入时访问浏览器 API 的库

某些组件或库在导入时会访问浏览器 API。要使用在导入时假定浏览器环境的代码,您需要动态导入它们。

在 Mounted 钩子中导入

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

onMounted(() => {
  import('./lib-that-access-window-on-import').then((module) => {
    // use code
  })
})
</script>

条件导入

您还可以使用 import.meta.env.SSR 标志(Vite 环境变量 的一部分)有条件地导入依赖项

js
if (!import.meta.env.SSR) {
  import('./lib-that-access-window-on-import').then((module) => {
    // use code
  })
}

由于 Theme.enhanceApp 可以是异步的,因此您可以有条件地导入和注册在导入时访问浏览器 API 的 Vue 插件

js
// .vitepress/theme/index.js
/** @type {import('vitepress').Theme} */
export default {
  // ...
  async enhanceApp({ app }) {
    if (!import.meta.env.SSR) {
      const plugin = await import('plugin-that-access-window-on-import')
      app.use(plugin.default)
    }
  }
}

如果您使用的是 TypeScript

ts
// .vitepress/theme/index.ts
import type { Theme } from 'vitepress'

export default {
  // ...
  async enhanceApp({ app }) {
    if (!import.meta.env.SSR) {
      const plugin = await import('plugin-that-access-window-on-import')
      app.use(plugin.default)
    }
  }
} satisfies Theme

defineClientComponent

VitePress 提供了一个方便的助手,用于导入在导入时访问浏览器 API 的 Vue 组件。

vue
<script setup>
import { defineClientComponent } from 'vitepress'

const ClientComp = defineClientComponent(() => {
  return import('component-that-access-window-on-import')
})
</script>

<template>
  <ClientComp />
</template>

您还可以将 props/children/slots 传递给目标组件

vue
<script setup>
import { ref } from 'vue'
import { defineClientComponent } from 'vitepress'

const clientCompRef = ref(null)
const ClientComp = defineClientComponent(
  () => import('component-that-access-window-on-import'),

  // args are passed to h() - https://vuejs.ac.cn/api/render-function.html#h
  [
    {
      ref: clientCompRef
    },
    {
      default: () => 'default slot',
      foo: () => h('div', 'foo'),
      bar: () => [h('span', 'one'), h('span', 'two')]
    }
  ],

  // callback after the component is loaded, can be async
  () => {
    console.log(clientCompRef.value)
  }
)
</script>

<template>
  <ClientComp />
</template>

目标组件仅在包装组件的 mounted 钩子中导入。