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