JavaScript TypeError: Failed to fetch の原因と解決方法【CORS/ネットワークエラーの徹底解説】

TypeError: Failed to fetch とは

Webアプリケーション開発でAPIと連携する際、ブラウザのコンソールに「TypeError: Failed to fetch」というエラーが表示されて困った経験はありませんか?このエラーはネットワーク通信に問題があることを示しており、特にCORS(Cross-Origin Resource Sharing)関連が原因で発生することが非常に多いです。本記事では、この厄介なエラーの原因を深掘りし、具体的な解決策をステップバイステップで解説します。

デプロイ太郎
デプロイ太郎

このエラー、Web開発では本当によく見かけますよね!特にCORS絡みは、フロントエンドとバックエンドの連携で必ずと言っていいほどぶつかる壁です。

TypeError: Failed to fetch は、fetch APIを使ったHTTPリクエストがネットワークレベルで失敗した場合に発生する汎用的なエラーです。多くの場合、CORS設定の不備か、サーバーが起動していない/到達できないことが原因です。

実行環境ごとのエラーメッセージ

環境エラーメッセージ
Chrome ConsoleGET http://localhost:8080/api/data net::ERR_CONNECTION_REFUSED Uncaught (in promise) TypeError: Failed to fetch
Firefox ConsoleCross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/api/data. (Reason: CORS header 'Access-Control-Allow-Origin' missing or null). TypeError: NetworkError when attempting to fetch a resource.
Safari ConsoleFetch API cannot load http://localhost:8080/api/data due to access control checks. TypeError: Load failed
Node.js (using node-fetch or native fetch in v18+)FetchError: request to http://localhost:8081/api/data failed, reason: connect ECONNREFUSED 127.0.0.1:8081

エラーの発生パターン

このエラーは主に以下のようなケースで発生します。

パターン1: CORSポリシー違反によるブロック

/** bad_code.js */
// クライアント側(例: localhost:3000)
fetch('http://localhost:8080/api/data') // 異なるオリジンへのリクエスト
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

// サーバー側(例: Node.js + Express)
// app.js (CORSミドルウェアが適切に設定されていない、または存在しない)
// app.use((req, res, next) => {
//   res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // 不足しているか、間違っている
//   res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
//   res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
//   next();
// });
// app.get('/api/data', (req, res) => res.json({ message: 'Hello from API' }));

ブラウザは、セキュリティ上の理由から、異なるオリジン(プロトコル、ドメイン、ポートのいずれかが異なる)へのHTTPリクエストを制限しています。これがCORSポリシーです。サーバーがリクエスト元のオリジンを許可する適切な Access-Control-Allow-Origin ヘッダーを返さない場合、ブラウザはこのエラーを発生させて通信をブロックします。

/** good_code.js */
// クライアント側は変更なし
fetch('http://localhost:8080/api/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

// サーバー側(例: Node.js + Express)
const express = require('express');
const cors = require('cors'); // corsミドルウェアを導入
const app = express();

// 適切なCORS設定を適用
app.use(cors({ origin: 'http://localhost:3000' })); // クライアントのオリジンを許可

app.get('/api/data', (req, res) => res.json({ message: 'Hello from API' }));

app.listen(8080, () => console.log('API server running on port 8080'));

パターン2: サーバーが起動していない、またはURLが間違っている

/** bad_code.js */
// 誤ったポート番号、またはサーバーが起動していない
fetch('http://localhost:8081/api/items') // サーバーが8080で動いているのに8081を指定
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

リクエスト先のサーバーがダウンしている、指定したURL(ドメイン、IPアドレス、ポート番号、パス)が間違っている、またはネットワークが切断されている場合にもこのエラーが発生します。ブラウザはサーバーに接続自体ができないため、レスポンスを受け取れず Failed to fetch となります。

/** good_code.js */
// 正しいポート番号とパスを指定し、サーバーが起動していることを確認
fetch('http://localhost:8080/api/items') // サーバーが8080で動いていることを確認
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

// サーバー側が正しく起動していることを確認
// Node.js Express server running on port 8080

パターン3: ネットワーク接続の問題(クライアント側)

/** bad_code.js */
// Wi-Fi接続が切れている、またはプロキシ設定が誤っている状態で実行
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

クライアント側のネットワーク接続(Wi-Fi、有線LANなど)が切断されている、またはファイアウォールやプロキシ設定が通信をブロックしている場合にもこのエラーが発生します。この場合、ブラウザはリモートサーバーに到達するための基本的な通信経路を確立できません。

/** good_code.js */
// ネットワーク接続を回復し、プロキシやファイアウォール設定を確認してから再実行
// 例: Wi-Fiに接続し直す、VPNを適切に設定する、企業ネットワークのポリシーを確認する
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));
デプロイ太郎
デプロイ太郎

