Open5

Next.jsをLambda+CloudFrontでデプロイする

296u296u

node: v20.11.0

next.jsを作成する
$ npx create-next-app@latest
作成時に聞かれることは以下のように回答

✔ What is your project named? … test-app
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No

以降の参考元
https://aws.amazon.com/jp/blogs/news/implementing-ssr-streaming-on-nextjs-with-aws-lambda-response-streaming/

最初にDockerfileを作成する。
利用するパッケージマネージャーはnpmを想定している。

# Base
FROM public.ecr.aws/docker/library/node:20.11.0-slim AS base
WORKDIR /app
COPY package*.json ./

# 依存関係を解決するためのステージ
FROM base as dependencies
RUN npm ci

# ビルドするためのステージ
FROM base AS build
COPY . .
RUN npm run build

# 本番環境で実行するためのステージ
FROM public.ecr.aws/docker/library/node:20.11.0-slim AS production
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.3 /lambda-adapter /opt/extensions/lambda-adapter

ENV PORT=3000
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
# カスタムサーバーで起動するためstandaloneをルートディレクトリ直下に展開する
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
COPY --from=build /app/package*.json ./
COPY --from=build /app/next.config.js ./next.config.js
COPY --from=build /app/run.sh ./run.sh
# Lambda上でNext.jsのキャッシュファイルを書き込みするための設定 cf.https://tmokmss.hatenablog.com/entry/20221213/1670891305
RUN ln -s /tmp/cache ./.next/cache

#start the application
ENTRYPOINT ["sh"]
CMD ["run.sh"]

次にrun.shを作成する

#!/bin/bash -x

[ ! -d '/tmp/cache' ] && mkdir -p /tmp/cache

exec node server.js

next.config.jsを以下のように編集しstandaloneでビルドするように設定する。ちなみにファイル名がnext.config.mjsになっていたらnext.config.jsにリネームしておくこと。

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  tailingSlash: true,
  output: 'standalone',
  compress: true,
}

module.exports = nextConfig
296u296u

.dockerignoreファイルを追加

.env.local
node_modules

.vscode
.next
.git
.gitignore

.aws-sam
samconfig.toml

.vscodeは人によって.ideaの人もいると思う。
また.aws-samsamconfig.tomlはのちに出てくる生成ファイル

デプロイには直接関係ないけど、この後生成されるファイルのうち、.aws-samsamconfig.tomlはgit管理する必要がないので、.gitignoreファイルに以下を追加する。

# aws-sam
/.aws-sam/
samconfig.toml

template.ymlを作成

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: "test app project"
Globals:
  Function:
    Timeout: 60

Resources:
  StreamingNextjsFunction:
    Type: AWS::Serverless::Function
    Properties:
      MemorySize: 256
      PackageType: Image
      Architectures:
        - arm64
      Environment:
        Variables:
          AWS_LWA_INVOKE_MODE: response_stream
      FunctionUrlConfig:
        AuthType: NONE
        InvokeMode: RESPONSE_STREAM
    Metadata:
      DockerTag: v1
      DockerContext: ./
      Dockerfile: Dockerfile

Outputs:
  StreamingNextjsFunctionOutput:
    Description: "Streaming Nextjs Function ARN"
    Value: !GetAtt StreamingNextjsFunction.Arn
  StreamingNextjsFunctionUrlOutput:
    Description: "nextjs streaming response function url"
    Value: !GetAtt StreamingNextjsFunctionUrl.FunctionUrl
296u296u

$ sam build && sam deploy --guided
を叩いてLambda上にデプロイする。
この時、Dockerが使えること、aws configureにて有効なトークンが設定されている必要がある。

この後デプロイについて聞かれるが、主に変更するのはStock NameStreamingNextjsFunction Function Url has no authentication. Is this okay?の時にyesを選択する。

 Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: test-app
        AWS Region [ap-northeast-1]: 
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: 
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: 
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [y/N]: 
        StreamingNextjsFunction Function Url has no authentication. Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

最後の出力結果のvalueに記載のURLでLambda上に上がったWebアプリにアクセスできる。

-------------------------------------------------------------------------------------------------------
Outputs                                                                                                                           
-------------------------------------------------------------------------------------------------------
Key                 StreamingNextjsFunctionOutput                                                                                 
(中略)    
Value               https://abc123def456ghr789jkl.lambda-url.ap-northeast-1.on.aws/
-------------------------------------------------------------------------------------------------------
296u296u

このままでは認証がないのでCloudFrontと繋げたい。
まずは関数URLの認証タイプをNONEからAWS_IAMに変更する。

CloudFrontを作成する。
Origin domainに先ほどのValueに記載されているURLを入れる。
WAFのセキュリティ保護は有効にする。
その後、作成する。

ここからは独自の設定。
CloudFrontのセキュリティタブにてWAFの設定をする。
Editを押した後、View details of your configrationをクリックする。

RulesタブでAdd rulesをクリック後、Add my own rules and rule groupsをクリックしてルールを追加する。
RuleTypeをIP setに変更して、RuleのNameは適当に入れる。IP setに事前に設定していたIPセットを選択する。

ここで一度Add ruleでルール追加する。
その後再びEditボタンを押して、If a requestのところをdoesn't match the statement(NOT)に変更する。
最後にすでに追加されている3つのルールを一度削除しておく。

296u296u

オリジンを編集する。
Create new OACを選択してOACを作成する。下にAWS CLIでLambdaの設定を変更するコマンドをローカルで叩く。この時FUNCTION _NAMEを入れること。
最後に「変更を保存」を押して完了する。