RORK LABEN
DEADLINE — Google Play は2026年8月31日以降、新規アプリと既存アプリの更新の双方に対象 API レベル36(Android 16)以上を要求します。残り13日ですEXTENSION — 期限に間に合わない場合は Play Console の期限延長フォームから申請することで11月1日まで配信を継続できます。延長は自動ではなく、申請自体を期限内に行う必要がありますTARGET SDK — Rork などのビルダーが出力する Expo / React Native アプリでも targetSdkVersion は自分で確認が必要です。テンプレートが古い SDK に固定されていると生成しただけでは要件を満たしませんPOLICY — スパムと最低限の機能に関するポリシーが改定され、高品質な機能とコンテンツ体験が求められます。薄いアプリを量産する運用は正面から影響を受ける領域ですPRIVACY — 収集するデータの項目・用途・第三者への共有の有無について詳細な説明が求められます。ビルダーが自動で組み込む解析 SDK や広告 ID も申告の対象ですRORK — 従来の Rork が React Native / Expo を出力するのに対し、Rork Max は SwiftUI を生成します。無料枠から始められ、有料プランは月25ドルからですDEADLINE — Google Play は2026年8月31日以降、新規アプリと既存アプリの更新の双方に対象 API レベル36(Android 16)以上を要求します。残り13日ですEXTENSION — 期限に間に合わない場合は Play Console の期限延長フォームから申請することで11月1日まで配信を継続できます。延長は自動ではなく、申請自体を期限内に行う必要がありますTARGET SDK — Rork などのビルダーが出力する Expo / React Native アプリでも targetSdkVersion は自分で確認が必要です。テンプレートが古い SDK に固定されていると生成しただけでは要件を満たしませんPOLICY — スパムと最低限の機能に関するポリシーが改定され、高品質な機能とコンテンツ体験が求められます。薄いアプリを量産する運用は正面から影響を受ける領域ですPRIVACY — 収集するデータの項目・用途・第三者への共有の有無について詳細な説明が求められます。ビルダーが自動で組み込む解析 SDK や広告 ID も申告の対象ですRORK — 従来の Rork が React Native / Expo を出力するのに対し、Rork Max は SwiftUI を生成します。無料枠から始められ、有料プランは月25ドルからです
記事一覧/開発ツール
開発ツール/2026-04-23上級

Rork で作ったアプリを「電波が悪い日」でも失望させない設計 — ネットワーク不安定時のUX設計ノート

Rork で作った個人アプリをストアに出したあと、私が一番たくさんレビューを落とされる原因は、「なにもない画面のまま固まる」ことでした。電波が弱い日でも、体験が静かに崩れないようにするためのUX設計・エラーステート・再試行戦略を、実装パターン込みで整理しました。

Rork535UX設計7ネットワーク3エラーハンドリング4個人開発197

Rork で個人アプリを出したあと、アクセス解析だけ眺めていると気付かないけれど、レビューを丁寧に読むと分かることがあります。「画面が真っ白のまま動かない」「ボタンを押しても何も起きない」という声が、思ったより多いのです。

こちらとしては「通信エラーが出たら再試行してください」と言いたくなるのですが、ユーザーから見ると、押したのに何も反応しない時点でアプリは壊れています。私は壁紙系と癒し系のアプリを長く運用するなかで、このギャップを埋めるためだけに UI を何度か作り直してきました。この記事は、その試行錯誤のなかで手元に残った設計指針のまとめです。

「なにもない画面」が星を削る

体感で一番多いクレームは、通信が遅いタイミングで画面が何も出ない状態です。スピナーすら出ない場合もあり、ユーザーは「フリーズした」「バグってる」と判断します。Rork で SDK 周りを使っていると、失敗の通知が非同期で遅れて届くため、ローディングとエラーの境目が曖昧になりがちです。

まず徹底すべきは、初回描画のタイミングで必ず何かを出すことです。スケルトン UI でも、プレースホルダのイラストでも構いません。「読み込み中」を視覚的に宣言するだけで、体感の壊れ方が大きく変わります。

// Rork + React Native 想定
export function FeedScreen() {
  const { data, isLoading, error, refetch } = useFeed();
 
  if (isLoading && !data) return <FeedSkeleton />;
  if (error && !data) return <FeedErrorEmpty onRetry={refetch} />;
  if (!data || data.length === 0) return <FeedEmpty onRetry={refetch} />;
 
  return <FeedList data={data} onRetry={refetch} />;
}

