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 · 23 comments
Closed

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

dengyaolong opened this issue Nov 18, 2016 · 23 comments

Comments

@dengyaolong
Copy link

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
Copy link
Member

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 as completed Nov 18, 2016
@kkinder
Copy link

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
Copy link
Author

I change the title in created hook for dynamic title

@marines
Copy link

marines commented Jan 22, 2017

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

@RodrigoAngeloValentini
Copy link

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

@ghost
Copy link

ghost 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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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.

@dv336699
Copy link

dv336699 commented Sep 5, 2017

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

@maggiew61
Copy link

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

@troxler
Copy link

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
Copy link

@troxler This is very nice component, thank you!

@abbrechen
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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>

@Kamandlou
Copy link

Kamandlou commented Jan 22, 2022

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

asdfMaciej added a commit to asdfMaciej/gdziejestsala.pl that referenced this issue Feb 7, 2023
@rudney5000
Copy link

With Vue 3 I am ended with this solution without the nextTick:

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

My index.js file

import { createRouter, createWebHistory } from 'vue-router'
const HomeView = () => import ('@/pages/HomeView.vue')

const APP_NAME = `APP-NAME`

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      components: {
        default: HomeView,
      },

      meta: {
        PageTitle: `${APP_NAME}  - Welcome`,
      },
    },
   ]
})

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

export default router

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests