服务端渲染 (SSR)
当使用 Element Plus 进行 SSR 开发时,您需要进行特殊处理来避免 hydration 错误。
提示
对于 Nuxt 用户,我们提供了一个包含这些特殊流程的 Nuxt 模块。您只需要安装它。
提供 ID
在 Element Plus 中,提供的值用于生成唯一的 ID。由于在 SSR 中不同的 ID 容易出现 hydration 错误,为了确保服务器端和客户端生成相同的 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
Teleport 在 Element Plus 的多个组件(例如 ElDialog、ElDrawer、ElTooltip、ElDropdown、ElSelect、ElDatePicker...)中被内部使用,因此在 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)