Настройка SWR для кэширования данных в React-приложении

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка SWR для кэширования данных в React-приложении
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка SWR для кэширования данных в React-приложении

SWR (stale-while-revalidate) — библиотека от Vercel для получения данных в React. Стратегия проста: сначала возвращает кэшированные данные (stale), параллельно делает запрос (revalidate), обновляет кэш. Пользователь видит данные мгновенно, они тихо обновляются в фоне.

Компактнее React Query, меньше API, оптимальна для проектов на Next.js и там, где не нужны мутации с оптимистичным обновлением и infinite query с пагинацией.

Что входит в работу

Настройка глобального конфига SWR, кастомный fetcher, типизированные хуки, мутации, инвалидация, оффлайн-режим, SSR с Next.js, DevTools.

Установка

npm install swr

Глобальная конфигурация

// main.tsx / _app.tsx
import { SWRConfig } from 'swr'
import { swrFetcher } from '@/lib/fetcher'

function App({ Component, pageProps }: AppProps) {
  return (
    <SWRConfig
      value={{
        fetcher: swrFetcher,
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        shouldRetryOnError: true,
        errorRetryCount: 3,
        dedupingInterval: 2000,
        onError: (error) => {
          if (error.status === 401) {
            authStore.logout()
          }
        },
      }}
    >
      <Component {...pageProps} />
    </SWRConfig>
  )
}

Fetcher

// lib/fetcher.ts
import type { SWRConfiguration } from 'swr'

class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message)
    this.name = 'ApiError'
  }
}

export const swrFetcher = async (url: string) => {
  const token = localStorage.getItem('token')

  const res = await fetch(url, {
    headers: {
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      'Content-Type': 'application/json',
    },
  })

  if (!res.ok) {
    const body = await res.json().catch(() => ({}))
    throw new ApiError(res.status, body.message ?? res.statusText)
  }

  return res.json()
}

Базовые хуки

// hooks/useUser.ts
import useSWR from 'swr'

export function useCurrentUser() {
  const { data, error, isLoading, mutate } = useSWR<User>('/api/me')

  return {
    user: data,
    isLoading,
    isError: !!error,
    error,
    revalidate: mutate,
  }
}

export function useUser(id: string | null) {
  const { data, error, isLoading } = useSWR<User>(
    id ? `/api/users/${id}` : null // null — отключает запрос
  )

  return { user: data, isLoading, isError: !!error }
}

Хук с параметрами

// hooks/useProducts.ts
import useSWR from 'swr'

interface ProductFilters {
  categoryId?: string
  search?: string
  page?: number
  sort?: 'price' | 'name' | 'date'
}

export function useProducts(filters: ProductFilters) {
  // ключ — URL с параметрами, null отключает запрос
  const params = new URLSearchParams(
    Object.entries(filters)
      .filter(([, v]) => v !== undefined)
      .map(([k, v]) => [k, String(v)])
  )

  const { data, error, isLoading, isValidating } = useSWR<PaginatedResponse<Product>>(
    `/api/products?${params.toString()}`
  )

  return {
    products: data?.items ?? [],
    total: data?.total ?? 0,
    isLoading,
    isValidating, // true при фоновой ревалидации
    isError: !!error,
  }
}

Мутации и инвалидация

import useSWR, { useSWRConfig } from 'swr'

function ProductEditor({ id }: { id: string }) {
  const { mutate } = useSWRConfig()
  const { data: product } = useSWR<Product>(`/api/products/${id}`)

  async function handleUpdate(data: UpdateProductDto) {
    // оптимистичное обновление
    await mutate(
      `/api/products/${id}`,
      async (current: Product) => {
        const updated = await api.patch<Product>(`/products/${id}`, data)
        return updated
      },
      {
        optimisticData: (current) => ({ ...current!, ...data }),
        rollbackOnError: true,
        revalidate: false, // не переспрашиваем после мутации — у нас уже актуальные данные
      }
    )

    // инвалидируем список продуктов
    await mutate((key) => typeof key === 'string' && key.startsWith('/api/products?'))
  }

  // ...
}

