docs: add dedicated section on SSR compatibility

pull/2104/head
Evan You 2 years ago
parent 2ad668cd54
commit 9b85e435fe

@ -126,6 +126,7 @@ function sidebarGuide() {
link: '/guide/extending-default-theme'
},
{ text: 'Build-Time Data Loading', link: '/guide/data-loading' },
{ text: 'SSR Compatibility', link: '/guide/ssr-compat' },
{ text: 'Connecting to a CMS', link: '/guide/cms' }
]
},

@ -66,7 +66,7 @@ export default {
The default export is the only contract for a custom theme, and only the `Layout` property is required. So technically, a VitePress theme can be as simple as a single Vue component.
Inside your layout component, it works just like a normal Vite + Vue 3 application. Do note the theme also needs to be [SSR-compatible](./using-vue#browser-api-access-restrictions).
Inside your layout component, it works just like a normal Vite + Vue 3 application. Do note the theme also needs to be [SSR-compatible](./ssr-compat).
## Building a Layout

@ -0,0 +1,84 @@
---
outline: deep
---
# SSR Compatibility
VitePress pre-renders the app in Node.js during the production build, using Vue's Server-Side Rendering (SSR) capabilities. This means all custom code in theme components are subject to SSR Compatibility.
The [SSR section in official Vue docs](https://vuejs.org/guide/scaling-up/ssr.html) provides more context on what is SSR, the relationship between SSR / SSG, and common notes on writing SSR-friendly code. The rule of thumb is to only access browser / DOM APIs in `beforeMount` or `mounted` hooks of Vue components.
## `<ClientOnly>`
If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the built-in `<ClientOnly>` component:
```md
<ClientOnly>
<NonSSRFriendlyComponent />
</ClientOnly>
```
## Libraries that Access Browser API on Import
Some components or libraries access browser APIs **on import**. To use code that assumes a browser environment on import, you need to dynamically import them.
### Importing in Mounted Hook
```vue
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
import('./lib-that-access-window-on-import').then((module) => {
// use code
})
})
</script>
```
### Conditional Import
You can also conditionally import a dependency using the `import.meta.env.SSR` flag (part of [Vite env variables](https://vitejs.dev/guide/env-and-mode.html#env-variables)):
```js
if (!import.meta.env.SSR) {
import('./lib-that-access-window-on-import').then((module) => {
// use code
})
}
```
Since [`Theme.enhanceApp`](/guide/custom-theme#theme-interface) can be async, you can conditionally import and register Vue plugins that access browser APIs on import:
```js
// .vitepress/theme/index.js
export default {
// ...
async enhanceApp({ app }) {
if (!import.meta.env.SSR) {
const plugin = await import('plugin-that-access-window-on-import')
app.use(plugin)
}
}
}
```
### `defineClientComponent`
VitePress provides a convenience helper for importing Vue components that access browser APIs on import.
```vue
<script setup>
import { defineClientComponent } from 'vitepress'
const ClientComp = defineClientComponent(() => {
return import('component-that-access-window-on-import')
})
</script>
<template>
<ClientComp />
</template>
```
The target component will only be imported in the mounted hook of the wrapper component.

@ -4,6 +4,10 @@ In VitePress, each Markdown file is compiled into HTML and then processed as a [
It's worth noting that VitePress leverages Vue's compiler to automatically detect and optimize the purely static parts of the Markdown content. Static contents are optimized into single placeholder nodes and eliminated from the page's JavaScript payload for initial visits. They are also skipped during client-side hydration. In short, you only pay for the dynamic parts on any given page.
:::tip SSR Compatibility
All Vue usage needs to be SSR-compatible. See [SSR Compatibility](./ssr-compat) for details and common workarounds.
:::
## Templating
### Interpolation
@ -216,63 +220,6 @@ Then you can use the following in Markdown and theme components:
</style>
```
## Browser API Access Restrictions
Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the [universal code requirements](https://vuejs.org/guide/scaling-up/ssr.html). In short, make sure to only access Browser / DOM APIs in `beforeMount` or `mounted` hooks.
If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the built-in `<ClientOnly>` component:
```md
<ClientOnly>
<NonSSRFriendlyComponent />
</ClientOnly>
```
Note this does not fix components or libraries that access Browser APIs **on import**. To use code that assumes a browser environment on import, you need to dynamically import them in proper lifecycle hooks:
```vue
<script>
export default {
mounted() {
import('./lib-that-access-window-on-import').then((module) => {
// use code
})
}
}
</script>
```
If your module `export default` a Vue component, you can register it dynamically:
```vue
<template>
<component
v-if="dynamicComponent"
:is="dynamicComponent">
</component>
</template>
<script>
export default {
data() {
return {
dynamicComponent: null
}
},
mounted() {
import('./lib-that-access-window-on-import').then((module) => {
this.dynamicComponent = module.default
})
}
}
</script>
```
**Also see:**
- [Vue.js > Dynamic Components](https://vuejs.org/guide/essentials/component-basics.html#dynamic-components)
## Using Teleports
Vitepress currently has SSG support for teleports to body only. For other targets, you can wrap them inside the built-in `<ClientOnly>` component or inject the teleport markup into the correct location in your final page HTML through [`postRender` hook](../reference/site-config#postrender).

@ -121,6 +121,8 @@ If you are using or demoing components that are not SSR-friendly (for example, c
</ClientOnly>
```
- Related: [SSR Compatibility](/guide/ssr-compat)
## `$frontmatter` <Badge type="info" text="template global" />
Directly access current page's [frontmatter](../guide/frontmatter) data in Vue expressions.

Loading…
Cancel
Save