Claude Code で建設業務システムをフルスクラッチ開発した話:AIペアプログラミングの実際
はじめに
Claude Code(Anthropicのエージェント型AIコーディングツール)を使って、中小建設会社向けの業務管理システムを継続的に開発してみました。スタックは Next.js 15 App Router + Prisma + PostgreSQL(Neon)+ NextAuth。フロントから API、DB マイグレーション、本番デプロイ、障害対応まで、基本的にすべての作業を Claude Code との対話で進めました。
大規模な指示書を渡して「一気に実装してください」とお願いするスタイルで、コードレビューや方針決定だけ人間がやる、という使い方です。結果としてかなり現実的な業務システムが出来上がったので、実際にハマったポイントを中心に記録します。
やったこと
主な実装は大きく 6 弾に分かれています。
第1弾:月次支払表 Excel 出力
仕入先別の月次支払一覧を Excel で出力する機能。
既存テーブル(Supplier / Purchase)から集計して、Excel の特定フォーマットに合わせて出力します。
-
xlsx(SheetJS community 版)で実装。ただし フォント・罫線は非対応なのが後で問題に。 - 集計ロジックを
lib/payment-statement.tsとlib/payment-statement-calc.tsに分離し、画面・Excel・PDF で数値が必ず一致するように設計した。
第2〜3弾:権限制御・Excel 書式完全再現・印刷プレビュー
-
exceljsに切り替えて罫線・太字・塗りつぶしを実装 -
requireAdminを auth-guard に追加して支払表系 API をすべてガード -
src/lib/year-month.tsを新設して和暦 → 西暦に切り替え(シート名は2026-05形式)
第4〜5弾:案件管理の期フィルタ・来期回し機能
- 「来期回し」チェックボックスで案件の計上期を翌期に持ち越す機能
-
recognizedFiscalYearIdカラムを追加し、getProjectFiscalYearWhereヘルパで期フィルタを統一 - 案件番号を
{期}-{3桁連番}(例:64-001)に変更し、既存 504 件を再採番
第6弾:決算月変更・仕入インライン編集・小出分割
- 決算月を「3月末」→「5月末」に変更(期 = 6月〜翌5月)
- 仕入一覧で金額・税率・仕入先をインライン編集できるように
- 仕入先「小出」を「(会社)」「(個人)」に分割し、支払表の調整行を自動集計
ハマったポイント
1. prisma migrate dev は絶対に実行してはいけない環境だった
このプロジェクト、調べてみると migrations フォルダに init が 1 つあるだけで、実体は すべて db push で構築された DB でした。
この状態で migrate dev を実行すると、ドリフト検知が走って 「データベースをリセットしますか?」と聞いてくる → 「はい」で 本番データ全消去。
Claude Code は指示書に migrate dev と書かれていてもきちんと理由を説明して db push に差し替えてくれましたが、最初の確認フェーズで気づかなかったら危なかったです。
# NG: ドリフト検知でデータ消去のリスク
npx prisma migrate dev
# OK: 追加カラム・新テーブルのみを非破壊で反映
npx prisma db push
2. Next.js standalone で静的ファイルが 404 になる
本番デプロイ後に CSS が全部 404 になりました。
GET /toyo-*/_next/static/css/b7d4d5e35d45c502.css → 404
原因:Next.js 13.5+ の standalone モードでは server.js が静的ファイルを配信しない仕様。公式ドキュメントに書いてあるけど見落としがちです。
解決策は 2 つ:
# 1. ビルド後に必ず static と public をコピー
cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public
# 2. nginx で alias 配信
location /toyo-*/_next/static/ {
alias /var/www/toyo-*/.next/static/;
expires 1y;
}
3. middleware のリダイレクトが localhost:3001 に飛ぶ
nginx の reverse proxy 経由でアクセスすると、認証が必要なページで Location: https://localhost:3001/toyo-*/login/ というリダイレクトが返ってきてブラウザに「接続できない」と言われる。
Next.js standalone の既知の挙動で、HOSTNAME=<ローカルホスト> を渡しても middleware のリダイレクト URL には反映されません。X-Forwarded-Host も効かない。
最終的に nginx の proxy_redirect 正規表現で吸収しました:
location /toyo-*/ {
proxy_pass http://<サーバーIP>:3001/toyo-*/;
proxy_redirect ~^https?://localhost:3001/(.*)$ https://sys.sns-tool.online/$1;
...
}
4. BigInt リテラル 0n が TypeScript でビルドエラー
// NG: tsconfig の target が ES2019 だと通らない
sums[g.supplierId] = Number(g._sum.amountTaxIncluded ?? 0n);
// OK
sums[g.supplierId] = Number(g._sum.amountTaxIncluded ?? BigInt(0));
Prisma の集計結果は BigInt | null で返ってくるので null 合体演算子が必要なのですが、BigInt リテラル(0n)は ES2020 以降の構文です。tsconfig.json の target を確認せずに書いてしまいがちなので注意。
5. npm install が親ディレクトリに誤インストールされた
Claude Code がシェルのカレントディレクトリを app/ だと思って作業しているときに、実際には親ディレクトリにいてそこで npm install exceljs を実行してしまう事故が発生しました。
/Volumes/○○HD/開発/○○/ ← ここに誤インストール
├── node_modules/ ← 本来ないはずのディレクトリ
├── package.json ← 誤生成
└── app/ ← 正しいプロジェクトルート
├── node_modules/
└── package.json
これは Claude Code 側で検知・報告して、誤生成物を削除 → 正しいディレクトリに入れ直してくれました。ただ、ビルドが通ってしまっていたので気づくのが遅れたのが反省点。
6. サーバーの route.ts が別ファイルの中身で上書きされていた
ある時点から /api/projects が dashboard の集計レスポンスを返すようになり、案件一覧が全く開けなくなりました。
ブラウザの alert 表示:
status=200
body={"projectCount":241,"totalSales":...,"monthlySummary":[...]}
原因:rsync で複数の route.ts を /tmp/ に転送したとき、ファイル名が被って dashboard/route.ts の内容が projects/route.ts を上書きしてしまっていた。
シェルでパスを扱うとき、(dashboard) という括弧付きディレクトリ名が glob として解釈されて予期せぬ動作をすることがあります。対策は scp で個別転送するか、パスをシングルクォートで囲む。
7. exceljs で A 列の幅が指定値にならない
exceljs の内部実装で デフォルト列幅が 9 と定義されており、幅 9 の列は「デフォルト扱い」として <col> 要素が XML に出力されません。結果として Excel で開くと幅 8.43(Excel 既定)になってしまう。
// 回避策: defaultColWidth を明示することで省略列も確実に適用される
worksheet.properties.defaultColWidth = 9;
これを入れてから A 列も正しく 9 幅で出力されるようになりました。
学び
「DB ドリフト問題」は最初に必ず確認する
本番 DB があるプロジェクトを Claude Code に渡すなら、最初に「migrate dev は使わず db push のみ」「本番 DB の状態確認を読み取り専用コマンドで行う」という制約を明示することが重要です。今回は prisma migrate status で確認フェーズを入れたおかげで事故を防げました。
計算ロジックは Prisma 非依存ファイルに集約する
画面・Excel 出力・PDF で「同じ計算をしているのに数値が違う」という事故を防ぐため、lib/payment-statement-calc.ts に純粋関数としてロジックを集約しました。この方針は後半の機能追加でも一貫して有効でした。
サーバーのファイルを複数同時転送するとき (dashboard) が glob に化ける
Next.js の App Router は (グループ名) という括弧付きディレクトリを使います。シェルでこのパスを素朴に扱うと glob として解釈されて意図しないファイルが転送される(または転送されない)ことがあります。個人的な感想ですが、デプロイスクリプトで App Router のパスを扱うなら、シングルクォートで囲むか rsync のフィルタファイルを使う方が安全だと思います。
業務仕様は「フォーマット」より「運用の文脈」
来期回し機能のバグ修正が最も複雑でした。「来期回し = 受注日の属する期の次の期に計上する」という仕様を最初に実装したところ、「受注日未入力の案件には来期回しできない」「受注日が過年度の案件は現行期から外れない」という問題が発生。実際の運用では「現在の期の次の期に回す」という意味だったため、基準を isCurrent: true の FiscalYear に変更する必要がありました。
コードの正しさよりも、ユーザーの運用フローから仕様を逆算することが重要だと改めて感じました(個人の感想です)。
まとめ
Claude Code は指示書を渡すと驚くほど多くのことを自動でやってくれますが、「何をしているか」を自分が理解していないと、エラー時に詰む場面が出てきます。今回のケースで言えば「db push 運用 DB に migrate dev を実行しそうになった」場面がそれです。AIが気づいて止めてくれましたが、仕組みを理解していなければ指摘も気づきも遅れていたはず。
AIペアプログラミングは「実装速度の向上」よりも「実装の幅を広げる」ために使うのが現状は向いているように感じています(個人の感想です)。普段なら後回しにするような Excel 書式の完全再現や、監査ログ・楽観的更新・権限制御のような「あると嬉しいが手を動かすのが面倒」な機能を惜しみなく入れられるのが最大のメリットでした。