Head Management in Vue Apps
Tags: vue, ssr, seo
Published: 2020-07-25 12:49:47

_In which I go over your options for managing the &lt;head&gt; 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 &lt;head&gt; 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](https://ssr.vuejs.org/).

If you don't have a server then I've written a post on how to do it with AWS Lambda@Edge.
{% link divporter/vue-serverless-side-rendering-with-aws-lambda-edge-1ep8 %}

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** <a name="mixins"></a>
If you've gone through [Vue SSR guide](https://ssr.vuejs.org/) then you've probably had a crack at this method or at least seen it. 

First build the mixin.
{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=head.js %}

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.

{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=App.vue %}

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 -> `{{{ }}}`
{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=index.html %}

#### **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](https://nuxtjs.org/) framework. If you don't know what Nuxt.js is here is the summary.  Named after [Next.js](https://nextjs.org/) 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.
{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=AppMeta.vue %}

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 `vmid`s 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.

{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=entry-server.js %}

And in `index.html` it now looks like this:

{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=index-vue-meta.html %}

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](https://github.com/nfl/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](https://github.com/jnields/vue-helmet) implementation which supports SSR.

In our components we use it like this:
{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=AppHelmet.vue %}

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.

{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=main.js %}

And finally in `index.html`

{% gist https://gist.github.com/divporter/99ef185dbc804163370dca1a09ef2b6a file=index-vue-helmet.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](https://vue-meta.nuxtjs.org/). If you want to get your hands dirty then go for the [mixin](#mixins) option.