ポイントは、isLoading && !dataerror && !datadata?.length === 0 の 3 つを別の画面として描くことです。「ロード中か?」だけで出し分けると、再読み込み時にちらついて空配列が一瞬見えたりして、それだけで体験が荒れます。

私がやっている細かい工夫としては、isLoading がずっと true のまま 8 秒を超えたら、ローディング画面内にそっと「通信が遅いようです。接続状況をご確認ください」と一文出すことです。進まない時間を、アプリの無言ではなくメッセージで埋めるだけで、レビューに書かれる「フリーズした」がだいぶ減ります。

ボタンの「反応ゼロ」をなくす

次に削れるのが、押したのに何も起きないボタンです。送信ボタンを押した瞬間から、サーバー応答が返ってくるまでの間に、UI が何も変化しないパターンがこれに当たります。

最低限、押した直後にボタンの見た目を変えることと、disabled を即時に入れることは徹底します。二重タップ防止の意味でも必須です。

export function SubscribeButton({ onSubscribe }: Props) {
  const [state, setState] = useState<"idle" | "loading" | "success" | "error">("idle");
 
  const handlePress = async () => {
    if (state === "loading") return;
    setState("loading");
    try {
      await onSubscribe();
      setState("success");
    } catch (e) {
      setState("error");
    }
  };
 
  return (
    <Button
      disabled={state === "loading"}
      onPress={handlePress}
      variant={state === "error" ? "danger" : "primary"}
    >
      {state === "loading" ? "送信中…" : state === "success" ? "送信しました" : state === "error" ? "送信できませんでした。もう一度お試しください" : "登録する"}
    </Button>
  );
}

ポイントは idle / loading / success / error の 4 状態を明示的に持つことです。この状態機械を最初に書くだけで、「押したのに何も起きない」はほぼ消えます。

もう 1 点、error に入ったときに、すぐ idle に戻さずボタン自体で「失敗したよ」と主張させるのが実務的には効きます。トースト通知を出すだけだと、ユーザーがスクロールしていると気付かれません。失敗の情報は、ユーザーの視線の位置に出すのが基本です。

不安定な通信を「飲み込む」再送キュー

UI が整ったあとに必要になるのが、失敗したリクエストを静かに飲み込む仕組みです。決済のような致命的な操作は別として、いいね・コメント・設定の保存・簡単なログ送信など、失敗しても致命的でないが、落ちるとユーザー体験を荒らす操作は、再送キューで吸収します。

最小構成はシンプルです。キューはメモリと AsyncStorage の二段構えにしておきます。

type QueuedOp = {
  id: string;
  type: "like" | "settings_save" | "log";
  payload: unknown;
  createdAt: number;
  attempts: number;
};
 
async function enqueue(op: QueuedOp) {
  const queue = await readQueue();
  queue.push(op);
  await writeQueue(queue);
  tryFlush();
}
 
async function tryFlush() {
  const queue = await readQueue();
  if (queue.length === 0) return;
 
  const next = queue[0];
  try {
    await sendOp(next);
    queue.shift();
    await writeQueue(queue);
    tryFlush();
  } catch {
    const updated = [{ ...next, attempts: next.attempts + 1 }, ...queue.slice(1)];
    await writeQueue(updated);
    // 次のネットワーク回復時 or 指数バックオフで再試行
  }
}

この実装で大切なのは、enqueue した時点で UI にはすぐ成功として反映することです。見た目上のレスポンスが即時になるので、通信の遅さが体感から消えます。もちろん、最終的に送信に失敗する可能性はあるので、キューが長時間解消しない場合は、そっとバナーで「同期が滞っています」と表示するなどのフォールバックを用意します。

もう 1 つの注意点は、操作の冪等性です。同じ id のリクエストを 2 回送っても問題ないようサーバー側を設計しておくと、再送キューが安心して動けます。私は個人アプリでも、POST のボディに client_op_id を必ず含める規約にしています。サーバー側は直近一定期間の client_op_id を見て、重複は静かに捨てます。

「低速通信中」を文言で伝える

