Serverless Nextjs Pluginを試してみた

Serverless Nextjs Pluginを試してみました。簡単セットアップ!簡単デプロイ!
2020.09.30

こんにちは、岩城です。

Serverless Nextjs Pluginについて調べる機会がありました。

このプラグインを使うと以下のような環境を作ってくれます。

(公式サイトからの引用)

本エントリでは、Serverless Frameworkの導入方法をはじめ、実際にデプロイしてみてどんなリソースが作成されるかを紹介します。

なお、筆者はアプリケーション開発全般よく分かりません。

やってみた

Serveless Framework

セットアップ

まっさらな環境でセットアップしたかったので、新規で起動したEC2上で試しました。

$ cat /etc/system-release
Amazon Linux release 2 (Karoo)

Amazon Linux 2はデフォルトでNode.jsがインストールされていません。 はじめにNode.jsをインストールします。

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
$ . ~/.nvm/nvm.sh
$ nvm install node

Node.jsがインストールされたことを確認します。

$ node -e "console.log('Running Node.js ' + process.version)"
Running Node.js v14.12.0

つぎにServerless Framworkをインストールします。

$ npm install -g serverless

Serverless Frameworkがインストールされたことを確認します。

$ serverless --version
Framework Core: 2.2.0
Plugin: 4.0.4
SDK: 2.3.2
Components: 3.1.4

Serverless Nextjs Plugin

セットアップ

つぎにServerless Nextjs Pluginに必要なパッケージをインストールします。

npm init -y
npm install -S react react-dom next

package.jsondependenciesに追記されることを確認します。

{
  "name": "ec2-user",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^9.5.3",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }
}

デプロイ

デプロイの前にAWSのクレデンシャルを設定します。

$ aws configure
AWS Access Key ID [None]:XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]:ap-northeast-1
Default output format [None]:

$ export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
$ export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Serverless Frameworkの設定ファイルを用意します。

$ vim serverless.yaml
# serverless.yml
myApp:
  component: "@sls-next/serverless-component@1.15.1"

普段CloudFormationやTerraformを使っている身からすると、この内容だけで前述のAWS環境がデプロイできてしまうのはすごいです。 ただし、この内容だとバケット名やLambda関数がランダムな文字列で設定されてしまいます。このため本エントリではinputsで必要な情報を設定しました。

# serverless.yml
myApp:
  component: "@sls-next/serverless-component@1.15.1"
  inputs:
    bucketName: "serverless-next-<AWSアカウント番号>"
    name: "serverless-next-func"

これ以外にもCloudFrontで利用するドメインの指定やディストリビューションの設定を行ったり、Lambda関数のメモリサイズ設定など色々カスタマイズできます。 詳細は公式サイトを確認してみてください。

デプロイ

いよいよデプロイです。

serverless

デプロイすると以下のようなエラーが出力されました。

$ serverless
  error:
  Error: Command failed with exit code 1: node_modules/.bin/next build

> Build error occurred
Error: > Couldn't find a `pages` directory. Please create one under the project root
    at findPagesDir (/home/ec2-user/node_modules/next/dist/lib/find-pages-dir.js:3:170)
    at build (/home/ec2-user/node_modules/next/dist/build/index.js:4:416)

pagesディレクトリがなくエラーになるので作業ディレクトリに作成してください。

$ mkdir pages

あらためてデプロイすると今度は成功するはずです。

$ serverless
  myApp:
    appUrl:     https://XXXXXXXXXXXX.cloudfront.net
    bucketName: serverless-next-XXXXXXXXXXXX

  37s › myApp › done

コマンド自体はすぐに返ってきますが、CloudFrontのデプロイに時間が掛かります。 しばらく経った後appUrlにアクセスすると以下のような404のページが表示されるようになります。

作成されたリソース

以前Serverless Frameworkを使ったことがあったので、当然CloudFormationが裏で動くものと想定していましたが違いました。 CloudFormationを使わずに作成されていました。

Cloud Front

Distributionの設定は以下のような感じ。

Originの設定は以下のような感じ。

Behaviorsの設定は以下のような感じ。

_next/data/*Defaultについては、以下のようにLambda関数が設定されていたのでLambda@Edgeへのデプロイもされていました。

Lambda

Lambda関数は以下のようなコードがデプロイされていました。コード見ても私にはさっぱりなのでキャプチャにとどめておきます。

基本設定は以下のような感じ。

ロールはAWSLambdaBasicExecutionRoleポリシーがアタッチされているだけなのでCloudWatch Logsへの権限だけ許可されていました。

IAMロール

ubj22yt-4hhif8yという名前でIAMロールが作成されていました。 ランダムな文字列と思いつつも何度作成し直しても同じ名前になったので理由はありそうです。(調べてません) なお、IAMロールの名前を指定する術はありませんでした。

デフォルトではAWSLambdaBasicExecutionRoleがアタッチされていましたが、DynamoDBや他のAWSリソースにアクセスする場合は事前にカスタムしたIAMポリシーを用意しておいてinputspolicyを指定します。

# serverless.yml
myApp:
  component: "@sls-next/serverless-component@1.15.1"
  inputs:
    bucketName: "serverless-next-<AWSアカウント番号>"
    name: "serverless-next-func"
    policy: "arn:aws:iam::XXXXXXXXXXXX:policy/MyCustomPolicy"

S3

作成されたS3バケットは以下のような構成になっていました。

$ s3-tree serverless-next-XXXXXXXXXXXX
serverless-next-XXXXXXXXXXXX
├── _next
│   └── static
│       ├── chunks
│       │   ├── commons.cb82874e43d6dd0d0fae.js
│       │   ├── framework.9ec1f7868b3e9d138cdd.js
│       │   ├── main-e3b219ceef4bb4fe6eb4.js
│       │   ├── pages
│       │   │   ├── _app-24382224f10887fed77f.js
│       │   │   └── _error-3203e7e37e39af6cd7ee.js
│       │   ├── polyfills-fd3597f65753721f35bc.js
│       │   └── webpack-e067438c4cf4ef2ef178.js
│       └── Y49oVB2sQWN_vB8RPC8B3
│           ├── _buildManifest.js
│           └── _ssgManifest.js
└── static-pages
    └── 404.html

削除

削除コマンドが用意されており、実行すると4秒とかで返ってきますが、実際はCloudFrontのディストリビューションがDisabledされるだけで削除されていません。 リソースを完全に削除したい場合は、マネジメントコンソールなどを使って個別に削除する必要があるので注意してください。

$ serverless remove
  4s › myApp › done

おわりに

簡単にLambda@Edgeな環境を構築できるのは魅力的ですが、何が設定されているか把握しないまま利用するのは危険です。まずは検証環境でどのような設定がされているかを確認しましょう。

本エントリがどなたかのお役に立てれば幸いです。

リファレンス

試す中で色々と躓い際、以下の記事に助けられました。ありがとうございます。