string(4) "blog"

CONTENTS

2020.8.11

AmplifyでOGP対応はできない。でもLambda@edgeを使えば大丈夫!

written by 山本 竜二

Amplify使ってますか?面倒なAWSのサーバレス環境構築がサクっとできたり、フロントエンド開発に便利なライブラリがあったり、一度使うと止められないですよね。

弊社でも新規プロダクトで使っているんですが、先日、TwitterやFacebookの共有機能を作っている時にハマってしまいました。

Amplifyは、SSR(サーバサイドレンダリング)に対応していないから、動的にOGP情報を作る事ができないんですよね。

OGP対応だけの為にAmplifyを捨ててSSR対応をするのは勿体ないし、時間も足りません。うーん。どうしたものか困ったぞ。。と、そんな時に、AWSのLambda@edgeを使えばOGP対応できる事を知りました。

という事で、今回はAmplifyを使ったプロジェクトで、SSRせずにOGP対応する方法を解説します。お役に立てばシェアして貰えると嬉しいです。尚、この方法はAmplifyを使っていない場合でも有効ですので、SSR無しでOGP対応したい方にも役立つ情報になります。

SSRなしでOGP対応する仕組み

今回は以下のような構成を実際に作ってみたいと思います。DynamoDBとS3公開用は必須ではありませんが、OGP情報のタイトル、説明、画像等を作成する為に使っています。環境に合わせて別のAWSサービスや他の仕組みを用いても大丈夫です。

あと、注意点がいくつかあります。

  • Amplifyコンソールを使ってデプロイしているとLambda@Edgeが使えないので、アプリのデプロイはAmplifyのホスティング機能か、Codepipelineなどを使って自前で行う必要があります。
  • Amplifyで作ったS3は署名付きURLでアクセスする前提なので、OGP画像用に公開用のS3を用意する必要があります。(※署名付きURLは最大有効期限が7日なのでNGです)
AWS構成図

まずは簡単に仕組みを解説します。

今回のキモとなるのは、Lambda@Edgeです。Lambda@EdgeはCDNであるCloudFrontへのリクエストを処理する際に呼び出す事ができるので、ここでOGP情報を作って返してあげます。

もう少し具体的に説明します。

TwitterやFacebookに共有すると、それぞれのbotからOGP情報を取りにきます。そこで、Lambda@Edgeでリクエストを拾い、user-agentを見てbotと判断したら、OGP情報だけの入った簡易的なHTMLを返すようにします。bot以外からのアクセスであれば、CloudFrontで指定しているオリジンサーバ(S3)へそのまま転送します。

サンプルの構築

それでは、実際にサンプルアプリを構築して動作を見てみましょう。

OGP対応の確認が目的なので、アプリの実装は最低限の機能に済ませます。

画面構成
  • トップ画面に画像が並び、画像をクリックすると詳細画面に遷移
  • 詳細画面にはTwitterとFacebookの共有機能をつける

表示するデータ(画像、説明文)は、AWSの管理コンソールから直接登録するものとしましょう。

React + Amplifyのインストール・設定

まずは、Amplifyを使ってサンプルのWebアプリを構築していきます。フロントエンド側の実装はReactを使う事にします。

また、今回はローカル環境を使わず、AWSのCloud9を使っていますが、ローカル環境で作業して貰っても問題ありません。以下、Node.js周りのバージョンを記載しているので、そこだけ合わせて貰った方がトラブルが無いと思います。

環境 (2020.8.6現在でCloud9を使った時にデフォルトで使えるものです)

  • Node: v10.22.0
  • npm: 6.14.6

create-react-appをインストールします。

$ npm install -g create-react-app

React雛形アプリを作成して起動確認します。

$ create-react-app ogp-sample
$ cd ogp-sample/
$ npm start

ブラウザで http://localhost:8080 にアクセスして以下の画面が表示される事を確認します。

Reactサンプル

Cloud9を使っている場合は、awsの認証情報を先に設定します。
※既に設定済みであったり、他の環境を使っている場合は必要に応じて対応してください。

$ aws configure
AWS Access Key ID [****************KWHB]: 
AWS Secret Access Key [****************QJfS]: 
Default region name [ap-northeast-1]: 
Default output format [None]: json

Amplifyをインストールして初期設定を行ないます。

$ npm install -g @aws-amplify/cli
$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project ogpsample
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use default

