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

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

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

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

といってサーバレスで楽しくやってるのにサーバサイドでゴニョゴニョするのもまた辛いので、[こちらの記事](https://qiita.com/zaru/items/e7c2c11f8a9267dc8bb7)を参考にさせて頂き、Lambda@Edgeで解決してみました。

# Lambda@Edgeって？
[こちら](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html)に記載の通り、CloudFront周りでリクエストやレスポンスが発生した際に、そこでフックして色んなことができる仕組みです。
リダイレクトとか、レスポンスの書き換えとか、色々できます。

# OGPの設定方法

- [Facebookはこちら](https://developers.facebook.com/docs/sharing/webmasters/?locale=ja_JP)
- [Twiterはこちら](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards)

# 今回のコード
ViewerRequestが発生した際に、こんな感じのLambdaを書いてみました。

```Javascript
'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="１２００" 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の中身はお好きな感じで。

## ちょっとハマったこと
今回は[公式のサンプル](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html#lambda-examples-generated-response-examples)を見ながら、レスポンスを完全に書き換える形で実装してみました。

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

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

私、フロントは素人なんですが、[ここ](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Encoding)にUTF-8って書いてなくない、、、？

# 検証方法

- [Facebookはこちら](https://developers.facebook.com/tools/debug/)
- [Twitterはこちら](https://cards-dev.twitter.com/validator)

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

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

[他にも困ってる方](https://qiita.com/yumikokh/items/d6a49524837214758170)いるようだし、[公式](https://developers.facebook.com/docs/sharing/webmasters/?locale=ja_JP#media)は公式で

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

とか仰るし、、、

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

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