pnpm + Next.js standalone + Docker で5回ハマった話 [第9回]

この記事で分かること pnpmのsymlink構造がNext.js standaloneでなぜ壊れるのか cp -rLで解決できない場合の対処法 Docker multi-stage buildでのsymlink解決パターン 5回のfix PRから得た教訓 背景 Saruは5つのNext.jsフロントエンド(Landing / System / Provider / Reseller / Consumer)をpnpmモノレポで管理している。開発環境ではvolume mountで動かすのでDockerfileは不要だが、本番・デモ環境へのデプロイにはDockerイメージが必要になる。 Next.jsの output: 'standalone' を使えば、必要なファイルだけを .next/standalone/ にトレースしてくれる。これをAlpineベースのrunnerステージにコピーすれば軽量なイメージができる——はずだった。 ハマり1: MODULE_NOT_FOUND(#557) 症状 Error: Cannot find module 'next/dist/compiled/next-server/app-page.runtime.prod.js' コンテナを docker run した瞬間にクラッシュ。 原因 pnpmは node_modules をsymlinkベースで構築する。例えば: standalone/node_modules/next → ../../node_modules/.pnpm/next@14.2.x/node_modules/next Next.jsの @vercel/nft(Node File Tracing)はこのsymlink構造をそのままstandalone出力にコピーする。builderステージ内ではsymlink先が存在するので問題ないが、COPY --from=builder でrunnerステージに持ってくると、symlink先のディレクトリが存在しない。 builder (standalone作成時) runner (COPYした後) ├── standalone/ ├── standalone/ │ └── node_modules/ │ └── node_modules/ │ └── next → ../../.pnpm/... │ └── next → ../../.pnpm/... └── node_modules/.pnpm/... ✅ 存在 └── (なし) ❌ 試した解決策 1 RUN cp -rL /app/apps/system/.next/standalone /app/standalone cp -rL はsymlinkを辿って実ファイルをコピーするPOSIXコマンド。これで一発解決——と思った。 ...

February 16, 2026 · 5 分 · ko-chan

pnpm + Next.js Standalone + Docker: 5 Failures Before Success [Part 9]

What You Will Learn Why pnpm symlinks break in Next.js standalone Docker builds When cp -rL is not enough Symlink resolution patterns in Docker multi-stage builds Lessons from 5 consecutive fix PRs Background Saru manages 5 Next.js frontends (Landing / System / Provider / Reseller / Consumer) in a pnpm monorepo. In development, we use volume mounts so Dockerfiles are not needed. But deploying to production or demo environments requires Docker images. ...

February 16, 2026 · 8 分 · ko-chan

Next.js + Go Monorepo: Managing 4 Portals × 4 APIs as a Solo Developer [Part 3]

What You’ll Learn Next.js + Go monorepo architecture patterns Practical use of pnpm workspace + Turborepo Sharing UI components across 4 portals Package splitting strategies that don’t break down in solo development Introduction As introduced in Part 1, Saru is a multi-tenant SaaS with a 4-tier account structure. To implement this, I adopted an architecture of 4 frontends + 4 backend APIs. Normally, this would mean managing 8 repositories. For solo development, that would be unsustainable. ...

January 14, 2026 · 8 分 · ko-chan

Next.js × Go モノレポ構成:4ポータル × 4APIを1人で保守する設計【第3回】

この記事で得られること Next.js + Go のモノレポ構成パターン pnpm workspace + Turborepo の実践的な使い方 4ポータルで共通UIを使い回す設計 ソロ開発で破綻しないパッケージ分割の考え方 はじめに 第1回で紹介した通り、Saruは4階層のアカウント構造を持つマルチテナントSaaSだ。これを実現するために、4つのフロントエンド + 4つのバックエンドAPIという構成を採用している。 普通に考えると、8つのリポジトリを管理することになる。ソロ開発では破綻する。 そこでモノレポを採用した。本記事では、その構成と設計判断を解説する。 1. なぜNext.js × Go なのか 技術選定の理由 領域 技術 選定理由 Frontend Next.js 14 App Router、RSC、豊富なエコシステム Backend Go + Echo シンプル、高速、型安全、デプロイが楽 DB PostgreSQL RLSによるマルチテナント分離 なぜフルスタックフレームワーク(Next.js API Routes)を使わないのか? 関心の分離: フロントエンドとバックエンドのデプロイサイクルを分けたい 言語の強み: Goの方が複雑なビジネスロジックを書きやすい(個人の感想) スケーラビリティ: 将来的にAPIだけスケールさせる可能性 認証フローは以下の分担:Keycloakがユーザー認証、NextAuthがOAuth/セッション管理、Go APIはKeycloakのアクセストークン(JWT)を検証して権限チェックを行う。 2. プロジェクト構成 saru/ ├── apps/ # 6つのNext.jsアプリ │ ├── system/ # System Portal (管理者) │ ├── provider/ # Provider Portal (サービス提供者) │ ├── reseller/ # Reseller Portal (販売代理) │ ├── consumer/ # Consumer Portal (利用者) │ ├── customer/ # Customer Portal (レガシー名、consumerと統合予定) │ └── landing/ # ランディングページ │ ├── packages/ # 共有パッケージ │ ├── types/ # TypeScript型定義 │ ├── ui/ # 共通UIコンポーネント │ ├── api-client/ # APIクライアント + React Query hooks │ ├── auth/ # NextAuth設定 │ ├── config/ # ESLint, TypeScript設定 │ └── env-validator/ # 環境変数バリデーション │ ├── backend/ # Go バックエンド │ ├── cmd/ │ │ ├── system-api/ # System API (port 8080) │ │ ├── provider-api/ # Provider API (port 8081) │ │ ├── reseller-api/ # Reseller API (port 8082) │ │ ├── consumer-api/ # Consumer API (port 8083) │ │ └── migrate/ # マイグレーションCLI │ └── internal/ # 共通ロジック │ ├── e2e/ # Playwright E2Eテスト ├── pnpm-workspace.yaml # pnpm workspace設定 └── turbo.json # Turborepo設定 なぜ4つのAPIに分けているのか 「1つのAPIで全部まかなえばいいのでは?」という疑問があるかもしれない。 ...

January 14, 2026 · 5 分 · ko-chan