😱 Remixサイトが真っ白に!デプロイ地獄から這い上がった3日間の記録
「ローカルでは動くのに本番で500エラー」あるある体験談。初心者の私がRemix + Cloudflare Pagesで地獄を見て、最終的に解決するまでの涙と汗の物語。
😭 いきなり絶望からのスタート
「よし!ついにブログサイトが完成した!」
そう思ってRemixで作ったサイトをCloudflare Pagesにデプロイした金曜日の夜。意気揚々とURLにアクセスしたら...
500 Internal Server Error 😱
は???ローカルでは完璧に動いてたのに!!!
この時の私の心境:「やっぱり私には無理だったんだ...」「月曜日に会社で報告するはずだったのに...」「なんで開発って毎回こうなるの?」
でも諦めるわけにはいかない。3日間、寝不足でコーヒーを飲みながら格闘した記録を、同じ地獄を味わっている人のために残します。
「あー、これ私も経験した!」 という人、一緒に解決していきましょう!
🔥 1日目:「なんでやねん」の連続
まずは冷静に症状を整理(するつもりだった)

- 😊 ローカル開発: サクサク動く、記事もちゃんと表示
- 😨 Cloudflare Pages: 500エラーで真っ白画面
- 😵 記事ページ: 個別ページ(
/articles/何とか)全滅 - 😓 ホームページ: 「記事が0件あります」→は???
最初は「まあ、ちょっとしたミスでしょ」って軽く考えてたんです。でも...
「動いてたコードが急に動かない」の謎
// 犯人はコイツだった(当時は知る由もない)
export const loader = async ({ params, request }) => {
const url = new URL(request.url);
// 「これでJSONファイル取れるでしょ?」と思ってた
const res = await fetch(`${url.origin}/data/articles.json`);
if (!res.ok) {
throw new Response("記事データの取得に失敗しました", { status: 500 });
}
const data = await res.json();
// ...
};
私の心の声:「fetchって、普通にファイル取ってくるだけでしょ?なんで500エラー?意味わからん」
この時はまだ、この1行が3日間の地獄の始まりだとは知りませんでした... 😅
🔍 2日目:探偵ごっこが始まった
「ファイルがない?そんなバカな...」
土曜日の朝、コーヒーを片手に再挑戦。まずは基本的なところから確認してみました。
# 恐る恐るアクセスしてみる
curl https://xxx.pages.dev/data/articles.json
# -> 404 Not Found
# えっ???
私の心の声:「は?だってローカルにはあるじゃん!」
# ローカルで確認
$ ls -la build/client/data/
# articles.json ← ちゃんとある!
まさか設定ファイル?(結果:違った)
「もしかして設定の問題?」と思って vite.config.ts を疑いました。
// この時の私の推測(外れ)
export default defineConfig({
plugins: [
tailwindcss(),
remix({...}),
tsconfigPaths(),
],
// publicDir: "public", // これが足りないのかも?
});
publicDir: "public" を追加してデプロイ...
結果: まだ500エラー 😤
夜中の3時、ついに犯人発見

真相:Cloudflare PagesとRemixの「縄張り争い」だった!
想像してみてください。JSONファイルが宅配便だとすると:
- 通常の宅配便(静的ファイル):「
public/data/articles.json宛ですね、はいどうぞ」 - RemixのSSR(特急便):「待て待て、そのURLは俺が処理する!」
結果、宅配便が迷子になって404エラー 📦❌
「なるほど、そういうことか!」(この瞬間の達成感、忘れません)
🎯 3日目:ついに光が見えた!
「fetch やめて import にすればいいじゃん!」
日曜日の朝、ふとひらめきました。
宅配便の例えで説明すると:
- fetch方式:「外に取りに行く」→ 道で迷子になる
- import方式:「最初から家に持ち込んでおく」→ 確実!

