Next.jsアプリをLambda@Edgeで超簡単にSSRできるserverless-next.jsのご紹介

Next.js をSSR対応でホスティングしたいなら [Vercel](https://vercel.com/home)(旧Now) を使うのが定石だと思います。
Next.jsとVercelはどちらも Vercel社 (旧ZEIT社) が開発しているため一緒に使った際のDXは大変良いものです。
しかし、AWS内で完結できるならそうした方が嬉しい事が多いのでいい方法はないかと調べた所、
[serverless-next.js](https://github.com/danielcondemarin/serverless-next.js/tree/master/packages/serverless-nextjs-component)というServerless Componentのプラグインがよさそうだったのでご紹介したいと思います。


以下のドキュメントの要点をまとめつつ、掘り下げたものです。
https://github.com/danielcondemarin/serverless-next.js/
https://serverless.com/blog/serverless-nextjs/

# serveless-next.js 
![](https://s3.amazonaws.com/assets.general.serverless.com/component_nextjs/serverless_nextjs_blog_header.png)

## 機能と特徴
- SSRをLambda@Edgeで行う
- [Dynamic Routing](https://nextjs.org/docs#dynamic-routing) (Next.js 9からの機能)と互換性のあるルーティングを行う
- [Automatic pre-rendering](https://nextjs.org/docs#automatic-prerendering) (Next.js 9からの機能) のサポート
    - SSR不要なページはNextによって静的HTMLに書き出される
    - serverless-next.jsはこれをS3にホスティングする
    - アクセスがあるとLambda@EdgeがS3にリクエストをフォワードする

- Nextが生成したファイル(jsのチャンクファイル等)のホスティング
    - Lambda@Edgeを通さず、CloudFrontを通してS3でホスティングする
- static フォルダ内のアセットのホスティング
    - 上記と同様CloudFrontを通してS3でホスティングする

これらの機能をゼロコンフィグで行える。

## デプロイについて
- pageはLambda@Edgeに、静的ファイルはS3にデプロイされる。
- デプロイは通常1分以内に終わる。
- 初回のみディストリビューションの有効化に時間がかかるが、それ以降は高速に終わる。

## アーキテクチャ



![](https://github.com/danielcondemarin/serverless-next.js/raw/master/arch_no_grid.png)


Cloud Frontに3つのキャッシュビヘイビアが作成される。
 <img width="846" alt="a.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4289/669934f1-a602-1909-5bbf-122b0c8650ef.png">



- static => S3へ (Lambda@Edgeなし)
- _next => S3へ (Lambda@Edgeなし)
- Default(*) =>  Lambda@Edge

### Defalut(*) の挙動
① SSRされるページ(getInitialPropsありのページ) を処理する
② Automatic pre-renderingされたHTMLページをS3にフォワード
③ favicon.ico などのルート直下のファイルをS3にフォワード

###### ②,③をする理由
ファイルごとにディストリビューションを作成しないため。
(ちなみにCloudFrontは最大25個しかディストリビューションを作れない)


## デプロイする

``` serverless.yml
myApp:
 component: serverless-next.js
```

``` shell
デプロイ
$ npx serverless

削除
$ npx serverless remove
```

これだけでとりあえず https://<文字列>.cloudfront.net のようなURLでアクセス可能な状態になる。


## ドメインを割り当てたいなら

``` serverless.yml
myApp:
  component: serverless-next.js
  inputs:
    domain: "example.com"
    # domain: ["sub", "example.com"] # サブドメインもこんな感じで指定できる
```


## 舞台裏

#### CloudFormationは使ってません
- 高速なデプロイのため
- CloudFormationは記述できるリソースが200個までと制限がある

ほとんどの作業は[serverless-component](https://github.com/serverless/components) の以下の4つが行っており、
serverless-next.js はこれらのオーケストレーションを単純に行っているだけ。

- [@serverless/aws-s3](https://github.com/serverless-components/aws-s3)
- [@serverless/aws-cloudfront](https://github.com/serverless-components/aws-cloudfront)
- [@serverless/aws-lambda](https://github.com/serverless-components/aws-lambda)
- [@serverless/domain](https://github.com/serverless-components/domain)

#### 1つ懸念があるとすると
serverless-next.js自体はbetaではないが、~~その裏側のserverless-compoentがよく見るとbetaではある。~~
[正式リリース](https://www.serverless.com/blog/serverless-components-ga/)された模様です

### 実際にリソースの作成を行っている箇所のソースを見てみる
[serverless-nextjs-component/serverless.js](https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/serverless.js)

serverless-componentはymlによるCloud Formationによる管理だけでなく、
プログラマティックにリソースを操作することも可能。
例えば [S3の例](https://github.com/serverless-components/aws-s3#5-upload)は以下。

```js
const bucket = await this.load('@serverless/aws-s3')

// deploy
await bucket({
  accelerated: true
})

// upload directory
await bucket.upload({ dir: './my-files' })

// upload file
await bucket.upload({ file: './my-file.txt' })
```

S3のリソース作成
https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/serverless.js#L272

Cloud Frontのリソース作成
https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/serverless.js#L407

Lambda@Edgeのリソース作成
https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/serverless.js#L391



## その他メモ
### デプロイの度にCloudFrontディストリビューションをdestroy/createせず、updateしたい場合

.serverless ディレクトリでリソースのstateが管理されており、これを一緒にgitでcommitしておく必要がある。

※ ちなみに現在 serverlessチームはstate管理をリモートストレージでできるように改修を行っているので、将来的にはgit管理しなくてよくなる。

---
#### 以上です
vercel以外にNext.jsアプリをサクッとデプロイできる選択肢が増えるのは嬉しい事なので、今後も開発が続く事を願っています。