HTTP/HTTPSデバッグの悪夢:Mixed Content地獄を24時間さまよった話
ReactアプリがHTTPSページからHTTPリクエストを送り続ける原因を24時間デバッグしました。コードは変換していたのに。犯人は衝撃的でした。
---
更新(2025年11月): STRAŦUMがPrivate Alphaでローンチしました!この記事で触れている9エージェントマーケティングプラットフォームが早期テスターを募集中です。stratum.chandlernguyen.comでアクセスをリクエストするか、フルローンチストーリーをお読みください。
---
背景:マーケティングプラットフォームの旅は続く
9月の記事で、昼寝をしながら10エージェントマーケティングプラットフォームをスピードランしていると書いたことを覚えていますか?4週間目で3つのエージェントが動作し、10月/11月のアルファローンチを目標にしていました。
さて、10月下旬です。進捗報告の時間です。
良いニュース:
- プラットフォームにようやく名前がつきました:STRAŦUM(Intelligence Over Execution)
- 完全なブランドガイドラインとデザインシステム(ブランディングはコーディングより時間がかかることが判明)
- 10のうち9つのエージェントが構築・統合済み
- マルチテナントアーキテクチャが実際に動作
- 最終段階:招待制テスト用のプレアルファ準備
現実のチェック:
8日間の休暇後に10日間体調を崩しました(人生にはそういうことがありますよね。)すべてが一時停止し、タイムラインがずれました。10月ローンチは無理ですよね?
でもソロ開発のいいところはこういうことです:自分でペースをコントロールできます。準備ができていない時にリリースするプレッシャーはありません。首筋に息がかかる投資家もいません。ただ...正しく作るだけです。
このブログ記事は、「正しく作る」の1つが6時間のデバッグマラソンに変わった話です。体調不良から回復してフルスピードに戻ろうとしていたまさにその時、デプロイメントプラットフォームが別の計画を持っていたからです。
AWSがダウンすると、エンジニアは創造的になる(そして時々後悔する)
コーディングブートキャンプでは教えてくれないことがあります:デプロイメントプラットフォームが突然...消えることがあるのです。あなたが何か間違ったことをしたからではなく、AWSがVercelのデプロイメントに影響するグローバルな大規模障害を起こすことを決めたからです。
それが私の月曜日の始まりでした。本番サイトがダウンし、Vercelがエラーを表示し、私の頭にはたった一つの考えがありました:「バックアップが必要だ。今すぐ。」
登場するのはCloudflare Pagesです。良い評判を聞いていました。優れたCDN、自動デプロイメント、シンプルなセットアップ。何がうまくいかないでしょうか?
ナレーター:すべてです。すべてがうまくいかない可能性がありました。
簡単すぎた移行
Cloudflare Pagesへの移行は驚くほどスムーズでした。GitHubリポジトリを接続し、ダッシュボードで環境変数を設定し、mainにプッシュ。3分後:デプロイ完了。
「わぁ」と思いました。「これはちょっと簡単すぎる。」
そして本番サイトを開きました。
```
Mixed Content: The page at 'https://my-site.com/...' was loaded over HTTPS,
but requested an insecure resource 'http://stratum-api.us-central1.run.app/...'
```
お祝いが早すぎたと気づいた時のあの沈む感覚?まさにそれです。
問題:HTTPSページからのHTTPコール
ReactアプリがHTTPSで読み込まれたページからバックエンドAPIへのHTTPリクエストを送っていました。ブラウザは(正当に)これをセキュリティリスクとしてブロックします。Mixed Contentエラー。すべてのAPIコールが失敗していました。
「でもちょっと待って」と自分に言いました。「ensureHttpsInProduction()がコードにあるじゃないか!HTTPをHTTPSに自動変換するはずだ!」
デプロイされたバンドルを確認しました。関数はそこにありました。ロジックは正しかったです。ブラウザコンソールは変換が行われていることを表示していました。ではなぜHTTPリクエストがまだ通っているのでしょうか?
最初のデバッグ試行:環境変数の調査
Cloudflareの環境変数が読み取られていないのかもしれません?
```bash
# Cloudflare Dashboardを確認
VITE_API_URL=https://stratum-api.us-central1.run.app ✓
VITE_SUPABASE_URL=https://your-project.supabase.co ✓
```
すべてHTTPS。すべて正しい。
リビルドをトリガーしました。待ちました。デプロイされました。サイトを開きました。
同じエラー。まだHTTPリクエスト。
2回目の試行:大インポート作戦
ファイルが中央集約されたAPI_BASE_URLを使っていないのかもしれません?
次の1時間をかけて、import.meta.env.VITE_API_URLを直接使う代わりに@/lib/apiからインポートするように24ファイルを更新しました。APIコールを行うすべてのコンポーネントが対象です。
```typescript
// Before
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/v1/...`);
// After
import { API_BASE_URL } from '@/lib/api';
const response = await fetch(`${API_BASE_URL}/api/v1/...`);
```
プッシュ。デプロイ。待機。
まだ壊れています。
この時点で、人生の選択を疑い始めていました。
衝撃の展開:Gitにファイルが存在しない
でも待ってください、さらに悪くなります。
HTTPS強制が機能しない理由を調査している間に、恐ろしいことを発見しました。ensureHttpsInProduction()を含むapi.tsファイルがGitリポジトリに入っていませんでした。
authService.tsも入っていませんでした。csvSanitizer.tsも。3つの重要なフロントエンドファイルが、ただ...なかったのです。
なぜ?.gitignoreファイルにこうありました:
```
# Python関連
lib/
build/
dist/
```
Pythonには合理的ですよね?ただし、フロントエンドのユーティリティはapps/web/src/lib/にありました。広範なlib/パターンが、フロントエンドのlibディレクトリ全体を誤って無視していたのです!
つまり:
1. Cloudflareはリポジトリから構築していた(これらのファイルが欠落)
2. ローカル開発にはこれらのファイルがあった(ローカルでは正常に動作)
3. 追跡されていないことに全く気づいていなかった
修正:
```diff
# .gitignore - Before
-lib/
# .gitignore - After
+apps/api/lib/ # Python固有
+!apps/web/src/lib/ # フロントエンドlibを明示的にインクルード
```
欠落したファイルを追加、コミット、プッシュ。Cloudflareにやっとhttps強制コードが入りました!
ただし...HTTPエラーは続きました。
3回目の試行:ビルド時バリデーション
この時点で、すべてを疑っていました。「よし」と思いました。「こういうことが続くなら、本番に到達する前に_防止_しないと。」
本番環境でHTTP URLを検出したらビルドを失敗させるViteプラグインを書きました:
```typescript
function validateProductionUrls(mode: string) {
if (mode !== 'production') return;
const apiUrl = process.env.VITE_API_URL || '';
if (apiUrl && apiUrl.trim().startsWith('http://')) {
if (!apiUrl.includes('localhost') && !apiUrl.includes('127.0.0.1')) {
throw new Error(
`❌ HTTPS ENFORCEMENT FAILED
Environment Variable: VITE_API_URL
Current Value: ${apiUrl}
This will cause Mixed Content errors in production!`
);
}
}
}
```
天才でしょう?これでHTTP URLでのデプロイは_不可能_です。
再びデプロイ。ビルド通過(環境変数はHTTPS)。サイトが読み込まれました。
同じ。エラー。
ひらめきの瞬間:ローカルファイルがデプロイされていた
(かなり遅い)夜、気づきがありました。
デプロイされたJavaScriptバンドルをもう一度確認しました。本当にしっかり見ました。中にあったURLは:
```javascript
"http://stratum-api.us-central1.run.app"
```
でもCloudflareの環境変数はHTTPSでした。このHTTP URLはどこから来ているのでしょうか?
そこで気づきました。ローカルの.env.productionファイルです。
```bash
# apps/web/.env.production (ローカルファイル)
VITE_API_URL=http://stratum-api.us-central1.run.app
```
Cloudflare Pagesがダッシュードの変数ではなく、ローカルの環境ファイルをデプロイしていたのです!
.cloudflare-pages-ignoreを確認しました:
```
# 環境ファイル
.env
.env.local
.env.development
.env.test
# .env.production ← 欠落!
```
顔。手のひら。
修正:1行
```diff
# apps/web/.cloudflare-pages-ignore
.env
.env.local
.env.development
.env.test
+.env.production
```
デプロイ。待機。
今度は別のエラーです!進歩!
```
Access to fetch at 'https://stratum-api.us-central1.run.app/...'
from origin 'https://preview-xyz.stratum-marketing-suite.pages.dev'
has been blocked by CORS policy
```
CORSエラーです!美しい、美しいCORSエラーです!HTTPSが動いているということです!
でもまだ続きます:キャッシュの陰謀
CORSを修正。再デプロイ。カスタムドメインを開きました。
またHTTPエラー。
何?!
CloudflareのCDNが古いバンドルを積極的にキャッシュしていたのです。新しいデプロイメント(HTTPSのもの)はプレビューURLではライブでしたが、カスタムドメインはHTTP URLを含むキャッシュされたコンテンツを提供していました。
Cloudflareのキャッシュパージには以下が必要です:
1. 正しいゾーン設定を見つける(Pagesダッシュボードにはない)
2. ドメイン設定を辿る(分かりにくい)
3. キャッシュを手動でパージ(デプロイのたびに)
HTTP/HTTPSの問題を何時間もデバッグした後、決断しました。
Vercelへの帰還:退屈がベストな時もある
AWSが復旧しました。Vercelが動作していました。
すべてをVercelに戻しました。なぜ?
1. 自動キャッシュ無効化 — 手動パージが不要
2. シンプルな環境変数の扱い — 設定した通りに動く
3. デバッグが速い — 推理すべきインフラが少ない
4. 実績がある — 癖を知っている
Vercelのデプロイメントは3分かかりました。HTTPエラーなし。キャッシュの問題なし。ただ...動きました。
学んだこと(苦労して)
1. デプロイメントプラットフォームでは必ず.env.productionをIgnoreする
```
# .vercelignore
# .cloudflare-pages-ignore
# .netlify-ignore
.env
.env.local
.env.development
.env.test
.env.production ← これを忘れないで
```
2. モノレポでの広範な.gitignoreパターンは危険
```diff
# ❌ 悪い - フロントエンドとバックエンドの両方のlibフォルダを無視
-lib/
-build/
-dist/
# ✅ 良い - 各コンテキストに固有
+apps/api/lib/ # Python固有
+apps/api/build/
+apps/api/dist/
+apps/web/dist/ # Vite出力のみ
```
常に確認しましょう:「このパターンが誤って重要なものを無視してしまう可能性はないか?」
複数の言語(Python + TypeScript)を持つモノレポでは、1つのエコシステム向けの広範なパターンが別のエコシステムの重要なファイルを誤って無視する可能性があります。
3. ビルド時バリデーションは価値がある
ローカルファイルの問題は検出できなかったものの、ビルド時バリデーションは_将来の_設定ミスを防止します:
```typescript
// vite.config.ts
export default defineConfig(({ mode }) => {
validateProductionUrls(mode);
return {
// ... config
};
});
```
4. 多層防御は有効
最終アーキテクチャには3つのレイヤーがあります:
- ビルド時:HTTP URLが検出されるとビルド失敗
- ランタイム:ページがHTTPSで読み込まれた場合にHTTP → HTTPS変換
- デプロイメント:ローカル.envファイルを除外
5. プレビューURLは味方
まずプレビューURLでテストしましょう。それが動いてカスタムドメインが動かない場合、通常はキャッシュの問題です。
6. プラットフォームの癖を知る
- Vercel:シンプル、自動キャッシュ無効化、環境変数が「ちゃんと動く」
- Cloudflare Pages:素晴らしいCDN、でも手動キャッシュパージとより複雑なセットアップ
助けてくれたコード
最終的なensureHttpsInProduction()関数がこちらです:
```typescript
function ensureHttpsInProduction(url: string): string {
// ブラウザコンテキストでサイトがHTTPSで読み込まれた場合のみ変換
if (typeof window !== 'undefined' && window.location.protocol === 'https:') {
// localhost/127.0.0.1 URLは変換しない(ローカル開発)
if (url.startsWith('http://') &&
!url.includes('localhost') &&
!url.includes('127.0.0.1')) {
console.warn('[API] Converting HTTP to HTTPS:', url);
return url.replace('http://', 'https://');
}
}
return url;
}
```
vite.config.tsのビルド時バリデーション:
```typescript
function validateProductionUrls(mode: string) {
if (mode !== 'production') return;
const apiUrl = process.env.VITE_API_URL || '';
// HTTPチェック(HTTPSであるべき)
if (apiUrl && apiUrl.trim().startsWith('http://')) {
if (!apiUrl.includes('localhost') && !apiUrl.includes('127.0.0.1')) {
throw new Error(`
❌ HTTPS ENFORCEMENT FAILED
Environment Variable: VITE_API_URL
Current Value: ${apiUrl}
Mixed Content Error Prevention:
Browsers block HTTP requests from HTTPS pages.
Fix: Update environment variables to use HTTPS URLs.
`);
}
}
}
```
本当の教訓:デバッグは探偵の仕事
これはコーディングの問題ではありませんでした。設定考古学の探検でした。
本当のバグは:
1. ✅ 重要なフロントエンドファイルを無視する広範な.gitignoreパターン
2. ✅ .cloudflare-pages-ignoreに.env.productionが欠落
3. ✅ 修正をマスクする積極的なCDNキャッシュ
4. ✅ 環境変数の優先順位の誤った想定
技術的な解決策は.ignoreファイルの1行でした。
デバッグ?6時間、14回のデプロイメント、そして飲みすぎたコーヒーでした。
価値はあったのか?
間違いなく。得たものはこちらです:
1. Mixed Contentセキュリティポリシーの深い理解
2. 将来の問題を防止するビルド時バリデーション
3. プラットフォームに依存しない多層HTTPS強制
4. Vercelのシンプルさへの本当の感謝
そして最も重要なこと:共有できる素晴らしいデバッグストーリー。 :P
Gitログが物語る
```bash
2033b9a fix: add .env.production to .vercelignore
3555390 docs: migrate deployment documentation from Cloudflare Pages to Vercel
a4edb09 chore: force clean Cloudflare Pages rebuild
590c271 fix: enhance ensureHttpsInProduction logging
15d69c6 chore: force rebuild of UserProfile bundle
a1c345f fix: add .env.production to cloudflare-pages-ignore ← THE .ENV FIX
635d80d docs: update documentation for Cloudflare Pages migration
edfa611 feat: add build-time HTTPS enforcement
802c1e9 fix: enforce HTTPS for all API calls across frontend
26f6b87 refactor: comprehensive .gitignore audit and cleanup
3758a9c fix: unignore frontend lib directory and add missing files ← THE GITIGNORE FIX
```
各コミットは旧世界では1時間のデバッグに相当しますが、Claude Codeのおかげで、ありがたいことにスピードは速くなりました。テストされた仮説。学ばれた教訓。
Mixed Contentエラーと戦っている他のエンジニアへ
同じ問題をデバッグしていてこの記事を読んでいるなら、チェックリストをどうぞ:
1. 環境変数を確認:
```bash
# 実際に使用されている値を表示
console.log('API URL:', import.meta.env.VITE_API_URL);
```
2. デプロイされたバンドルを確認:
```bash
# JavaScriptバンドルをダウンロードして検索
curl https://your-site.com/assets/index-ABC123.js | grep "http://"
```
3. Ignoreファイルを確認:
```bash
# .env.productionが除外されていることを確認
cat .vercelignore
cat .cloudflare-pages-ignore
cat .netlify-ignore
```
4. .gitignoreを確認(モノレポ):
```bash
# 重要なファイルが無視されていないことを確認
git ls-files apps/web/src/lib/ # api.tsなどが表示されるはず
# 空の場合、広範なパターンを確認
grep "^lib/" .gitignore # ❌ 広すぎる
grep "^apps/api/lib/" .gitignore # ✅ 具体的
```
5. キャッシュを確認:
```bash
# まずプレビューURLでテスト
# プレビューが動いて本番が動かない場合 = キャッシュの問題
```
6. ビルド時バリデーションを追加:
```typescript
// 二度と起きないように防止
if (mode === 'production' && url.startsWith('http://')) {
throw new Error('HTTPS required in production!');
}
```
あればよかった移行チェックリスト
デプロイメントプラットフォームを切り替える時:
- [ ] 旧プラットフォームからすべての環境変数をリスト化
- [ ] 新プラットフォームで環境変数を最初にセットアップ
- [ ] すべての.envファイルを.ignoreに追加(.env.productionを含む)
- [ ] .gitignoreが重要なファイルを無視していないか確認(git ls-filesでチェック)
- [ ] カスタムドメインの前にプレビューURLでテスト
- [ ] デプロイされたバンドルにHTTP URLがないか確認
- [ ] バックエンドが別の場合はCORS設定を確認
- [ ] プラットフォーム固有の癖を文書化
まだコーディング中、まだ学習中、まだ(時々)壊しています
1行の修正に6時間のデバッグ。それがソフトウェアエンジニアリングの本質です。
リリースするコードは確かに重要です。でも身につけたデバッグスキルは?それがより良いエンジニアにしてくれるのです。
次にデプロイメントプラットフォームがダウンした時(必ずあります)、準備はできています。持っているものは:
- ✅ プラットフォームに依存しないHTTPS強制
- ✅ ビルド時バリデーション
- ✅ 環境変数の優先順位の理解向上
- ✅ バックアップデプロイメント戦略
そして願わくば、このブログ記事が他の誰かの6時間のうちの数時間を節約できれば。 :)
恥ずかしいほど長い時間がかかった「1行の修正」はありますか?デバッグ探偵ストーリーをぜひ聞かせてください — 同病相憐れむというやつです!
よろしくお願いします、Chandler





