Vue: Official way to modify page metadata/title/etc.

Created on 3 Dec 2016  ·  25Comments  ·  Source: vuejs/vue

As briefly discussed here: https://github.com/vuejs/vue-hackernews-2.0/issues/78#issuecomment-264642351

After a quick check I have found there are at least 3 separate 3rd party libraries attempting to tackle this problem. None of them are particularly complete. The only one that handles server-side rendering is https://github.com/declandewet/vue-meta, and it is still quite buggy. The maintainer has told me that he doesn't have time at the moment to work on it due to working on his real project.

Adjusting page metadata is something that every serious site needs. It is as important as server-side rendering, and goes hand-in-hand with it.

There should probably be an official way to handle it, just like there is an official routing library, which is beautifully integrated into Vue and will always be kept up-to-date.

feature request

Most helpful comment

Ok. I don't have any experience with SSR.
But since it is 2017 I update my page titles and descriptions on SPA misused websites
on the client and regarding the only search engine with relevance in my region it is working quite ok.

I have Single File Components with the following code:

created: function() {  
  document.title = 'New Title'  
  document.head.querySelector('meta[name=description]').content = 'New Description'  
}

All 25 comments

Manually solved this issue by returning third argument as component.$data from "renderToString" function.. now I have all Component data in my server data variable:

bundleRenderer.renderToString((err, html, data) => { ... });

I hope in future update we will see this decision.

Meta information for SEO purposes doesn't need to be dynamically updated if at all.

This is due to services mechanisms to query your website, for a page URL they will index by the initial synchronous request. That means that even if you update via JS, search engines will most likely not index this way probably won't ever as this is indeterministic.

The best way to optimize for SEO purposes is to statically generate your content for consumption by web crawlers. Or manipulate the head with the correct meta content, at server level. This is fairly simple to achieve by injecting the content into the head of the served document.

The only real exception for user experience is to dynamically update the title of the document. Other than that there is no true benefit of updating the title dynamically.

There are exceptions such as AddThis which will require manipulation via there client API to ensure that on using the client functionality that the content is set as preferred.

Plugins that manipulate the while 'head' of the document are generally fairly expensive for there purpose. There is probably no reason you should be updating the head completely to regenerate the meta data client side.

That said, it is certainly on our agenda, to provide some more information for best practices when it comes to this common scenarios. We are planning to form a cookbook to provide this sort of information.

Perhaps asking in the Vue forum may be of aid, as there may be others that could provide there opinions in this subject.

My best advice if you are concerned with providing meta content to the head would be:

(Assuming you are using state management and you are able to provide to the context of the server renderer)

  • Add place holders to the html document template like {{ SEO_DESCRIPTION }}
  • Create a store for SEO, updating with correct data
  • When serving the head of document ect, replace the placeholders with correct data.

If using any client base tools, you can use the store data and update the title as necessary.

If you have any more questions or concerns let me know.

Considering mine is an SPA, and Vue's main use case is probably SPAs, I do think it is important to reactively update the title for a better user experience.

As you put it, it sounds fairly straightforward, so we can also assume that it wouldn't take much to integrate such a capability into Vue itself, then?

For user experience, in a SPA it's as simple as updating the title on a route change.

However, it's not deterministic when it may be needed to update. For example some use cases could dictate opening a model window is enough to change the title.

This is simple JavaScript, inevitably creating an abstraction in Vue to just change the documents title is too much. As said if using as a SPA, which now days is becoming and considered an anti-pattern, then your only real focus on meta data is the title for the UI experience. Which is simple to do and even a plugin would not cater for many use cases, and will essentially be a wrapper to change the document title.

The real problem with dynamic meta information, falls to the server level. At server level many tools can be used, many template engines and many other variants. Thus, suggestions and advice to a simple solution is best IMHO.

If Vue dictates a setup or we officially stamp approval on a plugin, it will prevent choice of tooling out of scope of Vue. Thus these are problem domains that are subject to what tools you use, which Vue should avoid creating friction with.

My current project has a modal, and yes, the title and other metadata (og image, etc.) need to change depending on what the modal is showing. It doesn't matter that it's just a modal, and Vue shouldn't care that it's just a modal. It's declared as a child in vue-router just like any other child. I should be able to just include the required metadata changes in the component declaratively, kinda how vue-meta works. As you said, changing something like the og image is obviously important to be handled by the server, but Vue doesn't offer a _clear_ way of going about that.

