Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to change document.title in vue-router? #914

Closed
dengyaolong opened this issue Nov 18, 2016 · 21 comments
Closed

how to change document.title in vue-router? #914

dengyaolong opened this issue Nov 18, 2016 · 21 comments

Comments

@dengyaolong
Copy link

@dengyaolong dengyaolong commented Nov 18, 2016

I really want set title when I declare routes. e.g

const routes = [{ path: '/search', component: ActivityList, title: 'Search' }]

then title will change by the vue-router.

how ?

ps, I see the pull issue closed. why?
#526

@fnlctrl

This comment has been minimized.

Copy link
Member

@fnlctrl fnlctrl commented Nov 18, 2016

Hi, thanks for filling this issue. You can simply define title in route's meta, and use a router.beforeEach hook to set title

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

The author of the PR closed it himself, so we don't know why. But setting title is trivially easy to implement with current API.

@fnlctrl fnlctrl closed this Nov 18, 2016
@kkinder

This comment has been minimized.

Copy link

@kkinder kkinder commented Jan 11, 2017

This seems like it would cover it, but what if the title should be dynamic, based on the content of the loaded component?

@dengyaolong

This comment has been minimized.

Copy link
Author

@dengyaolong dengyaolong commented Jan 12, 2017

I change the title in created hook for dynamic title

@marines

This comment has been minimized.

Copy link

@marines marines commented Jan 22, 2017

Does the dynamic title persist in the browser's history? @dengyaolong

@RodrigoAngeloValentini

This comment has been minimized.

Copy link

@RodrigoAngeloValentini RodrigoAngeloValentini commented Jun 5, 2017

{
path: '',
component: Home,
name: 'home',
meta: {title: 'Home'}
}

@ThePadawan

This comment has been minimized.

Copy link

@ThePadawan ThePadawan commented Jul 25, 2017

@fnlctrl Setting the title to a static value seems easy using 'meta'.
However, @kkinder has a point - I see no way of accessing the matched component instance in the beforeEach or afterEach hooks.

I guess I could just set document.title manually in the component, but that seems like a workaround. Is there a hook that can access the matched component?

@Baloche

This comment has been minimized.

Copy link

@Baloche Baloche commented Aug 3, 2017

It is possible if you define the title attribute as a function :

{
  meta: { title: route => { /* return custom title based on route, store or anything */ } }
}

and

router.beforeEach((to, from, next) => {
  document.title = to.meta.title(to)
  next()
})
@yTakkar

This comment has been minimized.

Copy link

@yTakkar yTakkar commented Sep 1, 2017

I've got a solution and used it on one of my projects.

First create a directive.

Vue.directive('title', {
  inserted: (el, binding) => document.title = binding.value,
  update: (el, binding) => document.title = binding.value
})

Then use that directive on the router-view component.

Suppose, we are working on 'MyComponent.vue' file.

<router-view v-title="title" ></router-view>
export default {
  data(){
    return {
      title: 'This will be the title'
    }
  }
}

This works even if the component is updated or the page is reloaded.

@aocneanu

This comment has been minimized.

Copy link

@aocneanu aocneanu commented Sep 2, 2017

@yTakkar nice solution.

You can also give the title as a value for the directive like this:

<router-view v-title="My Title" ></router-view>

and then in the directive definition:

Vue.directive('title', {
  inserted: (el,binding) => document.title = binding.value,
  update: (el,binding) => document.title = binding.value
})

But unfortunately couldn't make it work when I have nested routes.

Did you find a solution for this? Or maybe you don't have in your project nested routes.

@yTakkar

This comment has been minimized.

Copy link

@yTakkar yTakkar commented Sep 2, 2017

@aocneanu I updated the answer!!
I've used Nested routes, look at these files.

  1. router file.
  2. Usage of some routes.
@aocneanu

This comment has been minimized.

Copy link

@aocneanu aocneanu commented Sep 2, 2017

@yTakkar

1st, this scenario worked for me too, but try to add another component in the upper level and switch between that component and a child of notes.

2nd, I'm not sure that your use of (multiple times for multiple components) is correct. In my app I have a single component for all the components from one level.

3rd, this approach has a problem because for every component update the document title will be updated as well, even if it's not necessary. I know it's not a big thing but I don't like when my code does redundant stuff. Try add a console.log in the directive and see how many hits it gets, even if you don't switch the page.

4th, I ended using the router.beforeEach() method and it works seamlessly.

@diego-vieira

This comment has been minimized.

Copy link

@diego-vieira diego-vieira commented Sep 5, 2017

I've just come across the same thing and found vue-meta.

@maggiew61

This comment has been minimized.

Copy link

@maggiew61 maggiew61 commented Nov 1, 2017

combinging @fnlctrl and @RodrigoAngeloValentini 's solutions solved my problems. thanks

@troxler

This comment has been minimized.

Copy link

@troxler troxler commented Jan 20, 2018

I created a component to do that from the view. It is similar to yTakkar's solution but more complete in that you also can set the description, keywords, language and some other things (mostly useful when doing server-side rendering). It is called vue-headful and you can use it as follows:

