How to change page titles when using vue-router?

vue.jsVuejs2Vue Router

vue.js Problem Overview


I would like to specify my titles within the route definition if possible. What is normally specified in <head><title> and appears in the browser title bar.

I have my project set up as follows:

main.js

import Vue from 'vue'
import App from './App.vue'
import VeeValidate from 'vee-validate';
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(VeeValidate);
Vue.use(ElementUI);
Vue.config.productionTip = false

new Vue({
    router,
    render: h => h(App)
}).$mount('#app')

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Skills from './components/Skills.vue'
import About from './components/About.vue'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'skills',
      component: Skills,
      meta: { title: 'Skills - MyApp' } // <- I would to use this one
    },
    {
      path: '/about/:name',  // Add /:name here
      name: 'about',
      component: About,
      meta: { title: 'About - MyApp' }
    }
  ]
})

Preferably, I would want an automatic system instead of changing page title on the created function of every component. Thanks.

vue.js Solutions


Solution 1 - vue.js

You can use a navigation guard with the router definition:

import Vue from 'vue';

const DEFAULT_TITLE = 'Some Default Title';
router.afterEach((to, from) => {
    // Use next tick to handle router history correctly
    // see: https://github.com/vuejs/vue-router/issues/914#issuecomment-384477609
    Vue.nextTick(() => {
        document.title = to.meta.title || DEFAULT_TITLE;
    });
});

You'll need to change your export to:

const router = new Router({ ... });
...
export default router;

Or you can use an immediate watcher on your root component:

export default {
    name: 'App',
    watch: {
        $route: {
            immediate: true,
            handler(to, from) {
                document.title = to.meta.title || 'Some Default Title';
            }
        },
    }
};

Solution 2 - vue.js

Advanced variant

Using vue-meta

first run npm install vue-meta

and include it into your main.js;

import VueMeta from 'vue-meta'
Vue.use(VueMeta)

after doing so you can add a metaInfo() method to every vue component, handling meta data;

metaInfo() {
        return { 
            title: "Epiloge - Build your network in your field of interest",
            meta: [
                { name: 'description', content:  'Epiloge is about connecting in your field of interest. Our vision is to help people share their knowledge, work, projects, papers and ideas and build their network through what they do rather where they live, study or work.'},
                { property: 'og:title', content: "Epiloge - Build your network in your field of interest"},
                { property: 'og:site_name', content: 'Epiloge'},
                {property: 'og:type', content: 'website'},    
                {name: 'robots', content: 'index,follow'} 
            ]
        }
    }

Furthermore this can be used for dynamic meta info;

export default{
    name: 'SingleUser',
    data(){
        return{
            userData: {},
            ...
            aws_url: process.env.AWS_URL,
        }
    },  
    metaInfo() {
        return {
            title: `${this.userData.name} - Epiloge`,
            meta: [
                { name: 'description', content: 'Connect and follow ' + this.userData.name + ' on Epiloge - ' + this.userData.tagline},
                { property: 'og:title', content: this.userData.name + ' - Epiloge'},
                { property: 'og:site_name', content: 'Epiloge'},
                { property: 'og:description', content: 'Connect and follow ' + this.userData.name + ' on Epiloge - ' + this.userData.tagline},
                {property: 'og:type', content: 'profile'},
                {property: 'og:url', content: 'https://epiloge.com/@' + this.userData.username},
                {property: 'og:image', content: this.aws_url + '/users/' + this.userData.profileurl + '-main.jpg' }    
            ]
        }
    },
    ...
}

Source: Medium - How to add dynamic meta-tags to your Vue.js app for Google SEO

Solution 3 - vue.js

I'd like to add that above doesn't really preserve history as it should. See https://github.com/vuejs/vue-router/issues/914#issuecomment-384477609 for a better answer that actually takes care of the history (albeit a little bit hacky).

Solution 4 - vue.js

Latest Works way in 2021- Vue3:

Add the line name for related Component in .\router\index.js

  {
  path: '/',
  name: 'Home page'
  },

Load it in BeforeEach this function also write it in .\router\index.js

router.beforeEach((to, from, next) => {
  document.title = to.name;
  next();
});

Solution 5 - vue.js

Actually, based on my experiments with Steven B.'s solution, I've came up with something a bit better. The thing is this

watch: {
    $route(to, from) {
        document.title = to.meta.title || 'Some Default Title';
    },
}

doesn't work when we visit the page initially (by navigating via brower's address bar). Instead, we can create a getter like this:

computed: {
    pageTitle: function() {
        return this.$route.meta.title;
    }
}

Now in my case I was looking to set the "template"'s header (so that children routes don't bother about it) so that was it; for your case you may wonder how to set document's title once you have computed property, and there are some ways. Based on those answers, you can even do:

created () {
    document.title = this.$route.meta.title;
}

