AWS Amplify hostingを利用したNext.jsのSSG

# はじめに

:::note warn
<font color="#1e90ff">2023-11-14時点の情報です
アップデート等が行われていくため、記載内容と大きく変わる可能性があります</font>

:::

:::note warn
2024-02-19 にAmplify hostingでのビルドを確認したところ、ビルドするコンテナのイメージでAmazon Linux 2023は選択可能になっており、Amazon Linux 2023のNode.js 18.19.1 （2024-02-13リリース）を使いビルドすることが可能となっています
:::

:::note warn
2023-11-20 大きなアップデートが入ってます
Nuxt や SSRサポート
https://aws.amazon.com/jp/blogs/mobile/introducing-support-for-hosting-any-ssr-app-on-aws-amplify-hosting/

Amplify consoleのビルド設定でAmazon Linux2023が追加され、Node.js18.18.x after も使えます
:::


- Next.js + Vercel での静的サイト生成（SSG）は特段の作業なく、画面のUIで設定しいくだけでVercelにデプロイしてWeb公開できます、Vercelを利用することで手軽にホスティングを行うことができます
- Vercelと似たことができるホスティングとして、AWSの[AWS Amplify ホスティング](https://aws.amazon.com/jp/amplify/hosting/?amplify%20static%20website) があります
- この記事では、Amplify ホスティングを使い、Next.js SSG をデプロイ、公開をしてみます

## 結果、実用化
:::note info
[Vercel](https://vercel.com/)使った方が良いです
:::
- Amplify ホスティングを使い Next.js SSG としてWeb公開はできますが制約があります（あくまでも個人見解であり、公式ではありません）
- 制約は、Pages Router でないといけない。App Routerではstaticページは500エラーになる。staticではない、Reactのページ相当（`export default function About() {  return (    <div><ul>Aboutです。</ul>/div>);}`）は表示はできる。
- ビルド・デプロイのコンテナは、カスタムイメージを使う `public.ecr.aws/docker/library/node:18`
- Amplify Console画面だけでは設定できず、`aws amplify update-branch` , `aws amplify update-app` を実行しないといけない。awsコマンド実行はハードルが上がります。

### App Routerに関して、不明な情報含む
- https://zenn.dev/ototrip/articles/tech-nextjs-approuter-1 より App Routerが使えるらしい
- https://docs.amplify.aws/start/getting-started/setup/q/integration/next/
- https://docs.amplify.aws/lib/ssr/q/platform/js/#withssrcontext
- https://docs.amplify.aws/lib/ssr/q/platform/js/#use-amplify-with-nextjs-app-router-app-directory

## 対応例 Amplify hosting で Next.js14 Page Router SSGを動かす
### 1. Amplifyのアプリ設定の確認
- Amplify Consoleを使い設定された初期の値
![FireShot Capture 160 変数前_変更点.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/ee05b1d0-9e40-fccc-5565-9b94c69950a1.png)

- 赤枠のうち２箇所を以下のように変える必要がある

|項目  |デフォルト自動設定・読み込み値  |変更後  |
|---|---|---|
|プラットフォーム  |ウェブコンピューティング  |ウェブ  |
|フレームワーク|Next.js - SSR |Next.js - SSG  |

- `WEB_COMPUTE`    ->   `WEB`
- `'Next.js - SSR'`   ->   `'Next.js - SSG'`

- APP IDは３つの赤枠の最も右に位置するARNの一部です

|項目  |APPID  |
|---|---|
|ARNの/より後がAPP_ID  |d3....d1  |

### 2. Amplifyのアプリ設定の変更
- 先に記載しているプラットフォーム、フレームワークともに画面上からは変更できないので以下のようにコマンドで変更する

```
export APP_ID=d3....d1
aws amplify update-app --app-id ${APP_ID} --platform WEB
aws amplify update-branch --app-id ${APP_ID} --branch-name main --framework 'Next.js - SSG'
```

なお、`aws amplify list-apps`で Amplifyのアプリケーション、ホスティングの一覧が取得できる

- 変更後
青色枠が変わった箇所です

![FireShot Capture 161　変更後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/ebf92c26-f8bc-b575-59fa-d977f5defd75.png)


### 3. next.config.js で outputに合わせて、Amplifyでのビルド設定を変更する

- next build（`npm run build`）で out ディレクトリにhtml等が出力されるので、そのディレクトリをアーティファクトとして扱う。以下のようにbaseDirectoryで `.next` -> `out` に変更
- Amplify Console画面のビルド設定 -> アプリケーション構築の仕様の右にある編集ボタンを押して変更する

```File: amplify.yml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - node -v
        - npm ci
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: out
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*
```

### 4. next.config.js で outputを指定する

```File: next.config.js 
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  output: 'export'
}

module.exports = nextConfig
```

:::note info
`output: 'export'`にすると、ローカル環境等で `npm run dev` で以下のようなエラーになるけど無視
:::

```
 ⨯ API Routes cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export
```

### 5. ビルドするコンテナをカスタムイメージに変更
- Amazon ECRにあるNode `public.ecr.aws/docker/library/node:18` を指定します。
- Docker Hubだと、AWS内から -> Docker Hub へのリクエスト上限の制限に引っかかるためECRで公開しているリポジトリを指定します。`public.ecr.aws/docker/library/node:18.18.2` のようにバージョンを指定しても良いです
- Amazon ECRのpublicなリポジトリは以下で確認することができます
    - https://gallery.ecr.aws/
    - https://gallery.ecr.aws/docker/library/node
- 設定例
![FireShot Capture 165 - AWS Amplify - us-east-1 - us-east-1.console.aws.amazon.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/02539eab-0403-059c-d61b-958135d3bab2.png)

### 6. ビルドし直し

![FireShot Capture 167 - AWS Amplify - us-east-1 - us-east-1.console.aws.amazon.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/6f85ff03-4774-694b-3847-84f44c1d22dc.png)

- 添付↑ 画面の「このバージョンを再デプロイ」を押せば良い
- ビルドやデプロイエラーはこのボタンを押す、押せない場合は　左メニューの「ビルド設定」→「ウェブフックを作成」で、WebHook用のURLが払い出されるので curl 等で実行する

### 7. デプロイ確認
- 払い出しされたURL（添付 画像の青枠URL）にアクセスして確認

![FireShot Capture 168 - AWS Amplify - us-east-1 - us-east-1.console.aws.amazon.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/5e200441-2450-3222-beec-1e8cd2573095.png)

## 補足
### Amazon Linux2でのNode.js 18.16以降, 18.18.x
- デフォルトで作られるビルドの設定`amplify.yml`でnvm install で指定バージョンをinstallし、nvm useで指定バージョンに切替える場合だとダメです。
- 例えば、以下のように変えようにもAmazon Linux2ではGLIBC_2.27,GLIBC_2.28 not foundになりインストールエラーになる

```
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - nvm install 18.18.2
        - nvm use 18.18.2
        - npm ci
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: out
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*
```

### ビルドとデプロイまで成功した時のログ例

```
2023-11-14T12:50:01.280Z [INFO]: # Starting phase: preBuild
                                 # Executing command: node -v
2023-11-14T12:50:01.284Z [INFO]: v18.18.2
2023-11-14T12:50:01.285Z [INFO]: # Executing command: npm ci
略
                                 > next build
2023-11-14T12:50:12.649Z [INFO]: ⚠ No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache
2023-11-14T12:50:12.662Z [INFO]: Attention: Next.js now collects completely anonymous telemetry regarding usage.
                                 This information is used to shape Next.js' roadmap and prioritize features.
2023-11-14T12:50:12.663Z [INFO]: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
                                 https://nextjs.org/telemetry
2023-11-14T12:50:12.748Z [INFO]: Linting and checking validity of types ...
2023-11-14T12:50:15.229Z [INFO]: ▲ Next.js 14.0.2
2023-11-14T12:50:15.232Z [INFO]: Creating an optimized production build ...
2023-11-14T12:50:18.665Z [INFO]: ✓ Compiled successfully
2023-11-14T12:50:18.668Z [INFO]: Collecting page data ...
2023-11-14T12:50:19.953Z [INFO]: Generating static pages (0/6) ...
2023-11-14T12:50:20.069Z [INFO]: Generating static pages (1/6)
2023-11-14T12:50:20.121Z [INFO]: Generating static pages (2/6)
2023-11-14T12:50:20.747Z [INFO]: Generating static pages (4/6)
2023-11-14T12:50:20.914Z [INFO]: ✓ Generating static pages (6/6)
2023-11-14T12:50:22.062Z [INFO]: Finalizing page optimization ...
2023-11-14T12:50:22.064Z [INFO]: Collecting build traces ...
2023-11-14T12:50:24.491Z [WARNING]: ⚠ Statically exporting a Next.js application via `next export` disables API routes and middleware.
                                    This command is meant for static-only hosts, and is not necessary to make your application static.
                                    Pages in your application without server-side data dependencies will be automatically statically exported by `next build`, including pages powered by `getStaticProps`.
                                    Learn more: https://nextjs.org/docs/messages/api-routes-static-export
2023-11-14T12:50:28.034Z [INFO]: 
2023-11-14T12:50:28.041Z [INFO]: Route (pages)                              Size     First Load JS
                                 ┌ ● / (921 ms)                             2.67 kB        80.7 kB
                                 ├   /_app                                  0 B              78 kB
                                 ├ ○ /404                                   335 B          78.3 kB
                                 ├ ○ /about                                 284 B          78.3 kB
                                 ├ λ /api/hello                             0 B              78 kB
                                 └ ● /blog/[id] (1326 ms)                   400 B          78.4 kB
                                 ├ /blog/snv5gt1hehfk (738 ms)
                                 └ /blog/ez81eih--s7p (588 ms)
                                 + First Load JS shared by all              79.7 kB
                                 ├ chunks/framework-04b2df298aee5fa5.js   45.2 kB
                                 ├ chunks/main-50d44469728668f3.js        31.7 kB
                                 ├ chunks/pages/_app-df8e18ebbc1bfb84.js  297 B
                                 ├ chunks/webpack-8fa1640cc84ba8fe.js     750 B
                                 └ css/ff863e046f63fb6b.css               1.71 kB
                                 ○  (Static)   prerendered as static content
                                 ●  (SSG)      prerendered as static HTML (uses getStaticProps)
                                 λ  (Dynamic)  server-rendered on demand using Node.js
2023-11-14T12:50:28.074Z [INFO]: # Completed phase: build
2023-11-14T12:50:28.080Z [INFO]: ## Build completed successfully
2023-11-14T12:50:28.081Z [INFO]: # Starting caching...
2023-11-14T12:50:28.091Z [INFO]: # Creating cache artifact...
2023-11-14T12:50:33.777Z [INFO]: # Created cache artifact
2023-11-14T12:50:33.914Z [INFO]: # Uploading cache artifact...
2023-11-14T12:50:36.586Z [INFO]: # Uploaded cache artifact
2023-11-14T12:50:36.587Z [INFO]: # Caching completed
2023-11-14T12:50:36.630Z [INFO]: # No custom headers found.
2023-11-14T12:50:36.634Z [INFO]: # Starting build artifact upload process...
2023-11-14T12:50:36.723Z [INFO]: # Uploading build artifact '__artifacts.zip'...
2023-11-14T12:50:36.734Z [INFO]: # Uploading build artifact '__artifactsHash.zip'...
2023-11-14T12:50:37.003Z [INFO]: # Build artifact upload completed
2023-11-14T12:50:37.114Z [INFO]: # Starting environment caching...
2023-11-14T12:50:37.114Z [INFO]: # Environment caching completed
Terminating logging...
```


### Amplify Console 設定の「platformとframework」を変更するコマンドの実行環境

:::note info
aws コマンドを実行する環境がない！ そんな時はCloudShellが使える
:::

```
aws amplify update-app --app-id <APP_ID> --platform WEB
aws amplify update-branch --app-id <APP_ID> --branch-name main --framework 'Next.js - SSG'
```
- 上述のコマンド実行環境がない場合にどうするか？
- IAMユーザー権限での処理になるが、AWSのサービスにはCloudShellがあるので、それを使う。cloudshellからコマンド実行は手軽です。インストール等も不要でawsコマンド使えます。Cloud9のようにVPCは必要ありません。EC2作成やローカルでのコンテナ作成等も不要です。15～20分くらいで自動で停止します。
#### CloudShellのコンソール画面例
![cloudshell.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1482752/1d234bbb-4478-57c5-f0fc-81592b88a768.png)

### GitHub連携で認証した後にエラー
- Amplify ConsoleでGitHubリポジトリ連携をする際に、GitHub認証が終わった後にエラーになるのですが、画面をリロードする。または、いくつ前の画面に戻って「リポジトリブランチの追加」画面にくると、リポジトリのプルダウンに表示されるようになります。

## Vercel
- よくできてるなあ、Next.jsフレームワークを開発したところなので相性が良い
- 1ユーザー1ヶ月あたり20ドル、3人では60ドル/月、1$＝150円だと、9000円、AWS使うよりも高いこともある

<details><summary>VercelとAWSについて</summary>

- Next.js で SSG、SSRするならばVercel第一候補かな

- AWSで対応するとなると、Amplifyはアップデートが遅いので、選択肢から外す方が良い。個人利用やMVP,PoC,プロトタイプ程度で使うには良いと思う。


- AWSで自前（CodePipeline, CodeBuild, CodeDeploy,Cloudfront, S3, Cloudfront function）となるとけっこう大変

</details>


## 参考にさせてもらった情報
- https://developers.play.jp/entry/2023/11/06/085943
    -  App Router使ったSSGでもAmplify Hosting成功していそう
- https://zenn.dev/daumkuchen/articles/35a15b61ce3ca1
    - ここに記載内容の対応が必要
- App RouterはNext.js 13.4以降 stable版 https://nextjs.org/blog/next-13-4