クイックスタート:5分でNx Monorepoを作成する
Nxに移行する前、NestJS API、Next.jsフロントエンド、shared typesパッケージの3つの独立したリポジトリからなるコードベースで2週間近く苦労していました。移行が完了してから、キャッシュのおかげでビルド時間が8分から90秒に短縮されました。これが最も素早く始める方法です。
Nxをインストールして新しいワークスペースを作成する:
# フルスタックプリセットで新しいワークスペースを作成
npx create-nx-workspace@latest my-fullstack --preset=ts
cd my-fullstack
# NestJSとNext.jsのプラグインを追加
npm install --save-dev @nx/nest @nx/next
バックエンドとフロントエンドのアプリをすぐに作成する:
# NestJS APIを作成
npx nx g @nx/nest:app apps/api
# Next.jsフロントエンドを作成
npx nx g @nx/next:app apps/web
# 共有ライブラリを作成(apiとwebで共用)
npx nx g @nx/js:lib libs/shared-types
作成後のディレクトリ構造:
my-fullstack/
├── apps/
│ ├── api/ # NestJSバックエンド
│ └── web/ # Next.jsフロントエンド
├── libs/
│ └── shared-types/ # 共有型定義
├── nx.json
├── package.json
└── tsconfig.base.json
すぐに試してみる:
# 両方のアプリを並列実行
npx nx run-many -t serve -p api web
# すべてビルド
npx nx run-many -t build --all
Nxの内部動作を理解する
Project Graph — 自動依存関係マップ
Nxはワークスペース内のすべてのimport文を自動的にスキャンし、依存関係グラフを構築します。手動で宣言する必要は一切ありません。
# ブラウザで依存関係グラフを視覚的に確認
npx nx graph
apps/webがlibs/shared-typesからインポートすると、shared-typesが変更された場合にwebの再ビルドが必要だとNxはすぐに認識します。このグラフがNxのaffectedビルド計算の基盤であり、CIの時間を最も節約できる部分です。次にその詳細を説明します。
実践的なShared Library
最近の5人プロジェクトで最も嫌だったバグは:バックエンドがUserをcreatedAt: Dateとして定義しているのに、フロントエンドはcreated_at: stringを期待していた——フォーマットも名前も違い、ランタイムになってようやく発覚するという問題です。Shared libraryはこの問題を根本的に解決します。
libs/shared-types/src/lib/user.tsに共有インターフェースを作成する:
export interface User {
id: string;
email: string;
role: 'admin' | 'user' | 'guest';
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
libs/shared-types/src/index.tsからエクスポートする:
export * from './lib/user';
export * from './lib/pagination';
export * from './lib/error-codes';
バックエンドとフロントエンドの両方でインポート — Nxが自動でパスエイリアスを解決:
// NestJSコントローラーで
import { User, ApiResponse } from '@my-fullstack/shared-types';
// Next.jsコンポーネントで
import type { User } from '@my-fullstack/shared-types';
このパスエイリアスはtsconfig.base.jsonに自動的に設定されます——ライブラリ生成時にNxが追加するので、手動で変更する必要はありません。
ビルドキャッシュの最適化 — 最も時間を節約できる部分
ローカルキャッシュ
Nxは入力(ソースファイル、環境変数、設定)のハッシュに基づいてすべてのタスク結果をキャッシュします。変更がなければ?タスクは数分ではなく数ミリ秒で再実行されます。
# 初回ビルド:3分かかる
npx nx build api
# ✓ api:build [3m 12s]
# 2回目(変更なし):即座
npx nx build api
# ✓ api:build [read from cache] [45ms]
nx.jsonでキャッシュを設定する:
{
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"]
}
}
}
}
Nx Cloudを使ったリモートキャッシュ
ローカルキャッシュは自分のマシンだけで有効です。チームが3人以上になると、開発者Aがビルドを完了したら、開発者Bは再ビルドせずにキャッシュを利用したくなります。
# Nx Cloudに接続(小規模チームならフリープランで十分)
npx nx connect
このコマンドを実行すると、Nxは自動でnx.jsonに設定を追加します。キャッシュがクラウドに同期されるようになり、CIも対象です。チームでNx Cloudを有効にしたところ、CIが12分から3分に短縮されました——CIが速くなったのではなく、メンバーの開発マシンでのビルドがすでにキャッシュされていたからです。
Affected — 変更されたものだけをビルドする
PRを扱う際に最もよく使う機能です:
# mainとの差分で影響を受けるプロジェクトだけをテスト
npx nx affected -t test --base=main --head=HEAD
# 影響を受けるプロジェクトだけをビルド
npx nx affected -t build --base=main --head=HEAD
# どのプロジェクトが影響を受けるか確認
npx nx affected:graph --base=main --head=HEAD
libs/shared-typesのファイルを変更した場合、Nxはapps/apiとapps/webの両方がaffectedだと自動判定します。apps/apiだけ変更した場合、apps/webは影響を受けません——テストもビルドも行われません。
CI/CDの統合
Nx AffectedとGitHub Actions
.github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # nx affectedが動作するためにフルヒストリーが必要
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
# リモートキャッシュ使用のためNx Cloudに接続
- uses: nrwl/nx-set-shas@v4
- name: Lint affected
run: npx nx affected -t lint --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }}
- name: Test affected
run: npx nx affected -t test --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }}
- name: Build affected
run: npx nx affected -t build --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }}
nrwl/nx-set-shasアクションは適切なbase SHAとhead SHAを自動的に決定します——ハードコードする必要はありません。PRの場合はベースブランチと比較し、mainへのマージの場合は直前のコミットと比較します。
条件付きデプロイ
実際に変更されたサービスだけをデプロイする:
- name: APIが影響を受けた場合にデプロイ
run: |
AFFECTED=$(npx nx show projects --affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }})
if echo "$AFFECTED" | grep -q "api"; then
echo "Deploying API..."
# APIのデプロイコマンド
fi
- name: Webが影響を受けた場合にデプロイ
run: |
AFFECTED=$(npx nx show projects --affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }})
if echo "$AFFECTED" | grep -q "web"; then
echo "Deploying Web..."
# Webのデプロイコマンド
fi
実際のプロジェクトからの実践的なヒント
タグを使ってアーキテクチャ境界を強制する
Nxではプロジェクトにタグを付け、どのプロジェクトがどのプロジェクトをインポートできるかのルールを定義できます。このルールにより、開発者がデータベースのコードを誤ってフロントエンドに直接インポートするのを防ぎます——エラーはlint時に検出され、コードレビューを待つ必要はありません。
// 各アプリ/ライブラリのproject.json
{
"tags": ["scope:api", "type:app"]
}
// libs/shared-types/project.json
{
"tags": ["scope:shared", "type:lib"]
}
.eslintrc.jsonでルールを設定する:
{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:shared", "scope:api"]
},
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:shared", "scope:web"]
}
]
}
]
}
}
リソース制限付き並列タスク
# 最大3タスクを並列実行(スペックの低いマシンでのOOM防止)
npx nx run-many -t build --all --parallel=3
# 依存関係順に実行(shared-typesを先にビルドし、その後apiとweb)
npx nx run-many -t build --all
リセットが必要な場合のキャッシュ削除
# ローカルキャッシュを削除
npx nx reset
# またはキャッシュディレクトリを手動で削除
rm -rf .nx/cache
よくあるトラップ:.envファイルや環境設定を変更しても、Nxはそのファイルを追跡しないため、古いキャッシュから読み込み続けます。ビルドが成功と表示されるのにアプリの動作がおかしい場合は、nx resetしてから再実行してみてください——コマンドを打つのに2秒かかりますが、20分のデバッグより断然ましです。
新しいコードのための自動ジェネレーター
フォルダをコピー&ペーストする代わりに、ジェネレーターを使ってチームの規約に従った新しいモジュールを作成します:
# 新しいNestJSモジュールを作成
npx nx g @nx/nest:resource apps/api/src/users
# Reactコンポーネントを作成
npx nx g @nx/react:component Button --project=web
# 特定の機能向けに新しいライブラリを作成
npx nx g @nx/js:lib libs/feature-auth --directory=libs/feature-auth
数十種類の既製ジェネレーターに加えて、チーム独自のパターン向けにカスタムジェネレーターも作成できます。私はユニットテストのボイラープレートを含むNestJSモジュールを生成するものを持っています——新しいメンバーがコマンドを1つ実行するだけで正しい構造が出来上がり、誰かが規約を説明する手間もありません。
