Remix+Vite環境で`import.meta.glob`がファイルを見つけられない問題のデバッグログ

はじめに
Remix+Vite で import.meta.glob を使うと、ローカルでは動くのにビルド時に「ファイルが見つからない」500エラーが出ることがあります。
原因は一つではなく、Vite の監視範囲・tsconfig の include・assetsInclude・パスのタイポが複合的に絡んでいます。
Viteでのファイル読み込みでこんなことありませんか?
- パスを何度確認しても
import.meta.globが空の配列を返す。 - ローカルでは正常に動くのに、ビルドすると 500 エラーが出て原因の候補が多すぎる。
- 一つ修正すると別のエラーが出て、どこが根本原因なのかわからなくなる。
この記事をお勧めしない人
- ビルドエラーが発生しても、とりあえず動けばそれで十分だと考える人。
- Viteの監視範囲やTypeScriptの設定は、単なる細かい設定でしかないと考える人。
- デバッグプロセスを記録する必要性を、全く感じていない人。
もし一つでも当てはまらないなら、読み進める価値があるかもしれません。
設定の複合ミスが積み重なると
- 一つの設定ミスはすぐ気づけるが、Vite・tsconfig・パスが同時に噛み合っていないとエラーメッセージが正確な場所を指さない。
- 「ファイルが見つからない」と言われてパスを直しても直らない——それは最後の一つが原因ではないから。
- デバッグに費やす時間が長くなるほど、本来作るべき機能から遠ざかる。
こんな未来が手に入ります
- Viteの
import.meta.globを確実に動作させるための設定知識と、複合的な問題を論理的に解決するデバッグ手法が手に入る。 - 監視範囲、tsconfig、アセット認識、パス整合という4つの重要チェックポイントを習得し、ビルドエラーに惑わされない設計力が手に入る。
- この方法は机上の空論ではなく、まさにこのブログ自身のアーキテクチャとして実証済みであり、即戦力の知見となる。
- 単なる「動けばいい」パッチ当てではなく、ビルドツールの仕組みを理解し、複数の設定を整合させる本質的な解決力を習得できる。
私も同じでした
このブログの specLoader.server.ts で import.meta.glob('/app/spec//*-spec.yaml') と書いていました。「spec」と「specs」——たった1文字の違いが、Vite の監視範囲・tsconfig・assetsInclude を全部直した後も残り続けたバグでした。
複合原因はどれか一つを直しても再現するため、「直ったと思ったらまた出た」が繰り返されます。この記事は、4つの仮説を立てて一つずつ検証したデバッグの記録です。
📝 概要
モダンなWeb標準フレームワークとビルドツールで構成されたプロジェクトで、特定のページにアクセスすると500サーバーエラーが発生しました。この記事では、問題の発見から原因特定、そして解決に至るまでのデバッグプロセスを詳細に記録します。
発生環境の特徴
- アーキテクチャ : モダンフレームワーク + 高速ビルドツール
- 問題のスコープ : 複数ファイルの一括読み込み機能
- 環境差異 : 設定不備によるファイル検出失敗
⚠️ 問題の発見と症状
プロジェクトで特定のページにアクセスすると500サーバーエラーが発生しました。
原因は、サーバーサイドでメタデータを読み込むために使用していた一括ファイル読み込み機能が、対象ファイルを検出できず、空の結果を返してしまうことでした。
症状:
- 特定のページで500エラーが発生
- ファイル検出機能が空の結果を返す
- エラーメッセージは「ファイルが見つからない」を示唆
🔍 原因の絞り込みプロセス
この問題は単一の原因ではなく、 4つの要因が複合的に絡み合っていました 。一つずつ検証していくことで、最終的に根本原因にたどり着きました。
調査の結果、以下の4つの視点で問題を切り分ける必要があることが判明しました:
- ビルドツールの監視範囲 : ファイルが監視対象ディレクトリ内に配置されているか
- コンパイラ設定 : 対象ファイル形式がプロジェクトの一部として認識されているか
- アセット認識設定 : ビルドツールが特定のファイル形式を扱えるよう設定されているか
- パスの記述ミス : ファイルパスに誤字脱字がないか
これらの問題は、ビルドツールの設定を曖昧なままにしていたことと、パスの確認不足が原因でした。
💡 根本原因の特定
調査の結果、以下の4つの根本原因が特定されました。
- ビルドツールの監視範囲外 : ファイルが監視対象ディレクトリ外に配置されていた
- コンパイラ設定の不足 : 対象ファイル形式がコンパイル対象に含まれていなかった
- アセット認識設定の不足 : ビルドツールがファイル形式をアセットとして認識できていなかった
- パスの記述ミス : ファイルパスに単数形/複数形の誤りがあった
これらの問題は、ビルドツールの設定を曖昧なままにしていたことと、パスの確認不足が原因でした。
段階的デバッグ戦略
この問題を解決するため、「複数の原因を一つずつ検証する」というアプローチを採用しました。
具体的には、以下の戦略で問題を切り分けます:
- 監視範囲の確認 : ビルドツールが監視するディレクトリ内にファイルを配置
- コンパイラ設定の整合 : コンパイラ設定で対象ファイル形式を明示的に追加
- アセット認識設定の追加 : ビルドツールの設定で明示的に認識させる
- パスの再確認 : 単数形/複数形などの誤記を徹底的に確認
このアプローチにより、単に「ファイルを移動する」という対症療法ではなく、 ビルドツールの仕組みを理解した上での体系的な解決 を実現しました。
達成した成果
| 改善項目 | Before | After |
|---|---|---|
| ファイル検出 | import.meta.globが空の配列を返す |
正しくファイルを検出 |
| 設定の整合性 | Vite、TypeScript、Remixの設定がバラバラ | 3つの設定が整合した状態 |
| デバッグ効率 | エラー原因の特定に長時間かかる | チェックリストで体系的に検証 |
その結果、 「ファイルが見つからない」という複合的問題を、4段階の設定整合で解消する ことに成功しました。
AIに「ファイルが見つからないエラーを解決して」と頼むと、高確率で以下のような提案が返ってきます:
- 「とりあえずファイルを別の場所に移動してみましょう」
- 「パスを変更してみましょう」
- 場当たり的な試行錯誤を繰り返す
しかし、これは 場当たり的な試行錯誤 です。一時的に別のエラーに変わりますが、「なぜビルドツールがファイルを認識しないのか」という仕組みの理解が欠けているため、根本解決に届きません。
根本原因は、 ビルドツールの監視範囲、コンパイラ設定、アセット認識、パスの4つが複合的に絡み合っていた ことにありました。一つずつ仮説を立てて検証することで、初めて全体像が見えてきます。
ここから先は、公式ドキュメントにも書かれていない 「一括ファイル読み込みを機能させる4つのチェックリスト」 の全貌と、具体的な設定ファイル(tsconfig.json, vite.config.ts)の修正差分、使用したAPIの実名(import.meta.glob)、ビルドツールの監視範囲とコンパイラの連携の仕組み、実際のエラーメッセージ、そしてデバッグフローを視覚化したシーケンス図を、すべて公開します。
このチェックリストと設定をコピーすれば、ファイル検出問題ループを回避し、 初回から正しい設定でファイル読み込み を実現できます。私が実践で確立した段階的デバッグ手順と、実装済みの設定ファイル構成、4つの仮説検証の詳細を、ここで全て公開します。
🔧 解決策: 4段階の設定整合
では、実際に私が適用した4つの修正の具体的な手順と、各修正がどのチェックリスト項目に対応するか、実際のエラーメッセージ、そしてデバッグフロー全体を可視化したシーケンス図を公開します。
この手順と設定をそのままコピーすれば、「どの設定をどの順序で確認すべきか」を毎回試行錯誤することなく、 再現可能なファイル読み込み設定 を実現できます。また、なぜViteの監視範囲が重要なのか、TypeScriptのincludeとViteのassetsIncludeの違い、そしてパスのタイポを防ぐ命名規則も解説します。
使用した技術スタック
- フレームワーク : Remix v2
- ビルドツール : Vite
- 問題のファイル形式 :
.yaml - 使用API:
import.meta.glob
発生した実際のエラーメッセージ
Error: Spec file not found for feature: blog/posts (path: /app/specs/blog/posts-spec.yaml)サーバーサイドでメタデータを読み込むために使用していた specLoader.server.ts 内の import.meta.glob が、対象の .yaml ファイルを検出できず、specModules が空のオブジェクトになっていました。
// app/spec-loader/specLoader.server.ts
const specModules = import.meta.glob('/app/specs//*-spec.yaml', {
eager: true,
as: 'raw'
});
// specModules が空のため、yamlString が undefined となりエラー
if (typeof yamlString !== 'string') {
throw new Error(
`Spec file not found for feature: ${featurePath} (path: ${modulePath})`,
);
}4つの仮説検証プロセス
この問題は単一の原因ではなく、以下の4つの要因が複合的に絡み合っていました。一つずつ検証していくことで、最終的に根本原因にたどり着きました。
仮説1: Viteの監視範囲外にファイルが存在するのではないか?
- 事象 : 当初、
spec.yamlはプロジェクトルート直下のdevelopディレクトリに配置されていました - 問題 : RemixのViteプラグインは
appディレクトリを重点的に監視するため、その外にあるdevelopディレクトリ内のファイルはimport.meta.globの探索対象から漏れていました - 対策 : ファイルを
app/specs/ディレクトリに移動しました
仮説2: TypeScriptのコンパイル対象外だった
- 事象 :
tsconfig.jsonのinclude配列に、appディレクトリ内の.yamlファイルを対象とするパターン ("app//*.yaml") が含まれていませんでした - 問題 : これにより、たとえファイルを
app配下に移動しても、TypeScriptおよびViteからプロジェクトの一部として認識されませんでした - 対策 :
tsconfig.jsonのincludeに"app//*.yaml"を追加しました
仮説3: Viteのアセットとして認識されていない
- 事象 : Viteはデフォルトで
.yamlをインポート可能なアセットとして認識しません - 問題 :
import.meta.globで?rawクエリを使ってファイルを文字列としてインポートするには、Viteにそのファイル形式をアセットとして明示的に教える必要がありました - 対策 :
vite.config.tsにbuild.assetsInclude: ['/*.yaml']を追加しました
仮説4: パスのタイポ(1文字違い)を疑う
- 事象 : 上記1〜3をすべて解決した後もエラーが継続しました
- 問題 : ファイルは
app/specs/(複数形) に配置されていましたが、specLoader.server.ts内のimport.meta.globのパスが/app/spec/(単数形) になっていました。このわずか1文字の違いが、ファイルを見つけられない最後の原因でした - 対策 :
specLoader.server.tsのパスを/app/specs/に修正しました
実施した4つの修正
以下の4つの修正を順番に適用することで、問題を解決しました。
ステップ1: ファイルの移動
# develop/ から app/specs/ に移動
mv develop/blog/posts-spec.yaml app/specs/blog/posts-spec.yamlステップ2: tsconfig.json の修正
{
"include": [
"app//*",
+ "app//*.yaml"
]
}ステップ3: vite.config.ts の修正
export default defineConfig({
build: {
+ assetsInclude: ['/*.yaml']
}
})ステップ4: パスの修正
// app/spec-loader/specLoader.server.ts
- const specModules = import.meta.glob('/app/spec//*-spec.yaml', {
+ const specModules = import.meta.glob('/app/specs//*-spec.yaml', {
eager: true,
as: 'raw'
});シーケンス図で見る解決までの流れ
sequenceDiagram
participant User as 開発者
participant specLoader as specLoader.server.ts
participant Vite as Vite
participant RemixPlugin as Remix Plugin
participant tsconfig as tsconfig.json
User->>specLoader: loadSpec('blog/posts') を呼び出す
specLoader->>Vite: import.meta.glob('/app/specs//*-spec.yaml') を要求
Vite->>RemixPlugin: ファイル探索範囲を確認
RemixPlugin-->>Vite: 'app' ディレクトリ内を探索
Note over Vite: OK: ファイルは 'app' 内にある
Vite->>tsconfig: ファイルがプロジェクトに含まれるか確認
tsconfig-->>Vite: OK: "include": ["app//*.yaml"]
Note over Vite: OK: .yaml はプロジェクトの一部
Vite->>Vite: ファイルがアセットとして認識されるか確認
Note over Vite: OK: assetsInclude: ['/*.yaml']
Vite->>Vite: パス '/app/specs/...' に基づきファイルを探索
Note over Vite: OK: ファイル 'app/specs/blog/posts-spec.yaml' を発見!
Vite-->>specLoader: specModules に検出したファイル情報を格納して返す
specLoader->>specLoader: specModules['/app/specs/blog/posts-spec.yaml'] を参照
Note over specLoader: OK: yamlString を取得
specLoader->>specLoader: yaml.load(yamlString) を実行し、パース成功
specLoader-->>User: パース済みのSpecオブジェクトを返す
🎓 学んだこと・まとめ
技術的な学び
import.meta.glob が期待通りに動作しない場合、以下のチェックリストを確認することが重要です。
- ファイルパス :
import.meta.globのパスパターンは正しいか?(タイポはないか?) - Viteの監視範囲 : ファイルはViteが監視するディレクトリ(Remixの場合は
app)内にあるか? - TypeScriptの
include:tsconfig.jsonでファイルがプロジェクトの一部として認識されているか? - Viteの
assetsInclude:?rawなどでインポートする場合、ファイル形式がViteのアセットとして認識されているか?
今後のベストプラクティス
- 設定の整合性 : Vite、TypeScript、Remixの設定を常に整合させる
- パスの慎重な確認 : 単数形/複数形のような些細な違いが大きな問題を引き起こす
- 仮説検証アプローチ : 複数の原因が絡み合う場合は、一つずつ仮説を立てて検証する
- デバッグログの記録 : 問題解決のプロセスを記録することで、同じ問題の再発を防ぐ
今回のケースのように、複数の設定が絡み合って問題を引き起こすことは珍しくありません。一つずつ仮説を立てて検証していく地道な作業が、解決への一番の近道でした。
Vite + Remixでの「ローカルでは動くのに本番で動かない」問題は形を変えて繰り返し出現します。CSSのパスエイリアスが解決されない問題も同じ文脈で発生し、より大きなスコープではCloudflare PagesのファイルシステムAPI非互換問題として現れました。
