既存の Next.js 製アプリを Turborepo を使った monorepo に移行するメモ
背景
- 砂場代わりのポートフォリオサイトを Next.js で作っている
- ここでよりオーバーエンジニアリングしていくにあたって、構成変更(バックエンド増やすとか、へんなプロトコルで通信させるとか)を気軽にできるようにしたい
- → monorepo 化だ!
- 現状でも一部のロジックを CLI とサイト本体で共有するために yarn workspace を使った雑 monorepo になっているが、ビルド時の依存関係の管理とかキツい
現状
- ルートディレクトリが Next.js アプリのパッケージになっている
-
packages/
以下にいくつかサブパッケージがある
理想の状態
-
apps/
以下にアプリケーション(デプロイするやつ?)がある -
packages/
以下にいくつかサブパッケージがある - (将来的には Node.js 以外のコードも置きたいが、初手は諸々 Turborepo の doc に合わせて移行プロセスをシンプルにする)
そもそも、なにが実現したい?
- 雑にマイクロサービスを切り出したい
- 技術的な検証
- アーキテクチャの最適化
- e.g. 動的な OG 生成のために puppeteer を動かす必要があり、Next.js アプリに chrome が入ってる → ビルド・デプロイが遅くなるので分けたい
- 処理や設定を雑に共通化したい
- e.g.
- tsconfig, jest 等をいい感じに共通化して管理する
- ブログ記事のマークダウンを処理するロジックを webapp と CLI で共通化する
- e.g.
apps/
以下に移動
ルートパッケージの Next.js アプリを 今回はシンプルに apps/web
とした
- apps/web に移動
.env*
.babelrc
jest.config.js
-
tsconfig.json
-
tsconfig.base.json
とかを切り出していた場合はパスの修正が必要
-
- GraphQL まわり: VSCode Extension の設定, graphql-codegen の設定
- サーバから introspection をダンプしてる場合、
apps/web
以下に置くほうが楽かな?
- サーバから introspection をダンプしてる場合、
- デプロイ周り: Dockerfile, .dockerignore
-
src
,public
,next-env.d.ts
- その他 Next.js アプリを動かすのに必要なファイル
- e.g. ブログ記事の markdown file, sentry の設定ファイル
- 開発支援のスクリプトで、Next.js アプリ用のものがあれば
- ルートと
apps/web
それぞれに置くように分離-
.gitignore
: Next.js 由来のものをルートから移動 -
package.json
- パッケージ名を変えて
apps
に移動 - リポジトリ内で広く使うツールの devDependencies(jest, prettier 等)はルートに残す
-
workspaces
も設定変更が必要
- パッケージ名を変えて
-
(冷静に考えると、この時点で turbo 入れたりせず main branch に取り込んでおけばよかった…)
Turborepo 導入
- https://turborepo.org/docs/getting-started/existing-monorepo を参考に突っ込んでいく
- pipeline の設定について
- キャッシュに関する設定は後回しでも動くので、
inputs
とoutputs
は一旦無視 - 各タスクの
dependsOn
だけ設定して、yarn dev
やyarn test
で依存関係を見てサブパッケージのビルドとかやってくれれば成功
- キャッシュに関する設定は後回しでも動くので、
Dockerfile 修正
- アプリごとに必要な依存が変わるケースもあるので、理想的には Dockerfile はアプリごと(→
apps/<app>
ごと)に置きたい- e.g. puppeteer 使うアプリにだけ chrome いれたい
- が、いずれのアプリもビルドのためにルートの package.json や yarn.lock が必要
- ひとつのアプリをビルドしたいだけなのに全体のビルドが走る(
yarn install
も全体分で走る)ので悲しい
- ひとつのアプリをビルドしたいだけなのに全体のビルドが走る(
- Turborepo 0.4.0 からこの問題をいい感じにする
turbo prune
なる機能が入ったので使える- https://turborepo.org/blog/turbo-0-4-0#experimental-pruned-workspaces
- 自分が最初触ったときは v2 以降の yarn とのかみ合わせが悪かったが、今は問題なさそう
- (余談: vercel は pnpm 派らしいので、vercel とともに生きるなら pnpm のほうが楽なのかも)
- purne を使う・使わないに関わらず、docker build 時のパラメタは以下のようになる:
- context:
.
(root dir) - file:
./apps/<app>/Dockerfile
- context:
.git
の対応
turbo prune
は .git
に依存するらしい
(ちゃんと調べてないけどキャッシュのため?)
buildkit を使ってる場合、デフォルトでは .git
を無視するらしいのでちょっと手を加えないといけない
なんか環境変数指定するか、GHA で build-push-action つかってる場合は context: .
を入れてあげるといいらしい
(余談: build-push-action が暗黙的に checkout してるのをここで初めて知った。いままで無駄に checkout していた…)
Next.js の standalone build を使ってるときの追加対応
Next.js の standalone build を使ってるとき、実はパッケージを移動した時点でぶっ壊れている。
現状(next@v12.3.0
)では standalone build は Next.js アプリの package.json があるディレクトリをルートとしてファイルを集めるが、monorepo の場合でそれをやると node_modules が対象外になってしまう
なので、experimental.outputFileTracingRoot
なるオプションを指定してルートの位置を動かさないといけない。
Dockerfile の最終ステージでファイルを COPY する先も変わる
# Copy build artifacts
COPY /app/apps/web/.next/standalone /app/
COPY /app/apps/web/.next/static /app/apps/web/.next/static
# Copy static files
COPY /app/apps/web/public /app/apps/web/public
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "/app/apps/web/server.js"]
v2 以降の yarn(berry) の対応
.yarnrc.yml
と .yarn
を COPY しておくのを忘れない。
多分 builder と installer で必要。
雑に base につっこんでおいてもいいかも。
ところで、turbo prune
に使う Turborepo は RUN npm -i -g turbo@<version>
するしかないんだろうか…
package.json にあるものと2重管理になるの嫌だな