2 min read

React Query Offline-First 전략

staleTime, persistence, and sync patterns to implement cache-first UX in unstable network environments

React Query Offline-First 전략 thumbnail

Introduction

In an environment where the mobile network is unstable, data retrieval failure immediately leads to withdrawal. If you use React Query with only the default settings, the loading spinner will be exposed repeatedly, and the user will experience the status bouncing on the same screen. This article summarizes practical patterns that improve the quality of service experience by combining cache-first rendering, offline queuing, and online recovery point synchronization.

React Query Offline-First 전략 커버
Wikimedia Commons 기반 무료 이미지

Problem definition

Most offline-first strategies fail because they are missing product policies, not library settings. The following three common failure conditions are:

  • The same Query Key does not include user context, so caches are mixed when switching accounts.
  • The mutation retry policy is separated from the screen flow, so it looks like success to the user, but the reflection on the server fails.
  • Optimization by traffic pattern is impossible because staleTime and gcTime are applied simultaneously.

The key is to not treat network state the same as UX state. Policies for each function must be separated by allowing reading even when offline and queuing only writes.

Key concepts

perspectiveDesign criteriaVerification points
Inquiry PolicyCache priority + background revalidationWhether to block API calls when rendering the first screen
Writing Policyoffline queue + server idempotencyWhether or not duplicate retransmissions are reflected
Recovery PolicySequential flush during online eventsData crash rate immediately after recovery
observation indicatorscache hit rate + retry successNumber of perceived failures per session

Offline strategy is not just a front-end issue. If the backend does not support idempotency-key, the queuing strategy will create data corruption in the long run. Therefore, it must be designed together with the API contract.

Code example 1: Separate QueryClient default policy

import { QueryClient } from "@tanstack/react-query";

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000,
      gcTime: 10 * 60_000,
      retry: 1,
      refetchOnWindowFocus: false,
      networkMode: "offlineFirst",
    },
    mutations: {
      retry: 3,
      networkMode: "offlineFirst",
    },
  },
});

export const key = {
  feed: (userId: string) => ["feed", userId],
};

Code example 2: Offline queue flush

type PendingWrite = { id: string; endpoint: string; body: unknown; key: string };

const queue: PendingWrite[] = [];

export async function flushPendingWrites() {
  for (const item of queue) {
    const response = await fetch(item.endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Idempotency-Key": item.key,
      },
      body: JSON.stringify(item.body),
    });

    if (!response.ok) break;
  }
}

window.addEventListener("online", () => {
  void flushPendingWrites();
});

Architecture flow

Mermaid diagram rendering...

Tradeoffs

  • If you set the staleTime longer, API calls will decrease, but the responsibility for data freshness management will shift to the UI.
  • Adding an offline queue improves UX, but the server idempotency must be adjusted.
  • Increasing the number of retries increases the success rate, but if there is no diagnosis log for the cause of failure, response to failures becomes slower.

Cleanup

The core of React Query Offline-First is not option optimization but operational contracting. By separating lookup/write/recovery and specifying retry rules along with the API, you can maintain a stable user experience even in environments with low network quality.

Image source

  • Cover: source link
  • License: CC BY-SA 3.0 / Author: Dave Gandy
  • Note: After downloading the free license image from Wikimedia Commons, it was optimized to JPG at 1600px.

Comments