Add support for prerendering (SSG)
What problem does this feature solve?
I'm building a Vue 3 app and prerendering a few routes for SEO purposes, as recommended in the SSR guide. The meta tags are rendered using <teleport>
via vue-meta@3
.
From what I understand, prerendering is essentially starting an Express server in my /dist
dir and using puppeteer to visit each route and saving the DOM as /dist/about/index.html
etc.
Here's an example output file:
<!DOCTYPE html>
<html lang="en">
<head>
<title>About</title>
</head>
<body>
<div id="app"><!--teleport start--><!--teleport end--></div>
</body>
</html>
Currently, this DOM causes hydration mismatch with the client vnode being Symbol(Fragment)
and "server rendered DOM" being <!--teleport start-->
.
const app = createSSRApp(AppLoader)
app.mount('#app', true)
When I check the generated output for a real Vue 3 SSR app, I see that it's filled with <!--[--><!--]-->
comment nodes instead. As a result, I can only speculate that it's currently impossible to prerender Vue 3 apps without building an entire SSR app since @vue/server-renderer::renderToString
is required to generate the correct DOM.
What does the proposed API look like?
When createSSRApp
is called on the client side and have nothing to hydrate, assume it's being prerendered (or use an explicit flag on app.mount
) and generate the appropriate SSR tags
Alternatively, I think the prerender section in the SSR docs should be removed since it's no longer applicable/possible in Vue 3.
You don't need any SSR if you do pre-rendering, use a regular SPA. It is different from Static Site Generation BTW, which you can take a look at https://github.com/antfu/vitesse
Please, next time consider using the forum, the Discord server or StackOverflow for questions first. But feel free to come back and open an issue if it turns out to be a bug š
Maybe this title should omit "SSG" if my terminology is mixed up?
My issue is that prerendering seems to be impossible in Vue 3 since the DOM generated by an SPA cannot be hydrated later due to hydration mismatch.
This does not actually hydrate:
const app = createApp(App)
app.mount('#app', true) // This will delete my entire DOM and reconstruct the page and cause an unpleasant white flash.
The only way I've been able to find to hydrate in Vue 3 is:
const app = createSSRApp(App)
app.mount('#app', true)
Also <div id="app" data-server-rendered="true">
does not seem to do anything in Vue 3
Prerendering as you describe it is not meant to be hydrated. It's meant to create purely static pages.
If you want to hydrate, you should render the pages with an actual SSR setup. That will produce DOM that can actually be hydrated.
Edit: i misread. It seems you actually run a full Vue SSR setup from which you save the rendered output?
That should be hydratable. You should probably indeed ask for help in the community as it why that fails
About your comment concerning the mention of Prerendering in the SSR docs: It explicitly states that preerendering is used to create a fully static page (no client side JS).
No I don't have any SSR right now
Prerendering as you describe it is not meant to be hydrated. It's meant to create purely static pages.
Thanks for confirming my suspicions. I will be rewriting my app into SSR to fix my issue
The process that I've described for "prerendering" is exactly what the prerender-spa-plugin
does (this plugin was recommended in the official docs)
About your comment concerning the mention of Prerendering in the SSR docs: It explicitly states that preerendering is used to create a fully static page (no client side JS).
The docs only states The advantage is setting up prerendering is much simpler and allows you to keep your frontend as a fully static site.
. I interpreted this as referring to static html
js
css
(aka JAMstack) and can be hosted in any static host (S3) rather than a full node environment.
Well, as I said, I think that if you prerender in the sense of saving the output of a SSR-rendered app, it should be hydratable.
If you have troubles with that, find help in the community. If you think you found a bug, please open a bug issue with a reproduction.
@Trinovantes Hi! So, do you resolve your issue? Iām migration from vue2 to vue3 and have the same problem with prerender spa plugin and hydration.
Vue3 cannot be prerendered
You need to either:
- Rewrite to SSR
- Delete all
<script>
tags in thepostProcess
step - Delete all contents of your
<body>
in thepostProcess
step - Just accept the FOUC
We are facing the same problem: migration from vue2 to vue3, we were using prerender strategy instead of SSR. Our code is currently not ready without a lot of effort for SSR, and with vue3 migration, hydration from a prerender is impossible
- if you use createApp, mount does not take into account the second param, and erase all the DOM no matter what, leading to performance issue on our part
- if you use createSSRApp, hydration fail because it's expecting to have special markup only generated by renderToString
I have a question in the process of using it, can I avoid hydrate and only render the renderToString output?
This is such an essential feature. Would've been amazing if it was provided by the framework. It's very odd that Vue 3 SSR documentation points to prerender-spa-plugin
when it clearly doesn't work.
I think the crux of the problem is that Vue3 will not hydrate prerendered HTML
In Vue2 there was a kind of hack to add data-server-rendered="true"
to the dom element that Vue mounts to in your prerendered HTML. Then Vue2 would hydrate that prerendered HTML.
Now in Vue3 there is no such option. So static sites that use Vue3 and prerender their HTML have a flash as Vue3 takes over and rerenders the entire dom.
So I think the feature request here is to have Vue3 hydrate prerendered HTML similar to Vue2