💡 実際の修正:めちゃくちゃシンプル
ビフォー(迷子になる方式):
// 外にファイル取りに行って迷子になる
const res = await fetch(`${url.origin}/data/articles.json`);
const data = await res.json(); // ←ここで失敗してた
アフター(最初から持ち込む方式):
// 最初からアプリに組み込んでおく
try {
const { default: articlesData } = await import("~/data/articles.generated.json");
const data = articlesData; // ←確実に取れる!
if (!article) {
throw new Response("記事が見つかりません", { status: 404 });
}
} catch (error) {
throw new Response("記事データの読み込みに失敗しました", { status: 500 });
}
「なんだ、こんなシンプルなことだったのか!」
🏠 ホームページも同じように修正
同じ問題がホームページでも起きてました。「記事0件」って表示されてたやつです。
// ❌ ビフォー:またもや迷子方式
const res = await fetch(`${url.origin}/data/articles.json`);
if (!res.ok) return json({ articles: [] }); // 😭
const data = await res.json();
// ✅ アフター:持ち込み方式で安心
let data;
try {
const { default: articlesData } = await import("~/data/articles.generated.json");
data = articlesData; // 🎉 確実に取れる!
} catch (error) {
// もしダメでも親切にエラー処理
return json({
articles: [],
tags: [],
// ... その他のデフォルト値
});
}
📦 「両方作っちゃえ」作戦
せっかくなので、どっちでもアクセスできるように2つのファイルを作ることにしました。
// scripts/build-articles.mjs
// 「保険をかける」気持ちで両方生成
await Promise.all([
fs.writeFile('public/data/articles.json', jsonContent), // 静的ファイル用
fs.writeFile('app/data/articles.generated.json', jsonContent) // import用(メイン)
]);
なぜ2つ作るの?
public/data/articles.json→ 「もしかしたら使うかも」用app/data/articles.generated.json→ 「絶対に使う」メイン用
保険をかけておけば安心ですよね! 💪
🏗️ デプロイプロセスの改善
修正されたビルド・デプロイフロー
# 1. 記事データ生成
node scripts/build-articles.mjs
# → public/data/articles.json (静的配信用)
# → app/data/articles.generated.json (import用)
# 2. Remixビルド
npx remix vite:build
# → import用JSONがバンドルされる
# 3. Cloudflare Pagesデプロイ
npx wrangler pages deploy build --project-name web-lukes-remix
Vite設定の最適化
// vite.config.ts 完全版
export default defineConfig({
plugins: [
tailwindcss(),
remix({
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
v3_singleFetch: true,
v3_lazyRouteDiscovery: true,
},
}),
tsconfigPaths(),
],
publicDir: "public",
});
📊 問題解決の結果
パフォーマンス改善
const performanceComparison = {
修正前: {
記事ページ: "500エラー",
ホームページ: "記事0件表示",
読み込み方式: "Runtime fetch",
},
修正後: {
記事ページ: "正常表示",
ホームページ: "全記事表示",
読み込み方式: "Build-time import",
パフォーマンス向上: "静的バンドルによる高速化"
}
};
デプロイ安定性
修正前:
- fetchリクエストの失敗リスク
- 静的ファイル配信との競合
- ランタイムエラーの可能性
修正後:
- ビルド時エラー検出
- 静的バンドルによる安定性
- キャッシュ効率の向上
🛡️ トラブルシューティング手法
1. エラーの段階的切り分け
# Step 1: ローカル環境での動作確認
npm run dev
curl http://localhost:5173/articles/test-article
# Step 2: ビルド環境での確認
npm run build
ls -la build/client/data/
# Step 3: デプロイ環境での確認
curl https://xxx.pages.dev/data/articles.json
curl https://xxx.pages.dev/articles/test-article
2. Cloudflareデプロイ履歴の活用
# デプロイ履歴の確認
npx wrangler pages deployment list --project-name web-lukes-remix
# 特定デプロイの詳細確認
# Cloudflare Dashboard でビルドログを確認
3. 段階的修正アプローチ
// Step 1: エラーハンドリングの強化
try {
const res = await fetch(`${url.origin}/data/articles.json`);
if (!res.ok) {
console.error('Fetch failed:', res.status, res.statusText);
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
// ...
} catch (error) {
console.error('Article loading error:', error);
throw new Response("詳細なエラー情報", { status: 500 });
}
// Step 2: フォールバック機能の実装
const loadArticleData = async () => {
try {
// Method 1: Import
const { default: articlesData } = await import("~/data/articles.generated.json");
return articlesData;
} catch (importError) {
try {
// Method 2: Fetch (fallback)
const res = await fetch('/data/articles.json');
return await res.json();
} catch (fetchError) {
// Method 3: Default data
return {};
}
}
};
💡 学んだ教訓
1. プラットフォーム特性の理解
Cloudflare Pages の特徴:
- 静的ファイルの配信優先度
- Functions (SSR) との住み分け
- エッジでのキャッシュ戦略
Remix の特徴:
- ローダーでのサーバーサイド処理
- ビルド時最適化の重要性
- 動的インポートの活用
2. エラーハンドリング戦略
// 推奨: 詳細なエラー情報
throw new Response(`Article not found: ${slug}`, {
status: 404,
statusText: 'Article Not Found'
});
// 非推奨: 汎用的なエラー
throw new Response("エラー", { status: 500 });
3. デプロイ前のチェックリスト
- ローカル開発環境での動作確認
- ビルド生成物の確認
- 静的ファイルの配置確認
- エラーハンドリングのテスト
- 段階的デプロイでの検証
🔧 予防策とベストプラクティス
1. 開発環境と本番環境の差異対策
// 環境別の処理分岐
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = !isProduction;
if (isDevelopment) {
// 開発時: リアルタイムMDX読み込み
const { getArticleFromMDX } = await import("~/utils/markdown.server");
article = await getArticleFromMDX(slug);
} else {
// 本番時: 事前ビルドJSON読み込み
const { default: articlesData } = await import("~/data/articles.generated.json");
article = articlesData[slug];
}
2. 堅牢なエラーハンドリング
const safeArticleLoader = async (slug: string) => {
try {
const articleData = await loadArticleData();
const article = articleData[slug];
if (!article) {
throw new Response(`Article "${slug}" not found`, {
status: 404,
headers: { 'Content-Type': 'text/plain' }
});
}
return article;
} catch (error) {
// ログ出力(本番環境ではログサービスに送信)
console.error(`Article loading failed for ${slug}:`, error);
if (error instanceof Response) {
throw error;
}
throw new Response('Internal server error while loading article', {
status: 500
});
}
};
3. 継続的監視の実装
// パフォーマンス監視
const trackArticleLoad = (slug: string, startTime: number) => {
const duration = Date.now() - startTime;
// Cloudflare Analytics や外部サービスに送信
if (typeof window !== 'undefined') {
navigator.sendBeacon('/api/metrics', JSON.stringify({
event: 'article_load',
slug,
duration,
timestamp: Date.now()
}));
}
};
🎉 あなたへのメッセージ(同じ地獄を味わっている人へ)
このエラーで悩んでいるあなたへ
今この記事を読んでいるということは、きっと私と同じような絶望を味わっているんだと思います。
「ローカルでは動くのになんで!?」
「もう疲れた...」
「自分には無理なのかも...」
その気持ち、痛いほどわかります。私も3日間、本気で諦めようと思いました。
でも大丈夫!解決策はシンプル
✨ 今回学んだこと:
- 🔄 fetch → import: 「外に取りに行く」から「最初から持ち込む」へ
- 🌍 環境の違い: ローカルと本番は別世界だと心得よ
- 🕵️ 原因究明: 一歩ずつ、焦らず確実に
💪 次回への教訓:
- テスト: 小さくデプロイして確認
- ログ: エラーメッセージを恥ずかしがらずに読む
- 助け: わからないことは素直に調べる・聞く
🚀 あなたのサイトが動く瞬間の感動
修正してデプロイボタンを押した時の心臓のドキドキ。
URLにアクセスして、記事がちゃんと表示された時の**「やったー!!!」**という叫び。
きっとあなたも体験できます。
諦めないでください。
あなたの作ったサイトが世界中の人に見てもらえる日は近いです。
この記事が、あなたの助けに少しでもなれば幸いです。
一緒に頑張りましょう! 🌟
もし質問があれば、遠慮なくコメントしてくださいね。一緒に解決しましょう!
この記事が役に立ったらシェアしてください
📚 プログラミング・開発 の関連記事
🚀『もうコード読まなくていい!』AIエージェント開発で激変した開発現場の衝撃体験談
マルチタスク対応AIエージェントが開発現場を根底から変えた。コード読解地獄からの解放、並列開発の圧倒的効率化、そして開発者の疲労激減の生々しい体験を魂込めて語ります。
続きを読む😎『Claude Code CLI でEA作成マスター』になるための実践的コツと落とし穴回避法
「Claude Code使ってるけどEAがうまく作れない...」そんなあなたへ!2025年最新のCLI操作テクニック、効率的な指示出し方法、よくあるトラブル解決法を実体験ベースで完全解説。初心者でも上級者のようなEAが作れる秘密のコツ教えます。
続きを読む😭『なんで記事が反映されへんの!?』3時間の格闘から生まれたデプロイシステム完全改良記
「記事書いたのにサイトに出てこない...」そんな地獄から這い上がった、血と汗と涙のデプロイシステム改良プロジェクト。import地獄からfetch天国への道のり、全部見せます。
続きを読む