Reputation: 847
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
Upvotes: 6
Views: 12700
Reputation: 1429
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.
Upvotes: 5
Reputation: 63059
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.)
Upvotes: 5
Reputation: 50767
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
}
}
Upvotes: 8