Open michaelkplai opened 2 years ago
I’ve got a working local pnpm patch based on this Nuxt PR.
Misc. notes:
<div id=“…”></div>
. This wouldn’t be a huge compromise since Vue recommends dedicated containersDo you have a example of it causing a hydration error? I currently use Teleport perfectly fine within my vite-ssr app.
Is the element you're teleporting to rendered by Vue or outside of it like in the raw html?
I am teleporting to an element outside of Vue in the raw html.
I’ve created a simple reproduction on top of the vitesse template.
Oddly enough I’m not getting a hydration error. However, the teleported elements are not being rendered on the server (verified using view Page Source).
Has the problem been solved in the meantime?
@michaelkplai can you share please an example?
I have found a test in vue source code: ssrTeleport.spec.ts
And here we see how vite-ssr does renderToString
: src/vue/entry-server.ts#LL68C24-L68C38
And here the vue core docs about SSR and teleport: https://vuejs.org/guide/scaling-up/ssr.html#teleports
And here the nuxt code with html part bodyPrepend: renderer.ts#L308
And here an example: server.js
So, the problem adding a bodyPrepend
to SSR is, that the teleport are included twice in client (server side rendered teleports and client side teleports).
I haven't looked at this problem in the last year and have since migrated away from the library. I created a reproduction previously:
reproduction on top of the vitesse template.
Here's the pnpm patch I used before:
vite-ssr@0.16.0.patch
diff --git a/core/entry-server.js b/core/entry-server.js
index b4a4439f779ac6aa0c75960dd3e855797c75c50a..0d5c97420b0c6ad0e9b78b8403ebb2c261cc5398 100644
--- a/core/entry-server.js
+++ b/core/entry-server.js
@@ -52,6 +52,9 @@ export const viteSSR = function viteSSR(options, hook) {
htmlParts.headTags += renderPreloadLinks(htmlParts.dependencies);
}
}
+
+ htmlParts.teleports = context.teleports || {}
+
return {
html: buildHtmlDocument(template, htmlParts),
...htmlParts,
diff --git a/utils/html.js b/utils/html.js
index 9f8f2156aed52847626f1a2e934be8fa39441754..3694f165230ee7428dde8546fbc6e1ecb7e0c523 100644
--- a/utils/html.js
+++ b/utils/html.js
@@ -22,7 +22,7 @@ export function renderPreloadLinks(files) {
// @ts-ignore
const containerId = __CONTAINER_ID__;
const containerRE = new RegExp(`<div id="${containerId}"([\\s\\w\\-"'=[\\]]*)><\\/div>`);
-export function buildHtmlDocument(template, { htmlAttrs, bodyAttrs, headTags, body, initialState }) {
+export function buildHtmlDocument(template, { htmlAttrs, bodyAttrs, headTags, body, initialState, teleports }) {
// @ts-ignore
if (__DEV__) {
if (template.indexOf(`id="${containerId}"`) === -1) {
@@ -33,7 +33,7 @@ export function buildHtmlDocument(template, { htmlAttrs, bodyAttrs, headTags, bo
template = template.replace('<html', `<html ${htmlAttrs} `);
}
if (bodyAttrs) {
- template = template.replace('<body', `<body ${bodyAttrs} `);
+ template = template.replace('<body>', `<body ${bodyAttrs}>${ teleports.body || '' }`);
}
if (headTags) {
template = template.replace('</head>', `\n${headTags}\n</head>`);
Like described in the official documentation of vue, we should consider to use an own DOM node:
Avoid targeting body when using Teleports and SSR together - usually, will contain other server-rendered content which makes it impossible for Teleports to determine the correct starting location for hydration. Instead, prefer a dedicated container, e.g. <div id="teleported"></div>
which contains only teleported content.
We have two possible solutions:
<ClientOnly>
<Teleport to="body">
...
</Teleport>
</ClientOnly>
<Teleport to="#teleported">
...
</Teleport>
with index.html
like:
...
<body>
<div id="app"></div>
<div id="teleported"></div>
</body>
SSR rendering does something like:
teleported = context.teleports['#teleported']
...
// Replace <div id="teleported"> with <div id="teleported" data-server-rendered="true">${teleported}
This is a solution for body teleports and SSR only!
@michaelkplai please take a look at PR #207
I wanted to ask about support for Vue’s Teleport Component. Currently, using teleport leads to a hydration error.
Teleport SSR considerations have been documented here. Teleport content is exposed in the Vue context object and must be manually injected into the HTML.
I think this can be implemented by adding a teleports field to
DocParts
and adjusting buildHtmlDocument to inject the teleport content.In the beginning we can support only teleporting to body like Nuxt does. Additional teleport support could be added (full CSS selectors), but it would require a DOM parser or clever regex.
Please let me know if this is a feature we would be interested in adding. I’d be happy to look into creating a PR.