コンテンツにスキップする

SSR との互換性

VitePress は、本番ビルド中に Vue のサーバーサイドレンダリング (SSR) 機能を使用して、Node.js でアプリケーションを事前にレンダリングします。つまり、テーマコンポーネント内のすべてカスタムコードは、SSR の互換性の影響を受けます。

公式 Vue ドキュメントの SSR セクションでは、SSR の意味、SSR と SSG の関係について詳しく説明しており、SSR に優しいコードを記述するための一般的な注意が記載されています。ルールとして、Vue コンポーネントの beforeMount または mounted フックでのみブラウザ / DOM API にアクセスします。

<ClientOnly>

SSR に優しいものではないコンポーネントを使用またはデモしている場合 (カスタムディレクティブが含まれている場合など)、これらを組み込みの <ClientOnly> コンポーネントでラップできます。

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

インポート時にブラウザ API にアクセスするライブラリ

一部のコンポーネントまたはライブラリは、インポート時にブラウザ API にアクセスします。インポート時にブラウザ環境を想定したコードを使用するには、それらを動的にインポートする必要があります。

マウントフックでインポートする

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 env 変数 の一部)を使用して、依存関係を条件付きでインポートすることもできます。

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/slot を渡すこともできます。

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.org/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>

ターゲットコンポーネントは、ラッパコンポーネントのマウントフックでのみインポートされます。

MIT ライセンスでリリースされています。