この記事はAWS LambdaとServerless Advent Calendar 2022の15日目の記事です。
フロントエンドの開発トレンドが多様化しています。JSをクライアントのブラウザでのみ実行するCSR(Client-side rendering)及びSPA(Single page application)のような開発構成に続き、近年ではサーバーサイドからJSを利用して動的にHTMLを生成するSSR(Server-side rendering)フレームワークの利用が浸透するようになりました。例えばReactではNext.js、VueではNuxt.jsがあります。
アプリケーションの目的や性質によって適切な技術選定を行うことは当然のことですが、様々なニーズに合わせて自由度高くアプリケーションを開発する必要が出てくることも多いのではないでしょうか。
この記事ではAWS環境での「サーバーレスなSSR」として、AWS Lambda を使って Nuxt3 アプリケーションを動かす構成を紹介します。Next.jsに関してはAWS Amplifyを利用する選択肢がありますが、この記事ではAWS Lambdaを直接指定してビルドとデプロイが可能になった最新のNuxt3を使って説明していきます。
Nuxt2の経験がある方は是非キャッチアップしていただき、Nuxt自体よく分からないという方でも、「コンテナを使わなくても、AWS LambdaでSSRフレームワークが動くのね」という観点で読んでいただけますと幸いです。
この記事を作成している時点(2022/12/14)での最新のNuxt3ではSPA/SSGを想定した静的アセットでのビルドも可能ですが、基本的には Universal Rendering(初回SSR実行、以降CSR)がおすすめです。また、Nuxt3はNitroという新しいエンジンを利用したビルドと実行を行い、ビルドターゲットとしてAWS Lambda がサポートされています。expressに依存せず軽便に扱えるので、エッジコンピューティングの恩恵を受けることも狙いとされています(※Rendering on CDN Edge Workers)。
総じてNuxt2のSPAよりも優れた開発経験となることが多いので、是非一度お試しいただければと思います。
構成の概要
基本的にはCloudFrontのエンドポイントからリクエストを受け、SSRの実行と静的ファイルのパスを振り分ける対応を行います。Lambdaに関しては今年リリースされたFunction URLを設定することで、シンプルな形としてCloudFrontと連携できます。もしLambdaの機能を使いこなす必要がなく、機能が限定される等の制約を気にしなくて良い場合、Lambda@Edgeでレンダリングを行う構成もあります。
また、API Gateway のエンドポイントを利用するパターンがあります。開発するアプリケーションによっては、CloudFront を置くほどのものでもなかったり、クライアント証明を導入する必要があったり、細かいアクセス制御を行いたいケースがあります。具体的には空ページを1枚挟んでアプリ間連携を行ったり、ちょっとした設定を行う1枚画面などのユースケースもあるかと思います。
Lambdaから直接静的ファイルのアセットを配信することも可能で、REST API・HTTP API 両方のモードで動作します。Lambda proxy integration で統合することで特別な設定を行わなくてもつながるので、最も構築が簡単な構成になります。
Nuxt3 アプリケーションを準備する
それでは、実際に構築を行ってみます。まずはサンプルとなるNuxt3アプリケーションのプロジェクトを生成します。
npx nuxi init sample-lambda-ssr-project
nuxt.config.ts
にビルド設定を追記します。nuxt2 を使われた経験がある方は馴染みのある設定ファイルではないでしょうか。個人的にはここで aws-lambda
という設定が書けるだけでもテンションが上りました(^^)
export default defineNuxtConfig({
// ...config
nitro: {
preset: 'aws-lambda',
serveStatic: false,
}
})
また、静的ファイルの配信を確認するため、画像を1枚追加しておきます。public
ディレクトリに画像を追加し、画像を表示するページも作成します。
※Nuxt2での static
は、Nuxt3になって public
に変わっています。
ここまで準備ができたら、ビルドをしてデプロイするアセットを作成します。
問題なく完了すれば、.output
ディレクトリにデプロイする対象のファイルが生成されていることを確認できます。
デプロイの設定
Serverless Framework を利用してLambdaへデプロイを行います。 以下のコマンドでパッケージを追加します。
yarn add -D serverless
serverless.yml
ファイルを作成し、以下の設定を書いておきます。
service: sample-lambda-ssr-nuxt3
frameworkVersion: '3'
provider:
name: aws
stage: dev
region: ap-northeast-1
package:
patterns:
- '!**'
- '.output/server/**'
functions:
NuxtSsrCore:
runtime: nodejs16.x
handler: '.output/server/index.handler'
url: true
以下のコマンドで Lambda をデプロイします。
yarn sls deploy
続いて、画像を配置するS3バケットと、CloudFront Distributionを作成します。振り分けの設定については、CloudFrontのBehaviorを以下のように設定します。
S3バケットにはビルド済みアセット.output
の中から、public
以下のファイルをアップロードしておきます。
以下のようなAWS CLI コマンドをnpm scripts に登録しておくと簡単にアップロード作業の自動化を行うことができます。
aws s3 sync .output/public s3://{静的ファイルのオリジンS3バケット名}
動作確認
CloudFrontのURLを開き、実際にアプリケーションが動作する様子と指定した静的ファイル(写真)の取得が反映されているか確認します。
ブラウザの Developer Tool から Network タブを開き、意図した通りに必要なアセットが読み込まれていることを確認します。
URLを開いた後、Lambda コンソールのモニタリングタブから、呼び出し回数等を確認することができます。
東京リージョンの場合、Lambdaのメモリサイズとコールドスタート時のレイテンシーは256MBで2〜3s、512MBで1〜2s程度、1GBで1s未満となりほぼ影響を受けなくなります。ただし256MBでも2回目以降の実行では100ms-200msとかなり早くなりますので、アプリケーションの用途や実装にもよりますがLambdaの性能もさほど問題にならない水準であることが言えるかと思います。
API Gateway をエンドポイントして利用する場合
RESTAPI・HTTP API のいずれかでも構築できます。用途に合わせてどちらで作成するかを決めた上で、serverless.yml
で該当 Lambda の events
属性に以下のように追記します。
functions:
NuxtSsrCore:
runtime: nodejs16.x
handler: '.output/server/index.handler'
url: true
events:
- http: # REST API
method: ANY
path: /{proxy+}
- httpApi: # HTTP API
method: ANY
path: /{proxy+}