but I'd test this for the case of revisiting the same page (not sure if the component is created each time) before using in production.

Solution 6 - vue.js

I found this solution which is using mixins and needs minimal code.

https://medium.com/@Taha_Shashtari/the-easy-way-to-change-page-title-in-vue-6caf05006863 and originally https://github.com/vuejs/vue-hackernews-2.0/blob/master/src/util/title.js

I like it because you can define your title in your view components instead of the routes:

In src/mixins directory create a new file called titleMixin.js with the content below. It is checking if the value of the 'title' property of the component is a variable or function and returns the value of the title variable or return value of the title() function.

function getTitle (vm) {
  const { title } = vm.$options
  if (title) {
    return typeof title === 'function'
      ? title.call(vm)
      : title
  }
}export default {
  created () {
    const title = getTitle(this)
    if (title) {
      document.title = title
    }
  }
}

Register the mixin globally in your main.js. Add this before you create the Vue instance:

import titleMixin from './mixins/titleMixin'

Vue.mixin(titleMixin)

Last, in the view component file ( e.g Home.vue ) use a property named title to define the title of the page.

export default {
  name: 'Home',
  title: 'Homepage Title',
  components: {
    ...
  }
}

A little flaw: The mixin is registered globally, which makes the mixin available for all components , even for non view components where it makes no sense.

Solution 7 - vue.js

For vue 3:

Navigation Guards

import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(config.publicPath),
  routes,
});

const DEFAULT_TITLE = 'Some Default Title';
router.beforeEach((to) => {
  document.title = to.meta.title || DEFAULT_TITLE;
});

Solution 8 - vue.js

I was looking for editing the title but not necesseraly with a router. I found that with the mounted method it is possible to do it as well.

new Vue({
  mounted: function() {
    document.title = 'WOW VUE TITLE'
  }
})

Solution 9 - vue.js

Oh look another package

I see lots of great solutions here and at risk of beating a dead horse I submit: https://www.npmjs.com/package/@aminoeditor/vue-router-seo

It's <1mb, has no dependencies and has flexible implementations all easily controlled in your route config. It also has async support for title and other meta data.

Basic example
import { seoGuardWithNext } from '@aminoeditor/vue-router-seo';
const routes = [{
	path: '/',
	component: Home,
	meta: {
		seo: {
			title: 'My title here',
			metaTags: [
				{
					name: 'description',
					content: 'My Description here'
				},
				{
					name: 'keywords',
					content: 'some,keywords,here'
				}
			],
			richSnippet: {
				"@context": "https://schema.org",
				"@type": "Project",
				"name": "My Project",
				"url": "https://exampl.com",
				"logo": "https://example.com/images/logo.png",
				"sameAs": [
					"https://twitter.com/example",
					"https://github.com/example"
				]
			}
		}
	}
},{
	path: '/about',
	component: About,
	meta: {
		seo: {
			// do some async stuff for my page title
			title: async route => {
				const data = await fetch('somedataurl');
				return `${data} in my title!`;
			}
		}
	}
}]

const router = VueRouter.createRouter({
	history: VueRouter.createWebHashHistory(),
	routes,
})

// install the seo route guard here
router.beforeEach(seoGuardWithNext)

const app = Vue.createApp({})
app.use(router)
app.mount('#app')

Solution 10 - vue.js

As the page title often repeats some text content from the view (e.g. header on the page), I suggest to use a directive for this task.

const updatePageTitle = function (title) {
    document.title = title + ' - My Cool Website';
};

Vue.directive('title', {
    inserted: (el, binding) => updatePageTitle(binding.value || el.innerText),
    update: (el, binding) => updatePageTitle(binding.value || el.innerText),
    componentUpdated: (el, binding) => updatePageTitle(binding.value || el.innerText),
});

On every view you can decide which element content will be treated as the browser title:

<h1 v-title>Some static and {{ dynamic }} title</h1>

Or you can build the title inside the directive value:

<div v-title="'Some static and ' + dynamic + 'title'">...</div>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondarkhorseView Question on Stackoverflow
Solution 1 - vue.jsSteven B.View Answer on Stackoverflow
Solution 2 - vue.jsnonNumericalFloatView Answer on Stackoverflow
Solution 3 - vue.jscarl-johan.blomqvistView Answer on Stackoverflow
Solution 4 - vue.jsW KennyView Answer on Stackoverflow
Solution 5 - vue.jsYakovLView Answer on Stackoverflow
Solution 6 - vue.jsMichael SView Answer on Stackoverflow
Solution 7 - vue.jsWłodzimierzView Answer on Stackoverflow
Solution 8 - vue.jsJeremView Answer on Stackoverflow
Solution 9 - vue.jsEric UldallView Answer on Stackoverflow
Solution 10 - vue.jsfraczView Answer on Stackoverflow