この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
吉川@広島です。
案件でCloudFront+S3なSPAに対してOGP対応が必要になってきそうなため、Lambda@Edgeを使った対応について検証しました。
現状、FacebookやTwitterのBotは基本的にクライアントサイドJSを解釈できず、SPA単体でのOGP対応は難しいとされています。OGPメタタグはSSRで返してあげる必要があるため、「UserAgentでBot判定し、その時だけOGPメタタグ入りのHTMLをエッジサーバでレンダリングして返す」というのが基本戦略になります。
SPAをホスティングするCloudFront+S3を作成する
S3バケットを作成する
バケット名だけ入力し、後はデフォルト値で作成します。
そして、本来であればSPA用のHTML/JS/CSSリソースをアップロードするのですが、今回はLambda@Edgeの動作確認ができれば良かったためパスしました(バケットを空にしておく)。「SPAリソースがあるつもり」で進めていきます。
CloudFront Distributionを作成する
Create distributionから、
- Origin domain: 作成したS3バケット
- S3 bucket access: Yes...を選択
- OAI: Create new OAIから作成しそれを選択
- Bucket policy: Yes...を選択
- Viewer protocol policy: Redirect HTTP to HTTPSを選択
の設定で作成します。
Lambda@Edgeを作成する
リージョンをバージニア北部に切り替え、Lambda関数の新規作成をしていきます。
設計図の使用を選択します。「cloudfront」で検索し、cloudfront-modify-response-headerを雛形として使用することにします。選択したら「設定」を押下します。
関数名とロール名を入力します。
- ディストリビューション: 作成したCloudFront Distribution ARNを入力
- CloudFrontイベント: ビューアーリクエストを選択
- Lambda@Edgeへのデプロイを確認: チェックを入れる
としてデプロイします。
以上でLambda関数が作成されるので、次はコードの編集をしていきます。「コードを編集」を押下します。
埋め込みのCloud9コンソールよりコードを編集します。今回は次のようなコードとしました。
const URL_PREFIX = 'https://{SPA_CloudFront_サブドメイン}.cloudfront.net'
const BOTS = [
'Twitterbot',
'facebookexternalhit',
'line-poker',
'Discordbot',
'SkypeUriPreview',
'Slackbot-LinkExpanding',
'PlurkBot',
]
const generateContent = ({
title,
description,
imageURL,
mimeType,
width,
height,
url,
}) => {
console.log(
JSON.stringify(
{
title,
description,
imageURL,
mimeType,
width,
height,
url,
},
null,
2
)
)
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" />
<title>${title}</title>
<meta content="${description}" name="description">
<meta content="${url}" property="og:url" />
<meta content="article" property="og:type" />
<meta content="ja_JP" property="og:locale" />
<meta content="${title}" property="og:title" />
<meta content="${description}" property="og:description" />
<meta content="${width}" property="og:image:width" />
<meta content="${height}" property="og:image:height" />
<meta content="${imageURL}" property="og:image" />
<meta content="${imageURL}" property="og:image:secure_url" />
<meta content="${mimeType}" property="og:image:type" />
<meta content="summary_large_image" property="twitter:card" />
<meta content="@XXXXX" property="twitter:site" />
<meta content="${title}" property="twitter:title" />
<meta content="${description}" property="twitter:description" />
<meta content="${imageURL}" property="twitter:image" />
</head>
<body>
</body>
</html>
`
}
exports.handler = async (event) => {
console.log(JSON.stringify({ event }, null, 2))
const request = event.Records[0].cf.request
const path = request.uri
const userAgent = request.headers['user-agent'][0].value
console.log({ userAgent })
const isBot = BOTS.some((v) => {
return userAgent.includes(v)
})
console.log({ isBot })
if (isBot) {
const url = `${URL_PREFIX}${path}?${request.querystring}`
// pathによって画像を出し分ける
let body
switch (path) {
case '/foo':
body = generateContent({
title: 'FOO',
description: 'FOO Page',
imageURL: 'https://{OGP画像_CloudFront_サブドメイン}.cloudfront.net/2400x1260.png',
mimeType: 'image/png',
width: 1200,
height: 630,
url,
})
break
default:
body = generateContent({
title: 'OGPタイトル',
description: 'OGP説明',
imageURL: 'https://{OGP画像_CloudFront_サブドメイン}.cloudfront.net/1200x630.png',
mimeType: 'image/png',
width: 1200,
height: 630,
url,
})
}
return {
status: '200',
statusDescription: 'OK',
headers: {
'content-type': [
{
key: 'Content-Type',
value: 'text/html',
},
],
},
body,
}
return request
}
コードは主に下の記事を参考に、
AmplifyでOGP対応はできない。でもLambda@edgeを使えば大丈夫! | Fixel株式会社
BOTS
一覧はこちらの記事を参考にさせて頂きました。
OGP取得用クローラのユーザーエージェントを、TwitterやLINEなど8サービスで調べてみた - Crieit
コードを編集しデプロイしたら、新規にバージョンを発行します。
CloudFront Behaviorを編集して最新Lambda関数バージョンを反映する
今、Lambda関数バージョンを発行したので、CloudFrontに紐付けるバージョンも更新する必要があります。ARNのバージョン部分を書き換えて更新します。キャプチャはバージョン3と紐付ける例です。
OGP画像をホスティングするCloudFront+S3を作成する
S3バケット、CloudFront Distributionを作成する
SPAホスティングと同じ手順で作成します。
動作確認用の画像をアップロードする
OGPの出し分けを確認するために、画像を2つ用意します。Placehold.jpで1200x630と2400x1260のpng画像を作成し、S3バケットにアップロードしました。
S3バケットのCORS対応(ハマった)
S3バケットのアクセス許可からCross-Origin Resource Sharing (CORS)を編集します。任意のドメインからのアクセスを許可します。
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
これを行わない場合、後述するSharing Debugger - Facebook for Developersで確認した際、
Provided og:image URL, https://{SUB_DOMAIN}.cloudfront.net/example.png could not be processed as an image because it has an invalid content type
とエラーになり、OGP画像が表示されなかったので注意しましょう。
動作確認
デバッグツール
表示のされ方を確認したいというのもそうですが、特にUserAgent判定を動作確認するために本物のBotに見に行かせたいという事情があります。
を利用すると良いでしょう。
動作確認結果
Facebook、Twitterの両方とも、placehold.jpで作成したOGP画像を表示できました。URLパスによる出し分けも意図通り機能していました。
/fooを確認した場合:
/foo以外を確認した場合:
/fooを確認した場合:
/foo以外を確認した場合:
CloudWatchログは米国リージョンも確認する(ハマった)
上記デバッグツールからのアクセスがCloudWatchログに出力されず首をひねっていました。東京リージョンのCloudWatchを見ていて、手元のMacからのアクセスはログ出力されているのに、Botからのアクセスは出ない、なぜ・・・?と思っていたのですが、バージニア北部リージョンを確認すると出力されていました。
これはLambda@Edgeがアクセス元に近いエッジサーバで実行されるためと考えられます。(当たり前といえばそうですが)FacebookやTwitterのBotは米国で動作しているのでしょう。
参考
- Lambda@Edge で URLパスを書き換える | DevelopersIO
- Lambda@EdgeでSPAのOGPを動的に設定する - Qiita
- Lambda コンソールで Lambda@Edge 関数を作成する - Amazon CloudFront
- AmplifyでOGP対応はできない。でもLambda@edgeを使えば大丈夫! | Fixel株式会社
- FacebookやTwitterの投稿時表示画像(OGP)を確認・修正する方法 | New Standard
- facebook - FB OpenGraph og:image not pulling images (possibly https?) - Stack Overflow
- OGP画像の埋め込みを実装したい(しない) - The curse of λ
- Sharing Debugger - Facebook for Developers
- html - Provided og:image URL, {url to AWS S3} could not be processed as an image because it has an invalid content type - Stack Overflow
- http headers - Facebook says an open graph image has an invalid content type and ignores it - Stack Overflow
- CloudFrontとS3のCORS対応 - Qiita
- GAになったLambda@Edgeを使ってSPAをSSR無しでOGPとかに対応させてみる - Qiita
- OGP取得用クローラのユーザーエージェントを、TwitterやLINEなど8サービスで調べてみた - Crieit
- AWSマネジメントコンソールからのAmazon S3のCORS設定方法がJSON記法になっていました | DevelopersIO
- CloudFrontとS3のCORS対応 - Qiita