Teleport SSR mismatches and breaks the app
Version
3.2.26
Reproduction link
This is the simplest project created using vue-cli-service with 4 pages with one happy path and three teleport scenarios:
Steps to reproduce
Setup the project:
npm install
npm run build
Start the server (SSR):
npm run serve-prod
Access to the home and browse all pages, to see the expected behaviour (everything works fine).
Then access to the one teleport, two teleports and nested teleport cases (direct access or page refresh to trigger the SSR) to experiment several kinds of errors (see additional comments below).
What is expected?
Client and server HTML should match, and so there shouldn't be SSR mismatches. Even when there are no mismatches, the app should not break after changing the view (one teleport case).
What is actually happening?
The hydration fails for the two and nested teleports cases. In all cases where a teleport is SSRed, the app fails (disappears from the DOM) when trying to change the view.
All cases tested:
Happy path
Access to the home at http://localhost:8010/ and browse through the different pages:
- Home
- One teleport
- Two teleport
- Nested teleport
Everything works as expected. Teleports appear and disappear on all views as they should do.
One teleport case
Access to http://localhost:8010/one-teleport directly to trigger the SSR. What happens:
- No hydration errors
- After browsing to another page, the app disappears from the DOM.
Two teleport case
Access to http://localhost:8010/two-teleport directly to trigger the SSR. What happens:
- Hydration errors on the console
- After re-rendering (due to hydration errors), the second teleport appears twice in the DOM.
- After browsing to another page, the app disappears from the DOM.
Nested teleport case
Access to http://localhost:8010/nested-teleport directly to trigger the SSR. What happens:
- Hydration errors on the console
- After re-rendering (due to hydration errors), the app disappears from the DOM
SSR + Vite + Vue3 + Element-Plus Same error with teleport, but i got an "null" access error as below.
and then, all router operations will be a failureruntime-core.esm-bundler.js#L4807
if (shapeFlag & 64 /* TELEPORT */) {
vnode.type.remove(vnode, parentComponent, parentSuspense, optimized, internals, doRemove);
}
runtime-dom.esm-bundler.js#L13
remove: child => {
// ERROR -> child is null
const parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
}
After searching Element-plus's issues, I found that it may be an element issue. Now, I have to ignore rendering components using teleport at SSR, instead of rendering by CSR after mounted.
General note: avoid targeting body
with Teleport during SSR, because this makes it impossible for the Teleport to locate the correct starting location for hydration with other potential content. Instead, use a dedicated element:
<body>
<div id="app">...</div>
<div id="teleports"><!-- target teleports here --></div>
</body>
With this structure, both single and double teleports work as intended.
Nested teleports are supported after https://github.com/vuejs/core/commit/595263c0e9f5728c3650c6526dbed27cda9ba114.
@Xing-He I use Element-plus get the same warn tips. How did you solve the problem?Can you provide your code reference? Thanks a lot~
@mythshuazi Element使用的组件有些存在是否选用teleport的参数,这个可以试试,我的处理比较粗暴些,因为一般使用了teleport的组件都是些input select啥的,SEO的时候其实也不需要获取这些节点数据,因此就采用了页面加载完成后再渲染出来,直接v-if="mounted"
@mythshuazi Element使用的组件有些存在是否选用teleport的参数,这个可以试试,我的处理比较粗暴些,因为一般使用了teleport的组件都是些input select啥的,SEO的时候其实也不需要获取这些节点数据,因此就采用了页面加载完成后再渲染出来,直接
v-if="mounted"
@Xing-He 我的处理方法和你差不多,但是这个repo vue3-ts-vite-ssr-starter搭的ssr 没有 mismatch的问题,我一直没看找到原因,一起看看?
Hi there, sorry for the late reply. I have updated the demo project to the latest Vue version and changed from body
to #body-teleports
and everything worked as expected. Thanks for the fix!