Angular 4 - 为 Facebook 动态更新元标签(打开图)

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

我们如何 add/update 元标签动态地被 Facebook/Whatsapp 分享对话框选中?

我将我的 angular 2 应用程序升级到 angular 4,以便在我们从 API 获取组件中的数据后动态地使用元服务到 add/update 元标记.

到目前为止,在我的组件中,我有

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' });

我正在使用 updateTag,因为我已经添加了具有默认值的静态标签。当我检查元标记值时,此代码成功更新了它们。

我知道 Facebook/Whatsapp 调试器工具不执行任何 javascript 是有道理的,因此它可能永远不会在他们的环境中执行。

我正在使用 https://developers.facebook.com/tools/debug/,它总是选择有意义的默认标签值。

我的问题是,有什么方法可以让 Facebook/Whatsapp 动态获取更新后的标签值?我正在使用 Angular 4 并通过 API 调用加载所有数据,因此在页面加载和脚本执行之前无法获取任何类型的数据。

如果您正在使用 Angular 4 为什么不使用 Angular Universal 创建页面服务器端 - 这样您就可以在页面加载之前以编程方式构建您的 HEAD 标签浏览器

https://universal.angular.io/

Open Graph OG 标签必须在源代码中!

您需要在 html 源代码中提供一个静态 html 页面,其中包含 og:image og:title 和 og:description 等开放图表标签,因为facebook、twitter 和 co 只是 scraping 普通的 html 而没有通过 javascript 渲染它。 Angular 通过 js 动态更新 dom,因此爬虫只获取初始的 index.html。

有几种方法可以提供包含开放图的 html 标记 ans 解决您的问题:

  • 使用 angular 通用
  • 的服务器端呈现
  • 使用代理呈现您的页面
  • 即时覆盖 index.html 替换 og 标签
  • 提供静态 html 页面(不确定 angular 是否支持)

我猜你已经使用了 ngx-meta 之类的东西来添加 og 标签?

Angular 通用 - 在 Angular 2/3/4/5

中使用元标记进行服务器端呈现

我想服务器端渲染是解决您问题的最合适方法。为此,您可以托管节点服务器或使用例如。 AWS 拉姆达。这样做的缺点是,您的应用程序必须主动托管,不能再静态提供。无论如何,这似乎是最好的方法,因为它也改善了 SEO。 Angular 通用是要搜索的词:

Angular 构建时的通用预渲染

您还可以在构建过程中预呈现特定路由,并将 angular 作为具有多个预呈现 index.html 文件的静态应用程序提供服务。如果您只有很少的静态路由,那么这非常好。考虑带有动态部分的更通用的路由,这不是解决方案。去服务器端渲染。 angular universal boilerplate also contains an example for this. See prerender.ts

替代解决方案

使用代理预呈现 Angular 以提供 OG 标签

如果您想避免在构建过程中实现服务器端/预呈现(设置 angular 通用有时对于结构化不好的应用程序来说是一种痛苦。)您可以尝试使用代理服务预呈现您的页面。看看例如。 prerender.io

覆盖index.html

将所有请求重定向到覆盖 og:tags 的脚本。例如。 Using PHP and .htaccess to overwrite og tags 这在现代环境中也是可能的。例如。您可以使用 cloudfront / api 网关和 lambda 函数。不过还没有看到这方面的例子。

Facebook 缓存和打开图调试

请注意,缓存可能仍缓存了第一次抓取的开放图信息。确保您的源代码是最新的,并且清除所有缓存、nginxx、cloudfront 等反向代理。

使用Facebook Debugger调试开放图缓存和清除facebook开放图缓存

试试这个(使用 fb API: v2.12):

FB.ui({
  method: 'share_open_graph',
  action_type: 'og.shares',
  action_properties: JSON.stringify({
    object : {
      'og:url': 'url', // your url to share
      'og:title': 'title',
      'og:site_name':'site_name',
      'og:description':'description',
      'og:image': 'image Url',//
      'og:image:width':'250',//size of image in pixel
      'og:image:height':'257'
    }
  })
}, function(response){
  console.log("response is ",response);
});

在angular6、 index.html

中未反映动态元标记