As for saying SPA is an anti-pattern, well that's just going with the current winds of JavaScript trends, which change every few months. As Evan pointed out in a recent tweet: "People like different things, you know. I wish more programmers understood that too." And in many projects, an SPA makes the most sense.

On the client, what depends on the OG image meta data?

If a SPA it's very unlikely that search engines / Facebook ect will pick up the correct meta information if you are dynamically updating. As this update will be asyncronous, clever search engines will attempt to but if you are relying on requested data after page has loaded then this will not be picked up.

Could you explain you use case in more detail? So I can understand how a tool like vue-head / vue-meta makes sense to your application.

Yes, that's why I said:

changing something like the og image is obviously important to be handled by the server

That is only important for when something like Facebook gets this information from the page. It doesn't matter on the client.

The title and theme are two examples of metadata that _does_ matter on the client.

This is why it is important that handling of metadata is correct _both_ on the client _and_ on the server.

Projects like vue-meta matter because they prevent many devs "reinventing the wheel" of having declarative updating of page metadata, which is handled in a consistent way both on the client and with server-side rendering.

For example, if you have a list of items and when a user clicks on one you want a page with more details to open, you want it to open instantly since you already have the data (SPA is awesome) or very quickly because you just need to grab a couple of small bits of data (SPA still awesome). But, you also want this data page to be directly-accessible. In a SSR app the user would get this page pre-rendered with all the correct page metadata, which should be the same as if the user got there instantly from clicking a list item. In that case, the page should also have the correct metadata applied.

That is just one example.

It doesn't make sense to me that Vue has an excellent way of keeping the URL in sync with these navigational changes but does not have a sensible way to keep metadata in sync along with it.

@blake-newman How can I tell what page the user is on in order to display the right data? Could you give a more in-depth response on how to do this? Thanks!

Ok. I don't have any experience with SSR.
But since it is 2017 I update my page titles and descriptions on SPA misused websites
on the client and regarding the only search engine with relevance in my region it is working quite ok.

I have Single File Components with the following code:

created: function() {  
  document.title = 'New Title'  
  document.head.querySelector('meta[name=description]').content = 'New Description'  
}

Hi guys, I join this conversation cause I been burning my brains out trying to figure out how to do this.

I'm making a news site that fetches data from the wordpress api (we're using wordpress for the client to upload the news).

So naturally I made dynamic routes with the name of the news post in it like so: www.mywebsite.com/news/a-news-title

So the problem is, one of the requirements is that users should be able to copy and paste that link on facebook and get the right preview (the news post image, title and short description).

So first I tried my hands at the vue-meta plugin, which does work, I can see the meta tags updating when I inspect the page BUT when I paste the link on facebook I don't get the preview for the news post.

After a lot of googling my guess is that the facebook crawler does not wait for my site to run the javascript code and update the metadata before reading the og:tags and that's why the right information is not showing. Am I Right?

Is the only solution to my problem to use SSR to render these news posts? or is there another way?

I have zero experience with ssr.

@leocirculo You can either use SSR or pre-rendering. Since you are using wordpress, pre-rendering may be the best way to go. https://www.npmjs.com/package/prerender-spa-plugin

A userland solution is now documented in https://ssr.vuejs.org/en/head.html
Alternatively there are community solutions and Nuxt also handles this for you.

@ruchern I don't understand what goal you want to reach.

The ability to change the title seems to me the basic and thus fundamental feature.

I have a site developed with PHP and now I am doing a kind of practice to re-write it with React (Done) and Vue so that I can assess how convenient it would be.

My site is a book collection site, so to change the title of my dynamic page (site.com/books/12345.html) to reflect the book title this page is on is quite useful. I also need to update the description and keywords section to include the title, author, publisher, tags, etc.

Just a piece of my thought.

Changing title and meta is really important for professional websites, can't live without it. Vue team should take this as high priority and make this happen. How could this simple feature made unavailable to Vue? unbelievable.

Based on https://ssr.vuejs.org/en/head.html i create a function reusable, like the documentation expose i create mixin to handle a head, for now title and metas...

I have some questions about that, but i want to know what's your opinion about my code....

  1. I create a new mixin with the name head.js... this looks like this:
const cleanMetas = () => {
  return new Promise ((resolve, reject)=>{
    const items = document.head.querySelectorAll('meta')
    for(const i in items) {
      if(typeof items[i]==='object' && ['viewport'].findIndex(val=>val===items[i].name)!=0 && items[i].name!=='')
        document.head.removeChild(items[i])
    }
    resolve()
  })
}