useSWRMutation — явные мутации

import useSWRMutation from 'swr/mutation'

async function createProduct(url: string, { arg }: { arg: CreateProductDto }) {
  return api.post<Product>(url, arg)
}

function CreateProductForm() {
  const { trigger, isMutating, error } = useSWRMutation('/api/products', createProduct)
  const { mutate } = useSWRConfig()

  async function handleSubmit(data: CreateProductDto) {
    const newProduct = await trigger(data)
    // инвалидируем все списки
    await mutate((key) => typeof key === 'string' && key.includes('/api/products'))
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* поля формы */}
      <button disabled={isMutating}>
        {isMutating ? 'Создание...' : 'Создать'}
      </button>
      {error && <p className="error">{error.message}</p>}
    </form>
  )
}

Infinite loading

import useSWRInfinite from 'swr/infinite'

const PAGE_SIZE = 20

function getKey(pageIndex: number, previousPageData: PaginatedResponse<Product> | null) {
  if (previousPageData && !previousPageData.hasNextPage) return null
  return `/api/products?page=${pageIndex + 1}&pageSize=${PAGE_SIZE}`
}

export function useInfiniteProducts() {
  const { data, size, setSize, isLoading, isValidating } = useSWRInfinite<
    PaginatedResponse<Product>
  >(getKey)

  const products = data?.flatMap((page) => page.items) ?? []
  const isLoadingMore = isLoading || (size > 0 && data && data[size - 1] === undefined)
  const hasMore = data ? data[data.length - 1]?.hasNextPage : true

  return {
    products,
    isLoading,
    isLoadingMore,
    hasMore,
    loadMore: () => setSize(size + 1),
  }
}

Conditional fetching — зависимые запросы

function OrderDetails({ orderId }: { orderId: string }) {
  const { user } = useCurrentUser()

  // запрос выполняется только после получения user
  const { data: order } = useSWR<Order>(
    user ? `/api/orders/${orderId}` : null
  )

  // ещё один зависимый запрос
  const { data: products } = useSWR<Product[]>(
    order?.productIds ? `/api/products?ids=${order.productIds.join(',')}` : null
  )

  // ...
}

SSR с Next.js (Pages Router)

// pages/products/[id].tsx
import { unstable_serialize } from 'swr'
import { SWRConfig } from 'swr'

export async function getServerSideProps({ params }: GetServerSidePropsContext) {
  const product = await fetchProductServer(params!.id as string)

  return {
    props: {
      fallback: {
        [unstable_serialize(`/api/products/${params!.id}`)]: product,
      },
    },
  }
}

export default function ProductPage({ fallback }: { fallback: Record<string, Product> }) {
  return (
    <SWRConfig value={{ fallback }}>
      <ProductDetails />
    </SWRConfig>
  )
}

function ProductDetails() {
  const { id } = useRouter().query
  const { data } = useSWR<Product>(`/api/products/${id}`)
  // data сразу доступна из fallback, без загрузки
  return <div>{data?.name}</div>
}

Оффлайн и revalidate on focus

// Глобально отключаем revalidateOnFocus для редко меняющихся данных
const { data } = useSWR('/api/config', fetcher, {
  revalidateOnFocus: false,
  revalidateIfStale: false, // не переспрашивать, если данные ещё свежие
})

// Ручная инвалидация по событию
window.addEventListener('focus', () => {
  mutate('/api/notifications') // обновить только уведомления
})

Структура хуков

src/hooks/
  useCurrentUser.ts
  useProducts.ts
  useProduct.ts
  useOrders.ts
  useNotifications.ts

Что делаем

Настраиваем глобальный SWRConfig с кастомным fetcher, проектируем хуки под все API-эндпоинты, реализуем оптимистичные мутации для форм, настраиваем инвалидацию при изменениях, при необходимости добавляем SSR-prefetch.

Срок: 2–3 дня.