使用 vue-router 时如何更改页面标题?

如果可能的话,我想在路由定义中指定我的标题。通常在 <head><title>中指定并显示在浏览器标题栏中的内容。

我的项目设置如下:

主要的

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')

路由器 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' }
}
]
})

最好是,我想要一个自动系统,而不是改变每个组件的创建功能的页面标题。谢谢。

97682 次浏览

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';
}
},
}
};

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).

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.

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.

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

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();
});

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;
});


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'
}
})

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')

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>

With Vue 3 i am ended with this solution:

const routes = [
{
path: '/calendar',
name: 'calendar',
meta: { title: 'My Calendar' },
component: CalendarForm
},
{
path: '/',
name: 'main',
meta: { title: 'Home page' },
component: MainForm
}
]


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


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


export default router

If your VUE.JS site has an index.html file. You can just do this:

<html lang="en">
<head>
<title>YOUR TITLE HERE</title>

In my case, vue 2.x version working with Vuetify. Just went to the 'public' folder and changed the title tag from "<%= htmlWebpackPlugin.options.title %>" to the one I wanted.

for them how use vitness vue 3 template with typescript and ue Pages plugin for dynamically create routes from files, you can done it like this:

import { nextTick } from 'vue'


export const createApp = ViteSSG(
App,
{ routes, base: import.meta.env.BASE_URL },
(ctx) => {
// ...


ctx.router.afterEach((to) => {
      

let title = 'AppTitle'
if (to.meta.title)
title = `${to.meta.title} - ${title}`
nextTick(() => {
document.title = title
})
})
},
)


on each page component you can define specific title like this:

<route lang="yaml">
meta:
title: PageTitle
</route>


For composition API:

Create a new file i.e DefineMeta.ts

import type { Ref } from "vue";


interface MetaArgs
{
title?: Ref<string> | string;
}


export const defineMeta = (() =>
{
const title = ref("");
const metaStack = [] as MetaArgs[];


watch(title, () =>
{
document.title = unref(title);
});


return function (args: MetaArgs)
{
if (!getCurrentScope())
throw new Error("defineMeta must be called within a setup function.");


// Keep track of the last meta added to the stack, as the last meta in the stack is the current meta.
metaStack.push(args);


// When the component is unloaded, remove its meta args from the stack
onUnmounted(() =>
{
metaStack.splice(metaStack.indexOf(args), 1);


// Set the title to the last meta in the stack
title.value = unref(metaStack.at(-1)?.title) ?? "";
});


// Watch for when the title changes and update the document's title accordingly
if (args.title)
{
watch(ref(args.title), () =>
{
if (!metaStack.includes(args))
return;


title.value = unref(args.title)!;
}, { immediate: true });
}


return {
title
};
};
})();

Then inside your component's setup method:

const { title } = defineMeta({
title: computed(() => `${unref(formName)} Details`)
});

As this code keeps track of the title in a stack, make sure to define the default title in your entry / root / TheApp component:

defineMeta({
title: "Stackoverflow"
});

Similar to answer by @Steven.B and taken from > https://github.com/vuejs/vue-router/issues/914#issuecomment-1019253370

add this to main.js (Vue.js V3)

import {nextTick} from 'vue';


const DEFAULT_TITLE = "Default Title";
router.afterEach((to) => {
nextTick(() => {
document.title = to.meta.title || DEFAULT_TITLE;
});
});

In your router file, define a more detailed path with the title as a meta param

    {
path: '/somepath',
name: "mypath-name",
component: MyComponent,
meta: { title: 'MY DYNAMIC TITLE' },
},

and using basic JS

document.title = this.$route.meta.title;