CORSエラーはブラウザのセキュリティ機能によるものなので、サーバー側の設定が非常に重要です。プリフライトリクエストの挙動も理解しておくとデバッグがスムーズになりますよ。

このエラーはネットワークレベルで発生するため、サーバー側でHTTPステータスコードが返される前にブラウザが通信をブロックします。そのため、開発者ツールでネットワークタブを見ても、ステータスコードが 200 OK ではなく (failed)CORS error と表示されることが多いです。サーバー側でエラーログを確認しても、リクエストが到達していないため何も記録されていない場合があります。

よくあるバリエーション

TypeError: Failed to fetch with CORS policy

このエラーメッセージは、ブラウザがCORSポリシーに違反するリクエストを検出した際に表示されます。サーバーが Access-Control-Allow-Origin ヘッダーを適切に設定していない、またはリクエスト元オリジンを許可していないことが直接的な原因です。

/** サーバー側(Node.js Express)の修正例 */
const express = require('express');
const cors = require('cors');
const app = express();

// 特定のオリジンを許可する場合
app.use(cors({ origin: 'http://localhost:3000' }));

// または、開発環境で全てのオリジンを許可する場合(本番では非推奨)
// app.use(cors()); 

app.get('/api/data', (req, res) => res.json({ message: 'Hello CORS' }));

TypeError: Failed to fetch with NetworkError

ネットワークエラーは、サーバーに到達できない、DNS解決に失敗した、クライアントのネットワーク接続が失われたなど、より低レベルのネットワーク通信の問題を示します。これはCORSとは異なり、サーバーからの応答を全く受け取れない状態です。

/** クライアント側(JavaScript) */
async function fetchData() {
  try {
    // サーバーがダウンしている、またはURLが誤っている場合
    const response = await fetch('http://non-existent-domain.com/api/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    // このcatchブロックで 'TypeError: Failed to fetch' が補足される
    console.error('Network error occurred:', error); 
    // エラーメッセージの確認と、ping/telnetコマンドでの疎通確認が重要
  }
}

TypeError: Failed to fetch when POST request

POSTリクエストで Failed to fetch が発生する場合、GETリクエストよりもCORSの問題が複雑になることがあります。特に、Content-Typeapplication/json のような非標準ヘッダーを含む場合、ブラウザはプリフライトリクエスト(OPTIONSメソッド)を送信します。このOPTIONSリクエストに対するサーバーのCORS設定が不適切だと、本番のPOSTリクエストが実行される前にブロックされます。

/** クライアント側(JavaScript)のBad Code */
fetch('http://localhost:8080/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json' // プリフライトリクエストを発生させる
  },
  body: JSON.stringify({ name: 'John Doe' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Fetch error on POST:', error));

/** サーバー側(Node.js Express)のGood Code */
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({ 
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // OPTIONSメソッドも許可
  allowedHeaders: ['Content-Type', 'Authorization'] // 許可するヘッダーを指定
}));
app.use(express.json()); // JSONボディを解析

app.post('/api/users', (req, res) => {
  console.log('Received POST request:', req.body);
  res.status(201).json({ id: 1, ...req.body });
});

フレームワーク別の発生パターン

React (CRA/Vite)での発生パターン

useEffect フック内で外部APIからデータをフェッチしようとした際に、React開発サーバー(例: localhost:3000)とAPIサーバー(例: localhost:8080)のオリジンが異なるためCORSエラーが発生するケースが頻繁にあります。特に、開発環境でプロキシ設定を忘れるとこのエラーに直面します。

/** React Component */
import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Bad: CORSエラーを避けるためのプロキシ設定がない
    // fetch('http://localhost:8080/api/users') 
    // Good: package.jsonにproxy設定を追加するか、APIサーバー側でCORSを許可
    fetch('/api/users') // proxy設定があれば同じオリジンとして扱われる
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => setData(data))
      .catch(error => {
        console.error('Fetch error in React:', error);
        setError(error);
      });
  }, []);

  if (error) return <div>Error: {error.message}</div>;
  if (!data) return <div>Loading...</div>;

  return <div>Data: {JSON.stringify(data)}</div>;
}
export default MyComponent;

// package.json (CRAの場合)
// {
//   "name": "my-react-app",
//   "version": "0.1.0",
//   "proxy": "http://localhost:8080", // この行を追加
//   "private": true,
//   "dependencies": {...}
// }

Next.js (SSR/API Routes)での発生パターン

getServerSidePropsgetStaticProps 内で、外部APIを呼び出す際にこのエラーが発生することがあります。Next.jsのサーバーサイドで動作するためブラウザのCORS制限は受けませんが、APIサーバーがダウンしている、またはURLが誤っている場合に Failed to fetch と同様のネットワークエラー(ただし、Node.jsの内部エラーとして)が発生します。また、Next.jsのAPI Routesから外部APIを呼び出す際にも同様です。

/** pages/index.js (getServerSideProps) */
import React from 'react';

export async function getServerSideProps() {
  try {
    // Bad: 誤ったURLや未起動のAPIへのリクエスト
    // const res = await fetch('http://localhost:8081/api/products'); 
    // Good: 正しいURLと、APIサーバーが起動していることを確認
    const res = await fetch('http://localhost:8080/api/products'); 

    if (!res.ok) {
      // fetch API自体はネットワークエラー時に例外を投げないが、
      // Node.js環境ではConnection Refusedなどのエラーが直接発生する場合がある
      throw new Error(`Failed to fetch products: ${res.status}`);
    }
    const products = await res.json();
    return { props: { products } };
  } catch (error) {
    console.error('Error fetching products in getServerSideProps:', error);
    return { props: { products: [], error: error.message } };
  }
}

