Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue/Nuxt async meta tags generation

We have a webpage built with vue.js and nuxt. We use nuxt generate to generate static html files, which also should contain the SEO and og tags. That's why we use nuxt's head ()to generate the meta info.

So far so good. But now we have a page that asynchronously loads a post into a nested route. If you go to that page, it loads the post's data via ajax call. Then it would use the post's meta to populate the <head>.

The meta information is updated correctly after some time (after the post is loaded) BUT when we use nuxt generate the post's data and therefore it's meta info is not present at the time when we generate the meta information with head ().

That's why our static html does not contain the necessary meta info. What would be possible solutions for this? Ideal would be that the generation process waits for the post to be loaded. Could it be solved with promises? Or are there some other ideas?

Here this.post is set to false first. our helper function generateMetaInfo is called but obviously does not have the correct data.

head () {
  this.log('this.post: ', this.post)
  if (this.post) {
   return generateMetaInfo(this.post)
  }
}

We load the post like this, when calling the url:

getPost () {
  // Only if postSlug is present
  if (this.$route.params.postSlug) {
    // If postslug > try to get it from store
    if (this.getCompletePostBySlug(this.$route.params.postSlug)) {
      this.activePost = this.getCompletePostBySlug(this.$route.params.postSlug)
    }
    // if it is not in store get it via axios
    else {
      const ax = axios.create({
        baseURL: `${window.location.origin}/${this._checkRouteByString('programm') ? 'events' : 'posts'}`
      })
      ax.get(`posts.${this.$i18n.locale}.${this.$route.params.postSlug}.json`)
      .then((response) => {
        const newActivePost = response.data && response.data.items ? response.data.items.find(p => p.slug === this.$route.params.postSlug) : false
        if (newActivePost) {
          this.post = newActivePost
          this.$store.dispatch('data/saveCompletePosts', this.activePost)
        } else {
          this.post = '404'
        }
      })
      .catch((error) => {
        // console.log(error.response)
      })
    }
  } else {
    this.setActivePost()
  }
},

So we would have to wait for the ajax call to be finished.

Any idea that could help us find a solution is very much appreciated.

Cheers

============================

EDIT:

Using a promise did not work either:

methods: {
  getPostMeta: async function () {
    let promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const result = {
          title: 'Promise Title Test',
          meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            { name: 'keywords', content: 'keyword 1, keyword 2'},
            { hid: 'description', name: 'description', content: 'PROMISE. This is the generic promise descr.'}
          ]
        }
        resolve(result)
      }, 1000)
    })

    let result = await promise
    console.log('result: ', result)

    return result
  }
},
head () {
  return this.getPostMeta()
}

This would not wait until the promise is resolved... :( (of course this was only an example with a timeout, in the real world this would have to be exchanged with an ajax call, getting the post's data)

like image 697
Merc Avatar asked Oct 16 '22 14:10

Merc


2 Answers

Nuxt is an awesome framework, however SEO and assigning meta tags on dynamic routes is quite difficult. It's probably one of the very few reasons why it's not so good.

I think the main issue here is that you're trying to load meta data from a method that is only called once the promise or function has resolved, which means that it's never going to get the metadata until after the page has rendered. I wouldn't use functions or promises for this.

Essentially, the way I've found to get around this problem is that you need to load all of the meta data (coupled with the name of your post as an id or something) in the format

posts: [
    {
        slug: 'some_id',
        meta: [
            {
                name: 'title',
                content: 'some_title'
            },
            {
                ...
        ]
    },
    {
        ...

for your dynamic pages into one array - I'm using the VueX Store to do this, for example - and then you can use a statement such as

this.$store.state.array.find(p => p.slug === this.$route.params.postSlug).meta

in your .vue file where id is whatever value you want to compare with the route parameter. And this will return the array of metadata.

I realize that this is somewhat inefficient and most people would probably cringe at the thought of it, but it seems to work really well for dynamic routes.

My head() method looks like this

head() {
    return {
        title: "Title " + this.$route.params.id,
        meta: this.$store.state.results.find((result) => result.id === this.$route.params.id).meta
    }
}

This works perfectly for me. If you do try this, I'd be interested to hear how it goes.

Cheers

like image 134
Daynarc Avatar answered Oct 21 '22 03:10

Daynarc


Code explanation

export default {
    async asyncData({$content, params}) {
      const page = await $content('articles/some-page').fetch()

      return {
        page: page
      }
    },
    head() {
      // remember page is in function asyncData and it had a key title
      return {
        title: 'Articles: ' + this.page.title
      }
    }
}

Will set the page title to "Article + whatever the value is in this.page.title"

like image 40
Nadeem Sayyed Avatar answered Oct 21 '22 03:10

Nadeem Sayyed