TypeScriptを用いた開発において、ビルド後のディレクトリ構造が意図しない形になり、デプロイやモジュールの読み込みでトラブルが発生することは珍しくありません。

その挙動を制御する鍵となるのが、tsconfig.jsonにおけるrootDir設定です。

rootDirは、一見すると「コンパイル対象のソースコードが存在する場所」を指定するだけのオプションに見えますが、実際には出力ディレクトリ(outDir)内での構造を決定する基準点としての重要な役割を担っています。

2026年現在のモダンなTypeScript開発においても、この仕様を正しく理解していないと、複雑なモノレポ構成や外部ライブラリとの連携時に予期せぬエラーに直面することがあります。

本記事では、rootDirの基本的な定義から、outDirとの相関関係、そして開発現場で頻発するエラーの解決策までを詳しく解説します。

TypeScriptのrootDirとは何か

TypeScriptにおけるrootDirオプションは、コンパイラに対してソースファイルのルートとなるディレクトリを明示的に伝えるものです。

しかし、ここで多くの開発者が誤解しやすいポイントが1つあります。

それは、rootDirが「コンパイル対象を制限するフィルタではない」という点です。

rootDirの真の役割

TypeScriptコンパイラ(tsc)は、includefilesなどのオプション、あるいはソースファイル内のimport文を辿ってコンパイル対象を決定します。

rootDirの役割は、そうして決定されたソースファイルの集合が、出力先ディレクトリ(outDir)においてどのように配置されるかを計算するための「基準パス」を提供することにあります。

もしrootDirを指定しない場合、TypeScriptはコンパイル対象となるすべてのファイルの中から、それらに共通する最も深い親ディレクトリを自動的に「最長共通パス」として計算し、それを基準にディレクトリ構造を維持したまま出力を行います。

rootDirを指定すべき理由

自動計算に頼ることも可能ですが、明示的に設定することを推奨します。

その主な理由は以下の通りです。

  1. 新しいファイルを追加した際に、共通親ディレクトリが勝手に変わり、ビルド後のパス構造が崩れるのを防ぐ。
  2. ソースコード以外のファイル(テストコードや設定用スクリプトなど)が意図せずコンパイル対象に含まれた場合に、エラーとして検知できる。
  3. プロジェクトのディレクトリ構成をチーム内で標準化し、ビルドツールの設定と整合性を保つ。

rootDirとoutDirの相関関係

rootDirを理解する上で、outDirとの関係性は切り離せません。

TypeScriptは、rootDirで指定された位置からの相対パスを維持したまま、ファイルをoutDirへコピー(変換)します。

基本的なディレクトリ構成の例

例えば、以下のようなプロジェクト構成を想定してみましょう。

text
my-project/
├── src/
│   ├── index.ts
│   └── components/
│       └── button.ts
├── tsconfig.json
└── dist/ (ビルド出力先)

このとき、tsconfig.jsonの設定を次のように記述します。

JSON
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true
  },
  "include": ["src/**/*"]
}

この設定でビルドを行うと、出力結果は以下のようになります。

text
dist/
├── index.js
└── components/
    └── button.js

srcディレクトリ自体はdistの中に作成されず、srcの中身がそのままdist直下に展開されている点に注目してください。

これが、rootDirが「基準点」として機能している状態です。

rootDirを指定しない場合の挙動

もし上記の例でrootDirを未設定のままにし、かつsrcディレクトリの外側にあるファイルを不注意にimportしてしまった場合、どうなるでしょうか。

例えば、プロジェクトルートにあるutils.tsを読み込んだとします。

TypeScript
// src/index.ts
import { helper } from "../utils"; // srcの外にあるファイルをインポート

この場合、TypeScriptコンパイラは「共通の親ディレクトリ」をsrcではなくプロジェクトルート(my-project)であると判断します。

その結果、ビルド後のdistの中身は以下のようになります。

text
dist/
├── utils.js
└── src/
    ├── index.js
    └── components/
        └── button.js

このように、出力ディレクトリ内にsrcディレクトリが突然出現するという現象が発生します。

これは実行時のモジュール解決エラーを招く原因となるため、rootDirでガードをかけることが重要です。

rootDirに関するよくあるエラーと解決策

設定を進める中で、TypeScriptコンパイラからrootDirに関連するエラーが報告されることがあります。

これらは設定ミスを未然に防いでくれる便利な警告です。

エラー1:File is not under ‘rootDir’

最も頻繁に遭遇するのが、「File '...' is not under 'rootDir' '...'. 'rootDir' helps control the output directory structure.」というエラーです。

発生原因

このエラーは、rootDirで指定した範囲の外にあるファイルを、TypeScriptがコンパイル対象に含めようとしたときに発生します。

