4
votes

I have a simple Vue-based website created using Vue CLI, and I'm trying to figure out how to dynamically generate page titles for views w/ dynamic routes. The site is pulling JSON data from a headless CMS, and I have dynamic routes set up for detail views of a "project" content type. I have the dynamic route set up as follows:

// single project vue (dynamic)
{
    path: "/projects/:project_name_slug",
    name: "single-project",
    component: () =>
        import(
            /* webpackChunkName: "single-project" */ "./views/SingleProject.vue"
        ),
    meta: {
        title: "project detail",
    }
}

I have added the following, which works for adding static page titles:

// show page titles
const DEFAULT_TITLE = "my blog";
router.afterEach((to, from) => {
    // document.title = to.meta.title || DEFAULT_TITLE;
    document.title = "my blog - " + to.meta.title || DEFAULT_TITLE;
});

As it stands, upon visiting a project detail view, the title will read "my blog - project detail"; however, I'd like it to pull the actual project name from the JSON/field data for project_name (just as the route path is using project_name_slug), but nothing I've tried thus far has worked. For instance, using meta: { title: (route) => route.params.project_name } just results in a raw function text showing after "my blog - ". Thus, in cases for those dynamic views, I would like to.meta.title to resolve to the project name, creating "my blog - {project name}". Thank you for any assistance here, and please let me know if I need to provide any more details.

*Note: This question differs from that posed in this SO thread as my case involves dynamic routes using JSON data requested via Axios

3
@KeithNicholas - That link uses a static meta, this question is about dynamic meta (the title is dynamic in the linked question, but the meta is not)Dan
Is your JSON data available prior to the component life cycle events, or are you fetching it from an axios request once the component has been created?Ohgodwhy
@Ohgodwhy I'm fetching the data from an axios requestnickpish
@KeithNicholas That was one of the first posts I reviewed, but doesn't seem to work in my case w/ dynamic routesnickpish

3 Answers

4
votes

This is old but in case it helps anyone, I just had this issue and I found a combination using router.afterEach for static titles worked in combination with a mixin for dynamic titles.

In the case explained by OP, I would remove the title prop from any dynamic pages in the router and in the afterEach, just check for meta.title

router.afterEach(to => {
  if (to.meta.title) {
    document.title = `${to.meta.title} - my blog`;
  }
});

Then create a simple mixin for the other pages, something like:

export default {
  methods: {
    setTitle(str) {
        document.title = `${str} - my blog`
    }
  }
}

Then in those dynamic pages, you simply call this.setTitle() with whatever dynamic data once it has loaded from the server. I know it seems too easy, but it works. I was having trouble with all the beforeEach methods you find on SO.

Note: This is not for SEO or web crawlers (for which I recommend prerenderSpa) but does give you nice titles, back button history and bookmarks.

3
votes

I assume you're asking how to do this prior to the component being initialized because you have SEO concerns, otherwise it would be as simple as extracting it from the JSON response in your axios request and using document.title = myJson.page_title within the component.

If your ultimate goal is to eliminate that fear because of concerns with SEO (which is totally valid), then you'll need to change your approach to make the JSON data available before the component has even rendered.

I would suggest making the axios request in your route navigation guard, and hydrating a route property ahead of time, so you can actually have access to the page title. Observe:

Update your route to accept a new meta property:

meta: {
  title: "project detail",
  content: []
}

Import axios within the file housing your navigation guard and perform an axios request to fetch and hydrate the content. Migrate from afterEach to beforeEach instead so that you can block rendering of the component until your data has been received:

import axios from 'axios'

router.beforeEach(async(to, from, next) => {
  const { response } = await axios.get(`/my/cms/endpoint/${to.params.project_name_slug}`)

  document.title = `my blog - ${response.data.title}`

  to.meta.content = response.data.content

  next()
})

I made some assumptions about the shape of your response above, you'll need to modify that to fit your server response.

Now within your component, you'll be able to access this.$route.meta.content and you can utilize this in the mounted lifecycle to hydrate a local property, or use a getter to return it, which I assume you're doing something similar already:

export default {
  get pageContent () {
    return this.$route.meta.content
  }
}
2
votes

Set the meta in the router's beforeEach navigation guard:

router.beforeEach((to, from, next) => {
  to.meta.title = to.params.project_name_slug;
  next();
});

-or-

You could use the beforeRouteEnter and beforeRouteUpdate in-component guards:

export default {
  ...
  beforeRouteEnter(to, from, next) {
    to.meta.title = to.params.project_name_slug;
    next();
  },
  beforeRouteUpdate(to, from, next) {
    to.meta.title = to.params.project_name_slug;
    next();
  }
}

You can use the afterEach just like you would for a static meta since it will already be set:

const DEFAULT_TITLE = "my blog";
router.afterEach((to, from) => {
    // document.title = to.meta.title || DEFAULT_TITLE;
    document.title = "my blog - " + to.meta.title || DEFAULT_TITLE;
});

(Note: Using beforeEnter in the route definition wouldn't be enough because it doesn't trigger when going from one dynamic param to another. Even <router-view :key="$route.fullPath"></router-view> does not trigger beforeEnter when changing params.)