Tech blog Produced by FOURIER

Nuxt3 CloudFront × Lambda でSSR構成

Ichikawa Ichikawa 2023.06.12

はじめに

2022年11月中旬に Nuxt3 の安定版がリリースされました。リリースから数ヶ月が経過し、細かなバグも修正されたかと思います。

当ブログは Nuxt2 で作られていますが、構成はSSGとなっています。そのため生成した静的ファイルをサーバーに設置するだけのものでした。

SSR構成も作ってみたいという思いもあり、Nuxt3 から AWS Lambda での配信が Nuxt2 よりも飛躍的に簡単になったとのことで、 CloudFront × Lambda でSSR構成を作っていこうと思います。

前提条件

  • Nuxt3 のアプリケーションを動かせる環境がある
  • AWSアカウント を作成している
  • AWS CLI がインストールされている

環境

Nuxt.js … 3.3.1

Node.js … 16.20.0

構成

冒頭でお伝えした通り、CloudFront × LambdaでSSR構成となるので、以下の様になります。

動的コンテンツは Lambda 、静的コンテンツは S3 でそれぞれ配信します。また、 S3 のコンテンツは CloudFront でキャッシュします。

Nuxt3プロジェクト作成

まずは、 Nuxt3 のプロジェクトを用意します。以下の手順でコマンドを実行してください。

Nuxt の公式に掲載されている通り進めていくだけです。

  1. プロジェクトの作成
npx nuxi@3.3.1 init my-app
  1. ディレクトリの移動
$ cd my-app

3. 依存パッケージのインストール

$ yarn install

4. 開発モードで Nuxt アプリの起動

$ yarn dev

5. アプリの起動確認

http://localhost:3000 にアクセスしてください。以下ページが表示されれば完了です。

Nuxt側のデプロイ設定

デプロイ用パッケージのインストール

Serverless Framework を使って、 Lambda へデプロイします。

以下のコマンドを実行して、パッケージをインストールしてください。

$ yarn add -D serverless

続いてプロジェクトの直下に serverless.yml を作成して、以下の設定を記述します。

service: my-app
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

nuxt.config.tsの編集

nuxt.config.ts を以下のように編集します。

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

Nuxt側の設定は以上となります。

AWS側のデプロイ設定

IAMユーザー作成

デプロイ時にアクセスキーとシークレットアクセスキーが必要なので、IAMでユーザーを追加します。

ユーザー名は任意で大丈夫です。ここでは、「iam-serverless」と何の撚りもないものをつけました。

マネジメントコンソールへのアクセスは不要なので、特にチェックは入れず「次へ」を押します。

続いて付与する権限を指定します。「ポリシーを直接アタッチする」を選択し、AdministratorAccess にチェックを入れて「次へ」を押します。

設定を確認して問題ないようでしたら「ユーザーの作成」を押してください。

ユーザーが作成できたら、アクセスキーとシークレットアクセスキーを生成します。

ユーザー名 > セキュリティ認証情報 > アクセスキーを作成 の順で進めていきます。

「コマンドラインインターフェイス(CLI)」と「上記のレコメン…」にチェックをして、「次へ」を押します。

次にオプションで説明タグの設定ができるので、任意で入力してください。未入力でも大丈夫です。「アクセスキーを作成」を押すと以下のようにアクセスキーとシークレットアクセスキーが生成されるので、画面を閉じる前に必ずメモ(保存)してください。この画面を閉じてしまうと、2度とシークレットアクセスキーを確認できず、再度、アクセスキーの発行をするハメになります。

※csvファイルをダウンロードしておくのが無難かと思います。

S3バケット作成

ビルド時に生成される静的ファイルを設置するための、バケットを作成します。

バケット名、リージョンは任意のもので大丈夫です。ここでは「my-app-dev-serverless-public」「アジアパシフィック(東京)ap-notrtheast-1」とそれぞれ設定しました。

その他のパラメータは基本的に初期値で大丈夫ですが、後ほどバケットポリシーを追加します。

設定が済んだら「バケットを作成」を押してください。

デプロイ

アプリケーションのビルド

ビルドするため、以下のコマンドを実行します。nuxt.config.ts に preset: 'aws-lambda', と設定しているので、 Lambda 用にビルドされます。

$ yaru build

ビルドすると .output というディレクトリがプロジェクト直下に生成されます。ディレクトリの中身を確認すると、public と server のディレクトリがあります。

public は S3 に server は Lambda にそれぞれアップします。

my-app
  ├── .output
  │     ├─ public
  │     └- server
   ...

Lambdaで配信するコンテンツのデプロイ

デプロイするためにIAMで作成したアカウントを紐付けます。

[アクセスキー] と [シークレットアクセスキー] の部分を置き換えて以下のコマンドを実行してください。

$ yarn serverless config credentials --provider aws --key [アクセスキー] --secret [シークレットアクセスキー]

そして以下のコマンドで Lambda へデプロイします。デプロイは serverless.yml の設定を読み込んで実行してくれます。

