RORK LABEN
SDK58 — Expo SDK 58 のベータが始まりました。React Native 0.88 の RC を同梱し、ベータ期間は3〜4週間と公式に書かれています11/01 — Google Play の対象 API レベル、延長を申請した場合の配信期限は11月1日です。残り44日ですEASENV — ローカルビルドに渡したはずの秘密が、中身ではなく変数名の文字列のまま届く、という報告が長く開いたままですNEW — 推奨された移行先が、すでに停止していました。廃止表を74行突き合わせた記録ですUISCENE — iOS 27 では新しい画面ライフサイクルが必須です。SDK 57 では自分で有効にする設定で、既定になるのは 58 からですCREDIT — 「AI のエラーには消費しない」がどこまでを指すのかは、同じ修正を何度か頼んだ日の記録を取ると見えてきますSDK58 — Expo SDK 58 のベータが始まりました。React Native 0.88 の RC を同梱し、ベータ期間は3〜4週間と公式に書かれています11/01 — Google Play の対象 API レベル、延長を申請した場合の配信期限は11月1日です。残り44日ですEASENV — ローカルビルドに渡したはずの秘密が、中身ではなく変数名の文字列のまま届く、という報告が長く開いたままですNEW — 推奨された移行先が、すでに停止していました。廃止表を74行突き合わせた記録ですUISCENE — iOS 27 では新しい画面ライフサイクルが必須です。SDK 57 では自分で有効にする設定で、既定になるのは 58 からですCREDIT — 「AI のエラーには消費しない」がどこまでを指すのかは、同じ修正を何度か頼んだ日の記録を取ると見えてきます
記事一覧/開発ツール
開発ツール/2026-06-14上級

Rork アプリの API 通信を障害に強くする — Circuit Breaker・指数バックオフ・タイムアウトを実装で固める

Rork で生成したアプリの API 通信を本番品質に引き上げる実装メモ。タイムアウト・指数バックオフ・Circuit Breaker を段階的に組み込み、依存 API が不安定でも自動回復する通信層を設計します。

Rork569API通信Circuit Breaker指数バックオフタイムアウト2React Native238本番運用10

プレミアム記事

リリース直後のアプリで、API のエラー率が一晩で 5% を超えたことがあります。

依存していた外部 API が応答を遅延させ始め、タイムアウトを持たない fetch がそのまま詰まっていく。画面にはスピナーが回り続け、ユーザーには何が起きているのか伝わらない。ログを追って原因にたどり着いた頃には、すでに数百件のエラーレポートが積み上がっていました。あのとき通信層に最低限の防御があれば、被害は十分の一で済んだはずです。

個人開発でアプリを回していると、こうした通信の防御は後回しになりがちです。Rork でアプリを組むと、生成される API 呼び出しは素直な fetch です。開発中はそれで困りません。問題は、本番のネットワークが開発環境とはまるで別物だという点にあります。地下鉄で電波が切れ、Wi-Fi と 5G を行き来し、依存サービスが数分だけ不調になる——こうした揺らぎを前提にした通信層を、後付けではなく設計として持っておきたいところです。

ここでは Rork アプリの通信を本番品質へ引き上げる過程を、実際に動くコードとともに実装メモとして残します。派手な仕組みではありません。タイムアウト、指数バックオフ付きのリトライ、Circuit Breaker。この三つを正しい順序で重ねるだけで、依存 API が傾いてもアプリが巻き添えにならない通信層になります。

素の fetch が本番で崩れる三つの瞬間

最初に、何から守るのかをはっきりさせておきます。Rork が吐く典型的な呼び出しはこの形です。

const fetchUser = async (userId: string) => {
  const res = await fetch(`https://api.example.com/users/${userId}`);
  if (!res.ok) throw new Error("Failed to fetch user");
  return res.json();
};

このコードが本番で崩れる瞬間は、経験上ほぼ三つに集約されます。

