logo hsb.horse
← ブログ一覧に戻る

ブログ

TypeScript Monorepo 最適解を整理する 2026年版

pnpm workspace、Turborepo、TypeScript Project References、Biome、WXT の役割を整理し、汎用の無難解と 4 パッケージ構成での実務解を分けてまとめた。

公開日:

TypeScript の monorepo を考え始めると、すぐに選択肢が増える。pnpmbunTurborepoNxChangesetsBiome。どれも正しそうに見えるが、全部を最初から入れると重い。逆に全部切ると、あとで困る。

この手の話がまとまりにくいのは、前提を切らずに「最適解」だけを探してしまうからだ。汎用の無難解と、いま目の前にある構成での実務解は分けて考えた方がいい。

今回は次の 2 つを分けて整理する。

  1. これから TypeScript monorepo を新規に作るときの無難な構成
  2. apps/webapps/wxtpackages/uipackages/core という具体前提での現実的な構成

まず結論

前提まず選ぶ構成補足
汎用の新規 monorepo。将来的に package 公開、CI 最適化、Docker 連携も視野に入るpnpm workspace + Turborepo + TypeScript Project References + package.jsonexports + Changesets一番ぶれにくい
apps/webapps/wxtpackages/uipackages/core の 4 パッケージ構成。Docker は使わない。共有 package は npm 公開しないpnpm workspace + Biome + TypeScript Project Referencesこの段階では Turborepo はまだ不要
実行環境、package manager、テストまで Bun に寄せ切るbun workspace も候補ただし運用の無難さでは pnpm が強い

要するに、汎用論では pnpm + turbo が強い。でも今回の前提では workspace only で十分、という整理になる。

汎用の無難解

2026 年時点で、深く悩まずに始めるなら土台は pnpm workspace が一番安定している。workspace を標準で持っていて、workspace: protocol でローカル package 参照を明示できるし、--filter で対象 package をかなり細かく絞れる。

型境界には TypeScript Project References を使う。TypeScript 公式ドキュメントでも、分割された project によって build time の改善、論理的な分離、tsc --build による依存順ビルドができると明記されている。monorepo ではここがかなり効く。

その上で Turborepo を載せると、task graph、キャッシュ、並列実行、turbo prune まで一気に揃う。Turborepo の task 設定dependsOnoutputs を中心に組み立てるだけでよく、repo が大きくなったときの伸びしろがある。

公開する package を持つなら Changesets も自然に入る。pnpm の workspace ドキュメント でも、workspace 内 package の versioning には Changesets などの専用ツールを使う前提になっている。

つまり、汎用の無難解はこうなる。

役割推奨
workspace 管理pnpm workspace
型境界と増分ビルドTypeScript Project References
タスク実行、並列化、キャッシュTurborepo
package 境界の公開面package.jsonexports
versioning / changelogChangesets
lint / formatBiomeESLint + Prettier。今なら Biome で始める方が軽い

今回の前提ではなぜ Turborepo がまだ要らないか

今回の構成はかなり単純だ。

packages/core
packages/ui -> packages/core
apps/web -> packages/ui, packages/core
apps/wxt -> packages/ui, packages/core

依存関係がこの程度なら、pnpm workspace の filter 実行と tsc -b だけでも運用は十分回る。

Turborepo を入れる価値が一気に上がるのは、たとえばこういうときだ。

痛みTurborepo が効く理由
build / test / lint の総時間が長いキャッシュと並列実行が効く
task の依存順を明示したいdependsOn で graph を表現できる
CI を速くしたいremote cache が効く
Docker 用に必要 package だけ切り出したいturbo prune が使える

逆に、まだそこまで困っていない段階で入れると、設定ファイルが 1 枚増える以上の価値を感じにくい。Docker を切るなら prune の魅力も一段落ちる。共有 package を npm 公開しないなら Changesets も必須ではない。

だから、この前提ならまずは pnpm workspace + Biome + TypeScript Project References で始めるのが妥当だ。

レイヤーの切り方

この 4 分割では、役割を曖昧にしないことの方がツール選定より重要だ。

パス置くもの置かない方がいいもの
packages/coredomain、schema、validation、API client、pure function、state modelUI、routing、browser API
packages/ui環境依存しない React component / hookWXT 固有処理、router、extension storage
apps/webWeb アプリとしての組み立て、page、routing、adapterextension 固有事情
apps/wxtbackground / content script、browser API、WXT 固有設定Web 専用 routing、共有層に置ける pure logic

いちばん避けたいのは、packages/ui を「共有っぽいもの全部置き場」にすることだ。ここに browser extension 固有事情や routing が入り始めると、共有層ではなく結合点になる。そうなると monorepo の恩恵が薄れる。

apps/wxt の注意点

apps/wxt だけは少し気をつけた方がいい。WXT の TypeScript 設定 では、通常は root の tsconfig.json から .wxt/tsconfig.json を extend する。ただし monorepo ではその形を取らないこともあり、その場合は .wxt/wxt.d.ts を TypeScript project に含める必要がある。

つまり、apps/wxt は monorepo の一部ではあるが、TypeScript project としては独立気味に扱った方が安定する。

tsconfig.paths を越境参照の主手段にしない

ここはかなり大事だ。

TypeScript 5.7 の release notes では、baseUrlpaths に依存した import は JS 出力時に書き換えられないと明記されている。つまり、tsconfig.paths だけで package 境界をまたぐ設計は、型チェックでは通っても実行系や publish の段階で歪みやすい。

monorepo の越境参照は、基本的に次の順で考えた方がいい。

  1. package を分ける
  2. workspace: で依存関係を結ぶ
  3. package.jsonexports で公開面を決める
  4. TypeScript Project References で依存順ビルドを作る

paths は package 内の補助に留める方が壊れにくい。

Biome はこの前提と相性がいい

ESLint を外して Biome に寄せる判断はかなり自然だと思う。Biome の big projects ガイド でも、monorepo や workspace のような大きい repo 向けに root config と nested config を使い分ける前提が整理されている。v2 では monorepo を前提にした構成も案内されている。

この規模なら、lint / format を Biome に寄せて、project boundary と build だけを TypeScript 側で面倒を見る方が素直だ。

最小構成のイメージ

最初のセットアップはこのくらいで十分だ。

  1. root に pnpm-workspace.yaml を置く
  2. root に solution-style の tsconfig.json を置いて各 package を references する
  3. packages/corepackages/uicomposite を有効にする
  4. 共有 package は package.jsonexports を明示する
  5. root に biome.json を置く
  6. apps/wxt は WXT の型定義の扱いだけ別途確認する

ここまでで、かなり長く戦える。

いつ次の道具を足すか

追加するもの足すタイミング
Turborepobuild / test / lint が重い、CI 最適化が必要、task graph を明示したい
Changesets共有 package を version 管理して公開したい
Nx自動生成、polyglot、より強い monorepo 管理まで欲しくなった
bun workspace への移行runtime / package manager / test を Bun に統一したくなった

monorepo は、最初から全部入りにするより、痛みが出たところだけ足す方がうまくいく。

まとめ

TypeScript monorepo の最適解は 1 つではない。汎用の無難解なら pnpm workspace + Turborepo + TypeScript Project References が強い。でも、apps/webapps/wxtpackages/uipackages/core という 4 分割で、Docker も package 公開もまだ重くないなら、pnpm workspace + Biome + TypeScript Project References で始める方が軽くて壊れにくい。

大事なのは、ツールを増やすことではなく、coreui を薄く保ち、越境参照を workspace:exports で素直に表現することだ。そこが崩れなければ、あとから turbo を足しても遅くない。

参考