服务端渲染(SSR)
在 SSR 场景使用 Uniboot UI 时,需要做额外处理以避免水合(hydrate)错误。
TIP
Nuxt 用户可使用我们提供的 Nuxt 模块,其中已包含相关处理,安装即可。
注入 ID
该值用于在 Uniboot UI 内生成唯一 ID。SSR 下若服务端与客户端 ID 不一致,容易引发水合错误,因此需要向 Vue 注入 ID_INJECTION_KEY。
ts
// 无关代码已省略
import { createApp } from 'vue'
import { ID_INJECTION_KEY } from 'uniboot-ui'
import App from './App.vue'
const app = createApp(App)
app.provide(ID_INJECTION_KEY, {
prefix: 1024,
current: 0,
})注入 ZIndex
SSR 开发中可能因 z-index 导致水合错误,建议注入初始值以避免。
ts
// 无关代码已省略
import { createApp } from 'vue'
import { ZINDEX_INJECTION_KEY } from 'uniboot-ui'
import App from './App.vue'
const app = createApp(App)
app.provide(ZINDEX_INJECTION_KEY, { current: 0 })Teleports
Uniboot UI 中多个组件(如 UDialog、UDrawer、UTooltip、UDropdown、USelect、UDatePicker 等)内部使用了 Teleport,SSR 时需要特别处理。
仅在客户端挂载后再渲染 Teleport
较简单的做法是在挂载后再渲染 Teleport。
例如在 Nuxt 中使用 ClientOnly:
html
<client-only>
<u-tooltip content="the tooltip content">
<u-button>tooltip</u-button>
</u-tooltip>
</client-only>或:
vue
<script setup>
import { ref } from 'vue'
const isClient = ref(false)
onMounted(() => {
isClient.value = true
})
</script>
<template>
<u-tooltip v-if="isClient" content="the tooltip content">
<u-button>tooltip</u-button>
</u-tooltip>
</template>注入 teleport 标记
另一种做法是将 teleport 生成的标记注入到最终 HTML 的正确位置。
需要将其放在靠近 <body> 标签处。
html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Uniboot UI</title>
<!--preload-links-->
</head>
<body>
<!--app-teleports-->
<div id="app"><!--app-html--></div>
<script type="module" src="/src/entry-client.js"></script>
</body>
</html>TIP
若修改了命名空间或 append-to 属性,需要相应调整 #el-popper-container- 相关占位。
js
// 无关代码已省略
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'
export async function render(url, manifest) {
// ...
const ctx = {}
const html = await renderToString(app, ctx)
const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
const teleports = renderTeleports(ctx.teleports)
return [html, preloadLinks, teleports]
}
function renderTeleports(teleports) {
if (!teleports) return ''
return Object.entries(teleports).reduce((all, [key, value]) => {
if (key.startsWith('#el-popper-container-')) {
return `${all}<div id="${key.slice(1)}">${value}</div>`
}
return all
}, teleports.body || '')
}js
// 无关代码已省略
const [appHtml, preloadLinks, teleports] = await render(url, manifest)
const html = template
.replace('<!--preload-links-->', preloadLinks)
.replace('<!--app-html-->', appHtml)
.replace(/(\n|\r\n)\s*<!--app-teleports-->/, teleports)