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

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

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

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

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

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

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

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公式ドキュメントを参照してください。 ↩︎

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。