🐫

CloudFront + S3 + Next.js で綺麗にルーティングする

2022/11/02に公開

Probrem

Next.js で build した SSG のアプリをS3 + CloudFrontにデプロイすると、ルーティングやブラウザバックをした際にうまくいかず、403 が帰ってくる時があります。

解説

/hoge とルートを切った場合、 next build & next exportで吐き出されるファイルは /hoge.html になります。ところが next router は、 /hoge というパスにアクセスしようとするため存在しないURLにアクセスすることになります。この時 CloudFront の設定に応じたエラーページが返されます。

Solution

Vercelを使いましょう
Labmda@Edge で URL パスを処理してあげれば良い感じにアクセスできるようになります。

Requirements
  • aws-cli が install されている
  • aws configure が実行されていて、~/.aws/* に対象の aws アカウントがセットされている
  • Next.jsを用いて SSG したアプリがあり、S3 + CloudFront にデプロイされている
  • npmバージョン
npm --version
8.5.3

How to

  1. 事前準備をします。ここ人によってはハマるかもしれません。
sh
# ... Setup ... #

$ mkdir lambda && cd lambda
$ npm install -g aws-cdk typescript # 開発環境はAWS Cloud9とかにしても良いです
$ cdk bootstrap aws://[ACCOUNT_NUMBER]/us-east-1 # なぜus-east-1かはTipsで解説します
$ cdk init --language=typescript
$ npm install aws-sdk
$ npm install --D @types/aws-lambda @aws-cdk/aws-lambda-nodejs

  1. 関数を実装しましょう。lib/lambda-stack.[FUNC NAME].tsというファイルを作ります。
  2. パスを処理する関数を書きます。こちらの記事が参考になりました。
  3. lambda-stack.tsを書き換えます。
lib/lambda-stack.ts
import * as cdk from '@aws-cdk/core'
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs'

export class LambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)
    new NodejsFunction(this, 'origin-request')
  }
}
  1. lambda.tsを書き換えます。
bin/lambda.ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from '@aws-cdk/core'
import { LambdaStack } from '../lib/lambda-stack'

const app = new cdk.App()
new LambdaStack(app, 'LambdaStack', {
  /* Lambda@Edgeでは使えないので環境変数の設定などは書かない */
})
  1. いざデプロイ
sh
# ... Deploy ... #

$ cdk deploy
  1. 成功したら、https://us-east-1.console.aws.amazon.com/lambda/home を開きます
  2. Configuration > Environment variables に環境変数がセットされてるので消します
  3. Configuration > Permissions の Execution role を開いて Trust Relationships にある Principal を以下に更新します
Principal
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "lambda.amazonaws.com",
                    "edgelambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
  1. Actions > Deploy to Lambda@Edge でフォームに設定したら、Deploy ポチッ
  2. サイトを見るといい感じにルーティングされるようになってるはず!

Tips

最初に申し上げますと、手順のややこしさからも分かるとおりハマりどころは結構多いという印象です。いくつか列挙してみたのでご参考までに。

強い Permission を設定しないといけない

CloudFormation を使っていろんなことをするため、さまざまなPermissionが必要になります。ログイン中のユーザに権限がない場合は以下を実行して見ると良いかもしれないです。(私は都合もあり1つ1つ権限を付与しました)

sh
$ cdk bootstrap aws://[ACCOUNT_NUMBER]/us-east-1 --cloudformation-execution-policies 'arn:aws:iam::aws:policy/AdministratorAccess'

一度 cdk bootstrap するとやり直しづらい

ひとえに、CloudFormationが全く冪等でないことが原因なのですが、デプロイのやり直しが面倒臭いです。ただタスクに失敗した場合は、CloudFormation の Stack を削除することでうまくいく場合が多いです。

が、一度 cdk bootstrap が完了してしまうとロールバックが非常に面倒でした。Lambda@Edge は us-east-1 にデプロイする必要がある のですが、私はリージョンを間違えてデプロイしたのでやり直しました。試した範囲だと、以下のいずれもうまくやり直すことができませんでした。


  • CDKToolkitのstackをコンソールでdeleteしてからやり直す
    • cdk bootstrapno changes と見做されて成功したことになっている
  • コンソールからCDKのstateを管理するbucketを消してからやり直す
    • cdk bootstrapno changes と見做されて成功したことになっている
  • cdk destroyしてからやり直す
    • cdk bootstrapno changes と見做されて成功したことになっている
  • Docker imageを消してからやり直す(Region情報もcontainerに渡してると思った)
    • → docker buildに失敗しだす(後述)
  • AWS_DEFAULT_REGIONus-east-1 に書き換える
    • cdk bootstrapは成功する
    • cdk deployLambdaStack failed: Error: This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/unknown-region")となる
  • cdk.jsontoolkitStackName を別名で設定する
    • cdk bootstrap を叩くと Environment aws://[ACCOUNT_NUMBER]/us-east-1 failed bootstrapping: Error: The stack named [別名] failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_COMPLETE
    • もしかしたらCDK側のコードで命名を合わせてればうまくいったかも(?)

...ので結局こうなりました。
コンソールから手動で cdk deploy が見にいく bucket と同名の bucket を作成する。 これでうまくデプロイできました。

docker buildに失敗する

  • M1Macだと起きるっぽいですが、Intelでも起きました。
sh
 > [internal] load metadata for public.ecr.aws/sam/build-nodejs14.x:latest:
------
failed to solve with frontend dockerfile.v0: failed to create LLB definition: unexpected status code [manifests latest]: 400 Bad Request
  • Docker Engineをこうする (MacだとDocker DesktopのGUIからも触れる)
docker engine
{
  ...
  "features": {
    "buildkit": false
  }, 
  ...
}
  • 再度cdkコマンドを走らせるとうまくいき、一度imageをpullできたら次からはbuildkitを使ってもいい

cdk init で吐き出されるコードが aws-lambda-nodejs の求めてるコードと似てるけど違う

地味なやつNo.1。lambda-stack.ts などのimportで元々 import * as cdk from 'aws-cdk-lib'となってる行を import * as cdk from '@aws-cdk/core'と書き換える必要があります。

Create Lambda@Edge のフォームがJSのエラーを吐く

地味なやつNo.2。フォームの値を全て正しくセットしても、Cannot read properties of undefined (reading 'startsWith')などと言ってきます。これはLambda@Edgeのフォームのバリデータが横着しているようで、リロードすると直ります。(怖い)

Ref

https://qiita.com/shinnoki/items/9cf68e68cbe594732b4b
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/getting_started.html
https://stackoverflow.com/questions/74041987/lambdaedge-cannot-read-properties-of-undefined-reading-startswith

Discussion