Dev Highlights

Доки по разработке

This project is maintained by teniryte

12. Персистентность, офлайн и SSR

TanStack Query может переживать перезагрузку страницы, работать без сети и подготавливать данные на сервере.

Персистентность кеша

import { QueryClient } from '@tanstack/react-query';
import { persistQueryClient } from '@tanstack/react-query-persist-client';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';

const queryClient = new QueryClient();

persistQueryClient({
  queryClient,
  persister: createSyncStoragePersister({
    storage: window.localStorage,
    key: 'app-cache-v5',
    throttleTime: 1000,
  }),
  maxAge: 24 * 60 * 60 * 1000, // 24h
  hydrateOptions: {
    defaultOptions: {
      queries: {
        staleTime: 5 * 60 * 1000,
      },
    },
  },
  dehydrateOptions: {
    shouldDehydrateQuery: ({ state }) => state.status === 'success',
  },
});

Тонкая фильтрация при сериализации

const shouldKeep = (query) => {
  const isLargeList = query.queryHash.startsWith('products');
  return !isLargeList; // example: не пишем тяжёлые списки
};

persistQueryClient({
  queryClient,
  persister,
  dehydrateOptions: {
    shouldDehydrateQuery: shouldKeep,
  },
});

IndexedDB и шифрование

Async storage (React Native / Expo)

import AsyncStorage from '@react-native-async-storage/async-storage';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';

const persister = createAsyncStoragePersister({
  storage: AsyncStorage,
  retry: async (err) => {
    await wait(1000);
    return err?.message !== 'QuotaExceededError';
  },
});

Управление офлайн-состоянием

import { onlineManager, focusManager } from '@tanstack/react-query';

onlineManager.setEventListener(setOnline => {
  window.addEventListener('online', () => setOnline(true));
  window.addEventListener('offline', () => setOnline(false));
});

focusManager.setEventListener(handleFocus => {
  window.addEventListener('visibilitychange', () => handleFocus(!document.hidden));
});

Очередь мутаций офлайн

import { persistQueryClientRestore } from '@tanstack/react-query-persist-client';

await persistQueryClientRestore({
  queryClient,
  persister,
  maxAge: Infinity,
});

// useMutation({ networkMode: 'offlineFirst' });

networkMode: 'offlineFirst' заставляет мутацию помещаться в очередь и выполняться после возвращения сети.

const mutationCache = new MutationCache({
  onSuccess: (_data, variables, _ctx, mutation) => {
    if (mutation.meta?.flushOnReconnect) {
      queryClient.invalidateQueries(['invoices']);
    }
  },
});

const queryClient = new QueryClient({ mutationCache });

useMutation(updateInvoice, {
  networkMode: 'offlineFirst',
  meta: { flushOnReconnect: true },
});

Триггеры повторного рендера после восстановления кеша

SSR / SSG

  1. Создайте QueryClient на сервере.
  2. Выполните prefetchQuery/prefetchInfiniteQuery для нужных данных.
  3. Вызовите dehydrate(queryClient).
  4. Передайте состояние клиенту и оберните приложение в Hydrate.
export async function render(url: string) {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery(appBootstrapQuery());
  const html = renderToString(
    <QueryClientProvider client={queryClient}>
      <Hydrate state={dehydrate(queryClient)}>
        <App url={url} />
      </Hydrate>
    </QueryClientProvider>,
  );
  return html;
}

Next.js (App Router)

Remix

Безопасность и размер кеша

Диагностика офлайн-режима

Service Worker и персистентность

Частые паттерны