因此只有借助 .htaccess 才能获取动态元内容。

如果你想呈现你需要的动态内容,请使用 .htaccess。

RewriteCond %{HTTP_USER_AGENT} facebookexternalhit/1.1|Twitterbot|Pinterest|linkedinbot|WhatsApp|Viber|SkypeUriPreview|Google.*snippet [NC,OR]

更多信息:

https://gist.github.com/thoop/8072354

https://www.winhelp.info/create-browser-whitelist-with-htaccess.html

截至 2018/19 年,如果您的主要目标是 SEO(或者可能更多 "SMO" - 社交媒体优化 - 因为 Google 机器人在评估 JavaScript 方面做得很好但大多数社交媒体机器人不这样做)您选择的 SSR 解决方案可能不应该 Angular 通用,而是使用无头浏览器的东西。

这将属于 Manuel 的回答中的 "proxy" 类别,但由于我还没有看到他们在这里发布两个(半)非常好的解决方案:

Rendertron

这个由 Google Chrome 团队自己维护,是呈现和返回应用程序的一个很好的端点。

Rendora

与 Rendertron 非常相似,但它具有内置的中间件(即您决定在何处以及如何决定哪些请求被渲染,哪些不渲染)已经内置,并且还具有一些更高级但更方便的功能,例如缓存。因此,它非常接近它的 "zero configuration needed" 目标,甚至比 Rendertron 更容易设置。

Puppeteer

再次由 Google Chrome 团队维护(实际上由 Rendertron 使用)Puppeteer 为无头 Chrome 提供了基于节点的高级 API。因此,如果之前的项目对您来说是两个僵硬的项目,您可能可以使用 Puppeteer 实现一个合适的解决方案,但显然它比仅使用 Rendertron 或 Rendora 需要更多的工作。

与 Angular Universal 相比,此解决方案具有 巨大 优势,即您的应用项目可以完全独立于所使用的 SSR 工具(它甚至可以使用任何其他工具) Angular 之外的技术)。这显然不仅为您自己的代码提供了更大的灵活性,而且还为您的包选择提供了更大的灵活性,因为您不必担心它们是否 Angular 通用兼容。 它们的缺点可能是性能开销有点大,但如果您只针对机器人,这可能无关紧要。如果您使用 Rendora 的缓存,这甚至可能不是真的,您实际上可能会提高性能。但是,如果这可以与您使用 Angular Universal 实现的性能提升相提并论,我不知道。但请记住,当我们谈论 SSR 带来的性能提升时,我们总是只谈论首页加载的时间。因此,通常这一点的重要性并不太高,因为您的用户在首次加载后会与您的应用程序进行更多交互。如果他们不这样做,并且您主要是匿名用户,他们只检查一个页面然后离开,您可能不会构建 PWA,而是首先构建经典网页...

tl;dr 只需查看 Rendora 和 Rendertron,它们可能就是您正在寻找的,并且可以非常轻松快速地帮助您。

只需在 khushali 的回答中加上 2 美分,这就帮助我找到了一个临时解决方案。

在我的托管服务提供商 (Dreamhost) 上,[NC,OR] 在 copy/pasting 时产生了奇怪的结果。在只有一行的 RewriteCond 上,我不得不把它写成 RewriteCond … googlebot|yandex|…|…|… [NC] (每行一个重写 RewriteCond 也有效,但在 first 行上使用 [OR] 无效。这会有效:)

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

注意第一行看似缺失的 OR

另一方面,我的第二分钱是最后一个 WhatsApp 条目 - 结果 WhatsApp 直接从应用程序内部进行抓取(至少它今天在我的 android phone 上做了 ;)所以我的全线现在是 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]

(还有我的完整 htaccess

    RewriteEngine On
        
        # 
        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= [NC,L,QSA]

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

</IfModule>

我刚刚为链接创建了一个简单的 PHP 站点,它实现了 Open Graph 标签并通过 JavaScript 将用户重定向到“真实”站点。该脚本执行以下操作:

  1. 从您的 API 中获取所需数据。
  2. Return 一个简单的 HTML 站点 og-Tags
  3. 使用 JavaScript
  4. 将用户重定向到“真实”站点

示例:

<?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>