Siddhant DevalAuthor
Senior Full-Stack Engineer·Sep 15, 2026·11 min read
Client Configuration & the Apollo Link Chain
The Apollo Link chain is the nervous system of every Apollo Client setup — authentication, error interception, retry logic, APQ, and observability all wire into it. Copying a link setup without understanding the chain model guarantees silent failures at the layer you didn't understand.
Technical Series
GraphQL Frontend Engineering
Part 3 of 8
Client Configuration & the Apollo Link Chain
Every Apollo Client setup has an
ApolloClient constructor call somewhere. Most of them look like this:typescript
This setup works — until it silently doesn't. The
Authorization header is read once at module load time. When the token expires and a refresh returns a new token, this client still sends the old one. There is no retry on network failures. There is no central place to intercept UNAUTHENTICATED errors and redirect to login. Every error handling decision gets made per-component, inconsistently.The answer is not to add an
onError callback to each useQuery. The answer is an ApolloLink chain — where each cross-cutting concern lives in its own link, the order of links is explicit and correct, and every request passes through all of them automatically.A GraphQL client is not a data fetcher — it is a local replica of your server's data graph. The link chain is the nervous system through which every operation flows before reaching the network and every response flows back before updating the replica.
1. What a Link Is
An
ApolloLink is a function that receives an operation (the GraphQL document + variables + context) and either:- Calls
forward(operation)to pass it to the next link in the chain, or - Returns an
Observabledirectly (for terminating links, which send the actual request).
typescript
1.1 Terminating vs. Non-Terminating Links
Terminating links end the chain — they send the operation to the network and return an
Observable of the result. HttpLink is the standard terminating link. It must always be last in the chain.Non-terminating links intercept the operation, optionally modify it, and call
forward(operation) to continue the chain. Auth links, retry links, and error links are all non-terminating.typescript
1.2 Why Order Is a Correctness Constraint
typescript
The chain is like a middleware stack. Links closer to the front of the array wrap those closer to the end.
retryLink at position 2 wraps errorLink at position 3 and httpLink at position 4 — which means RetryLink can re-execute those links on each retry attempt.
Expand
2. The Essential Link Stack
2.1 AuthLink — Per-Request Token Attachment
The
setContext link reads the token at request time, not at initialization time:typescript
Crucial Requirement
setContext receives the current headers and must return the updated headers. Always spread ...headers — other links (logging, tracing) may have already set headers upstream.2.2 RetryLink — Automatic Retry with Exponential Backoff
typescript
Performance / Safety Warning
RetryLink retries the entire remaining chain — including HttpLink. Do not use it to retry mutations unless your mutations are idempotent. A non-idempotent mutation (e.g., placeOrder) retried three times will create three orders.2.3 ErrorLink — Centralized Error Interception
typescript
Architectural Note
onError receives graphQLErrors (resolver-level failures in the errors array) separately from networkError (transport-level HTTP failures). These correspond to the two GraphQL error categories covered in Part 6.2.4 HttpLink — The Terminus
typescript
2.5 Assembling the Chain
typescript

Expand
3. ApolloProvider Setup
3.1 Client Initialization and Provider Placement
typescript
The
ApolloProvider must wrap every component that calls useQuery, useMutation, or useSubscription. Place it at the root of your component tree — above routing, above layout, above everything.3.2 SSR with Next.js App Router
The
ApolloWrapper pattern for Next.js App Router uses useMemo to create the client once per render context:typescript
Crucial Requirement
The
ssrMode: true flag prevents the Apollo Client from using window and disables features that assume a browser environment. Set it to typeof window === 'undefined' so it's only active during server-side rendering.4. makeVar and Reactive Variables
The replica store (the
InMemoryCache) holds server-derived data. But some client state — whether a modal is open, the current theme, an unsaved form draft — needs to survive component unmounts without being a server entity.Reactive variables (
makeVar) are Apollo's local state primitive. They live outside the cache but integrate with the reactive update system.4.1 Defining and Using Reactive Variables
typescript
4.2 The Boundary: Replica Data vs. UI State
typescript
Mental Model Check
If the data should persist across page reloads, it belongs in the server (and in the cache when fetched). If it should reset on page reload, it belongs in a reactive variable. If it should reset on component unmount, it belongs in
useState.5. Terminating Link Variants
5.1 BatchHttpLink — When to Enable and Disable
BatchHttpLink combines multiple GraphQL operations fired in the same tick into a single HTTP request:typescript
Use
BatchHttpLink only for HTTP/1.1. In HTTP/1.1, each request occupies a connection (max 6 per domain). Batching reduces connection contention. In HTTP/2, multiplexing sends all requests concurrently on one connection — the batchInterval delay degrades time-to-first-byte without any connection benefit.typescript
5.2 SchemaLink — In-Process Execution for Tests
SchemaLink executes operations against a local schema without HTTP. It is the correct client for integration tests:typescript
Pro Tip & Optimization
Use
SchemaLink in component tests that exercise the full query-to-render path. It is faster than mocking fetch and more accurate than mocking useQuery — it exercises the actual Apollo Client execution pipeline including field policies and cache normalization.Summary
| Concept | Rule |
|---|---|
| Link chain order | Link chain order is a correctness constraint, not a style preference — placing ErrorLink before RetryLink means retry never fires on network errors. |
| Cross-cutting concerns | Every cross-cutting concern (auth, logging, retry, APQ) belongs in a dedicated link; embedding them in useQuery options is the monolith anti-pattern for GraphQL clients. |
| Reactive variables | makeVar is for UI state that should survive component unmounts but not page reloads — never store server-derived data in a reactive variable; that data belongs in the cache. |
| HTTP/2 and batching | Disable BatchHttpLink in any HTTP/2 environment — multiplexing handles concurrent requests natively; batching adds an artificial delay that degrades time-to-first-byte. |
| Testing | SchemaLink (executes against a local schema without HTTP) is the correct client for integration tests — use it in test setup instead of mocking fetch. |
What's Next
In Part 4, we move from client configuration to the query design discipline that makes the replica useful: fragment colocation. We'll see why fragments are not just a way to share field selections — they are the typed data contract between a component and the server graph, and the only mechanism that prevents silent over-fetching at the component boundary.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#GraphQL#Apollo Client#ApolloLink#Authentication#Error Handling#Retry#TypeScript
Technical Series
GraphQL Frontend Engineering
Part 3 of 8