AWS Lambda Web AdapterでServerless Next.jsを実現する
こんにちは、フロントエンドエンジニアの堀江(@nandemo_3_)です。
2023年6月22、23日にAWS Dev Day 2023が開催されましたが、
「モダンフロントエンド デザインパターン〜優れたUXを実現するには〜」というフロントエンドの最新動向に関するセッションがありました。
そこで、Serverless Next.jsとそれをAWSで実現するインフラストラクチャーが紹介されており、
今回は、それを具体的に実現する方法をまとめました。
はじめに
まず、今回Serverless Next.jsを実現するために、AWS Lambda Web Adapterを使います。
Lambdaは、主にAPIなどのサーバサイド処理をサーバレスで実現するために使われますが、
AWS Lambda Web Adapterを用いることで、Lambda関数をWebアプリケーションのバックエンドとして使用することができます。
紛らわしくて申し訳ありませんが、「Serverless Next.js」というライブラリのことではないので、ご了承ください。
環境構築
Next.jsのプロジェクト作成
では、Next.jsのプロジェクト作成をします。
$ npx create-next-app What is your project named? … . Would you like to use TypeScript? … Yes Would you like to use ESLint? … Yes Would you like to use Tailwind CSS? … No Would you like to use `src/` directory? … Yes Would you like to use App Router? (recommended) … Yes Would you like to customize the default import alias? … No
Next.js Standaloneの設定
今回、LambdaにNext.jsのビルド成果物をデプロイしますが、Lambdaはパッケージファイルサイズが250MBと決められているため、ビルド成果物のファイルサイズを削減する必要があります。
そのため、Next.jsのStandaloneモードに設定をします。
設定方法は、以下の通りnext.confing.js
に1行追加するだけです。
// next.confing.js module.exports = { output: 'standalone', }
npm run build
することで、.next/standalone
が生成されます。
これで、Standaloneモードにすることができました。
Dockerfile作成
次に、Dockerfileを作成します。
AWS Lambda Web Adapterを使用したLambda関数を作成するには、コンテナイメージを用いるのが一般的のようです。
そのため、Dockerfileを作成します。
今回は、こちらのコードを参考にしました。(まるパクり)
すでに、Dockerfileがある場合は、以下のコードを1行追加するだけです。
# Dockerfile COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.5.0 /lambda-adapter /opt/extensions/lambda-adapter
AWS CDKの設定
続いて、AWS CDKでデプロイを行うので、その設定をしていきます。
今回は手軽にAWS CDKを使いましたが、AWS SAMでもできるので、慣れている方をお使いいただければと思います。
aws-cdk
もcdk
もほぼ同じコマンドのようですが、aws-cdk
の方が無駄なパッケージをインストールしないということだったので、こちらを使いました。
※aws cliのインストールは省略
$ npx aws-cdk@2 --version Need to install the following packages: aws-cdk@2.87.0 Ok to proceed? (y) y
$ mkdir cdk $ cd cdk $ npx aws-cdk@2 init app --language typescript $ npm install @aws-cdk/aws-apigatewayv2-integrations-alpha @aws-cdk/aws-apigatewayv2-alpha
CDKのスタックの設定
LambdaとAPI Gatewayを作成する定義をスタックに記述していきます。
code: DockerImageCode.fromImageAsset
にはDockerfileのある相対パスを指定します。
// /cdk/lib/cdk-stack.ts import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { Duration } from 'aws-cdk-lib'; import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha'; import { DockerImageCode, DockerImageFunction } from 'aws-cdk-lib/aws-lambda'; import { HttpApi } from '@aws-cdk/aws-apigatewayv2-alpha'; import { Platform } from 'aws-cdk-lib/aws-ecr-assets'; export class Frontend extends cdk.Stack { readonly endpoint: string; constructor(scope: Construct, id: string, props: cdk.StackProps) { super(scope, id); // Lambdaの定義 const handler = new DockerImageFunction(this, 'Handler', { code: DockerImageCode.fromImageAsset('../', { platform: Platform.LINUX_AMD64, }), memorySize: 256, timeout: Duration.seconds(30), }); // API GatewayIの定義 new HttpApi(this, 'Api', { apiName: 'Frontend', defaultIntegration: new HttpLambdaIntegration('Integration', handler), }); } }
デプロイ準備
では、デプロイの作業に入りますが、
その前にcdk bootstrap
を実行して、リソースをプロビジョニングします。
$ npm run cdk bootstrap Error: ENAMETOOLONG: name too long, copyfile ...
しかし、エラーが発生しました。cdkフォルダ配下もコピー対象となっており、ファイルパスの文字数上限を超えてしまったようです。
.dockerignore
にcdk
を追加したら、エラーが解消されました。
// .dockerignore node_modules .next cdk
再度実行しましたが、再びエラーが発生しました。
認証情報の設定をし忘れていたため、アクセス拒否されてしまいました。(初歩的なミス・・・)
⏳ Bootstrapping environment aws://xxxxxxxxxx/us-east-1... ❌ Environment aws://xxxxxxxxxx/us-east-1 failed bootstrapping: AccessDenied: User: arn:aws:iam::xxxxxxxxxx:user/xxxxxxxxxx is not authorized to perform: cloudformation:DescribeStacks on resource: arn:aws:cloudformation:us-east-1:xxxxxxxxxx:stack/CDKToolkit/* with an explicit deny in an identity-based policy
AWS マネージメントコンソールにてIAMユーザの作成と、aws configure
でAWS CDKの認証情報とリージョンの指定を行いました。
$ aws configure AWS Access Key ID [****************W7NP]: xxxxxxxxxx AWS Secret Access Key [****************dBWK]: xxxxxxxxxx Default region name [ap-northeast-1]: ap-northeast-1 Default output format [text]:
最初、Default region name
を未設定にしてしまい、cdk bootstrap
で作成されるサービス群が「us-east-1」に作成されてしまいました。
(この時は気づかなかったのですが、上記のエラーメッセージにも、デプロイ先のリージョンが「us-east-1」になっておりますね)
そのため、この後実行するcdk deploy
にて、参照リージョンの差異により、デプロイに失敗したので、指定のリージョンを設定しましょう。(今回は、「ap-northeast-1」にしました)
これで、cdk bootstrap
が成功しました。
AWS マネージメントコンソールにて、S3バケットが作成されているのが分かるかと思います。(リージョンも「ap-northeast-1」になっていますね)
$ npm run cdk bootstrap > cdk@0.1.0 cdk > cdk bootstrap ⏳ Bootstrapping environment aws://xxxxxxxxxx/ap-northeast-1... Trusted accounts for deployment: (none) Trusted accounts for lookup: (none) Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize. CDKToolkit: creating CloudFormation changeset... ✅ Environment aws://xxxxxxxxxx/ap-northeast-1 bootstrapped.
デプロイ
最後に、デプロイを行います。デプロイは1発で成功しました。
$ npm run cdk deploy Do you wish to deploy these changes (y/n)? y Frontend: deploying... [1/1] Frontend: creating CloudFormation changeset... ✅ Frontend ✨ Deployment time: 64.44s Stack ARN: arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxx:stack/Frontend/xxxxxxxxxx ✨ Total time: 68.11s
これで、AWS CDKによるAWS Lambda Web Adapterのデプロイが完了です。
マネジメントコンソールにて、API GatewayやLambda関数が作成されていることが確認できます。
API GatewayのAPIエンドポイントをブラウザでアクセスすると、Webサイトが表示されます。
ここからフロントエンドのカスタマイズをしましょう。(今回はこんな感じにしました)
ソースコード
今回のソースコードはこちらです。
最後に
今回は、簡単ではありますが、AWS Lambda Web Adapterを用いて、Next.jsのサーバレス化を実現してみました。
この次のステップに、LambdaでSSR Streamingを実現するというものがあります。(参考資料「Next.js 13 の SSR Streaming を AWS Lambda Response Streaming で実装する方法」より)
Next.jsのSSRは、サーバでのレンダリング処理が完了後にレスポンスを返すため、TTFB(Time To First Byte)が遅いという問題があります。
そのために、レンダリング処理が完了したものから、五月雨でレスポンスを返すSSR StreamingというものがNext.js 13でリリースされました。
AWS Lambda Response Streamingを持ちいれば実現できるようですが、今回は対象外としました。(トライしたものの簡単にはできませんでした・・頑張ります)
最後まで、読んでいただきありがとうございました。