服务端渲染 (SSR)
当使用 Element Plus 进行 SSR 开发时,您需要进行特殊的处理,以避免 hydrate 错误。
提示
对于 Nuxt 用户,我们提供了一个 Nuxt 模块,其中包含了这些特殊的处理过程。你只需要安装它即可。
提供一个 ID
所提供的值用于在 Element Plus 中生成唯一的 ID。由于在 SSR 中不同的 ID 容易出现 hydrate 错误,为了确保服务端和客户端生成相同的 ID,我们需要将 ID_injection_key 注入到 Vue 中。
ts
// irrelevant code omitted
import { createApp } from 'vue'
import { ID_INJECTION_KEY } from 'element-plus'
import App from './App.vue'
const app = createApp(App)
app.provide(ID_INJECTION_KEY, {
prefix: 1024,
current: 0,
})提供 ZIndex
当您使用 SSR 进行开发时,您可能会遇到由 z-index 引起的水合 (hydration) 错误。在这种情况下,我们建议注入一个初始值以避免此类错误。
ts
// irrelevant code omitted
import { createApp } from 'vue'
import { ZINDEX_INJECTION_KEY } from 'element-plus'
import App from './App.vue'
const app = createApp(App)
app.provide(ZINDEX_INJECTION_KEY, { current: 0 })Teleports
Element Plus 中的多个组件(如 ElDialog, ElDrawer, ElTooltip, ElDropdown, ElSelect, ElDatePicker ...)内部使用了 Teleport,因此在 SSR 期间需要进行特殊处理。
在挂载时渲染 Teleport
一个更简单的解决方案是在挂载时有条件地渲染 Teleport。
例如,在 Nuxt 中使用 ClientOnly 组件。
html
<client-only>
<el-tooltip content="the tooltip content">
<el-button>tooltip</el-button>
</el-tooltip>
</client-only>或
vue
<script setup>
import { ref } from 'vue'
const isClient = ref(false)
onMounted(() => {
isClient.value = true
})
</script>
<template>
<el-tooltip v-if="isClient" content="the tooltip content">
<el-button>tooltip</el-button>
</el-tooltip>
</template>注入 teleport 标记
另一种方法是将 teleport 标记注入到最终页面 HTML 的正确位置。
你需要将 teleport 标记注入到靠近 <body> 标签的位置。
html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Element Plus</title>
<!--preload-links-->
</head>
<body>
<!--app-teleports-->
<div id="app"><!--app-html--></div>
<script type="module" src="/src/entry-client.js"></script>
</body>
</html>提示
如果你修改了 命名空间 或 append-to 属性,你需要调整 #el-popper-container- 的值。
js
// irrelevant code omitted
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
// irrelevant code omitted
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)