Subscribe on changes!

Teleport SSR mismatches and breaks the app

avatar
Jan 11th 2022

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:

Teleport demo

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
avatar
Feb 25th 2022
  • 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 failure

  • runtime-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);
        }
}
avatar
Feb 26th 2022

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.

avatar
May 18th 2022

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.

avatar
Jun 6th 2022

@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~

avatar
Jun 8th 2022

@mythshuazi Element使用的组件有些存在是否选用teleport的参数,这个可以试试,我的处理比较粗暴些,因为一般使用了teleport的组件都是些input select啥的,SEO的时候其实也不需要获取这些节点数据,因此就采用了页面加载完成后再渲染出来,直接v-if="mounted"

avatar
Jun 9th 2022

@mythshuazi Element使用的组件有些存在是否选用teleport的参数,这个可以试试,我的处理比较粗暴些,因为一般使用了teleport的组件都是些input select啥的,SEO的时候其实也不需要获取这些节点数据,因此就采用了页面加载完成后再渲染出来,直接v-if="mounted"

@Xing-He 我的处理方法和你差不多,但是这个repo vue3-ts-vite-ssr-starter搭的ssr 没有 mismatch的问题,我一直没看找到原因,一起看看?

avatar
Jun 21st 2022

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!