PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

AWS Amplify で Next.js 14 アプリを SSG(静的サイト)としてホスティングする [2023年11月版]

こんにちは、SaaS プロダクト開発部テックリードの丸山です。

2023 年 10 月 26 日に、Next.js の最新バージョンである Next.js 14 がリリースされました。

nextjs.org

そこで早速、Next.js 14 で作ったウェブサイトを AWS Amplify でホスティングしようとしたところ、想定外に苦戦しましたので、備忘録がてらブログの記事に残しておこうと思います。

なお、本記事は 2023 年 11 月 2 日時点での AWS Amplify の仕様に基づいて執筆しております。今後の AWS Amplify のアップデート等により、状況は変わる可能性が十分にありますので、ご留意ください。

結論(お急ぎの方へ)

AWS Amplify で Next.js 14 アプリを SSG としてデプロイするには、以下の 4 点が必要です。

Next.js 側の設定

  • next.config.jsoutput: 'export' を追加する

Amplify 側の設定

  • ビルド設定 YAML の artifacts.baseDirectory の値を .next から out に変更する
  • ビルドイメージをデフォルトから public.ecr.aws/docker/library/node:18 に変更する
  • アプリ作成後に以下のコマンドを実行して platform と framework の値を上書きする
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 とは

AWS Amplify については検索すれば情報が色々と出てきますので、詳細は割愛させていただきますが、簡単に言えば、フロントエンドの開発者が、AWS に関する詳しい知識がなくてもバックエンドまで含めたアプリケーションを簡単に開発およびデプロイできるようにするフレームワーク であり、Amplify CLI や Amplify Hosting、Amplify Studio など複数のツールから構成されています。

docs.amplify.aws

今回はその中でも、Next.js で作ったアプリケーション(静的サイト)をデプロイするために、Amplify Hosting の機能のみを使用します。Amplify Hosting はその名前の通り、ホスティングの機能をもったサービスですが、GitHub などのリポジトリと組み合わせることで CI/CD による自動ビルド、自動デプロイが可能です。なお、同様の機能を持ったサービスとして VercelNetlify などが知られています。

Next.js 14 アプリを作成する

それでは早速 Next.js 14 アプリを作成していきましょう。

npx create-next-app@latest

すべてデフォルトの設定で作成していきます。

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

yarn run dev を実行すると http://localhost:3000 で Next.js アプリが立ち上がります。

今回はページ内に動的なコンテンツがないため、ページ表示時間の高速化の観点から、SSG でホスティングすることにします。SSG (Static Site Generation) とは、事前に静的な HTML/CSS/JavaScript を生成しておく方式のことで、SSR (Server Side Rendering) のように、リクエストを受け付けてから HTML を生成する方式とは対照的な考え方になります。

SSG を有効化するため next.config.jsoutput: 'export' を追加します。

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export'
}

module.exports = nextConfig

なお、Next.js 13.2 以前のバージョンでは next export コマンドにより静的なアセットを出力できたため、以下のように package.json の script に記述しておくことが一般的でした。

package.json(Next.js 13.2 以前)

"scripts": {
  "build": "next build && next export"
}

Next.js 13.3 以降は、next export コマンドは非推奨となり、代わりに next.config.js で設定する方法が推奨されています。そして Next.js 14 では next export コマンドは完全に削除されています。*1

ここまでできたら、ソースコードを GitHub にアップロードしておきます。

Amplify でホスティングしてみる

作成した Next.js 14 アプリをホスティングするため、Amplify Hosting 側の設定を始めます。

最初に Git リポジトリの場所を選択します。先ほどソースコードを GitHub にアップロードしておいたので、ここでは GitHub を選択します。

続いて、連携する GitHub リポジトリを選択します。

続いて、アプリケーションのビルドに関する設定を行います。Next.js 14 で SSG を有効化している場合、out という名前のディレクトリに静的な HTML/CSS/JavaScript が出力されるため、artifacts の baseDirectory を out に書き換える必要があります。

そしてこの次が最初のハマりポイント。Amplify がデフォルトで使用するビルド環境には、複数のバージョンの Node.js がインストールされているのですが、2023 年 11 月 2 日現在、最新のバージョンは Node.js 18.13 です。Next.js 14 は Node.js 18.17 以上を要求するため、yarn install 実行時にエラーが発生してしまいます。そのため、ビルド環境のイメージをデフォルトから変更し、public.ecr.aws/docker/library/node:18 *2 など Node.js 18.17 以上がインストールされているイメージを指定する必要があります