const createMeta = (vm, name, ...attr) => {
  const meta = document.createElement('meta')
  meta.setAttribute(name[0], name[1])
  for(const i in attr){
    const at = attr[i]
    for(const k in at) {
      meta.setAttribute(at[k][0], getString(vm, at[k][1]))
    }
  }
  document.head.appendChild(meta);
}

const getString = (vm, content) => {
  return typeof content === 'function'
    ? content.call(vm)
    : content
}

export const getMeta = (vm, meta, env) => {
  if(typeof meta !== 'object')
    return

  if(env){
    return Object.keys(meta)
    .map(value => {
      return Object.keys(meta[value])
        .map(key => `${key}="${getString(vm, meta[value][key])}"`)
        .join(" ");
    })
    .map(value => `  <meta ${value} >`)
    .join("\n");

  } else {
    return meta
  }
}

const serverHeadMixin = {
  created () {
    const { head } = this.$options
    if(head){
      const { title } = head 
      if(title){
        this.$ssrContext.title = `${getString(this, title)} :: Vue SSR`
      }

      const { meta } = head 
      if(meta)
        this.$ssrContext.meta = `\n${getMeta(this, meta, true)}`
    }
  }
}

const clientHeadMixin = {
  mounted () {
    const vm = this

    const { head } = this.$options
    if(head){
      const { title } = head 
      if(title){
        document.title = `${getString(this, title)} :: Vue SSR`
      }

      cleanMetas().then(()=>{
        const { meta } = head 
        if(meta){
          for(const nm in meta) {
            const name = Object.entries(meta[nm])[0]
            const attr = Object.entries(meta[nm]).splice(1,Object.entries(meta[nm]).length)
            createMeta(vm, name, attr)
          }
        }
      })
    }
  }
}

export default process.env.VUE_ENV === 'server'
  ? serverHeadMixin
  : clientHeadMixin

  1. And add to src/app.js
...
import headMixin from './util/head'

Vue.mixin(headMixin)
...
  1. Set up my components like this:
<script>
  export default {
    head: {
      title() {return `${this.name}  con `} ,
      meta: [
        { name: 'description', content() { return `El perfil de ${this.name}`} },
        { name: 'keywords', content() { return `${this.name} ${this.lastname} ${this.email}` } },
        { name:"twitter:card", content:"summary"},
        { name:"twitter:site", content:"@jqEmprendedorVE"},
        { name:"twitter:creator", content:"@jqEmprendedorVE"},
        { name:"twitter:url", content:"https://vue-firebase-ssr.firebaseapp.com"},
        { name:"twitter:title", content() {return `${this.name} creando Vuejs SSR + Firebase`}},
        { name:"twitter:description", content:"Modelo de Vuejs SSR con Firebase Cloud Function + Hosting"},
        { name:"twitter:image", content:"https://www.filepicker.io/api/file/nS7a8itSTcaAsyct6rVp"}
      ]
    },
    asyncData ({ store, route }) {
      // return the Promise from the action
      return store.dispatch('fetchItem', 1)
    },
    computed: {
      // display the item from store state.
      item () {
        return this.$store.state.items[1]
      },
      name () {
        return this.$store.state.items[1].nombre
      },
      lastname () {
        return this.$store.state.items[1].apellido
      },
      email () {
        return this.$store.state.items[1].correo
      }
    },
    data() {
      return {
        data: ':: SSR'
      }
    },
    created() {
      this.$firebase.db().ref('data').once('value', snapshot=>{
        // console.log(snapshot.val())
      })
    }
  }
</script>
  1. Finally my template look like this:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ title }}</title>{{{ meta!=='' ? meta : '' }}}
  <meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">
</head>
<body>
  <!--vue-ssr-outlet-->
</body>
</html>

I don't know if that is a good practices, but is my first intent in manage the head tag...

My first question. Is really necessary show de meta in the client? what is the benefys. Anyway, like a in the title, in my code i recreate metas for the client, like as updated the title... what's your opinion about that

DEMO
Repo

As I don't like setting these properties in the controllers, I created a component with which you can set them in the views. The project is called vue-headful and based on Headful, a generic library to set meta tags with JavaScript. vue-headful supports setting the title, description, keywords, language and some other properties.


How to use vue-headful

Register the component:

import Vue from 'vue';
import vueHeadful from 'vue-headful';

