Angular 4 - Update Meta tags dynamically for Facebook (Open graph)

Viewed 34760

How do we add/update meta tags dynamically so that they get picked by Facebook/Whatsapp share dialog?

I upgraded my angular 2 application to angular 4 in order to use Meta service to add/update meta tags dynamically once we get the data in component from API.

So far in my component, I have

this.metaService.updateTag({ property: 'og:title', content: pageTitle });
this.metaService.updateTag({ property: 'og:url', 'www.domain.com/page' });
this.metaService.updateTag({ property: 'og:image', content: coverUrl, itemprop: 'image' });
this.metaService.updateTag({ property: 'og:image:url', content: coverUrl, itemprop: 'image' });
this.metaService.updateTag({ property: 'og:image:type', content: 'image/png' });

I am using updateTag because I have added static tags already with default values. This code successfully updates the meta tags values when I inspect them.

I know it makes sense that Facebook/Whatsapp debugger tools doesn't execute any javascript so it won't ever probably be executed in their environment.

I'm using https://developers.facebook.com/tools/debug/ and it always picks up the default tag values which makes sense.

My question is, what is the way around so that Facebook/Whatsapp picks up the updated tag values dynamically? I'm using Angular 4 and loading all data via API calls so its not possible to get any sort of data before the page loads and script is executed.

7 Answers

As of 2018/19 and if your main goal is SEO (or probably more "SMO" - Social Media Optimization - since the Googlebot does a great job at evaluating JavaScript but most social media bots don't) your SSR solution of choice maybe should not be Angular Universal but something which uses a headless browser.

This would fall under the "proxy" categorie from Manuel's answer but since I haven't seen them posted yet here two (and a half) really great solutions:

Rendertron

This one is maintained by the Google Chrome team itself and is simply a great endpoint for rendering your app and returning it.

Rendora

Much like Rendertron but this one has the middleware (i.e. where and how you decide which requests get rendered and which not) already built in and also comes with some more advanced but handy features like caching. Therefore it is really very close to it's "zero configuration needed" goal and even easier to set up than Rendertron.

Puppeteer

Again maintained by the Google Chrome team (and actually used by Rendertron) Puppeteer provides a node based high level API for headless Chrome. So if the previous projects are two stiff for you, you probably will be able to implement a fitting solution with Puppeteer but obviously it will be more work than just using Rendertron or Rendora.

Compared to Angular Universal this solutions have the huge advantage that your app project can stay completely agnostic towards the used SSR tool (it could even be using any other technology besides Angular). And this obviously not only gives you more flexibility for your own code but for your package choices as well since you don't have to worry if they are Angular Universal compatible or not. Their disadvantage could be a little performance overhead but if you just target bots this probably will not matter. And if you use Rendora's caching this may not even be true and you may actually have a performance increase. However if that could be comparable to the performance increase you can achieve with Angular Universal I don't know. But keep in mind that when we talk about performance increase from SSR we always only talk about the time to first page loads anyways. So typically the importance of this is not too high since your users will interact a lot more with your app after its first load. If they don't and you have mainly anonymous users which just check one page and then leave you probably wouldn't be building a PWA but a classical web page in the first place...

tl;dr just check out Rendora and Rendertron, they may be what you are looking for and get you there very easy and fast.

I just created a simple PHP site for the links, which implements the Open Graph tags and redirects the user to the "real" site via JavaScript. The script does the following:

  1. Fetch the required data from your API.
  2. Return a simple HTML site with the og-Tags
  3. Redirect the user to the "real" site with JavaScript

Example:

<?php
  $articleId = $_GET['id'];
  $redirectUrl = 'https://yourapp.com/app/tabs/start/article/'.$articleId;

  // get the article metadata
  $response = file_get_contents('https://api.yourapp.com/articles/'.$articleId);
  $response = json_decode($response);

  $title = $response->title;
  $description = $response->excerpt;
  if(property_exists ($response, 'mainImageUrl') ) {
    $imageUrl = $response->mainImageUrl;
  }
  $publishedTime = $response->published;
?>

<html prefix="og: http://ogp.me/ns#">
<head>
  <title><?php echo $title ?></title>
  <meta name="description" content="<?php echo $description ?>">
  <meta property="og:title" content="<?php echo $title ?>">
  <meta property="og:description" content="<?php echo $description ?>">
  <meta property="og:site_name" content="Your App">
  <meta property="og:locale" content="en_US">
  <meta property="og:type" content="article">
  <meta property="og:url" content="https://yourapp.com/article/<?php echo $articleId ?>">
  <?php if(isset($imageUrl)) { echo '<meta property="og:image" content="'.$imageUrl.'">'; } ?>
  <meta property="og:image" content="<?php echo $imageUrl ?>">
  <meta property="article:published_time" content="<?php echo $publishedTime ?>">

  <script>
    window.location.href = '<?php echo $redirectUrl ?>';
  </script>
</head>
<body>
    <a href="<?php echo $redirectUrl ?>">Click here to proceed...</a>
</body>
</html>

Just adding 2 cents to khushali's answer which helped me with a makeshift solution.

On my hosting provider (Dreamhost), the [NC,OR] yielded strange results when just copy/pasting. On RewriteCond with only one line, I had to write it as RewriteCond … googlebot|yandex|…|…|… [NC] (re-writing the RewriteCond with one per line also worked, but not with [OR] on the first line. This would work:)

RewriteCond … googlebot [NC]
RewriteCond … yandex [NC,OR]
RewriteCond … WhatsApp [NC,OR]

note the seemingly missing OR on the first line

On the other hand, my second cent is that last WhatsApp entry - turns out WhatsApp does the scraping directly from inside the app (at least it did on my android phone today ;) So my full line is now RewriteCond %{HTTP_USER_AGENT} googlebot|bingbot|yandex|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora\ link\ preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|WhatsApp [NC]

(And my full htaccess

    RewriteEngine On
        
        # https://stackoverflow.com/questions/18406156/redirect-all-to-index-php-using-htaccess
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{HTTP_USER_AGENT} googlebot|bingbot|yandex|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora\ link\ preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|WhatsApp [NC]
        #        RewriteCond %{HTTP_USER_AGENT} facebookexternalhit|googlebot [NC]   MUST BE WRITTEN WITHOUT OR
        #        RewriteCond %{HTTP_USER_AGENT} googlebot [NC]
        #        RewriteCond %{HTTP_USER_AGENT} facebookexternalhit [NC,OR]          'OR' IS FOR SECOND LINE (AND THIRD AND FOURTH ETC. WON'T WORK ON FIRST LINE)
        RewriteRule ^(.*)$ opengraph.php?q=$1 [NC,L,QSA]

        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule ^(.*)$ redir.php?orig_path=$1 [NC,L,QSA]

</IfModule>
Related