CloudFront + S3 + Next.js で綺麗にルーティングする
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
- 事前準備をします。ここ人によってはハマるかもしれません。
# ... 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
- 関数を実装しましょう。
lib/
にlambda-stack.[FUNC NAME].ts
というファイルを作ります。 - パスを処理する関数を書きます。こちらの記事が参考になりました。
-
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')
}
}
-
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では使えないので環境変数の設定などは書かない */
})
- いざデプロイ
# ... Deploy ... #
$ cdk deploy
- 成功したら、https://us-east-1.console.aws.amazon.com/lambda/home を開きます
-
Configuration
>Environment variables
に環境変数がセットされてるので消します -
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"
}
]
}
-
Actions
>Deploy to Lambda@Edge
でフォームに設定したら、Deploy
ポチッ - サイトを見るといい感じにルーティングされるようになってるはず!
Tips
最初に申し上げますと、手順のややこしさからも分かるとおりハマりどころは結構多いという印象です。いくつか列挙してみたのでご参考までに。
強い Permission を設定しないといけない
CloudFormation を使っていろんなことをするため、さまざまなPermissionが必要になります。ログイン中のユーザに権限がない場合は以下を実行して見ると良いかもしれないです。(私は都合もあり1つ1つ権限を付与しました)
$ 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 bootstrap
がno changes
と見做されて成功したことになっている
- →
- コンソールからCDKのstateを管理するbucketを消してからやり直す
- →
cdk bootstrap
がno changes
と見做されて成功したことになっている
- →
-
cdk destroy
してからやり直す- →
cdk bootstrap
がno changes
と見做されて成功したことになっている
- →
- Docker imageを消してからやり直す(Region情報もcontainerに渡してると思った)
- → docker buildに失敗しだす(後述)
-
AWS_DEFAULT_REGION
をus-east-1
に書き換える- →
cdk bootstrap
は成功する - →
cdk deploy
がLambdaStack 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.json
にtoolkitStackName
を別名で設定する- →
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でも起きました。
> [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からも触れる)
{
...
"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
Discussion