$ yarn serverless deploy

デプロイ完了後、AWSマネジメントコンソールで Lambda と S3 を確認すると、画像の様に関数とバケットがそれぞれ作成されています。

Lambda

S3

Lambdaの関数URL

CloudFront で配信する際に、Lambda の関数URLを紐付けるので事前に確認しておきます。

Lambda > 関数 > 関数名-NuxtSsrCore > 設定 > 関数URL にて確認がきます。このURLをブラウザに打ち込むとウェルカムページが表示されます。

※ゴールが CloudFront を経由しての配信になるので、もう少し設定をしていきます。

静的コンテンツのデプロイ

AWS CLI を使ってビルド時に生成された静的ファイルを S3 にアップします。

AWS CLI がインストールしていない方は、事前にインストールをお願いします。

https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

コマンドは以下の通りです。my-app-dev-serverless-public は先ほど、作成したバケット名に置き換えてください。

$ aws s3 sync .output/public s3://my-app-dev-serverless-public

コマンドが問題なく実行できたらS3のバケットを確認してみてください。.output/public 配下のコンテンツがアップされているはずです。

CloudFrontの設定

ディストリビュージョンの作成

CloudFront > ディストリビュージョン > ディストリビュージョンを作成 の順で作成画面を開きます。設定項目が多く、少々大変です…

ポイントとなる部分をピックアップします。残りはよしなにお願いします。

オリジンドメイン

オリジンドメインには、 Lambda の関数URLをコピペてください。

キャッシュキーとオリジンリクエスト

オリジンドメインに Lambda の関数URLを入力すると、以下のようにおすすめの設定が出るのでその通り、設定していきます。

キャッシュポリシー

Lambda で配信するコンテンツは、キャッシュを無効にします。

この記事ではウェルカムページを表示しているだけですが、ユーザーのプライベート情報をキャッシュして、他のユーザーが見てしまうと問題ですから。

また、SSRのメリットであるリアルタイム性も弱くなってしまいます。

オリジンリクエストポリシー

ローカル環境では、問題なく表示 or 動作するのに Lambda だと意図した挙動にならない場合は、ここの設定を確認してみてください。

自身のアプリケーションに合った設定がない場合には、独自にポリシーを作成しましょう。

オリジンの追加

さらに作成したディストリビューションにオリジン(静的ファイルを配置するS3)を追加します。

CloudFront > ディストリビュージョン > ××××××× > オリジンを作成 の順で作成ページを開きます。

オリジンドメインは、静的コンテンツ用のS3バケットを選択します。

オリジンアクセスは、Origin access control settings (recommended) を選択します。チェックを入れると、新たに設定項目が表示されます。

「コントロール設定を作成」を押して、以下のように設定したら「作成」を押してください。

続いて「ポリシーをコピー」を押します。以下の様なポリシーがコピーされるので my-app-dev-serverless-public のバケットポリシーに貼り付け、 CloudFront からのアクセスを許可します。

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-app-dev-serverless-public/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::337432780752:distribution/EU6SY1IYQHACI"
                }
            }
        }
    ]
}

ビヘイビアの作成

ビヘイビアを作成してパスパターンごとに Lambda と S3 の振り分けを設定します。

記事の手順通りに設定いただいた方は、デフォルトで Lambda に振り分けられるよう設定されているはずです。静的コンテンツのリクエストには、 S3 を参照するように設定を追加します。

CloudFront > ディストリビュージョン > ××××××× > ビヘイビアを作成

パスパターンに /_nuxt/* と入力し、オリジンとオリジングループには、静的コンテンツの S3バケット を選択します。

キャッシュポリシーは、CachingOptimized を選択し、 CloudFront でキャッシュされるよう設定します。

オリジンリクエストポリシーは特に設定はなくて大丈夫です。

Nuxt3 の public ディレクトリに配置したファイルは、 .output/public/ の配下にそのまま出力されるのため、 /*.* のパスパターン で S3 へのビヘイビアをもう一つ追加してください。

※初期設定のままでしたら favicon.ico が出力されます。

最終的に以下の様に設定できていればOKです。

ブラウザで表示

CloudFront のディストリビュージョンドメインにブラウザからアクセスしてみましょう。

ウェルカムページが表示されていれば、設定完了です。

最後に

CloudFront のディストリビュージョンのドメイン名を Route53 に登録すれば、自身が取得したドメインで配信することができるので、設定してみてください。その手順も書こうかと思いましが、ごめんなさい力尽きました。

CSR(SPA)、SSR、SSGに加え、ISRといったレンダリング方法等、様々ありますが、それぞれの特性を理解しておくことで要件に適した方法を選ぶことができます。是非、SSR以外の方法も試してみてください。

Ichikawa

Ichikawa / Engineer

パン屋から転身してエンジニア3年目。主にPHP/Laravelを使っています。最近ではVue.js/Nuxt.jsと人間に興味あり。