LoginSignup
25
11

More than 5 years have passed since last update.

Firebase + SPA + CloudFront + Lambda で SSR なしに OGP 対応

Last updated at Posted at 2018-03-10

この記事はFirebase + SPA で SSR なしに OGP 対応の改善版です。今回は CloudFront + Lambda@edge を使って Facebook / Twitter BOT の場合のみ別の URL へ振り分けてあげることで OGP に対応しようという試みです。

Lambda@edge

Lambda@edge を使えば CloudFront へのリクエストの際に細かい制御をすることができます。今回は UserAgent を見て条件に一致したら OGP 用の URL へ変更するようにします。

'use strict'

const bots = [
  'Twitterbot',
  'facebookexternalhit'
]

module.exports.redirect = (event, context, callback) => {
  const request = event.Records[0].cf.request
  const headers = request.headers
  const isBot = bots.some(v => {
    return headers['user-agent'][0].value.includes(v)
  })
  if (isBot) {
    request.uri = request.uri.replace(/^\/@note/g, '/@note_bot')
  }
  callback(null, request)
}

これだけです。

UserAgent の中身を見て、Twitterbotfacebookexternalhit が含まれていたら、/@note の URL を /@note_bot へ変更をします。

手動で Lambda を作るのは面倒くさいので、ServerlessFramework を使います。

sls create -t aws-nodejs --name cloudfront-url-rewrite

ServerlessFramework 設定

IAM ロールなどが必要なので serverless.yml ファイルに定義します。@edge で使うためには us-east-1 リージョンへデプロイする必要があるので注意してください。

service: cloudfront-url-rewrite

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1
  memorySize: 128
  timeout: 1
  role: LambdaEdgeRole

functions:
  hello:
    handler: handler.redirect

resources:
  Resources:
    LambdaEdgeRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
                  - edgelambda.amazonaws.com
              Action:
                - sts:AssumeRole
        Policies:
          - PolicyName: ${opt:stage}-serverless-lambdaedge
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                    - logs:DescribeLogStreams
                  Resource: 'arn:aws:logs:*:*:*'
                - Effect: "Allow"
                  Action:
                    - "s3:PutObject"
                  Resource:
                    Fn::Join:
                      - ""
                      - - "arn:aws:s3:::"
                        - "Ref" : "ServerlessDeploymentBucket"

あとは普通にデプロイすれば OK です。

sls deploy -v --stage production

CloudFront 設定

次は作成した Lambda@edge を CloudFront へ紐付けます。

「Behaviors」を新規作成し、一番下にある「Lambda Function Associations」に追加します。

  • Viewer Request : ARN

Lambda@edge の ARN を設定するのですが、バージョンも含めて指定する必要があるので注意してください。

例 (この場合バージョン6) arn:aws:lambda:us-east-1:1234:function:cloudfront-url-rewrite-production:6

BOT 用の URL を用意

BOT は /@note_bot という URL に振り分けられているので Firebase 側で用意してあげます。

{
  "hosting": {
    "rewrites": [
      {
        "source": "/@note_bot/*",
        "function": "note"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

note Function

import * as functions from 'firebase-functions';
import DocumentSnapshot = FirebaseFirestore.DocumentSnapshot;
import DocumentData = FirebaseFirestore.DocumentData;
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);
const db = admin.firestore()

function buildHtmlWithPost (id: string, noteObj:DocumentData) : string {
  return `<!DOCTYPE html><head>
  <title>${noteObj.title}</title>
  <meta property="og:title" content="${noteObj.title}">
  <meta property="og:image" content="${noteObj.image}">
  <meta property="og:image:width" content="600">
  <meta property="og:image:height" content="600">
  <meta name="twitter:card" content="summary">
  <meta name="twitter:title" content="${noteObj.title}">
  <meta name="twitter:image" content="${noteObj.image}">
  <link rel="canonical" href="/@note/${id}">
  </head><body>
  <script>window.location="/@note/?noteId=${id}";</script>
  </body></html>`
}

export const note = functions.https.onRequest(function(req, res) {
  const path = req.path.split('/')
  const noteId = path[2]
  db.collection('note').doc(noteId).get().then((doc:DocumentSnapshot) : void => {
    const htmlString = buildHtmlWithPost(noteId, doc.data())
    res.status(200).end(htmlString)
  }).catch(err => {
    res.status(500).end(err)
  })
})

以上です。

なお、今は ServerlessFramework 自身が Lambda@edge に対応していませんが、対応予定で進んでいるみたいなのでしばらくしたら、もっと簡単にできるかもしれません。

25
11
0

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
25
11