こんにちは!
NuxtJS や NextJS での開発事例がここ最近では急激に増えてきました。ここ 1 年でフロントエンド界隈のフレームワーク宗教論が少し落ち着いてきたかなと感じます。(私は個人的には HTML ライクに書ける NuxtJS 派です)
本記事では、AWS サーバーレスで実現できる NuxtJS のサーバーサイドレンダリングテクニックを紹介します。
本記事のテクニックは、NuxtJS でも NextJS でも使用可能です。
お好きなフレームワークでデプロイして SSR 動作をお試しください!
SSR 環境の構築には、APIGateway と Lambda を使用します。APIGateway で WEB ブラウザの表示要求を受信し、Lambda で NuxtJS をレンダリング → DOM をレスポンスするような流れとなります。
それでは必要なパッケージのインストールから、 serverlessFramework の実装方法を解説します。
SSR を高速に完了させるためには、Lambda Layer の導入は必要不可欠です。node_modules は Layer に格納しましょう。 serverless-layers をプロジェクトにインストールします。
$ yarn add serverless-layers -D
# or
$ serverless plugin install --name serverless-layers
後述で serverless-layers プラグインの設定を行います。
APIGateway で画像等のファイルを扱う ( バイナリーサポート ) 為に、以下のプラグインをインストールしてください。
yarn add serverless-apigw-binary -D
Serverless Framework を使用して、関連する AWS リソースを構築していきます。本記事では、 Serverless Framework の提供する API Gateway や Lambda を数行で構築できる便利な機能を使用します。(他にも、 Serverless Framework ではサーバーレス系のリソースを簡単に立ち上げる機能が用意されていますので気になるヒトは公式サイトをご覧ください)
provider:
runtime: nodejs18.x
deploymentBucket:
name: "${self:custom.deploymentBucketName}"
functions:
nuxt:
# NuxtJSのビルド結果のパスを指定
handler: .output/server/index.handler
# eventsには全てのリクエストをNuxtJSへ流すように設定(場合によってはGETに限定してもOK)
events:
- http: ANY /
- http: ANY /{proxy+}
plugins:
- serverless-apigw-binary
- serverless-layers
custom:
deploymentBucketName: "layer-bucket-name"
apigwBinary:
types:
# 全てのバイナリーをサポート
- "*/*"
webpack:
packager: "yarn"
serverless-layers:
packageManager: "yarn"
package:
# NuxtJSのServerモジュールをパッケージに含める
patterns:
- "!**/**"
- ".output/server/**"
excludeDevDependencies: true
individually: true
※ 関連しない部分のソースコードを省略しています
serverless-layers プラグインで使用するバケットを生成します。バケットは CloudFormation で作成し GetAtt 等で ARN や名称を参照したいところですが、技術検証したところ自動生成される CloudFormation が原因で参照できませんでした。そのため、事前に AWS CLI でバケットを作成しておきます。
$ aws s3 mb "s3://layer-bucket-name"
Lambda デプロイの為に、NuxtJSへ以下設定を行います。以下は nuxt.config.ts
の例です。
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
nitro: {
preset: "aws-lambda",
serveStatic: false, // 画像はCloudFrontによるCDN提供を見据えてfalse
},
});
NuxtJS では Lambda での SSR を見越して上記のような設定が提供されています。
必要な設定さえ終われば、デプロイは至ってシンプルです。
$ yarn install
$ nuxt build
$ npx sls deploy
デプロイが完了したらAPIGatewayのエンドポイントを表示してみてください。SSR結果のDOMが表示されるはずです。
通常のSSR構成のままでは、Lambda が画像データを全て扱う構成となってしまい、以下のような懸念があります。
画像やその他アセットの参照のたびに Lambda を参照するのはコスパが悪いです。また、せっかく node_modules を Layer 化したのに、バイナリーデータが Lambda に含まれると Layer 化した効果を最大限に発揮できません。
そのため、画像データ等々の静的アセットは、CloudFrontとS3等の構成へ逃しましょう。
以下は、画像データ等々の CloudFront と S3 のアセットを扱う例です。
Resources:
AssetsHostingCloudFront:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
# General
Comment: "AssetsHostingCloudFront"
Enabled: true
HttpVersion: http2
PriceClass: PriceClass_All
Aliases:
- "{your custom domain}"
ViewerCertificate:
MinimumProtocolVersion: TLSv1.2_2019
AcmCertificateArn: "{your ACM arn}"
SslSupportMethod: sni-only
Origins:
- DomainName: '{your bucket name}.s3-${self:provider.region}.amazonaws.com'
Id: 'S3-xxxx-xxxx'
S3OriginConfig:
OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${AssetsHostingOriginAccessIdentity}
ConnectionAttempts: 3
ConnectionTimeout: 10
# Behaviors
DefaultCacheBehavior:
TargetOriginId: 'S3-xxxx-xxxx'
ViewerProtocolPolicy: allow-all
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
Compress: true
# Error Pages
CustomErrorResponses:
- ErrorCode: 403
ErrorCachingMinTTL: 10
ResponsePagePath: "/"
ResponseCode: 200
# Restrictions
Restrictions:
GeoRestriction:
RestrictionType: none
AssetsHostingOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: "AssetsHostingOriginAccessIdentity"
AssetsHostingBucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: 'your bucket name'
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
AssetsHostingBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref AssetsHostingBucket
PolicyDocument:
Id: PolicyForCloudFrontPrivateContent
Version: "2012-10-17"
Statement:
- Sid: "1"
Effect: Allow
Action: s3:GetObject
Resource: 'your bucket arn'
Principal:
CanonicalUser: !GetAtt AssetsHostingOriginAccessIdentity.S3CanonicalUserId
画像を上記のS3へ同期する方法については、以下のいずれかの案を検討してみてください。
画像データをCloudFrontとS3構成へ逃した後は、フロントエンドの画像参照パスの変更も忘れずに行ってください。
本記事では、お客様からも良く要望のある NuxtJS の SSR 方法について解説しました。Vercel 等々を使用してもほぼほぼ同じことが実現できますが、コスト面及び性能面で本記事のほうが圧倒的に良いです。開発環境を AWS 内に閉じれるのも魅力ですね。
AWSサーバーレス、フロントエンドの開発はお気軽にお問い合わせください。