バックエンド構築

AppSync + DyanmoDB 構築

Amplify CLIを使い、AppSync+DynamoDBを構築します。

$ amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: ogpsample
? Choose the default authorization type for the API API key
? Enter a description for the API key: 
? After how many days from now the API key should expire (1-365): 365
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)

The following types do not have '@auth' enabled. Consider using @auth with @model
         - Todo
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/directives#auth

GraphQL schema compiled successfully.

Edit your schema at /home/ec2-user/environment/ogp-sample/amplify/backend/api/ogpsample/schema.graphql or place .graphql files in a directory at /home/ec2-user/environment/ogp-sample/amplify/backend/api/ogpsample/schema
? Do you want to edit the schema now? No
Successfully added resource ogpsample locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

AppSyncのmodelを編集し、OGPに必要な画像情報を保存できるようにします。

/ogp—sample/backend/api/ogpsample/schema.graphql (修正)

編集前

type Todo @model {
  id: ID!
  name: String!
  description: String
}

編集後

type Image @model {
  id: ID
  path: String
  name: String
  description: String
  mimeType: String
  width: Int
  height: Int
}

S3を構築

続いて、Amplify CLIを使い S3 を構築します。ここで構築するS3は、Webアプリから登録する画像を保存する場所になります。

$ amplify add storage
? Please select from one of the below mentioned services: Content (Images, audio, video, etc.)
? You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want to add auth now? Yes
Using service: Cognito, provided by: awscloudformation

 The current configured provider is Amazon Cognito. 

 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Email
 Do you want to configure advanced settings? No, I am done.
Successfully added auth resource
? Please provide a friendly name for your resource that will be used to label this category in the project: OgpSample
? Please provide bucket name: ogp-sample
? Who should have access: Auth and guest users
? What kind of access do you want for Authenticated users? create/update, read, delete
? What kind of access do you want for Guest users? create/update, read, delete
? Do you want to add a Lambda Trigger for your S3 Bucket? No
Successfully updated auth resource locally.
Successfully added resource OgpSample locally

If a user is part of a user pool group, run "amplify update storage" to enable IAM group policies for CRUD operations
Some next steps:
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud

AWSに反映します。

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name     | Operation | Provider plugin   |
| -------- | ----------------- | --------- | ----------------- |
| Api      | ogpsample         | Create    | awscloudformation |
| Auth     | ogpsamplebd408503 | Create    | awscloudformation |
| Storage  | OgpSample         | Create    | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
         - Todo
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/directives#auth

GraphQL schema compiled successfully.

Edit your schema at /home/ec2-user/environment/ogp-sample/amplify/backend/api/ogpsample/schema.graphql or place .graphql files in a directory at /home/ec2-user/environment/ogp-sample/amplify/backend/api/ogpsample/schema
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
⠏ Updating resources in the cloud. This may take a few minutes...

公開用S3バケット作成

OGP画像を置く公開用S3バケットを作成します。

バケット作成

AWS管理コンソール > S3 と選択し、任意の名前でバケットを作成します。今回は、ogp-sample-ogp-imageとしておきます。諸々の設定は一旦デフォルトのままでOKです。

IAMロール作成

公開用のS3なのでアクセス制御の為にIAMロールを触っておきましょう。AmplifyでCognitoが使える状態になっているので、今回は「認証されていないロール」を使います。「認証されていないロール」はCognitoにログインしていない状態で使えるロールになります。今回のサンプルWebアプリはログイン機能を使わないので、これだけで十分でしょう。

AWS管理コンソールから Cognito > IDプールの管理 と開きます。

ogpsample〜〜というIDプールがあるはずなのでこれを選択します。

Cognito1

画面右上のID プールの編集 を選択し、「認証されていないロール」のロール名をコピーします。

Cognito2

次に、AWS管理コンソールから IAM > ロール と選択して、前手順のロール名を探します。

IAM1

インラインポリシーの追加 を選択します。

IAM2

