workspaceをつかったモノレポ構成のNext.jsをAWS Amplifyでホスティングするときに必要だった後処理
npmやyarnのworkspaceをつかったmonorepoになっているNext.jsのアプリをAWS Amplifyでホスティングするのに少し手こずったので、メモとして残しておきます。
TL;DR
outputをstandalone, outputFileTracingRootを設定して、最後にpostBuildのフェーズで
shopt -s dotglob
rm .next/standalone/package.json
cp -r .next/standalone/packages/appA/ .next/standalone
rm -rf .next/standalone/packages
して.next/standalone
の中の構造を整えよう
Next.jsがAmplifyでHostingされるときに期待されている構造
昨年、AmplifyでのNext.js 12.x/13.xのホスティングがサポートされました
Amplify HostingでNext.js 12以降ののアプリをホスティングするときには
NEXT_PRIVATE_STANDALONE
という環境変数がtrue
に設定されてoutput: "standalone"が有効になった状態でビルドされるようです。
そして、その際の結果の .next/standalone
ディレクトリの構造は主に以下のような構造が期待されています
.next/standalone
├── .next
│ ├── package.json
│ └── server
├── node_modules
│ ├── ...
│ ├── next
│ └── ...
├── package.json
└── server.js
このように
- .next/standalone直下に.nextが存在すること
- .next/standalone直下に起動用のserver.jsが存在すること
- .next/standalone直下に必要な依存ライブラリを全て含んだnode_modulesが存在すること
などが期待されています。(Next.jsの自然なstandaloneの形式です)
workspaceでnode_modulesがインストールされる場所
yarnやnpmなどのパッケージ管理ツールではpackage.jsonに"workspace"を記載することで
monorepoの各パッケージ(以下 サブアプリ)を認識して依存関係の管理などをしてくれます
{
"private": true,
"workspaces": [
"packages/*"
],
....
}
このようになっているときに、パッケージをインストールすると、各サブアプリの依存関係を分析して適宜各サブアプリ内のディレクトリに保存するか、ルートディレクトリで共通化するかなどの対応をしてくれます。
これは、hoistingという共通化できるnode_modulesをルートで共有することでnode_modulesのサイズを削減する機構です、詳しくは他の記事の方にゆずります。
私は以下の記事を参考にさせていただきました
そのため、単純に通常のビルドと同様に、ルートレベルでパッケージのインストールコマンドを実行すると、設定によっては依存するライブラリがルートレベルのnode_modulesと各アプリのディレクトリ内のnode_modulesに分かれて保存される可能性があります。
outputFileTracingRootでルートレベルに保存されているライブラリを正しく認識する
上記のような形で実行に必要なnode_modulesがルートレベル側に保存されているかもしれない関係から、
Next.jsではoutputFileTracingRootというオプションをnext.conf.jsに設定することで、ルートレベルのnode_modulesに保存されている依存パッケージも追跡してstandalone内にきちんとコピーするように設定ができます。
例えば以下の設定だと、サブアプリから見て2つ上のディレクトリがルートレベルになっているという想定になっています( ../../
の箇所)
outputFileTracingRoot: path.join(__dirname, '../../'),
この設定を忘れてしまうと、実行時に MODULE_NOT_FOUND
のエラーが発生してしまいます。
outputFileTracingRootの設定のみだと、.next/standaloneが期待した形にならない
上記のようにnode_modulesを集めてくれるoutputFileTracingRootですが、これを設定した状態でoutpout: "standalone"なビルドをすると、.next/standalone
ディレクトリが以下のような構造になってしまいます
.next/standalone
├── node_modules
│ ├── ...
│ ├── next
│ └── ...
├── package.json <- ルートレベルのpackage.json
└── packages
└── appA
├── .next
├── package.json <- サブアプリのpackage.json
└── server.js
このように、outputFileTracingRootを設定していなかった時とは違い、.next/standalone直下ではなく、packages/appA
とルートレベルから見たときのサブアプリの相対パスに期待している構造が保存されてしまいます
コマンドをつかってファイルを移動して期待している形に修正する
そこで、冒頭に紹介したコマンドをつかって、packages/appAからファイルを.next/standalone直下に移動してやることで期待されている構造に変換することができます
# *を書いたときの対象に隠しファイルやディレクトリも含める(.nextを移動するため)
shopt -s dotglob
# ルートレベルのではなく、サブアプリのpackage.jsonを使いたいためまずは削除
rm .next/standalone/package.json
# サブアプリのパスから.next/standalone直下に中身を全てコピーする
cp -r .next/standalone/packages/appA/ .next/standalone
# 不要になったpackagesディレクトリは削除する
rm -rf .next/standalone/packages
まとめ
いろいろと試行錯誤を経て、依存ライブラリがどこに保存されるべきなのかなどを探ってようやく無事設定できました。
もっと簡単な方法や公式でちゃんとオプションが実は提供されていたりしたら是非コメントでおしえてくださいませ!
Discussion