UX のチューニングで、意外なほど効くのが文言です。「読み込めませんでした」だけ表示するアプリと、「通信が不安定なようです。電波のよい場所で再度お試しください」と表示するアプリでは、ストアレビューの荒れ方が明確に違います。

私が使い回している表現を、いくつか共有しておきます。

  • 低速で読み込み中: 「通信が少し遅いようです。もうしばらくお待ちください」
  • 読み込み失敗(自動再試行中): 「うまく取得できませんでした。自動で再試行しています…」
  • 読み込み失敗(手動再試行): 「うまく取得できませんでした。電波の状況を確認のうえ、もう一度お試しいただけますか?」
  • オフライン時: 「インターネットに接続されていないようです。接続が戻ったら自動で再取得します」

「再試行」という技術用語を避け、ユーザーに何をしてほしいかを一文でお願いする形にしているのがコツです。「押してください」ではなく「お試しいただけますか?」のような柔らかい問いかけのほうが、レビュー欄での印象はかなり違います。

英語版でも同じ方針で、「Please try again in a moment. Your last action will sync automatically once the connection returns.」のように、ユーザーが何もしなくても復帰することを明記します。操作の責任をユーザーだけに押し付けない書き方です。

オフライン時に壊してはいけない機能

最後に、オフライン時でも最低限維持したい機能を洗い出す話をしておきます。個人アプリでも、全機能をオフライン対応する必要はありません。ただし、オフラインでも動くべき機能が動かないと、ユーザーは「このアプリは壊れやすい」と判断します。

私が自分の壁紙アプリで決めているルールはこの 3 点です。

  • すでにダウンロード済みの壁紙は、オフラインでもいつも通り表示・設定できる
  • 設定画面(テーマ切り替え、言語設定など)はオフラインでも動く
  • お気に入り登録は、オフラインでも UI 上はできて、あとから同期される

逆に、ランキング表示や新着更新など、ネットワークが前提の画面は、オフライン時にそれとわかる空状態を出すだけに留めます。ここを分けて設計するだけで、電波のない電車の中で触られても「壊れている感」が減ります。

ネットワークの悪い日を、アプリ品質のテスト日にする

Rork で作ったアプリをもう一段上のクオリティに持っていきたいなら、わざと電波の悪い状態で自分のアプリを触る日を作るのが近道です。iOS の「通信状況をシミュレート」や Android Studio の Network Throttling を使って、3G 相当や Offline で 15 分ほど操作してみると、普段気付かない穴がたくさん見つかります。

私はリリース前に、この「低速通信チェック」を必ず通しています。ダミーのサーバー遅延を挟んで、全画面を順に触り、ローディング・空状態・エラー・再試行がすべて気持ちよく動くかを確認します。ここでクレームの 7 〜 8 割は潰せている実感があります。

個人で運用していると、機能追加の優先度が上がりがちで、こうした地味な UX 改修は後回しにしがちです。それでも、このカテゴリの改善は星 4.0 と 4.5 の間を分けることが多く、長期的に見れば広告収益や課金率にも効いてきます。電波の悪い日を敵ではなく、自分のアプリの弱点を教えてくれるコーチとして扱ってみてください。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-08-14
ライセンス表示に載せる依存は、lockfile の dev/prod では決まりません
AI ビルダーが書き出したプロジェクトの依存を実際に分類し、ライセンス表示に載せる範囲をどう決めたかの記録です。copyleft の検出結果と配布物の境界の食い違いを、本番プロジェクトの実データで確かめました。
開発ツール2026-07-10
クラッシュとして記録されない強制終了 — Rork アプリの JetsamEvent を読む
Crashlytics には何も出ていないのにレビューで「勝手に閉じる」と書かれる。その多くはメモリ超過による OS からの強制終了です。JetsamEvent レポートの読み方と、画像中心アプリのメモリ上限を実測から設計する手順をまとめます。
開発ツール2026-07-04
「続きを読む」を出すべきか、テキストの実測で決める — Rork(Expo)の折りたたみ表示設計
商品説明やレビュー本文を3行で畳み「続きを読む」を出す実装で、短い文にまでトグルが付いてしまう問題を解決します。onTextLayout で実際の行数を測り、はみ出す時だけトグルを出す設計を、iOS と Android の差、展開アニメーション、文字サイズ拡大への配慮まで動くコードでまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →