症状 ―― CORS 設定を直しても消えない「CORS error」
- フロント(Next.js)から
POST /api/entry
を呼ぶと DevTools に “CORS error” と表示される。 - ネットワークタブには Status 200 / Size 0 B / Content-Type
text/html
が並ぶ(JSON を返すはずが HTML になっている)。 config/cors.php
を編集したり、nginx でadd_header
を加えても改善しない。
> ここで「本当に CORS が原因なのか?」を疑うのがポイントです。
調査手順と気付き
1. curl でエンドポイントを叩く
まず curl -i -X POST http://localhost/api/entry
を実行してみました。すると、ブラウザでは 200 ステータスが返ってきていたにもかかわらず、curl では 500 Internal Server Error が返ってきました。これにより、問題は CORS ではなくサーバーエラーであることが判明しました。
2. ブラウザのネットワークタブを再確認
ブラウザのネットワークタブを見ると、レスポンスのコンテンツタイプが text/html
になっていることに気付きました。本来 API レスポンスであれば application/json
のはずです。これは 何かが先に HTML を出力している可能性 を強く示唆していました。
3. Postman で同じリクエストを送信
Postman で同じリクエストを送信したところ、レスポンスボディには <pre class="xdebug-var-dump"...>
で始まる HTML(dump の内容)がそのまま返ってきました。これは dump の出力がそのまま HTTP レスポンスになっていることを示しています。
4. storage/logs/laravel.log
を tail
storage/logs/laravel.log
を tail したところ、外部 API 呼び出し前後でログが途切れていることがわかりました。これは 途中で例外または予期せぬ出力が挟まっている可能性 を示しています。
5. Xdebug でブレークポイントを設定
サービス層のメソッド中で dump($xml)
が実行されている箇所を発見しました。これは dump が HTML を即時出力しヘッダーを確定させていることを示しています。
原因 ―― dump/dd/echo がヘッダーを先に送信していた
dump()
はデバッグ用関数で、呼ばれた瞬間にブラウザへ HTML を送り出します。レスポンスヘッダーが Content-Type: text/html
で確定してしまうため、その後に付与されるはずだった Access-Control-Allow-Origin
などの CORS ヘッダーが入らなくなります。結果、ブラウザからは 「CORS エラー」としか見えません。
修正方法 ―― サービス層の dump
を Log::debug
に差し替える
// app/Services/HogeSyncService.php
class HogeSyncService
{
public function sync(array $payload): array
{
$xml = $this->buildXml($payload);
// ✗ NG: dump するとヘッダーが確定してしまう
// dump($xml);
// 〇 OK: Log に残せばブラウザへは何も送られない
Log::debug('Hoge XML payload', ['xml' => $xml]);
Http::post(config('hoge.endpoint'), $xml);
return ['status' => 'ok'];
}
}
改善ポイントとベストプラクティス
本番環境のコードには dump
、dd
、echo
などのデバッグ用の出力を残さないようにしましょう。これらの関数はヘッダーが確定する前に呼び出されると、意図しないレスポンスヘッダーが送信されてしまう可能性があります。代わりに、ログ出力を使用するか、デバッグが完了したら完全に削除することをお勧めします。
まとめ
1. ブラウザが出す “CORS error” は 必ずしも CORS 設定の誤りを示すわけではない。
2. Laravel/PHP では ヘッダー確定前に dump/echo を呼ぶ と CORS ヘッダーが付けられず、同じエラー表示になる。
3. デバッグ出力は Log クラスへ切り替え(もしくは削除)、レスポンスは常に JSON で返すようにすれば解決。
> まずは curl と Postman とログを確認する。それだけで「CORS か、それ以外か」を簡単に切り分けられます。