Configuring URQL for GraphQL in React Apps

Why Choose URQL for React Apps?

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1283
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1237
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    980
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1029
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    552

Why Choose URQL for React Apps?

Imagine your React project growing, API calls increasing, and Apollo Client starting to lag while the bundle bloats to 300KB. You consider migration but fear losing functionality. URQL solves this: its modular exchange architecture lets you plug in only what you need, shrinking the bundle to 7KB gzip—3x smaller than Apollo. Over the years, we've configured URQL for dozens of projects, from startups to enterprise solutions, consistently achieving a 40% reduction in TTFB and a leaner codebase. With proper cache configuration, server cost savings can reach 30%.

Typical pain points with GraphQL clients: bloated bundles, complex cache setup, and authentication headaches. URQL addresses these with modular exchanges—you only load what's required. For example, authExchange handles tokens, retryExchange retries on network errors, and subscriptionExchange manages WebSocket subscriptions. This gives you flexibility and control. Contact us for a consultation on configuring URQL for your project.

Criteria URQL Apollo Client
Bundle size ~7KB gzip ~25KB gzip
Architecture Exchanges (chain) Monolithic
Normalized cache Graphcache (optional) Built-in
SSR support Native (next-urql) getDataFromTree
Subscriptions subscriptionExchange Built-in

Real-World Implementation

In an e-commerce project with a product catalog of 50,000 items, we replaced Apollo Client with URQL. Results:

  • Bundle size shrank from 250KB to 80KB gzip.
  • TTFB improved by 45%, LCP by 20%.
  • Server costs dropped by 30% due to fewer redundant requests.
  • Developers no longer spent time on manual cache invalidation—Graphcache handled it automatically.

This case confirms that URQL is not only lighter but also more performant in complex projects. If you want to replicate this success, get a consultation on configuring URQL for your project.

How to Configure Exchanges for Authentication and Retries

Client and exchange configuration is key. Below is a typical production setup:

npm install urql graphql @urql/exchange-auth @urql/exchange-retry graphql-ws @urql/exchange-graphcache 
// lib/urql/client.ts import { createClient, cacheExchange, fetchExchange, subscriptionExchange, mapExchange, } from 'urql' import { authExchange } from '@urql/exchange-auth' import { retryExchange } from '@urql/exchange-retry' import { createClient as createWsClient } from 'graphql-ws' const wsClient = createWsClient({ url: import.meta.env.VITE_WS_URL ?? 'ws://localhost:4000/graphql', connectionParams: () => ({ authorization: `Bearer ${localStorage.getItem('token')}`, }), }) export const urqlClient = createClient({ url: import.meta.env.VITE_GRAPHQL_URL ?? '/graphql', exchanges: [ mapExchange({ onError(error) { if (error.response?.status === 401) { authStore.logout() } console.error('[URQL error]', error) }, }), cacheExchange, authExchange(async (utils) => { return { addAuthToOperation(operation) { const token = localStorage.getItem('token') if (!token) return operation return utils.appendHeaders(operation, { Authorization: `Bearer ${token}`, }) }, didAuthError(error) { return error.graphQLErrors.some( (e) => e.extensions?.code === 'UNAUTHENTICATED' ) }, async refreshAuth() { const newToken = await refreshTokenRequest() if (newToken) { localStorage.setItem('token', newToken) } else { localStorage.removeItem('token') authStore.logout() } }, willAuthError() { const token = localStorage.getItem('token') return !token }, } }), retryExchange({ initialDelayMs: 1000, maxDelayMs: 15000, maxNumberAttempts: 3, retryIf: (err) => !!(err && err.networkError), }), fetchExchange, subscriptionExchange({ forwardSubscription(request) { const input = { ...request, query: request.query ?? '' } return { subscribe(sink) { const dispose = wsClient.subscribe(input, sink) return { unsubscribe: dispose } }, } }, }), ], }) 

When to Use the Graphcache Normalized Cache?

