This post is an addition to "Vue.js server side rendering with ASP.NET Core", written by Mihály Gyöngyösi. Check it out before reading this post!
There are good reasons to have your meta tags resolved through SSR. Most prominently SEO, but also a good alternative to the people without javascript enabled in their browsers.
To enable meta tags in your server rendered application you need to do a couple of things.
npm install vue-meta
and register the package.- Expose the resolved meta object in the
render.js
file. - Load that object through
ViewBag
.
Adding vue-meta to the app
We'll be using the vue-meta
package. This package gives you the possibility to add a property in the vue component to define the tags that belong in the head tag.
<template>
...
</template>
<script>
export default {
metaInfo: {
title: 'My Example App', // set a title
titleTemplate: '%s - Yay!', // title is now "My Example App - Yay!"
htmlAttrs: {
lang: 'en',
amp: undefined // "amp" has no value
}
}
}
</script>
Very powerful. To utilize this in the application you must register this package in vue.js.
import Vue from 'vue';
import VueMeta from 'vue-meta'
Vue.use(VueMeta);
You can read more about this package on the vue-meta github page. The above code will insert $meta
into your vue.js
instance. The client side rendering will be handled in the module without any additional configuration. To make this work with SSR, however, will require some extra setup.
Hooking up vue-meta to the server.js file
Now we have $meta
exposed in our instance, we may access it from the server loaded files.
import { app, router, store } from './app';
const meta = app.$meta();
export default context => {
return new Promise((resolve, reject) => {
router.push(context.url);
context.meta = meta;
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject({ code: 404 });
}
Promise.all(matchedComponents.map(component => {
if (component.asyncData) {
return component.asyncData({
store,
route: router.currentRoute
});
}
})).then(() => {
context.state = store.state;
resolve(app);
}).catch(reject);
},
reject);
});
};
$meta
is initialized and delivered to the context. The context now contains a "meta" property. Now we can add that to the module that renders the markup and delivers it to the controller.
process.env.VUE_ENV = 'server';
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, '../wwwroot/dist/main-server.js');
const code = fs.readFileSync(filePath, 'utf8');
const bundleRenderer = require('vue-server-renderer').createBundleRenderer(code);
const createServerRenderer = require('aspnet-prerendering').createServerRenderer;
module.exports = createServerRenderer(function (params) {
return new Promise(function (resolve, reject) {
const context = { url: params.url };
bundleRenderer.renderToString(context, (err, resultHtml) => {
if (err) {
reject(err.message);
}
const {
title,
link,
style,
script,
noscript,
meta
} = context.meta.inject();
const metadata = `${meta.text()}${title.text()}${link.text()}${style.text()}${script.text()}${noscript.text()}`;
resolve({
html: resultHtml,
globals: {
__INITIAL_STATE__: context.state,
Metadata: metadata
}
});
});
});
});
We generate a section of markup that vue-meta
returns in a string. This value will be resolved and injected into JavascriptServices.
Render the markup!
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.Prerendering;
namespace MySite.Controllers
{
public class HomeController : Controller
{
private readonly ISpaPrerenderer _spaPrerenderer;
public HomeController(ISpaPrerenderer spaPrerenderer)
{
_spaPrerenderer = spaPrerenderer;
}
public async Task<IActionResult> Index()
{
var result = await _spaPrerenderer.RenderToString("./Vue/render");
ViewData["PrerenderedHtml"] = result.Html;
ViewData["PrerenderedGlobals"] = result.CreateGlobalsAssignmentScript();
ViewData["PrerenderedMetadata"] = result.Globals["Metadata"];
return View();
}
}
}
The part where this post deviates from the original is delivering the rendered html and initial state in the Controller. In addition we expose the meta property in the ViewBag. Now we can add this property in the "_Layout.cshtml" view. Replace the asp-prerender
tags with the resolved rendered html.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no" />
@Html.Raw(ViewData["PrerenderedMetadata"])
@RenderSection("styles", required: false)
</head>
<body>
@RenderBody()
@RenderSection("scripts", required: false)
</body>
</html>
Remember to add the rendered app as well!
<div id="app-root">@Html.Raw(ViewData["PrerenderedHtml"])</div>
<script>@Html.Raw(ViewData["PrerenderedGlobals"])</script>
Now you can reload the page and see that the tags in the head is rendered!
That concludes our vue-meta
.NET Core SSR tutorial. I hope you found this post useful :)