Install

npm i vue-headful

Usage

Register the component:

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

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

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

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

Every attribute is also reactive, see vue-headful for more information. vue-headful is based on Headful, a vanilla JavaScript library to set document title and meta tags with JavaScript.

@plashenkov

This comment has been minimized.

Copy link

@plashenkov plashenkov commented Feb 27, 2018

@troxler This is very nice component, thank you!

@abbrechen

This comment has been minimized.

Copy link

@abbrechen abbrechen commented Mar 28, 2018

For all who are struggling, too. I used @yTakkar's way and expanded it with the route name.

methods: {
    setTitle: function () {
      this.metaTitle = this.$route.name
    }
  },
  watch: {
      '$route': 'setTitle'
    },
    mounted: function () {
      this.setTitle()
    }
@gladishukD

This comment has been minimized.

Copy link

@gladishukD gladishukD commented Mar 28, 2018

Thanks! I find solution for me

created () {
document.title = store.getters.translation.pages[router.currentRoute.meta.title]
},
watch: {
'$route' (to, from) {
document.title = store.getters.translation.pages[to.meta.title]
}
}

@lysz210

This comment has been minimized.

Copy link

@lysz210 lysz210 commented Mar 28, 2018

A plugin can help.

  var docTitlePlugin = {
    install: function (Vue) {
      var _prop = {
        get: function () { return document.title },
        set: function (v) { document.title = v }
      }
      Object.defineProperty(Vue, 'docTitle', _prop)
      Object.defineProperty(Vue.prototype, '$docTitle', _prop)
    }
  }
  Vue.use(docTitlePlugin)

Then calling Vue.docTitle = 'My Title' will change the page title, and in the component instance this.$docTitle = 'My New Title'.

@bradoyler

This comment has been minimized.

Copy link

@bradoyler bradoyler commented Mar 29, 2018

My little solution for handling routes with params (dynamic):

router.beforeEach((to, from, next) => {
  let title = to.name;
  const keys = Object.keys(to.params);
  if (keys.length) {
    title = `${to.name}: ${to.params[keys[0]]}`;
    if (to.params[keys[1]]) {
      title += ` ${to.params[keys[1]]}`;
    }
  }
  document.title = title;
  next();
});
@MatthewCochrane

This comment has been minimized.

Copy link

@MatthewCochrane MatthewCochrane commented Apr 26, 2018

To get it to work with browser history (in history mode) I had to modify @Waals code.

The problem with I think all of the above approaches is that they change the title before the browser's history is updated. This leaves you with history that is off-by-one. Ie if you start home then navigate to contacts then users, the history would show:

  • users
  • contacts

If you clicked users you'd be taken to the contacts page, and if you clicked contacts you'd be taken to the home page.

To fix this we need to wait until after the router navigation before updating the document title.

This code works:

Route configuration gets meta.title

{
  meta: { title: route => { /* return custom title based on route, store or anything */ } }
}

and then set document title in nextTick in afterEach.

router.afterEach((to, from) => {
  Vue.nextTick(() => {
    document.title = to.meta.title(to)
  })
})

To reference the Vue docs on navigation guards:
The Full Navigation Resolution Flow:

  1. Navigation triggered.
  2. Call leave guards in deactivated components.
  3. Call global beforeEach guards.
  4. Call beforeRouteUpdate guards in reused components (2.2+).
  5. Call beforeEnter in route configs.
  6. Resolve async route components.
  7. Call beforeRouteEnter in activated components.
  8. Call global beforeResolve guards (2.5+).
  9. Navigation confirmed.
  10. Call global afterEach hooks.
  11. DOM updates triggered.
  12. Call callbacks passed to next in beforeRouteEnter guards with instantiated instances.

Ideally we would want to update the title at step 12, but we can't access it with global hooks :(..
Vue.nextTick works reasonably well but it would be good to have a less 'hacky' solution.

@sense-it-gmbh

This comment has been minimized.

Copy link

@sense-it-gmbh sense-it-gmbh commented Feb 5, 2020

Here is what i came up with:

I'm using a base title + extension for every route. The title is localized with vue-i18n. With this approach the title will persist in the history. Also upon locale-change the title will change to the new locale too.

App.vue

<template>
  <div></div>
</template>

<script>
export default {
  name: 'App',
  components: {
  },
  data () {
    return {
    }
  },
  computed: {
    cWindowTitle () {
      // route-name is part of the localisation-key
      const routeName = this.$route.name
      const home = routeName === 'home'

      let title = this.$t('windowTitle.base')
      if (!home) {
        // only add title extension if this is not the main/home route
        title = `${title} - ${this.$t(`windowTitle.${routeName}`)}`
      }

      return title
    }
  },
  watch: {
    cWindowTitle: 'setWindowTitle'
  },
  created () {
    this.setWindowTitle()
  },
  methods: {
    setWindowTitle () {
      document.title = this.cWindowTitle
    }
  }
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.