본문으로 건너뛰기

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>

"import" 시 브라우저 API 에 접근하는 라이브러리

일부 컴포넌트나 라이브러리는 "import" 시 브라우저 API 에 접근합니다. "import" 시 브라우저 환경을 가정하는 코드를 사용하려면, 동적 "import"를 사용해야 합니다.

Mounted 훅에서 가져오기

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

onMounted(() => {
  import('./lib-that-access-window-on-import').then((module) => {
    // 코드 사용하기
  })
})
</script>

조건부 가져오기

import.meta.env.SSR 플래그 (Vite 환경 변수의 일부) 를 사용하여 종속성을 조건부로 "import" 할 수도 있습니다:

js
if (!import.meta.env.SSR) {
  import('./lib-that-access-window-on-import').then((module) => {
    // 코드 사용하기
  })
}

Theme.enhanceApp은 비동기로 사용할 수 있기 때문에, "import" 시 브라우저 API 에 접근하는 Vue 플러그인을 조건부로 "import"하고 등록할 수 있습니다:

.vitepress/theme/index.js
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
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 는 "import" 시 브라우저 API 에 접근하는 Vue 컴포넌트를 "import" 할 수 있는 편리한 헬퍼를 제공합니다.

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'),

  // h() 에 전달된 인자 - https://vuejs.org/api/render-function.html#h
  [
    {
      ref: clientCompRef
    },
    {
      default: () => '기본 슬롯',
      foo: () => h('div', 'foo'),
      bar: () => [h('span', '하나'), h('span', '둘')]
    }
  ],

  // 컴포넌트가 로드된 후 콜백함수, 비동기일 수 있음
  () => {
    console.log(clientCompRef.value)
  }
)
</script>

<template>
  <ClientComp />
</template>

대상 컴포넌트는 래퍼 컴포넌트의 mounted 훅에서만 "import" 됩니다.

Released under the MIT License.