Nuxt3入門(第9回) - Nuxt3アプリケーションをサーバーレス環境にデプロイする

| 9 min read
Author: noboru-kudo noboru-kudoの画像

前回はNuxt3のステート管理について見てきました。

連載最後の今回は、サンプルアプリとして作成したNuxtアプリケーションをAWS環境にデプロイしてみます。

デプロイするアプリケーションは、以下の回で作成した仮想のブログシステムです。

使用するレンダリングモードは、Nuxtデフォルトのユニバーサルレンダリングを使います。
なお、ここではプリレンダリングは使用せず、サーバーサイド実行環境を配置してNuxtを動作させるものとします[1]

Nuxt3のサーバーエンジンであるNitroは、マルチプラットフォームで動作可能です。
今回は代表的なサーバーレスサービスのAWS Lambdaに、Nuxtアプリケーションをデプロイしてみたいと思います。

Contents

LambdaにNuxtアプリケーションをデプロイする

#

まずは、作成したNuxtアプリケーションをシンプルにLambdaにデプロイします。
ここではAPI Gateway等を前面に配置せず、Lambda Function URLを使います。

Information

Lambda Function URLは、2022/4より導入された比較的新しいLambdaの機能です。
別記事でも紹介していますので、興味のある方は以下をご参照ください。

Nitroプリセット指定

#

Nuxt3をLambdaで動作させるには、サーバーエンジンのNitroのプリセット設定が必要です。
プリセットはnuxt.config.tsまたは環境変数より指定できますが、今回はnuxt.config.tsを使います。
以下のように修正します。

export default defineNuxtConfig({
nitro: {
preset: 'aws-lambda',
serveStatic: true,
},
})

NitroのLambda用のプリセット(aws-lambda)を指定します。
デフォルトでは、Nitroビルド結果の.output/public配下は公開されません。Lambda単体でデプロイするには、別途serveStatic: trueを指定する必要があります。

これでNuxtアプリケーションをビルドしておきます。

npm run build

.outputディレクトリにビルドした結果が出力されます。
ここでは、デフォルトのNode.js Serverではなく、Lambda向けのハンドラーソースコードが生成されます。

Lambdaデプロイ

#

LambdaのデプロイにはServerless Frameworkを使って簡単にデプロイします。
まず、Serverless FrameworkをNuxtプロジェクトにインストールしておきます。Lambda Function URLはServerless Frameworkのv3.12.0以上で利用可能です。

npm install --save-dev serverless

プロジェクトルート直下にserverless.ymlを作成します。以下のような内容となります。

service: nuxt3-sample
frameworkVersion: '3'
provider:
name: aws
stage: dev
region: ap-northeast-1
runtime: nodejs16.x
environment:
BLOG_DB: db.json
package:
patterns:
- '!**'
- '.output/**' # nuxt run buildの出力ディレクトリ
- 'db.json' # 記事情報を格納したJSONファイル
functions:
NuxtSsrEngine:
handler: '.output/server/index.handler'
url: true # Lambda function URLを有効