Vue.component('vue-headful', vueHeadful);

new Vue({
    // your configuration
});

And then use the vue-headful component in every of your views:

<template>
    <div>
        <vue-headful
            title="Title from vue-headful"
            description="Description from vue-headful"
        />
    </div>
</template>

@johnleider I have the same issue, but I believe that it is unclear if pre render will work because of this text on site.

Now, here's where prerendering isn't appropriate:

User-specific content: For a route like /my-profile, prerendering won't be effective, because the
content of that page will be very different depending on who's looking at it. You can sometimes
update your routing strategy to compensate, e.g. with /users/:username/profile, but only if these

are public profiles. Otherwise, you risk leaking private information to the world

It says not appropriate for user specific content....sometimes update routing strategy...

I got the answer.
All I had to do was use 2 plugins - vue-headful and prerender-spa-plugin.
@troxler sorry for the downvote man, the information that you have provided was already there with the vue-headful plugin, the problem was with the rendering of the SPAs for CRAWLERs.
Please check this - https://www.youtube.com/watch?v=HWDcSRHBC9M

@siddhartharora02 Downvoting my post first and then asking for help seems rather impolite to me. Why should I even invest my time to help you when all I get in return is a downvote?

Anyway, vue-headful sets the title with JavaScript. Google and other search engines do support running scripts when indexing your page (with some caveats). By "google crawl tool", do you mean "Fetch as Google" which is part of the Google Search Console? If yes, which button did you click: "Fetch" or "Fetch and Render"?

You might want to have a look at the help article about "Use Fetch as Google for websites":

Fetch: Fetches a specified URL in your site and displays the HTTP response. Does not request or run any associated resources (such as images or scripts) on the page. [...]

Fetch and render: Fetches a specified URL in your site, displays the HTTP response and also renders the page according to a specified platform (desktop or smartphone). This operation requests and runs all resources on the page (such as images and scripts). [...]

Ok. I don't have any experience with SSR.
But since it is 2017 I update my page titles and descriptions on SPA misused websites
on the client and regarding the only search engine with relevance in my region it is working quite ok.

I have Single File Components with the following code:

created: function() {  
  document.title = 'New Title'  
  document.head.querySelector('meta[name=description]').content = 'New Description'  
}

Great work, thanks.

I am surprised there's so much discussion whether the ability of changing meta tags or title is an important feature.

I am managing a large application. Content marketing, SEO, sharing on social media is important marketing tool. It's no question to me, whether it's a needed feature or not.

I went through available plugins. There's always something missing. I am using vue-server-render to serve complete application. It's already working fine, I took the example from vue-hackernews-2.0 template

What I would like to do now, is to change the meta tags, directly from component. Here's why.

Every route has a coresponding page, most of them are dynamic pages, for example product/:slug. When a page mounts, I am fetching corresponding data from API, in this example the product informaiton. From the same endpoint I would like to serve meta-data. Which means one api call per page. Other solutions propose to fetch data on server level. That would mean that I have to modify the componenets, to use this data. Or to make two API calls (and have additional API endpoints). It seems overly complicated to me, while the setup already is insanly complex.

For the proposed solution to work, I would have to change the meta tags from component level.

  • @jqEmprendedorVE I've tried your solution, but the meta ends up in the body as Object [object]
  • I've tried to use vue-server-render, which exposes this.$ssrContext, except it doesn't expose it in my case, I am not sure why and how to fix it
  • I've tried @nuxt/vue-meta, which is not working, not to mention the entire @nuxt package has completely different architecture, to "simplify" things, which in my case would be much additional work fixing nuxt bugs or developing workarounds (things just don't work there as they are supposed to)

Does anyone have a solution to modify the tags from component level?

Ok. I don't have any experience with SSR.
But since it is 2017 I update my page titles and descriptions on SPA misused websites
on the client and regarding the only search engine with relevance in my region it is working quite ok.
I have Single File Components with the following code:

created: function() {  
  document.title = 'New Title'  
  document.head.querySelector('meta[name=description]').content = 'New Description'  
}

Great work, thanks.

This won't work through SSR, as document, or head is not defined. It would break SSR actually, unless you move the code to mounted event.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

guan6 picture guan6  ·  3Comments

robertleeplummerjr picture robertleeplummerjr  ·  3Comments

fergaldoyle picture fergaldoyle  ·  3Comments

6pm picture 6pm  ·  3Comments

bdedardel picture bdedardel  ·  3Comments