As your project grows, the number of GraphQL queries multiplies—and the N+1 problem and cache desync become real headaches. We've seen code where every component fires its own useQuery, and on mutation you have to manually reset the cache. In one e-commerce project with 50,000 products, each catalog component executed its own useQuery, causing N+1 and slowdowns. After configuring Apollo Client with InMemoryCache, API load dropped by 40%.
Apollo Client with normalized InMemoryCache solves this: it automatically updates all components subscribed to changed entities. Over 5 years, we've implemented it on 15+ projects—from online stores to real-time dashboards. According to Apollo Client documentation, using a normalized cache reduces manual updates by 30%.
Below is a concrete setup guide: from client configuration to code generation and cache optimization. No fluff—only working configs and patterns.
Why Apollo Client over urql for complex projects?
| Criterion | Apollo Client | urql | Relay |
|---|---|---|---|
| Normalized cache | Yes, InMemoryCache | Yes, document cache | Yes |
| Subscriptions | Yes | Yes | Via subscriptions-transport-ws |
| Size (min+gzip) | ~34 KB | ~15 KB | ~45 KB |
| Popularity (npm/week) | 2.5M+ | 500K+ | 400K+ |
| React integration | Hooks + HOC | Hooks | Fragment model |
Apollo Client is 5× more popular than urql by npm downloads and provides a more mature toolbox. For products with dozens of interconnections, a normalized cache is a critical advantage: it saves up to 30% development time because you don't have to write manual updates. Additionally, Apollo automatically deduplicates identical queries, reducing server load in high-traffic apps—urql lacks this feature.
How to properly configure Apollo Client?
Installation and client configuration
Install packages and create a client with HTTP and WebSocket links, authentication, and error handling. The key element is InMemoryCache with typePolicies for pagination:
import { ApolloClient, InMemoryCache, createHttpLink, from, split, ApolloLink } from '@apollo/client' import { setContext } from '@apollo/client/link/context' import { onError } from '@apollo/client/link/error' import { GraphQLWsLink } from '@apollo/client/link/subscriptions' import { createClient as createWsClient } from 'graphql-ws' import { getMainDefinition } from '@apollo/client/utilities' const httpLink = createHttpLink({ uri: import.meta.env.VITE_GRAPHQL_URL ?? '/graphql' }) const authLink = setContext((_, { headers }) => { const token = localStorage.getItem('token') return { headers: { ...headers, ...(token ? { authorization: `Bearer ${token}` } : {}) } } }) const errorLink = onError(({ graphQLErrors, networkError }) => { graphQLErrors?.forEach(({ message, extensions }) => { if (extensions?.code === 'UNAUTHENTICATED') { /* logout */ } }) }) const wsLink = new GraphQLWsLink(createWsClient({ url: import.meta.env.VITE_GRAPHQL_WS_URL, connectionParams: { authorization: `Bearer ${localStorage.getItem('token')}` } })) const splitLink = split( ({ query }) => getMainDefinition(query).operation === 'subscription', wsLink, from([errorLink, authLink, httpLink]) ) export const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache({ typePolicies: { Query: { fields: { products: { keyArgs: ['categoryId'], merge(existing, incoming, { args }) { return args?.offset ? { ...incoming, items: [...(existing?.items ?? []), ...incoming.items] } : incoming } } } } } }), defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network', errorPolicy: 'all' }, query: { fetchPolicy: 'network-only', errorPolicy: 'all' } } }) Fragment matcher for interface fragments
If your schema uses interfaces or union types, configure IntrospectionFragmentMatcher or possibleTypes in the cache. Otherwise Apollo won't normalize fragments, and the cache will reset on every request. Generate possibleTypes automatically with @graphql-codegen/fragment-matcher.
Optimistic updates
Apollo Client supports optimistic updates: you can instantly update the UI while the server request is in flight. Configure the update function in useMutation and pass optimisticResponse. This reduces perceived latency and improves UX.
How to speed up development with code generation?
Code generation of types from the GraphQL schema saves up to 30% of time writing TypeScript interfaces. Configure @graphql-codegen:
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset npx graphql-codegen init In codegen.ts specify schema and documents, enable strictScalars:
import type { CodegenConfig } from '@graphql-codegen/cli' const config: CodegenConfig = { overwrite: true, schema: 'http://localhost:4000/graphql', documents: 'src/**/*.graphql', generates: { 'src/gql/': { preset: 'client', config: { useTypeImports: true, strictScalars: true, scalars: { DateTime: 'string', UUID: 'string' } } } } } export default config Run npx graphql-codegen --watch in development and npx graphql-codegen in CI.
Common mistakes when configuring Apollo Client
The most frequent one is incorrect typePolicies setup. If you don't set keyArgs for pagination fields, the cache will mix data from different pages. The second mistake is ignoring errorPolicy: 'all' when handling partial errors. The third is forgetting to pass connectionParams for WebSocket authentication, causing subscriptions to fail with 401.
How to debug cache issues in Apollo Client?
Use Apollo DevTools: the Chrome/Firefox extension visualizes the cache, query history, and mutations. It lets you execute arbitrary GraphQL queries directly from the browser. DevTools cuts debugging time by 40%.
Working with queries, mutations, and subscriptions
After code generation, use generated types and hooks. Example documents and hooks:
query GetProducts($categoryId: ID!, $offset: Int, $limit: Int) { products(categoryId: $categoryId, offset: $offset, limit: $limit) { items { id name price stock } total hasMore } } import { useQuery } from '@apollo/client' import { GetProductsDocument } from '@/gql/graphql' export function useProducts(categoryId: string) { return useQuery(GetProductsDocument, { variables: { categoryId }, notifyOnNetworkStatusChange: true }) } What's included in the work?
- Analysis of existing GraphQL schema and documents
- Client configuration with HTTP/WS links, authentication, error handling
- InMemoryCache setup with typePolicies for your entities
- Code generation of TypeScript types and hooks (CI integration)
- Subscription and authentication integration
- Testing and debugging with Apollo DevTools
- Usage and maintenance documentation
Process and timeline
| Stage | Duration |
|---|---|
| Schema and document analysis | 0.5 day |
| Client and cache configuration | 1 day |
| Code generation and hooks | 1 day |
| Subscription and auth integration | 0.5 day |
| Testing and debugging | 1 day |
| Documentation | 0.5 day |
Cost is calculated individually based on schema complexity and scope. Estimated timeline: from 3 to 5 days.
We guarantee: compatibility with any React stack, support for the latest @apollo/client versions, detailed documentation. Our experience: 15+ projects on Apollo Client, including high-load e-commerce. Get a consultation—we'll evaluate your project in 1 day. Order Apollo Client setup for your project—we'll contact you within an hour.