次の画面に進むと、何かがおかしいことに気づきます。Framework のところに Next.js - SSR と表示されています。ここは、ソースコードの内容を元に Amplify 側で自動的に判定されるのですが、実際のソースコードでは SSR ではなく SSG になるように設定しているので、自動判定が間違っています。右上にある「Edit」ボタンを押しても、この Framework の値を変更することはできません。

このままデプロイに進むと、ビルドの過程で以下のエラーが発生してデプロイに失敗します。

2023-11-03T09:59:47.078Z [ERROR]: !!! CustomerError: Build output not found in /codebuild/output/src2076943348/src/next14-test/amplify-compute-bundle-output/compute/.next/server, please check your build artifacts path

これは、Amplify 側が SSR 向けの出力を期待しているのに対し、実際に build スクリプトの実行後に out ディレクトリに出力されるのは SSG 向けの静的ファイルなので、期待したファイルが出力の中に見つけられず、build に失敗したと判定されているわけです。

Framework を Next.js - SSG に変更してみる

Amplify の管理画面上では、一度自動判定された Framework の値を変更することはできませんが、実は AWS CLI を使用することで、Framework の値を変更することができます。*3 変更するには、以下のコマンドを実行します。

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'

APP_ID は、アプリケーションごとに割り当てられている ID で、aws amplify list-apps コマンドなどで確認することができます。上記ではまず、アプリケーションの platform の値を WEB_COMPUTE から WEB に変更しています。次に、framework の値を Next.js - SSR から Next.js - SSG に変更しています。

上記のコマンドを実行してから、再デプロイを行えば、無事にデプロイに成功するはずです。

[参考] Amplify による Framework の自動判定

そもそも、Amplify によって Framework が正しく自動判定されていれば、今回のように AWS CLI を使って Framework の値を更新する必要もありません。では、Amplify はどのようにして、ソースコードから SSR か SSG かを判定しているのでしょうか。

Amplify の ドキュメント に以下のような記述があります。

When you deploy a Next.js app, Amplify inspects the app's build script in the package.json file to detect whether the app is static (SSG) or server-side rendered (SSR).

つまり、package.json の build スクリプトの記述を見て SSR か SSG かを判定しているようです。

本記事の前半で説明した通り、Next.js 13.2 以前のバージョンでは、SSG を行うために以下のように package.json の script に記述していました。

package.json(Next.js 13.2 以前)

"scripts": {
  "build": "next build && next export"
}

SSG の場合は next build && next export と記述し、SSR の場合は next build とだけ記述していました。そのため、Amplify は next export という記述が package.json の build スクリプトに含まれているかどうかで、SSR か SSG かを判定するという仕様のようです。

ところが、Next.js 13.3 以降 next export は非推奨となり、Next.js 14 では完全に削除されました。つまり、この Amplify の判定方法は 最新の Next.js では通用しない というわけです。

逆に、この仕様を逆手に取ることで、以下のような方法で SSG と認識させることも可能です。

package.json

"scripts": {
  "build": "next build && next export",
  "build2": "next build"
}

package.json の build スクリプトに next build && next export と書いておき、それとは別に「本物の」build スクリプトとして build2 というスクリプトを用意しておきます。Amplify のビルド設定 YAML で、実際のビルド時に実行されるコマンドとして npm run build2 と書いておけば、実際に実行されるのは next build だけですが、Amplify の Framework 自動判定には Next.js - SSG と判定させることができます。このような小細工を package.json にしておくことで、わざわざ AWS CLI を使って Framework を更新せずとも一発で Amplify にデプロイできるようになります。

まとめ

Next.js のホスティングには Vercel を使いましょう(笑)

今回 Amplify を触ってみて、最新の Next.js の仕様に追従できていないことが明らかになりました。Vercel は Next.js の開発元である Vercel 社が提供するサービスですので、Next.js との親和性は間違いなく最高です。Vercel は商用利用の場合はユーザーごとに最低 $20 /月の料金がかかるのがネックですが、今後サービスを選定する際には、最新バージョンへの対応速度という観点も気にしておきたいところです。

*1:https://nextjs.org/docs/app/building-your-application/deploying/static-exports#version-history

*2:node:18 を指定すると Docker Hub の pull 回数制限に引っかかるため、ECR Public 上で公開されている Docker オフィシャルイメージである public.ecr.aws/docker/library/node:18 を指定しています。参考:https://dev.classmethod.jp/articles/docker-official-images-on-ecr/

*3:https://github.com/aws-amplify/amplify-hosting/blob/main/FAQ.md#convert-an-ssr-app-to-ssg