一つ目は一時的な切断です。モバイル回線は一瞬で復活することが多く、本来なら一度リトライすれば通る通信を、そのままエラーとしてユーザーに見せてしまいます。二つ目は無限待機です。fetch にはタイムアウトがないため、応答しないサーバーを待ち続け、スピナーが何十秒も回ります。三つ目が連鎖障害です。遅い API を待つリクエストが積み上がり、すでに壊れている相手へ送り続けることで、自分側の状態まで悪化させます。

この三つに、それぞれタイムアウト・リトライ・Circuit Breaker が対応します。導入する順序もこの通りが現実的です。効果が出るのが早く、実装コストが低いものから入れていきます。

まずタイムアウト — 今日入れて一番効く一手

無限待機を断つだけで、ユーザー体感のエラーの大半は消えます。2026 年時点では AbortSignal.timeout() が React Native でも使えるようになり、AbortController を手で組む必要が薄れました。ただし「どのエラーがタイムアウト由来か」を呼び出し側で判別できるよう、独自のエラー型に正規化しておきます。

export class ApiError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number,
    public readonly body?: unknown,
  ) {
    super(message);
    this.name = "ApiError";
  }
}
 
interface TimedFetchOptions extends RequestInit {
  timeoutMs: number;
}
 
export async function fetchWithTimeout(
  url: string,
  { timeoutMs, signal, ...init }: TimedFetchOptions,
): Promise<Response> {
  // 呼び出し側の signal(アンマウント等)と timeout を合成する
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
  const merged = signal
    ? AbortSignal.any([signal, timeoutSignal])
    : timeoutSignal;
 
  try {
    const res = await fetch(url, { ...init, signal: merged });
    if (!res.ok) {
      const body = await res.json().catch(() => null);
      throw new ApiError(`HTTP ${res.status} ${res.statusText}`, res.status, body);
    }
    return res;
  } catch (err) {
    // timeout 由来の中断を 408 に正規化し、呼び出し側で扱いやすくする
    if (err instanceof DOMException && err.name === "TimeoutError") {
      throw new ApiError(`Request timed out after ${timeoutMs}ms`, 408);
    }
    throw err;
  }
}

タイムアウト値は一律にしないほうが扱いやすいです。私は用途で三段階に分けています。一覧取得のような軽い GET は 5〜8 秒、ユーザー操作に紐づく詳細取得は 8〜12 秒、アップロードや重い処理は 30〜60 秒。短すぎると低速回線のユーザーに不要なエラーを見せ、長すぎると無限待機の問題が戻ってきます。AbortSignal.any() で画面側の中断シグナルと合成しておくと、コンポーネントがアンマウントされた瞬間に通信も確実に止まります。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
タイムアウト→リトライ→Circuit Breaker を段階導入する実装順序と、それぞれの具体的なしきい値の決め方
AbortSignal.timeout() と指数バックオフ+ジッターを組み合わせた、コピーして動く通信クライアントの全コード
Circuit Breaker の状態をスナップショットで観測し、TanStack Query と二重リトライさせない統合パターン
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-09-04
EAS の secret は「アプリに入れない」設定ではありません — 接頭辞と可視性を別々に決める
EXPO_PUBLIC_ の接頭辞は成果物に入るかを決め、EAS の可視性は誰が読めるかを決めます。二つを重ねたときに OTA 更新でだけ値が消える筋道と、出荷前に自分の手で確かめる手順をまとめました。
開発ツール2026-08-22
一括置換は全ファイル成功しました。壊れたのは、消さなかった行のほうです
生成コードに一括置換をかけると、一致した行ではなく隣の行が壊れます。運用中のプロジェクトで実際に起きた破損と、置換の不変条件を検査する20行のガードスクリプトをまとめました。
開発ツール2026-08-14
Expo SDK 57 に上げる前に、prebuild で消えるネイティブ変更を洗い出す
Expo SDK 57 では expo prebuild が既定で ios と android を消してから再生成します。手編集を上げる前に洗い出す方法、config plugin への移し替え、57.0.9 で解消したメモリ回帰までを実務の順序でまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます