The example for using vue-meta with SSR and renderToString() is too brief and somewhat hackish. What is more, you have to give up many autoinjection features of SSR and hot reloading. Some of them can be restored manually with ctx.renderScripts(), ctx.renderStyles()... but others are much more difficult to implement.
I propose to use the template interpolation feature provided by vue-server-renderer. According to the documentation, this feature is used to implement head management.
On server-entry.js
import app from './app'
const router = app.$router
const meta = app.$meta()
export default (context) => {
router.push(context.url)
context.meta = meta.inject() // New, look at .inject()
return app
}
Make sure that the very same context used in server-entry.js is passed to renderToString() in your code.
On your template index.html:
<!DOCTYPE html>
<html data-vue-meta-server-rendered {{{ meta.htmlAttrs.text() }}}>
<head>
{{{ meta.meta.text() + meta.title.text() + meta.link.text() + meta.style.text() + meta.script.text() + meta.noscript.text() }}}
</head>
<body {{{ meta.bodyAttrs.text() }}}>
<!--vue-ssr-outlet-->
</body>
</html>
That's all.
Ok, after further testing I've encountered some cases in which the above doesn't work. The problem is that I was calling .inject() too early and some properties were not calculated yet. I'm not aware of any hook that let's you call .inject() after rendering the app and before constructing the template. Properly injecting vue-meta properties into the template is still doable after some changes.
import app from './app'
const router = app.$router
const meta = app.$meta()
export default (context) => {
router.push(context.url)
context.meta = { inject: function () { Object.assign(this, meta.inject()) }}
return app
}
Variable assignment and manipulations aren't allowed in the template, only simple expressions can be used. That is why I created a self injectable object. Just call meta.inject() at the beginning of the template and it will populate itself with the meta properties.
<!DOCTYPE html>{{ meta.inject() }}
<html data-vue-meta-server-rendered {{{ meta.htmlAttrs.text() }}}>
<head>
{{{ meta.meta.text() + meta.title.text() + meta.link.text() + meta.style.text() + meta.script.text() + meta.noscript.text() }}}
</head>
<body {{{ meta.bodyAttrs.text() }}}>
<!--vue-ssr-outlet-->
</body>
</html>
I know that this is rather ugly. It would be great to have more control over vue-server-renderer but so far this is the most that can be done.
+1
Most helpful comment
Ok, after further testing I've encountered some cases in which the above doesn't work. The problem is that I was calling
.inject()too early and some properties were not calculated yet. I'm not aware of any hook that let's you call.inject()after rendering the app and before constructing the template. Properly injectingvue-metaproperties into the template is still doable after some changes.Variable assignment and manipulations aren't allowed in the template, only simple expressions can be used. That is why I created a self injectable object. Just call
meta.inject()at the beginning of the template and it will populate itself with the meta properties.I know that this is rather ugly. It would be great to have more control over
vue-server-rendererbut so far this is the most that can be done.