22
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Lambda@EdgeでSPAのOGPを動的に設定する

Last updated at Posted at 2018-09-08

背景

AngularでSNSシェアボタン作ってた時に、「このページは画像、このページは動画でシェアしたいな」と思ってMetaでも使えばいいのかなーと動的に書き換えてみたんですが、全然動きませんでした。

調べてみると、例えばFacebookでURL貼り付けた時にプレビュー画像がロードされる時、Facebook謹製のクローラがそのページを読みに行ってるそうなんですが、そいつがJSを解釈しないのだとか。

おお、まぁ、クローラがJS解釈するのも大変ですよね、そりゃそうか、で、それじゃSPAでシェアしようとしたら、トップページに静的に書いたmetaしか読んでくれんのですか。なにそれ辛い。

といってサーバレスで楽しくやってるのにサーバサイドでゴニョゴニョするのもまた辛いので、こちらの記事を参考にさせて頂き、Lambda@Edgeで解決してみました。

Lambda@Edgeって?

こちらに記載の通り、CloudFront周りでリクエストやレスポンスが発生した際に、そこでフックして色んなことができる仕組みです。
リダイレクトとか、レスポンスの書き換えとか、色々できます。

OGPの設定方法

今回のコード

ViewerRequestが発生した際に、こんな感じのLambdaを書いてみました。

'use strict';

const querystring = require('querystring');
const bots = ['Twitterbot', 'facebookexternalhit'];
const hookUrls = ['/hoge/', '/fuga/'];
const URL_PREFIX = 'https://hoge.com';
const FB_APP_ID = '1234567890';

exports.handle = (event, context, callback) => {
  const request = event.Records[0].cf.request;
  const uri = request.uri;
  const userAgent = request.headers['user-agent'][0].value;
  const isBot = bots.some(v => {
    return userAgent.includes(v)
  });
  const isHookUrl = hookUrls.some(v => {
    return uri.includes(v) //  ほんとは正規表現でちゃんと書くべきだけどサポった
  });
  console.log(`userAgent:${userAgent}, isBot:${isBot}, isHookUrl:${isHookUrl}`);
  if (isBot && isHookUrl) {
    const url = `${URL_PREFIX}${uri}?${request.querystring}`;
    const response = {
      status: '200',
      statusDescription: 'OK',
      headers: {
        'content-type': [{
          key: 'Content-Type',
          value: 'text/html'
        }]
      },
      body: getContent(url)
    };
    callback(null, response);
    return;
  }
  callback(null, request);
};

const getContent = (url) => {
  return `
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
  <meta content="width=device-width, initial-scale=1.0" name="viewport" />
  <meta content="R-bies,INC." name="author" />
  <title>タイトル</title>
  <meta content="詳細" name="description">

  <meta content="${FB_APP_ID}" property="fb:app_id" />
  <meta content="${url}" property="og:url" />
  <meta content="website" property="og:type" />
  <meta content="ja_JP" property="og:locale" />
  <meta content="タイトル" property="og:title" />
  <meta content="詳細" property="og:description" />
  <meta content="${URL_PREFIX}/assets/og_image.png" property="og:image" />
  <meta content="${URL_PREFIX}/assets/og_image.png" property="og:image:secure_url" />
  <meta content="image/png" property="og:image:type" />
  <meta content="1200" property="og:image:width" />
  <meta content="630" property="og:image:height" />

  <meta content="summary_large_image" property="twitter:card" />
  <meta content="@RUNNETJP" property="twitter:site" />
  <meta content="タイトル" property="twitter:title" />
  <meta content="詳細" property="twitter:description" />
  <meta content="${URL_PREFIX}/assets/og_image.png" property="twitter:image" />
</head>
<body>
</body>
</html>
`;
};

isFacebookとかisTwitterに分けてgetContentの中身変えてもOKです。
OGPの中身はお好きな感じで。

ちょっとハマったこと

今回は公式のサンプルを見ながら、レスポンスを完全に書き換える形で実装してみました。

だけどこのサンプルに従うと、FacebookがBAD_CONTENT_ENCODINGで動きません。
犯人はサンプル内のこれ。

'content-encoding': [{
   key: 'Content-Encoding',
   value: 'UTF-8'
 }]

私、フロントは素人なんですが、ここにUTF-8って書いてなくない、、、?

検証方法

デバッガーを使うとミスってても何が悪いか色々教えてくれるので便利です。

残課題

半日がかりでやった動いたー!
と喜んだんですが、Facebookで動画動かそうとすると思うように行かず。

他にも困ってる方いるようだし、公式は公式で

様々な要因が関係するため、動画のインライン再生は保証されません。

とか仰るし、、、

YouTubeのインライン再生できてる動画をデバッガにかけてみるとよくわからんタグいっぱいついてるので、これを今後勉強するのが残課題かなぁ。

最後までお付き合い頂きありがとうございました。

22
12
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?