Vue meta not getting updates
Asked Answered
B

4

19

When I visit my internal pages vue-meta will not get updated by new page values.

Code

app.js

import VueMeta from 'vue-meta'
Vue.use(VueMeta, {
    refreshOnceOnNavigation: true
})

App.vue (main component)

export default {
  metaInfo() {
    return {
      title: process.env.MIX_APP_NAME,
      titleTemplate: `%s | ${process.env.MIX_APP_NAME}`,
      meta: [
        { name: "robots", content: "index,follow" },
        {
          vmid: "description",
          name: "description",
          content:
            "..........",
        },
        // and many more....
      ],
   }
  }
}

post.vue (internal component)

export default {
  name: "singePost",
  data() {
    return {
      post: "",
    };
},
metaInfo() {
    return {
        title: this.post.name, // not receiving data
        meta: [
            {
                vmid: "description",
                name: "description",
                content: this.post.metas[0].description, // not receiving data
            },
            // others......
        ],
    }
},
mounted() {
    this.getPost();
},
methods: {
    getPost() {
        axios
        .get("/api/articles/" + this.$route.params.slug, {
          headers: {
            Authorization: localStorage.getItem("access_token"),
          },
        })
        .then((response) => {
          this.post = response.data.data;
        })
        .catch((error) => {
            //....
        });
    },
},

Any idea?

Update

By the time I published this question it was about not getting updated then after doing some researches I've and playing around with my code I've realized that my vue-meta gets updated but, late and it causes social network websites and SEO checkers not be able to retrieve my URLs correctly.

Clarify

  1. Vue-meta gets update but late
  2. This late update causes SEO not be presented by the time link being shared and validate.

My full meta tags code

metaInfo() {
    return {
      title: this.post.name,
      meta: [
        {
          vmid: "keyword",
          name: "keyword",
          content: this.post.metas[0].tags,
        },
        {
          vmid: "description",
          name: "description",
          content: this.post.metas[0].description,
        },
        // Open Graph / Facebook
        { vmid: "og:type", name: "og:type", content: "website" },
        {
          vmid: "og:url",
          name: "og:url",
          content: process.env.MIX_APP_URL + this.$router.currentRoute.fullPath,
        },
        {
          vmid: "og:site_name",
          name: "og:site_name",
          content: `"${process.env.MIX_APP_NAME}"`,
        },
        {
          vmid: "og:title",
          name: "og:title",
          content: this.post.name,
        },
        {
          vmid: "og:description",
          name: "og:description",
          content: this.post.metas[0].description,
        },
        {
          vmid: "og:image",
          name: "og:image",
          content: this.post.imagebig,
        },
        //   Twitter
        {
          vmid: "twitter:card",
          name: "twitter:card",
          content: "summary",
        },

        {
          vmid: "twitter:author",
          name: "twitter:author",
          content: "@xxxxxx",
        },
        {
          vmid: "twitter:site",
          name: "twitter:site",
          content: "@xxxxxx",
        },
        {
          vmid: "twitter:creator",
          name: "twitter:creator",
          content: "@xxxxxx",
        },
        {
          vmid: "twitter:url",
          name: "twitter:url",
          content: process.env.MIX_APP_URL + this.$router.currentRoute.fullPath,
        },
        {
          vmid: "twitter:title",
          name: "twitter:title",
          content: this.post.name,
        },
        {
          vmid: "twitter:description",
          name: "twitter:description",
          content: this.post.metas[0].description,
        },
        {
          vmid: "twitter:image",
          name: "twitter:image",
          content: this.post.imagebig,
        },
      ],
    };
},

Extra

  1. Recently I've read an article that because vue-meta (Vue in general) loads based on JavaScript Social media crawlers will not cache them therefore it's impossible to see my link details when I share them in FB or Twitter etc.

  2. The suggested solution there was to use Nuxt and return meta data server side.

Questions

  1. I'm not sure how much #1 above is correct but its a possibility
  2. My app in general doesn't use Nuxt but I just installed npm package of it so it might be worth to try (as I mentioned I never used Nuxt so if that's the case of your helping solution I would be appreciate if you include a bit extra details into your answers about that).
Breland answered 5/1, 2021 at 5:17 Comment(0)
L
13

Vue itself is client-side JS framework. When you build, your index.html does not have any content - only JS that generates the content when executed. Same applies to VueMeta. Problem is, when you are sharing links (FB, Twitter etc), they download linked page by using their own bot (crawler essentially) and analyze the content without executing any JS inside - so yes, they don't see any meta generated by VueMeta...

Only solution to this is to deliver fully (or partially) prerendered page containing all important information without executing JS

One way of doing so is to use Vue server side rendering - and you are right, frameworks like Nuxt use exactly that.

Generally there are two flavors:

SSR - page is rendered at the moment it is requested by the client (or bot). In most cases it requires running Node server (because Vue SSR is implemented in JS). Most prominent example of this is Nuxt.js

SSG - server side generation. Pages are generated at build time including all HTML. When loaded into the browser server returns HTML + all the JS/CSS but when it loads it's the same Vue SPA. You don't need Node server for that so you can host on CDN or any static hosting service like Netlify. Examples in Vue world are Gridsome, VuePress, Nuxt can do it too...

Note: there are other ways for example using headless chrome/puppeteer or services like https://prerender.io/

Nuxt

As said before, Nuxt is great but is very opinionated about how your app is structured (file based routing), how to get data etc. So switching to Nuxt can mean a complete app rewrite. On top of that it requires running NODE server which has consequences of its own (hosting).

BUT it seems to me that you are already using server - Laravel. So your best bet is probably to implement your meta rendering directly in Laravel.

UPDATE: It seems it is possible to do Vue SSR directly in Laravel

Loram answered 7/1, 2021 at 12:40 Comment(4)
I am interested in your UPDATE part and I've read the article but I didn't get how I'm suppose to return for instance my post meta tags (as my of my code above) there was nothing about returning actual data from database.Breland
From the article it seems like regular Vue SSR. In short before server returns response, it executes your Vue app inside V8 JS virtual machine INSIDE the server and returns any HTML rendered by it. So you are not writing any PHP code to generate meta tags or querying database. All is done inside Vue app. I'm not saying your Vue code will just work as is now but for specific details you will need to check Vue server side rendering link in my post....Glyphography
But I really don't know much about Laravel. Maybe trying SSR is just overkill here. Maybe you can just make a controller with same route as your Post component, add some DB call, generate meta tags in PHP and attach the VUE js scripts into response...Glyphography
in my project SSR is false & using Nuxt, is there any other way to update title tag before page load. I have facing exact same issue but in my case just SSR is false. just i want to update title tag on Api response & the title should be display on view source. If anyone can help me on this.Boylan
U
4

Your assumptions are correct. I've also spent quite some time on trying to find a solution to this very same issue a while ago. Here is what I came up with at the end of the day:

  1. Keep using vue-meta, for those crawlers that run JavaScript (there is no harm in it, right?).
  2. Implement a server side solution (using a Laravel package).

Option 1 should be clear, since you already have a similar implementation.

For option 2, here is my approach:

  • I picked this package for my Laravel application. It's easy to install and register. I'm sure there are many packages for Laravel or other frameworks and languages that do the same.

  • I added this route at the end of my route files (web.php if you are using Laravel) that catches all the frontend routes requests:

Route::get('/{any}', 'IndexController@index')->where('any', '.*');

In IndexController, I first check the request to see if it's coming from a crawler. If so, I apply the relevant meta tags. Here is a glimpse:

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Butschster\Head\Facades\Meta;
use Butschster\Head\Packages\Entities\OpenGraphPackage;

class IndexController extends Controller
{
    const CRAWLERS = [
        'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 (compatible; AdsBot-Google-Mobile; +http://www.google.com/mobile/adsbot.html)',
        'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Safari/537.36',
        'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
        'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)',
        'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)',
        'Googlebot-Image/1.0',
        'Mediapartners-Google',
        'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
        'facebookexternalhit/1.1',
        'Twitterbot/1.0',
        'TelegramBot (like TwitterBot)',
    ];

    public function index()
    {
        if ($this->isACrawler()) {
            $this->applyMetaTags();

            return view('layouts.crawler');
        }

        return view('layouts.index');
    }

    public function isACrawler()
    {
        if (in_array(request()->userAgent(), self::CRAWLERS)) {
            return true;
        }

        return false;
    }

    private function applyMetaTags()
    {
        // Here you can check the request and apply the tags accordingly
        // e.g.
        //        preg_match("/articles\/[0-9]+/i", request()->path(), $url)
        //        preg_match("/[0-9]+/i", $url[0], $id);
        //        $article = Article::find($id);
        //
        //        Meta::prependTitle($article->name)
        //            ->addMeta('description', ['content' => $article->description]);
        //
        //        $og = new OpenGraphPackage('some_name');
        //
        //        $og->setType('Website')
        //            ->setSiteName('Your website')
        //            ->setTitle($article->name)
        //            ->setUrl(request()->fullUrl())
        //            ->setDescription($article->description);
        //
        //        if ($article->picture) {
        //            $og->addImage(asset($article->picture));
        //        }
        //
        //        Meta::registerPackage($og);
    }
}

And finally I created a template with only the head section (that's the only part of html a crawler cares about) and apply the meta tags:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        @meta_tags

        <link rel="shortcut icon" href="{{ asset('favicon.ico') }}">
    </head>
</html>

Caveats:

  • You need to customize the meta tags per request
  • You need to maintain a list of crawlers

Benefits:

  • It's simple and doesn't require much changes in your code
  • It returns a fast and lightweight HTML to the crawler
  • You have the full control in the backend and with a bit of adjustment you can implement a maintainable solution

Hope this helps! Please let me know if something is unclear.

Underpart answered 7/1, 2021 at 15:15 Comment(5)
Hi hatef thanks for the answer, if I use the package you've mentioned, do I still need to provide crowlers list? because that list can be vary and I won't be able to provide every bot to that, having custom bots sort of first hard to do second not making sense. and that being said providing vue-meta in all front-end pages beside taking care of controllers seems lots of work to provide meta in 2 different places. what do you think?Breland
Hi, the package only applies the meta tags. List of crawlers was my own addition. The reason is, I want to apply the backend meta tags only when the request is coming from a crawler. You can omit it and apply them straight to every request. In general having a good SEO is a lot of work and differs per your needs. In my case, I decided to apply the backend meta tags only to the pages that I know could be shared to social media (to support the preview feature). It might seem redundant to have it in two places but I think at the end it pays off.Underpart
Furthermore, you might be able to just use this backend package and it could satisfy your needs. You just need to see how it works with frontend routing since you will most likely be doing API calls that don't update the <head> of your html. For example, you could think of a way that when a certain API response is received, you dynamically update the head of your page. I haven't really dug into that myself but it's a possibility. I might dig into that when I have some free time :)Underpart
Yes i just tried it this morning and I ran to some issues with it still haven't get any respond to those issues on github as you've mentioned you didn't dig into it that much so I think there is no point of me refer you to those issues :) Thanks a lot for explanation and your time.Breland
Sure. I will update my answer if I have any findings. Btw, I added laravel to your question tags, this way maybe some Laravel developers could also contribute to the answersUnderpart
R
3

You need to implement serverside rendering to process meta tags. Because almost all crawler doesn't support javascript process.

Here is the example for PHP - Laravel.

As we know vue.js is a Single Page Application. So every time it renders from one root page.

So for laravel, I configured the route as it is, and every time I return the index page with tags array and render that page in view (index page)

  1. Laravel Routing

<?php
    
    use Illuminate\Support\Facades\Route;
    
    Route::get('users/{id}', 'UserController@show');
    
    Route::get('posts/{id}', function () {
        $tags = [
            'og:app_id' => '4549589748545',
            'og:image' => 'image.jpg',
            'og:description' => 'Testing'
        ];
    
        return view('index', compact('tags'));
    });
    
    Route::get('/{any}', function () {
        $tags = [
            'description' => 'Testing',
            'keywords' => 'Testing, Hello world',
        ];
    
        return view('index', compact('tags'));
    })->where('any', '.*');
    
    ?>
  1. Controller

<?php
    
    use App\Http\Controllers\Controller;
    use App\Models\User;
    
    class UserController extends Controller
    {
        public function show(User $user)
        {
            $tags = [
                'og:app_id' => '4549589748545',
                'og:image' => 'image.jpg',
                'og:description' => 'Testing'
            ];
    
            return view('index', compact('tags'));
        }
    }
    
    ?>
  1. Index page

<!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        @foreach($tags as $key => $value)
            <meta property="{{$key}}" content="{{$value}}">
        @endforeach
    </head>
    <body id="app">
    
    <script type="text/javascript" src="{{ asset('js/app.js') }}"></script>
    </body>
    </html>
Rant answered 12/1, 2021 at 6:6 Comment(0)
G
0

you can configure webpack to inject static tags

vue-cli 3 abstracts webpack config files away (generated at runtime) so in order to configure it you need to add a vue.config.js to your project root (if you don't have one, which typically you won't)

for instance:

// vue.config.js
module.exports = {
    configureWebpack: {
        output: {
            publicPath: '/static/'
        },

        plugins: [
          new HtmlWebpackPlugin(),
          new HtmlWebpackTagsPlugin(
            {tags: ['a.js', 'b.css'], append: true },
            {metas: [{
                path: 'asset/path',
                attributes: {
                    name: 'the-meta-name'
                    }}]
            })
        ]
    }
}

(using https://github.com/jharris4/html-webpack-tags-plugin see examples in link for its concrete output)

Glacis answered 18/1, 2021 at 23:26 Comment(1)
Thanks but i don't want static data otherwise i would add it directly to my html file header, what I want is dynamic tags based on visited pagesBreland

© 2022 - 2024 — McMap. All rights reserved.