SSR との互換性
VitePress は、本番ビルド中に Vue のサーバーサイドレンダリング (SSR) 機能を使用して、Node.js でアプリケーションを事前にレンダリングします。つまり、テーマコンポーネント内のすべてカスタムコードは、SSR の互換性の影響を受けます。
公式 Vue ドキュメントの SSR セクションでは、SSR の意味、SSR と SSG の関係について詳しく説明しており、SSR に優しいコードを記述するための一般的な注意が記載されています。ルールとして、Vue コンポーネントの beforeMount
または mounted
フックでのみブラウザ / DOM API にアクセスします。
<ClientOnly>
SSR に優しいものではないコンポーネントを使用またはデモしている場合 (カスタムディレクティブが含まれている場合など)、これらを組み込みの <ClientOnly>
コンポーネントでラップできます。
<ClientOnly>
<NonSSRFriendlyComponent />
</ClientOnly>
インポート時にブラウザ API にアクセスするライブラリ
一部のコンポーネントまたはライブラリは、インポート時にブラウザ API にアクセスします。インポート時にブラウザ環境を想定したコードを使用するには、それらを動的にインポートする必要があります。
マウントフックでインポートする
<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 変数 の一部)
を使用して、依存関係を条件付きでインポートすることもできます。
if (!import.meta.env.SSR) {
import('./lib-that-access-window-on-import').then((module) => {
// use code
})
}
Theme.enhanceApp
は非同期である可能性があるため、インポート時にブラウザ API にアクセスする Vue プラグインを条件付きでインポートして登録できます。
// .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 を使用する場合は、
// .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 コンポーネントをインポートするための便利なヘルパーが用意されています。
<script setup>
import { defineClientComponent } from 'vitepress'
const ClientComp = defineClientComponent(() => {
return import('component-that-access-window-on-import')
})
</script>
<template>
<ClientComp />
</template>
ターゲットコンポーネントに props/children/slot を渡すこともできます。
<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>
ターゲットコンポーネントは、ラッパコンポーネントのマウントフックでのみインポートされます。