For simple cases, cacheExchange suffices. But when you have lots of interrelated data, Graphcache helps. It automatically updates the cache on mutations by ID. We often use it in product catalogs and social networks. Development savings: no need to write custom invalidation handlers—the codebase shrinks by 30%.

import { offlineExchange } from '@urql/exchange-graphcache' import schema from './schema.json' const cache = offlineExchange({ schema, keys: { Product: (data) => data.id ?? null, User: (data) => data.id ?? null, Category: (data) => data.id ?? null, }, resolvers: { Query: { product: (_, args) => ({ __typename: 'Product', id: args.id }), }, }, updates: { Mutation: { createProduct: (result, _args, cache) => { cache.invalidate('Query', 'products') }, deleteProduct: (result, args, cache) => { cache.invalidate({ __typename: 'Product', id: args.id as string }) }, updateProduct: (_result, _args, cache) => { // normalized cache updates automatically by id }, }, }, optimistic: { updateProduct: (args) => ({ __typename: 'Product', id: args.id, ...(args.input as object), }), }, }) 

Code Generation for Types

URQL uses the same @graphql-codegen/client-preset. Example configuration:

// codegen.ts import type { CodegenConfig } from '@graphql-codegen/cli' const config: CodegenConfig = { schema: 'http://localhost:4000/graphql', documents: 'src/**/*.graphql', generates: { 'src/gql/': { preset: 'client', config: { useTypeImports: true, }, }, 'src/lib/urql/schema.json': { plugins: ['introspection'], }, }, } export default config 

Using URQL in React

Hooks for Queries, Mutations, and Subscriptions

// features/products/useProducts.ts import { useQuery, useMutation, useSubscription } from 'urql' import { GetProductsDocument, CreateProductDocument, OnOrderStatusDocument, } from '@/gql/graphql' export function useProducts(categoryId: string, page = 1) { const [result, reexecute] = useQuery({ query: GetProductsDocument, variables: { categoryId, page, pageSize: 20 }, requestPolicy: 'cache-and-network', }) return { products: result.data?.products.items ?? [], total: result.data?.products.total ?? 0, hasNextPage: result.data?.products.hasNextPage ?? false, fetching: result.fetching, error: result.error, refresh: () => reexecute({ requestPolicy: 'network-only' }), } } export function useCreateProduct() { const [result, createProduct] = useMutation(CreateProductDocument) return { createProduct: (input: CreateProductInput) => createProduct({ input }), fetching: result.fetching, error: result.error, } } export function useOrderStatus(orderId: string) { const [result] = useSubscription({ query: OnOrderStatusDocument, variables: { orderId }, pause: !orderId, }) return { status: result.data?.orderStatusChanged?.status, fetching: result.fetching, error: result.error, } } 
Caching Policy Description When to Use
cache-first Cache first, then network Rarely changing data
cache-and-network Return cache, but update Frequently requested, up-to-date
network-only Always network Critical data (e.g., balance)
cache-only Only cache Offline mode

Process and Timelines

  1. Analyze project requirements and architecture.
  2. Design the exchange chain and caching scheme.
  3. Implement client configuration, code generation, and hooks.
  4. Test with real scenarios (auth, subscriptions, errors).
  5. Deploy and monitor performance.

Setup takes from 2 to 4 days depending on complexity. Pricing is determined after assessment.

Common URQL Configuration Mistakes

  • Forgetting to add authExchange to the chain—requests go without tokens.
  • Using cacheExchange instead of graphcache for complex relations—leading to manual invalidation.
  • Not configuring retryExchange—data loss on transient network errors.
  • Skipping willAuthError—causing unnecessary token refresh requests.

What’s Included

  • Optimal exchange chain configuration
  • Authentication setup (token, refresh)
  • Normalized cache integration when needed
  • TypeScript type code generation
  • React hooks for queries/mutations/subscriptions
  • SSR support (Next.js)
  • API and usage documentation
  • Team training (1 hour)
  • 30-day post-deployment support

You can verify URQL’s effectiveness by ordering a demo setup on your project. If you need professional URQL configuration, contact us—we’ll tailor the setup to your project and guarantee results.

Additional resources: URQL, GraphQL.