package.patternsで、パッケージング対象を.output/**としてNitroビルド結果のみを対象としています。
Nitroは必要なもののみをバンドルしますので、ソースコード自体やnode_modules等を含める必要はありません。
なお、db.jsonについては、アプリケーション(API)内で利用しているブログ記事を格納するDBです。本来はCMS等の外部システムから取得することを想定していますが、今回はパッケージング対象に含めています。

functionsでは、1つのLambda(NuxtSsrEngine)を定義しています。
handlerにはNitroのビルド結果で単一のエンドポイントとなる.output/server/index.handlerを指定します。
また、url: trueとすることで、作成するLambdaに対して接続用のURLが割り当てられます。

これで準備は完了です。Serverless FrameworkのCLIを使ってデプロイします。

npx serverless deploy

> Deploying nuxt3-sample to stage dev (ap-northeast-1)
>
> ✔ Service deployed to stack nuxt3-sample-dev (37s)
>
> endpoint: https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/
> functions:
> NuxtSsrEngine: nuxt3-sample-dev-NuxtSsrEngine (970 kB)

デプロイが終わると、AWS コンソールから作成したLambdaが確認できます。

記載されているLambdaに割り当てられたURLにブラウザからアクセスします。
ローカル環境で実行したときと同じように、サンプルアプリケーションが実行されていることが分かるはずです。

プリセット変更のみで、簡単にAWSサーバーレス環境にデプロイできました。

静的リソースをCDNから配信する

#

先程はLambda単体でNuxtアプリケーションをデプロイしました。
シンプルで簡単ですが、サーバーサイドレンダリングだけでなく、JavaScript等の静的リソースもLambdaで処理しています。簡単な検証にはいいですが、パフォーマンスやLambdaコストの観点で実用的なものとは言えません。

NitroのserveStatic設定のドキュメントでも以下のように言及されています。

Note: It is highly recommended that your edge CDN (nginx, apache, cloud) serves the public/ directory instead.

AWS環境であれば、静的リソースはCDN機能を提供するCloudFront経由とするのが理想的です。
ここでは、サーバーサイドレンダリングのみLambdaで実行し、静的リソース(.output/public)はS3に配置してCloudFront(CDN)経由で配信するように変更します。

Nuxt/Lambda設定変更

#

まず、nuxt.config.tsを以下のように修正します。

export default defineNuxtConfig({
nitro: {
preset: 'aws-lambda',
},
})

serveStaticを削除し、静的リソースを実行環境のNitroエンジンから配信しないようにしました(aws-lambdaプリセットデフォルトの挙動)。

serverless.ymlも以下のように修正します。

service: nuxt3-sample
frameworkVersion: '3'
provider:
name: aws
stage: dev
region: ap-northeast-1
runtime: nodejs16.x
environment:
BLOG_DB: db.json
package:
patterns:
- '!**'
- '.output/server/**' # nuxt run buildの出力ディレクトリのサーバーサイドのみを指定
- 'db.json' # 記事情報を格納したJSONファイル
functions:
NuxtSsrEngine:
handler: '.output/server/index.handler'
url: true

package.patternsでNitroビルド結果のパッケージング対象を.output/server/**のみとし、静的リソースが作成される.output/publicディレクトリを除外しています。

先程と同じように、これでビルド・デプロイします。

npm run build
npx serverless deploy

静的リソース用S3バケットとCloudFrontディストリビューション作成

#

次に、静的リソースをS3に配置し、CloudFront(CDN)経由で配信するようにします。
なお、CORS設定を回避するために、LambdaのNitroエンジンについてもCloudFront経由とします。

こちらのプロビジョニングにはCloudFormationを使います。
少し長いですが、以下のテンプレート(cdn.yml)を用意します。

AWSTemplateFormatVersion: "2010-09-09"
Description: Nuxt3 application distribution template

Parameters:
NuxtSsrEnginDomain:
Type: String
Description: Lambda Function URL Domain

Resources:
# 静的リソース配信用のS3バケット
StaticResourceBucket:
Type: AWS::S3::Bucket
Properties:
# グローバルで一意な名前を指定してください
BucketName: nuxt3-sample-public-bucket
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# CloudFront -> S3のアクセスコントロール
StaticResourceOriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: "nuxt3-sample-oac"
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
# S3バケットポリシー(CloudFrontからのみを許可)
StaticResourceBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref StaticResourceBucket
PolicyDocument:
Version: 2012-10-17
Statement:
Sid: AllowCloudFrontServicePrincipalReadOnly
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub "arn:aws:s3:::${StaticResourceBucket}/*"
Condition:
StringEquals:
AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${NuxtSampleAppDistribution}"
# CloudFrontディストリビューション
NuxtSampleAppDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
HttpVersion: http2
CacheBehaviors:
- PathPattern: "/_nuxt/*"
TargetOriginId: "nuxt-static-resources"
ViewerProtocolPolicy: redirect-to-https
# AWS Managed Cache Policy(CachingOptimized)
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
DefaultCacheBehavior:
TargetOriginId: "nuxt-ssr-engine"
# AWS Managed Cache Policy(CachingDisabled)
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
ViewerProtocolPolicy: redirect-to-https
Origins:
- Id: "nuxt-static-resources"
DomainName: !GetAtt StaticResourceBucket.RegionalDomainName
OriginAccessControlId: !GetAtt StaticResourceOriginAccessControl.Id
S3OriginConfig: {}
- Id: "nuxt-ssr-engine"
DomainName: !Ref NuxtSsrEnginDomain
CustomOriginConfig:
OriginProtocolPolicy: https-only
Outputs:
CloudFrontDomain:
Value: !GetAtt NuxtSampleAppDistribution.DomainName

細かい内容(各リソースの概要はインラインコメントに記載)は省略しますが、2つのオリジンを作成しています。

  • 静的リソースを格納したS3オリジン(nuxt-static-resources)。パス/_nuxt/*の場合はこちらを使用。
  • Lambda Function URLをエンドポイントとするカスタムオリジン(nuxt-ssr-engine)。上記以外のデフォルト。

S3オリジンの方はCloudFrontのキャッシュを有効とし、カスタムオリジン側は無効としています。シンプルな記載とするためにカスタムキャッシュポリシーではなく、AWS管理のものを適用しています。

これでCloudFormationを実行します。以下はAWS CLIで実行していますが、もちろんAWSコンソール上からでも構いません。
パラメータ(NuxtSsrEnginDomain)としてNitroエンジンを実行するLambda Function URLのドメインを指定します。

aws cloudformation deploy --template-file cdn.yml --stack-name nuxt-distribution \
--parameter-overrides NuxtSsrEnginDomain=xxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws

最後に、作成された静的リソース用のS3バケットに.output/public配下のリソースをアップロードします。

# バケット名はテンプレートで指定したもの
aws s3 sync --delete .output/public s3://nuxt3-sample-public-bucket

後はアプリケーションにアクセスするだけです。ここではLambda Function URLではなく、CloudFrontに割当てられたURLを使います[2]
URLはCloudFormationの出力から確認できます。

CloudFront distribution URL

ブラウザから上記URLにアクセスすると、先程と同様にアプリケーションが実行されます。
見た目からは分かりませんが、ここでは静的リソースはS3から取得しています。
Chrome DevツールのネットワークからHTTPレスポンスヘッダ(x-cache)を確認すると、2回目以降のアクセスはHit from cloudfrontとなっています。

ここでの内容をまとめると、以下の構成となっています。

こうすることで、Lambdaは初期ロード時のサーバーサイドレンダリングのみの実行となり、静的リソースはS3からCloudFront経由で配信されるようになります。

まとめ

#

Nuxt3アプリケーションをAWSサーバーレス環境にデプロイする方法をご紹介しました。
また、Lambda単体のシンプルな方法からCDN(CloudFront)を使った実用的なデプロイも見てきました。

Nuxt2だといろいろ工夫しないとLambdaにデプロイするのは難しかった感がありますが、Nuxt3がNitroを採用したことでプリセットの切り替えだけでよくなりました。
未検証ですが、これはLambdaに限らずNitroがサポートするプラットフォーム全てに適用できるはずです[3]

連載はここで一旦終了となりますが、Nuxt3はまだ今も進化しています。面白い機能が出たらまた紹介したいと思います。


  1. プリレンダリングを使用する場合は、SSG同様で単純にビルドされた出力結果をS3等へホスティングするだけです。 ↩︎

  2. もちろん実運用する場合は、カスタムドメインを別途割り当てる必要があります。 ↩︎

  3. NitroがサポートするプラットフォームはNitro公式ドキュメントを参照してください。 ↩︎

豆蔵デベロッパーサイト - 先週のアクセスランキング
  1. Nuxt3入門(第1回) - Nuxtがサポートするレンダリングモードを理解する (2022-09-25)
  2. 自然言語処理初心者が「GPT2-japanese」で遊んでみた (2022-07-08)
  3. GitHub Codespaces を使いはじめる (2022-05-18)
  4. Jest再入門 - 関数・モジュールモック編 (2022-07-03)
  5. ORマッパーのTypeORMをTypeScriptで使う (2022-07-27)
  6. Nuxt3入門(第4回) - Nuxtのルーティングを理解する (2022-10-09)
  7. Nuxt3入門(第3回) - ユニバーサルフェッチでデータを取得する (2022-10-06)
  8. 第1回 OpenAPI Generator を使ったコード生成 (2022-06-04)
  9. Nuxt3入門(第8回) - Nuxt3のuseStateでコンポーネント間で状態を共有する (2022-10-28)
  10. Nuxt3入門(第2回) - 簡単なNuxtアプリケーションを作成する (2022-10-02)