Lambda+API Gateway+CloudFrontとVueを使ってフロントエンドのみでOGP画像の自動生成をしてみたので備忘録。
構成
まずVueでSVGを返すページを用意しておく。
Lambda側はchrome-aws-lambda
でスクリーンショットを撮って、base64で返すようにする。
よくあるLambda@Edgeを使ったダイナミックレンダリングを行いつつ、Edgeで返すMetaタグのog:image
やtwitter:image
のURLへのアクセスがあったら、用意しておいたSVGページをLambdaでスクリーンショット撮ってAPI Gateway経由でpngにして返す、というちょっと面倒くさい構成。
バックエンド側でLambdaを起動させてスクリーンショット撮ってS3に保存とかでもよかったのだけど、今回はあくまでもアクセスがあったらOGP画像を返すようにしたかったので、こんな感じの構成にした。
VueでSVG生成
VueでSVGを生成するのはこちらの記事を参考にさせていただいた。
Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう
<template>
<div class="hello">
<svg ref="svgCard">
<text transform="translate(103.29 347.281)" fill="#e51f4e" font-size="29" font-family="HiraginoSans-W5, Hiragino Sans" letter-spacing="-0.002em">
<tspan x="0" y="26">{{ data.content }}</tspan>
</text>
</svg>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'Svg',
data () {
return {
data: {}
}
},
beforeMount () {
this.fetchData(response => {
this.data = response
})
},
methods: {
...mapActions([
'fetchData',
])
},
}
</script>
svgタグの中にvueのデータを埋め込めるので、APIから持ってきたデータを表示できるようにする。
注意が必要なのが、svgタグではテキストを自動で折り返してくれないので、途中で切って配列にしてv-for
で回すとかしないといけない。
あとはこのページをrouterに登録する。
import Svg from '@/views/Svg.vue'
Vue.use(VueRouter)
const routes = [
...
{
path: '/path/to/svg',
name: 'Svg',
component: Svg
}
]
Lambda+API Gateway
次にスクリーンショットを撮るLambdaを作る。ほんとはserverless frameworkで作りたかったのだけど、serverlessで作ると何故かchromeが動いてくれなかったのと、API Gateway側の設定がイマイチ把握しきれなかったので、今回はコンソールでポチポチした。
Lambda
const chromeLambda = require("chrome-aws-lambda");
const defaultViewport = {
width: 1200,
height: 630
};
exports.handler = async event => {
const browser = await chromeLambda.puppeteer.launch({
args: chromeLambda.args,
executablePath: await chromeLambda.executablePath,
defaultViewport
});
const sleep = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
const page = await browser.newPage();
const url = "https://" + process.env.DOMAIN + "/path/to/svg";
await page.goto(url, { waitUntil: "networkidle0" });
sleep(1000)
const buffer = await page.screenshot({ encoding: "base64", type: "png" });
return {
"statusCode": 200,
"headers": {"Content-Type": "image/png"},
"isBase64Encoded": true,
"body": buffer
};
};
Lambda側のソースコードはこんな感じ。chrome-aws-lambda
はLambda Layerを使わせてもらった。
https://github.com/shelfio/chrome-aws-lambda-layer#available-regions
API Gateway
API Gateway側は適当なリソースを作って、GETメソッドを用意する。
-
/path/to/svg - GET - 統合リクエスト
で上で作ったLambdaに繋いで、Lambda プロキシ統合の使用
にチェックを入れる - HTTP リクエストヘッダーに
Accept
を追加する - メソッドレスポンスのコンテンツタイプに
image/png
を追加する - APIの設定で、バイナリメディアタイプに
image/png
を追加する
APIキーや使用量プランは必要に応じて設定して、ステージにデプロイする。ここでは仮にprod
ステージにデプロイしたと仮定して進める。
これでcurlコマンドでAPI Gatewayを叩くと画像が返ってくるようになる。
curl -H "Accept: image/png" --output test.png https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/path/to/svg
CloudFront
この状態でブラウザでアクセスすると、Acceptヘッダーがリクエストに含まれないのでjsonが返ってきてしまう。CloudFrontを経由させることで、Acceptヘッダーを付けつつ、一度アクセスのあった画像はキャッシュしてもらえる。
まずDistributionsの作成してOriginを追加する。
Origin Domain Name: xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Origin Path: /prod
Origin Custom Headers: Accept: image/png
Behavior側はOrigin or Origin Groupで先ほど追加したOriginを選択する。
CloudFrontのデプロイが完了したら、CloudFrontのURL経由でブラウザで画像が表示されるようになる。
Lambda@EdgeでOGPタグ
Lambda@Edgeを使ったOGPの生成はこのあたりを参考に。
Lambda@EdgeでSPAのOGPを動的に設定する
SSRをやめる。OGP対応はLambda@Edgeでダイナミックレンダリングする。
今回は1個目の記事のような感じで、botからのアクセスだった場合はバックエンドのAPIにアクセスしてタイトルとかを整えつつ、上で用意した画像のURLを含んだOGPタグを生成して返すようにした。
botの種類はこのあたり。
const crawlers = [
"Googlebot",
"facebookexternalhit",
"Twitterbot",
"bingbot",
"msnbot",
"Slackbot",
"Discordbot"
];
まとめ
とまあ、こんな具合でOGP用の画像を自動生成できた。あとは必要に応じてコールドスタート対策もしておきたいところ。
Serverless Frameworkで行うLambdaのコールドスタート対策
いつもながら先人の知恵や知識に感謝。
参考URL
https://scottbartell.com/2019/03/25/automating-og-images-with-aws-lambda/
https://codissimo.sinumo.tech/2019/12/27/serverless-puppeteer-with-aws-lambda-layers-and-node-js/
https://qiita.com/junara/items/5563ad7ee133ce736ed0
https://qiita.com/kodai-saito/items/9051d2b30a29c7d64f7d