loading...

Head Management in Vue Apps

divporter profile image David Porter ・5 min read

In which I go over your options for managing the <head> section in Vue applications

If you've just polished off your first Single Page App (SPA) with Vue, you may have a missed a detail. All your pages have the same title; the title defined in index.html. If you don't have this problem then well done to you! If you do have this problem and you want to know what you can do then read on.

Why do I need to worry about it?

For the client

Not a huge deal, but without head management all of your pages will have the same title, so no matter what page your user is on, the browser tab will read the same.

For the web crawler

Oops, this is a big deal. Having the same title, meta, structured data will be the same for all pages and this will impact your Search Engine Optimisation (SEO). I'm not an SEO expert so I won't dwell too much on why, but at the very least your Google search results will all look the same if your <head> section is identical for all pages. At the worst Google may treat all your pages as duplicates and only list your home page.

How do I do it?

Before we launch straight into it I'm going to assume you've got some Server Side Rendering (SSR) in place. If you don't then checkout out the Vue SSR Guide.

If you don't have a server then I've written a post on how to do it with AWS Lambda@Edge.

It's important to have SSR in place otherwise search engines and other web crawlers will not see your efforts. They will only see index.html.

Target Output

Here's what we're hoping our SSR rendered application looks when we're done.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Parent Title</title>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <meta name="keywords" content="vue head management">
    <meta name="description" content="Description set by Parent">

    <script type="application/ld+json">
        {
        "@context": "http://schema.org",
        "@type": "WebSite",
        "name": "Parent Title",
        "description": "Description set by Parent"
        }
    </script>

    <link rel="preload" href="/static/js/main.3e60a259.js" as="script">
</head>

<body>
  <div data-server-rendered="true">
    <h2>Parent</h2> 
  </div>
  <script src="/static/js/main.3e60a259.js" defer></script>
</body>
</html>

1. Mixins

If you've gone through Vue SSR guide then you've probably had a crack at this method or at least seen it.

First build the mixin.

Now use the mixin in a component. First import the mixin as standard. In the created/mounted hooks these mixins will look for a title and/or meta key. You can either pass a string or a function.

Finally in our SSR html template we have access to our title string and meta object and we can reference these inside mustache tags. If you need to use the raw output use a triple mustache -> {{{ }}}

Advantages

  • We have full control of our head management

Drawbacks

  • Lots of code to get it going

2. Vue-Meta

Part of the Nuxt.js framework. If you don't know what Nuxt.js is here is the summary. Named after Next.js for React, Nuxt is a complete framework based on Vue, which includes routing, state management, SSR and more. Nuxt handles all the heavy lifting so you can rapidly develop fully featured web applications.

Enough about Nuxt. Vue-Meta is a quick and easy-to-use library for head management. Let's see it in action.

First include it in your application

import VueMeta from 'vue-meta'

Vue.use(VueMeta)

Here's an example of how you include it in your components.

Note the vmid, this is important if you have child components that may overwrite the meta information. If you don't use the same vmid it will write duplicate tags in the output html (which is bad), so make sure you have matching vmids for the same meta name. The same goes for script tags with application/json+ld types which house structured data.

Now to include this in our SSR we need to make a little change to our entry-server.js file and add the vue-meta results to the context.

And in index.html it now looks like this:

Much nicer! OK, {{{ meta.inject().title.text() }}} is a bit of a mouthful but now we don't have to write and manage our own mixins.

3. Vue Helmet

A nod to React here, React Helmet is a head management library for React via reusable components. Vue Helmet is inspired by the React version and there are a couple of implementations floating around. In this example I'm using the @jnields/vue-helmet implementation which supports SSR.

In our components we use it like this:

Note this:

<helmet-provider :context="context">

We need to pass the application context as a prop to HelmetProvider and so we need to modify main.js a little to pass the context through.

And finally in index.html

Easier than the mixin option, but in my opinion not as nice as the Vue Meta option. I find it nicer to work with Objects than with components.

One issue I did find is that if you define a <script type="application/ld+json"> tag in a parent component and then try it overwrite it in a child component it will generate two script tags. I haven't tested how Google feels about this, but results are likely to be unreliable. If you recall I stressed the importance of setting the vmid in your Vue Meta objects. Unfortunately we don't have that option here, but perhaps one of us should submit a PR for it.

Anyway there you have it. Make your own mind up but I recommend using Vue Meta. If you want to get your hands dirty then go for the mixin option.

Posted on by:

divporter profile

David Porter

@divporter

Serverless and SPA/PWA enthusiast

Discussion

pic
Editor guide