具体的なシナリオ

  • src以外のディレクトリ(例:testsconfig)にあるファイルをimportしている。
  • プロジェクトルートにある設定ファイルを誤ってincludeに含めている。

解決方法

解決策は3つあります。

  1. インポートの修正:もし外部ファイルの読み込みがミスであれば、それを削除するか、src配下に移動します。
  2. rootDirの拡張:プロジェクト全体をカバーするようにrootDir./に変更します。ただし、この場合はoutDir内のディレクトリ構造が変わることに注意が必要です。
  3. excludeの活用:コンパイルの必要がないファイルであれば、exclude設定に明示的に追加します。

エラー2:Cannot write file … because it would overwrite input file

これは、rootDiroutDirが同じ場所を指している、あるいは設定が矛盾している場合に発生します。

項目確認ポイント
rootDirソースファイル(.ts)の親フォルダを指しているか
outDir生成ファイル(.js)を出力する独立したフォルダを指しているか
allowJsJSファイルを扱う場合、出力先と入力元が重なっていないか

TypeScriptは元のファイルを破壊しないよう設計されているため、入力ファイルと出力ファイルが衝突する可能性がある設定は拒否されます。

必ずdistbuildといった別名のディレクトリをoutDirに指定しましょう。

実践的な設定例:さまざまなプロジェクト構造

2026年の開発環境では、シンプルな構造だけでなく、複雑なプロジェクト構成も一般的です。

それぞれのケースにおけるrootDirのベストプラクティスを見ていきましょう。

標準的なアプリケーション開発

多くのウェブアプリケーションでは、ソースコードをsrcに集約します。

JSON
{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true
  }
}

この設定により、型定義ファイル(.d.ts)やソースマップファイル(.js.map)も、srcディレクトリをルートとした構造でdistに出力されます。

複数のルートディレクトリを持つ場合(rootDirs)

rootDir(単数形)と似たオプションにrootDirs(複数形)があります。

これは、複数の異なるディレクトリを、実行時にまるで1つの同じディレクトリにあるかのように仮想的に結合するための設定です。

例えば、ビルド時に生成される一時ファイルとソースファイルを組み合わせてインポートしたい場合に有効です。

JSON
{
  "compilerOptions": {
    "rootDirs": ["src", "generated"]
  }
}

ただし、これは型チェックやエディタの補完のための機能であり、実際にファイルを物理的に統合して出力するものではありません。

ビルド結果の構造を制御したい場合は、あくまでrootDirを使用します。

モノレポ(Project References)環境

複数のパッケージを1つのリポジトリで管理するモノレポ構成では、各パッケージごとにtsconfig.jsonを持ちます。

この場合、共通のtsconfig.base.jsonから設定を継承しつつ、各パッケージのrootDirを個別に設定するのが一般的です。

JSON
// packages/api/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "composite": true
  }
}

このようにcomposite: trueと組み合わせることで、プロジェクト間の依存関係を厳密に管理できます。

rootDir設定時の注意点とTips

設定を正しく機能させるための、いくつかの細かい注意点を紹介します。

パス指定の「/」に注意

Windows環境であっても、tsconfig.json内のパス区切り文字にはスラッシュ(/)を使用してください。

バックスラッシュ()はエスケープ文字として扱われるため、予期せぬ挙動を招く恐れがあります。

型定義ファイル(.d.ts)の配置

ライブラリを開発している場合、declaration: trueを設定して型定義ファイルを出力するでしょう。

このときもrootDirが基準となります。

rootDirを正しく設定しておくことで、利用者がライブラリを読み込む際のパス構造が直感的になります。

2026年のビルドツール事情

ViteやBun、swcといった次世代ビルドツールを使用している場合、TypeScriptのトランスパイル自体はこれらのツールが行い、tscは型チェックのみを担当することが増えています。

しかし、型定義ファイルの生成や、IDEのパス解決能力を維持するためには依然としてtsconfig.jsonのrootDir設定が不可欠です。

まとめ

TypeScriptのrootDirは、単に入力先を指定するだけのものではなく、ビルド後のディレクトリ構造を決定づける羅針盤のような役割を果たします。

  • rootDirを明示することで、不注意なインポートによる出力構造の崩れを防ぐことができる。
  • outDirとの組み合わせによって、ソースコードの階層をそのまま反映したビルド結果が得られる。
  • エラーが発生した際は、rootDirの外にあるファイルが含まれていないかをまず確認する。

プロジェクトの規模が大きくなればなるほど、ディレクトリ構造の管理は重要になります。

今回解説した内容を参考に、tsconfig.jsonrootDirを正しく設定し、クリーンでメンテナンスしやすいTypeScriptプロジェクトを構築してください。