JSONタブを開き、以下の内容を記載し、ポリシーを作成します。ポリシー名は何でも結構です。(public-s3-access-policyとか)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::ogp-sample-ogp-image/*"
            ],
            "Effect": "Allow"
        }
    ]
}

作成したら、ロールARNをコピーしておきましょう。後で実装の使います。

IAM3

再び、S3のバケットを開いてアクセス制御を行います。

AWS管理コンソールから S3 > ogp-sample-ogp-image を開き、アクセス権限 > ブロックパブリックアクセスを選択します。

S3_1

編集ボタンを押して、「パブリックアクセスをすべてブロック」のチェックを外し、保存します。

S3_2

続いて、バケットポリシー タブを開き、以下のバケットポリシーを設定し、保存します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::ogp-sample-ogp-image/*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "{Cognitoの認証されていないロールARN}"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::ogp-sample-ogp-image/*"
        }
    ]
}

最後に、CORSの設定 タブを開き、以下のCORS設定を行い、保存します。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <ExposeHeader>ETag</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

サンプルWebアプリ構築

ここからはWebアプリを構築していきます。

Amplify関連とAWS SDKのパッケージをインストールします。AWS SDKはAmplifyを使わずに公開用S3にアップする為に使います。

npm install aws-amplify aws-amplify-react
npm install aws-sdk

画面遷移用にreact-routerをインストールします。

$ npm install react-router-dom

Amplifyの設定を読み込む

/ogp-sample/src/index.js (修正)

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import * as serviceWorker from './serviceWorker'
import Amplify from "aws-amplify"
import config from "./aws-exports"
Amplify.configure(config)

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()

6〜8行目にAmplify関連の処理を記述します。設定ファイル(aws-exports.js)を読み込み、Amplifyに渡しているだけです。これで、Amplifyを使ってS3やAppSyncへアクセスする事が可能になります。

トップ画面の作成

App.jsにてトップ画面へのルーティングを行います。

/ogp-sample/src/App.js (修正)

import React from 'react'
import { BrowserRouter, Route } from 'react-router-dom'
import { Top }  from "./pages/Top"

const App = () => {
  return (
    <div>
      <BrowserRouter>
        <div>
          <Route path="/" exact component={Top} />
        </div>
      </BrowserRouter>
    </div>
  );
};

export default App

画像を一覧表示したり、新規画像を登録する簡易的なトップ画面を作成します。コード量を減らす為、デザインは考慮していません。

/ogp-sample/src/pages/Top.js (新規作成)

import React, { useEffect, useState } from "react"
// import { withRouter } from 'react-router'
import { useHistory } from "react-router-dom"
import { API, graphqlOperation, Storage, Auth } from "aws-amplify"
import { listImages } from '../graphql/queries'
import { createImage } from '../graphql/mutations'
import AWS from 'aws-sdk'

export const Top = () => {
  const history = useHistory();
  const [images, setImages] = useState([])
  const [name, setName] = useState("")
  const [description, setDescription] = useState("")
  const [file, setFile] = useState({})

  const getImage = async () => {
    const res = await API.graphql(graphqlOperation(listImages))
    const images = res.data.listImages.items
    for (var i in images) {
      images[i].path = await Storage.get(images[i].path)
    }
    setImages(images)
  }

  useEffect(() => {
    getImage()
  }, [])

  const handleToDetailPage = (id) => {
    history.push(`/detail/${id}`)
  }
  const registerImage = async () => {
    if (!name || !description || !file.name) {
      return
    }
    // 画像情報取得
    const mimeType = file.type;
    const { width, height } = await loadImage(file)
    // 画像情報保存
    await API.graphql(graphqlOperation(createImage, { input: {
      path: file.name,
      name,
      description,
      width,
      height,
      mimeType
    }}))
    // Webアプリ用の画像保存用
    await Storage.put(file.name, file)
    // OGP用の画像保存
    const credentials = await Auth.currentCredentials()
    AWS.config.update({ credentials: credentials, region: 'ap-northeast-1' })
    var s3 = new AWS.S3({ params: { Bucket: 'ogp-sample-ogp-image' } })
    await s3.putObject({ Body: file, Key: file.name }).promise()
    // 一覧を再表示
    getImage()
  }
  const loadImage = (file) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onload = function (e) {
        const img = new Image()
        img.onload = () => resolve(img)
        img.onerror = (e) => reject(e.message)
        img.src = e.target.result
      }
    })
  }
  const renderImages = () => {
    return images.map((image, i) => (
      <img key={i} src={image.path} style={{ width: 320, marginRight: 16 }} onClick={ () => { handleToDetailPage(image.id) }} />
    ))
  }
  return (
    <div style={{padding: 16}} >
      <h1>Top</h1>
      {renderImages()}
      <div style={{marginTop: 24}}>
        <div>名前<input value={name} name="name" style={{margin: 4}} onChange={ (e) => setName(e.target.value) }></input></div>
        <div>説明<input value={description} name="name" style={{margin: 4}} onChange={ (e) => setDescription(e.target.value) }></input></div>
        <div>画像<input name="file" type="file" style={{margin: 4}} onChange={ (e) => setFile(e.target.files[0]) }></input></div>
        <button onClick={registerImage}>登録</button>
      </div>
    </div>
  )
}

ではアプリを起動して確認しておきましょう。

$ npm start

ブラウザで http://localhost:8080 にアクセスします。

Top画面

※上記画面は、既に画像を2枚登録している状態です。

かなり簡易的ですが、以下ができるようになりました。

  • 画像の新規登録
  • 画像の一覧表示

詳細画面の作成

トップ画面の画像をクリックしたら詳細画面へ遷移するようにします。

/ogp-sample/src/App.js (修正)

import React from 'react'
import { BrowserRouter, Route } from 'react-router-dom'
import { Top }  from "./pages/Top"
import { Detail } from "./pages/Detail"

const App = () => {
  return (
    <div>
      <BrowserRouter>
        <div>
          <Route path="/" exact component={Top} />
          <Route path="/detail/:id" exact component={Detail} />
        </div>
      </BrowserRouter>
    </div>
  );
};

export default App

App.jsにて詳細画面(/detail)へのルーティングを追加します。

では詳細画面を作っていきます。

ogp-sample/src/pages/Detail.js (新規作成)

import React, { useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import { API, graphqlOperation, Storage } from "aws-amplify"
import { getImage } from '../graphql/queries'

export const Detail = () => {
  const { id } = useParams();
  const [image, setImage] = useState({})

  useEffect(() => {
    const f = async () => {
      const res = await API.graphql(graphqlOperation(getImage, { id: id }))
      const image = res.data.getImage
      image.path = await Storage.get(image.path)
      setImage(image)
    }
    f();
  }, [ id ]);

  return (
    <div>
      <h1>Detail</h1>
      <div>
        <img src={image.path} style={{ width: 640 }} />
        <p>{image.description}</p>
      </div>
    </div>
  )
}

トップ画面から渡されたキー情報を基に、AppSyncとS3から必要なデータを取得して表示しているだけです。

トップ画面の画像をクリックすると無事に詳細画面へ遷移しました。

詳細画面

Twitter、Facebook共有機能の追加

ではでは、TwitterとFacebookへの共有機能を実装していきます。

ReactでSNSへの共有機能を簡単に実装できる react-share を使いますので、まずはインストールから。

npm install react-share

Twitter、Facebookの共有ボタンを詳細画面に設置します。

ogp-sample/src/pages/Detail.js (修正)

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { API, graphqlOperation, Storage } from "aws-amplify";
import { getImage } from '../graphql/queries';
import { TwitterShareButton, TwitterIcon, FacebookShareButton, FacebookIcon } from "react-share";

export const Detail = () => {
  const { id } = useParams();
  const [image, setImage] = useState({})

  useEffect(() => {
    const f = async () => {
      const res = await API.graphql(graphqlOperation(getImage, { id: id }))
      const image = res.data.getImage
      image.path = await Storage.get(image.path)
      console.log(image);
      setImage(image)
    }
    f();
  }, []);

  return (
    <div>
      <h1>Detail</h1>
      <div>
        <img src={image.path} style={{ width: 640 }} />
        <p>{image.description}</p>
      </div>
      <div>
        <TwitterShareButton url={window.location.href} title={image.description} >
          <TwitterIcon size={24} round />
        </TwitterShareButton>
        <FacebookShareButton url={window.location.href} quote={image.description}>
          <FacebookIcon size={24} round />
        </FacebookShareButton>
     </div>
    </div>
  )
}

めっちゃ簡単ですね!

詳細画面_SNS共有有り

Twitterの共有ボタンをクリックすると別ウィンドウが開きます。

Twitter_OGP無し

同じく、Facebookの共有ボタンをクリックすると別ウィンドウが開きます。

Facebook_OGP無し

ここまでで、Webアプリの構築は終了です。

Twitter、Facebook共に画像が表示されていないのが確認できますね。

ようやく本題のOGP対応を行いましょう!

CloudFront+S3によるWebアプリの公開

作成したWebアプリをAmplifyのホスティング機能を使って、CloudFront+S3で公開します。

$ amplify add hosting
Scanning for plugins...
Plugin scan successful
? Select the plugin module to execute Amazon CloudFront and S3
? Select the environment setup: PROD (S3 with CloudFront using HTTPS)
? hosting bucket name ogpsample-20200809181949-hostingbucket
Static webhosting is disabled for the hosting bucket when CloudFront Distribution is enabled.

You can now publish your app using the following command:
Command: amplify publish

Select the environment setup で PRODを選択しないとCloudFrontが構築されないのでご注意を。

ではAWSに反映します。

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
| Category | Resource name     | Operation | Provider plugin   |
| -------- | ----------------- | --------- | ----------------- |
| Hosting  | S3AndCloudFront   | Create    | awscloudformation |
| Api      | ogpsample         | No Change | awscloudformation |
| Auth     | ogpsamplebd408503 | No Change | awscloudformation |
| Storage  | OgpSample         | No Change | awscloudformation |
? Are you sure you want to continue? Yes
⠏ Updating resources in the cloud. This may take a few minutes...
〜中略〜
✔ All resources are updated in the cloud

Hosting endpoint: https://XXXXXXXXXXXXX.cloudfront.net

構築が完了するとCloudFrontのURLが表示されます。

続いて、Webアプリをデプロイして公開します。

$ amplify publish

完了したらブラウザでアクセスしてみましょう。トップ画面が表示されればOKです。

※URLにアクセスした時にエラーがでる場合

CloudFrontエラー

CloudFront+S3で構築すると1〜2時間ぐらい待たないとアクセスができない事があります(最大24時間との事)。ずっと待っていても時間が勿体ないので、Lambda@Edgeの構築に取り掛かりましょう。

Lambda@Edgeを追加

OGP情報を生成するLambdaを作ります。

Lambda作成

AWS管理コンソール > Lambda と開き、リージョンをバージニア北部にして、関数の作成 を選択します。※ CloudFrontから呼び出すLambda@Edgeのコードは、バージニア北部リージョンに設置する必要があります。

Lambda1

以下の通り入力、選択し、関数の作成を選択します。

  • 関数名:ogp-sample-lambda (任意の名前でOK)
  • ランタイム: Node.js 12.x
  • 実行ロール:AWSポリシーテンプレートから新しいロールを作成
  • ロール名:ogp-sample-lambda-role (任意の名前でOK)
  • ポリシーテンプレート:基本的なLambda@Edgeのアクセス権限(CloudFrontトリガーの場合)

関数コードに以下のコードを登録して保存します。

'use strict';

const AWS = require('aws-sdk');
const querystring = require('querystring');
const bots = [
  'Twitterbot',
  'facebookexternalhit'
];
const FB_APP_ID = 'AAAAAAAAAA';
const DDB_TABLE_NAME = 'Image-BBBBBBBBBB-dev';
const URL_PREFIX = 'https://CCCCCCCCCC.cloudfront.net';
const OGP_IMAGE_PREFIX = 'https://DDDDDDDDDD.s3-ap-northeast-1.amazonaws.com';

const ddb = new AWS.DynamoDB.DocumentClient({
  apiVersion: '2012-08-10',
  region: 'ap-northeast-1'
});

exports.handler = async (event, context, callback) => {
  console.log("start event", event);

  const request = event.Records[0].cf.request;
  const uri = request.uri;
  const userAgent = request.headers['user-agent'][0].value;
  console.log("userAgent", userAgent);
  const isBot = bots.some(bot => {
    return userAgent.includes(bot)
  });
  if (isBot) {
    const id = uri.substring(uri.lastIndexOf("/") + 1);
    const info = await getInfo(id);
    const imagePath = await getS3Url(info.path);
    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, info, imagePath)
    };
    callback(null, response);
    return;
  }
  callback(null, request);
};

function getContent(url, info, imagePath) {
  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="Fixel Inc." name="author" />
      <title>${info.name}</title>
      <meta content="${info.description}" name="description">

      <meta content="${FB_APP_ID}" property="fb:app_id" />
      <meta content="${url}" property="og:url" />
      <meta content="article" property="og:type" />
      <meta content="ja_JP" property="og:locale" />
      <meta content="${info.name}" property="og:title" />
      <meta content="${info.description}" property="og:description" />
      <meta content="${imagePath}" property="og:image" />
      <meta content="${imagePath}" property="og:image:secure_url" />
      <meta content="${info.mime_type}" property="og:image:type" />
      <meta content="${info.width}" property="og:image:width" />
      <meta content="${info.height}" property="og:image:height" />

      <meta content="summary_large_image" property="twitter:card" />
      <meta content="@XXXXX" property="twitter:site" />
      <meta content="${info.name}" property="twitter:title" />
      <meta content="${info.description}" property="twitter:description" />
      <meta content="${imagePath}" property="twitter:image" />
    </head>
    <body>
    </body>
    </html>
    `;
};

async function getInfo(id) {
  try {
    var params = {
      TableName: DDB_TABLE_NAME,
      Key: { 'id': id.toString() }
    };
    var result = await ddb.get(params).promise();
    return result.Item;
  } catch (e) {
    console.error("getInfo error", e);
    throw e;
  }
}

async function getS3Url(key) {
  try {
    const url = `${OGP_IMAGE_PREFIX}/${key}`;
    return url;
  } catch (e) {
    console.error("getS3Url error", e);
    throw e;
  }
}

以下、ご自身の環境に合わせて変更してください。

※1 FacebookのアプリIDの取得方法は公式ページをご確認ください。

https://developers.facebook.com/docs/apps#register

Lambdaの実行ロールにポリシーを追加

LambdaからDynamoDBとS3にアクセスするので、先ほどの手順で作ったロール(ogp-sample-lambda-role)にポリシーを追加します。

AWS管理コンソールから、IAM > ロール と選択し、 先ほど作ったロール(ogp-sample-lambda-role)を選択します。

以下2つのポリシーをアタッチします。

  • AmazonDynamoDBReadOnlyAccess
  • AmazonS3ReadOnlyAccess
IAM4
IAM5

Lambda関数のバージョン作成

バージョンを作成します。※CloudFrontから呼び出す際にバージョン指定が必要です。

AWS管理コンソールから、 Lambda > 関数(ogp-sample-lambda)を開き、アクションから新しいバージョンを作成します。

Lambda2
Lambda2

CloudFrontに設定するARNをコピーしておきます。(後ほど使います)

Lambda4

CoudFrontからLambda@Edgeを呼び出す設定

CloudFrontからLambdaを呼び出すよう設定します。

AWS管理コンソールで CloudFront を開き、該当ディストリビューションのIDをクリックします。

CloudFront1

Behaviors タブ を選択し、Path Patternが Default(*) のものを選択してEditボタンをクリックします。

CloudFront2

Lambda Function Associationsに必要な情報を入力して、Yes,Editボタンをクリックします。

  • CloudFront Event:Viewer Request
  • Lambda Function ARN:LambdaのARN(先ほどコピーしたもの)
    • 例)arn:aws:lambda:us-east-1:111122223333:function:ogp-sample-lambda:1

これで準備完了です!

動作確認

ではでは、動作確認をしてみましょう。

ブラウザでCloudFrontのURLを開きます。※ローカル実行だとOGP表示できないのでご注意を!!

Top画面2

右画像をクリックし詳細画面に遷移してみます。

Twitterボタンをクリックします。

Twitter_OGP有り

出ました!!

次はFacebookボタンをクリックします。

Facebook_OGP有り

こちらも出ました!

もし画像が表示されない場合は、少し時間を置いてもう一度、Twitter、Facebookの共有ボタンを押してみてください。

最後に

いかがでしょうか?結構長い記事になってしまいましたが、サンプル用のWebアプリを実装するコードが多く、実際のOGP対応のコード量はそれ程多くないと思います。

OGP用に画像を公開用S3バケットに置く必要があったり多少の手間はありますが、SSR対応のためにバックエンド用のサーバを用意する必要が無いので開発コスト、運用コスト共に削減が期待できるのではないでしょうか?

Fixelは常に新しい技術を追いながら、単なる技術集団では無く、UXデザインから実装、インフラ構築、運用まで一貫してご支援させて頂きます。

「○○したいんだけどどうすればいいの?」などあれば、弊社まで是非お気軽にお声掛けください。

一覧に戻る
お問合わせ