function HomePage({ products, error }) {
  if (error) return <div>Error loading products: {error}</div>;
  return (
    <div>
      <h1>Products</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}
export default HomePage;

根本原因の特定方法

ブラウザの開発者ツール(F12キー)を最大限に活用してください。「ネットワーク」タブでエラーとなっているリクエストを探し、ステータスが (failed)CORS error となっていないか確認します。特にヘッダーやプリフライトリクエスト(OPTIONS)のレスポンスヘッダーに Access-Control-Allow-Origin が含まれているか、正しい値が設定されているかをチェックします。また、「コンソール」タブに表示される詳細なエラーメッセージもヒントになります。サーバー側のログも確認し、そもそもリクエストが到達しているか、もし到達していればCORS関連のエラーが出力されていないか確認しましょう。

/** ブラウザコンソールで試す */
// 失敗するリクエストの例 (CORS違反、またはサーバー未起動)
fetch('http://localhost:9999/api/test') // 存在しないポートやドメイン
  .then(response => {
    if (!response.ok) {
      // ネットワークエラーの場合、ここには到達しないことが多い
      console.error('HTTP Error:', response.status);
    }
    return response.json();
  })
  .catch(error => {
    console.error('Caught error in fetch:', error); // ここで 'TypeError: Failed to fetch' が補足される
  });

// サーバー側でCORSヘッダーを確認する(例: curlコマンド)
// curl -v -H "Origin: http://localhost:3000" http://localhost:8080/api/data

CORSのプリフライトリクエスト(OPTIONSメソッド)の理解

CORSエラーのデバッグで特に重要なのが、プリフライトリクエストの存在です。特定の条件(例: POST/PUT/DELETEメソッド、Content-Type: application/json ヘッダーの使用など)を満たすクロスオリジンリクエストの場合、ブラウザは本番のリクエストを送信する前に、まず OPTIONSメソッドで「このリクエストは許可されますか?」という事前確認を行います。サーバーがこのOPTIONSリクエストに対して適切なCORSヘッダーを返さないと、本番のリクエストは実行されることなく Failed to fetch エラーが発生します。開発者ツールのネットワークタブでOPTIONSリクエストのレスポンスヘッダーを確認することが解決の鍵となります。

防止策とベストプラクティス

このエラーを防ぐには、まずAPIサーバー側でCORS設定を適切に行うことが最も重要です。開発環境ではプロキシ設定(例: Reactの package.jsonproxy)を利用してCORSを回避し、本番環境ではサーバー側で許可するオリジンを明確に設定します。また、フロントエンドとバックエンドでAPIのベースURLを環境変数で管理し、環境ごとに切り替えることでURL間違いを防ぎます。クライアント側では、try-catchブロックで fetch のエラーを捕捉し、ユーザーフレンドリーなメッセージを表示するなどのエラーハンドリングを実装しましょう。

/** サーバー側(Node.js Express)のCORS設定 */
const express = require('express');
const cors = require('cors');
const app = express();

const allowedOrigins = process.env.NODE_ENV === 'production'
  ? ['https://your-production-domain.com']
  : ['http://localhost:3000', 'http://localhost:5173']; // 開発環境のオリジンも許可

app.use(cors({ origin: allowedOrigins }));

/** クライアント側(JavaScript)でのエラーハンドリング */
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080';

async function safeFetchData() {
  try {
    const response = await fetch(`${API_BASE_URL}/api/users`);
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`HTTP Error: ${response.status} - ${errorText}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('データ取得中にエラーが発生しました:', error.message);
    alert('データの読み込みに失敗しました。時間をおいて再度お試しください。'); // ユーザー向けメッセージ
  }
}
safeFetchData();
CORS設定は本番環境のセキュリティに直結するため、安易に * (全許可) を使わず、必要なオリジンのみを許可するようにしましょう。また、環境変数を使って開発環境と本番環境で異なるAPIエンドポイントやCORS設定を適用するのは、トラブルを避けるための必須テクニックです。
デプロイ太郎
デプロイ太郎

CORS対策は開発環境と本番環境で異なることが多いので、環境変数で切り替えられるようにしておくのがベストプラクティスです。これでデプロイ時のトラブルを減らせますね。

公式ドキュメントで詳細を確認:
デプロイ太郎
デプロイ太郎

TypeError: Failed to fetchは、一見すると難しそうですが、原因はネットワークかCORSのどちらかがほとんどです。落ち着いて一つずつ確認していきましょう!

よくある質問(FAQ)

Q
本番環境でだけ Failed to fetch が発生するのですが、なぜですか?
A

開発環境ではプロキシ設定やCORSを無効化していることが多く、本番環境では厳格なCORSポリシーが適用されるためです。本番環境のAPIサーバーが、フロントエンドのドメインを Access-Control-Allow-Origin ヘッダーで許可しているか確認してください。また、HTTPSへのリダイレクトやファイアウォールの設定も影響することがあります。

Q
React (Next.js/Vue.js) でこのエラーが発生した場合、どのようにデバッグすれば良いですか?
A

まずブラウザの開発者ツールでネットワークタブを確認し、CORSエラーかネットワーク接続エラーかを特定します。React (Next.js/Vue.js) の開発サーバーを使っている場合は、package.jsonproxy 設定が正しいか、またはAPIサーバー側で開発サーバーのオリジンが許可されているかを確認してください。サーバーサイドレンダリング (SSR) の場合は、サーバーのコンソールログも確認しましょう。

Q
LinterやIDEのツールで Failed to fetch を事前に防ぐ方法はありますか?
A

直接的な Failed to fetch エラーを防ぐLinterはありませんが、API URLのハードコーディングを避け、環境変数を使用することを強制するルールを設定できます。また、fetchcatch ブロックでのエラーハンドリングを強制するルールを導入することで、未捕捉のエラーを防ぐことができます。

Q
エラー発生時にユーザー向けに表示すべき適切なエラーハンドリングはありますか?
A

catch ブロックでエラーを捕捉し、「データの取得に失敗しました。しばらく経ってから再度お試しください。」といった一般的なメッセージを表示するのが良いでしょう。具体的なエラー内容をユーザーに表示するのはセキュリティ上のリスクがあるため避けるべきです。リトライボタンや、サポートへの問い合わせを促すメッセージも有効です。

Q
CORSエラーとネットワーク接続エラーの見分け方は?
A

ブラウザの開発者ツールのコンソールとネットワークタブで判断できます。CORSエラーの場合、コンソールに Cross-Origin Request BlockedCORS policy といったメッセージが表示され、ネットワークタブのリクエストには (failed) とともにCORS関連のエラー詳細が表示されます。ネットワーク接続エラーの場合は、net::ERR_CONNECTION_REFUSEDNetworkError といったメッセージが表示され、サーバーからの応答がない状態を示します。

Q
HTTPSとHTTPの混在が原因で Failed to fetch になることはありますか?
A

はい、あります。HTTPSページからHTTPリソースを要求すると、ブラウザはセキュリティ上の理由からそのリクエストをブロックします。これを「Mixed Content (混合コンテンツ)」と呼びます。開発環境では見過ごされがちですが、本番環境でHTTPSを導入した際に発生しやすい問題です。すべてのリソースをHTTPS経由で取得するように修正する必要があります。

免責事項: 当記事の情報は執筆時点の内容に基づいています。最新情報は各公式サイトをご確認ください。当サイトは情報提供を目的としており、資格取得・技術的対応の結果について一切の責任を負いません。

コメント

タイトルとURLをコピーしました