Open yanbeixiang opened 2 months ago
无论你的构建配置或顶层框架的选择如何,下面的原则在所有 Vue SSR 应用中都适用。
在 SSR 期间,每一个请求 URL 都会映射到我们应用中的一个期望状态。因为没有用户交互和 DOM 更新,所以响应性在服务端是不必要的。为了更好的性能,默认情况下响应性在 SSR 期间是禁用的。
因为没有任何动态更新,所以像 onMounted或者 onUpdated这样的生命周期钩子不会在 SSR 期间被调用,而只会在客户端运行。 你应该避免在 setup() 或者 <script setup> 的根作用域中使用会产生副作用且需要被清理的代码。这类副作用的常见例子是使用 setInterval设置定时器。我们可能会在客户端特有的代码中设置定时器,然后在 onBeforeUnmount或 onUnmounted中清除。然而,由于unmount 钩子不会在 SSR 期间被调用,所以定时器会永远存在。为了避免这种情况,请将含有副作用的代码放到 onMounted中。
onMounted
onUpdated
setup()
<script setup>
setInterval
onBeforeUnmount
onUnmounted
unmount
比如 window 或 document
window
document
对于浏览器特有的 API,通常的方法是在仅客户端特有的生命周期钩子中惰性地访问它们,例如 onMounted。
请注意,如果一个第三方库编写时没有考虑到通用性,那么要将它集成到一个 SSR 应用中可能会很棘手。你或许可以通过模拟一些全局变量来让它工作,但这只是一种 hack 手段并且可能会影响到其他库的环境检测代码。
组件模板中存在不符合规范的 HTML 结构,渲染后的 HTML 被浏览器原生的 HTML 解析行为纠正导致不匹配。举例来说,一个常见的错误是
中
<p><div>hi</div></p>
渲染所用的数据中包含随机生成的值。由于同一个应用会在服务端和客户端执行两次,每次执行生成的随机数都不能保证相同。避免随机数不匹配有两种选择:
服务端和客户端的时区不一致。有时候我们可能会想要把一个时间转换为用户的当地时间,但在服务端的时区跟用户的时区可能并不一致,我们也并不能可靠的在服务端预先知道用户的时区。这种情况下,当地时间的转换也应该作为纯客户端逻辑去执行。
因为大多数的自定义指令都包含了对 DOM 的直接操作,所以它们会在 SSR 时被忽略。但如果你想要自己控制一个自定义指令在 SSR 时应该如何被渲染 (即应该在渲染的元素上添加哪些 attribute),你可以使用 getSSRProps 指令钩子:
const myDirective = { mounted(el, binding) { // 客户端实现: // 直接更新 DOM el.id = binding.value }, getSSRProps(binding) { // 服务端实现: // 返回需要渲染的 prop // getSSRProps 只接收一个 binding 参数 return { id: binding.value } } }
在 SSR 的过程中 Teleport 需要特殊处理。如果渲染的应用包含 Teleport,那么其传送的内容将不会包含在主应用渲染出的字符串中。在大多数情况下,更推荐的方案是在客户端挂载时条件式地渲染 Teleport。
如果你需要激活 Teleport 内容,它们会暴露在服务端渲染上下文对象的 teleports 属性下:
const ctx = {} const html = await renderToString(app, ctx) console.log(ctx.teleports) // { '#teleported': 'teleported content' }
跟主应用的 HTML 一样,你需要自己将 Teleport 对应的 HTML 嵌入到最终页面上的正确位置处。
书写 SSR 友好的代码
无论你的构建配置或顶层框架的选择如何,下面的原则在所有 Vue SSR 应用中都适用。
服务端的响应性
在 SSR 期间,每一个请求 URL 都会映射到我们应用中的一个期望状态。因为没有用户交互和 DOM 更新,所以响应性在服务端是不必要的。为了更好的性能,默认情况下响应性在 SSR 期间是禁用的。
组件生命周期钩子
因为没有任何动态更新,所以像
onMounted
或者onUpdated
这样的生命周期钩子不会在 SSR 期间被调用,而只会在客户端运行。 你应该避免在setup()
或者<script setup>
的根作用域中使用会产生副作用且需要被清理的代码。这类副作用的常见例子是使用setInterval
设置定时器。我们可能会在客户端特有的代码中设置定时器,然后在onBeforeUnmount
或onUnmounted
中清除。然而,由于unmount
钩子不会在 SSR 期间被调用,所以定时器会永远存在。为了避免这种情况,请将含有副作用的代码放到onMounted
中。访问平台特有 API
比如
window
或document
对于浏览器特有的 API,通常的方法是在仅客户端特有的生命周期钩子中惰性地访问它们,例如
onMounted
。请注意,如果一个第三方库编写时没有考虑到通用性,那么要将它集成到一个 SSR 应用中可能会很棘手。你或许可以通过模拟一些全局变量来让它工作,但这只是一种 hack 手段并且可能会影响到其他库的环境检测代码。
跨请求状态污染
激活不匹配
组件模板中存在不符合规范的 HTML 结构,渲染后的 HTML 被浏览器原生的 HTML 解析行为纠正导致不匹配。举例来说,一个常见的错误是
中
渲染所用的数据中包含随机生成的值。由于同一个应用会在服务端和客户端执行两次,每次执行生成的随机数都不能保证相同。避免随机数不匹配有两种选择:
服务端和客户端的时区不一致。有时候我们可能会想要把一个时间转换为用户的当地时间,但在服务端的时区跟用户的时区可能并不一致,我们也并不能可靠的在服务端预先知道用户的时区。这种情况下,当地时间的转换也应该作为纯客户端逻辑去执行。
自定义指令
因为大多数的自定义指令都包含了对 DOM 的直接操作,所以它们会在 SSR 时被忽略。但如果你想要自己控制一个自定义指令在 SSR 时应该如何被渲染 (即应该在渲染的元素上添加哪些 attribute),你可以使用 getSSRProps 指令钩子:
Teleports
在 SSR 的过程中 Teleport 需要特殊处理。如果渲染的应用包含 Teleport,那么其传送的内容将不会包含在主应用渲染出的字符串中。在大多数情况下,更推荐的方案是在客户端挂载时条件式地渲染 Teleport。
如果你需要激活 Teleport 内容,它们会暴露在服务端渲染上下文对象的 teleports 属性下:
跟主应用的 HTML 一样,你需要自己将 Teleport 对应的 HTML 嵌入到最终页面上的正确位置处。