skip to content

In Apollo Client 4, what does createFragmentRegistry change about how PriceTagFragment reaches queries, and what can go wrong with it?

level: seniorimportance: nice to knowfreq 18%

answer

  1. spread by name, no import
  2. the cache owns the definitions
  3. appended before the request
  4. the document's own definition wins
  5. register before the query runs

basics

~10 s

A registry from createFragmentRegistry, passed to InMemoryCache as fragments, lets any query spread ...PriceTagFragment by name; the cache appends the registered definition before sending. Late registration, shadowed definitions and lost typing are the risks.

solid answer

~40 s

`createFragmentRegistry`, imported from `@apollo/client/cache`, builds a registry that you pass to `new InMemoryCache({ fragments })`. Queries can then spread `...PriceTagFragment` without importing and interpolating its `gql` document: before a request goes out, the cache's document transform finds spreads the document does not define, looks them up in the registry, follows their own nested spreads, and appends those definitions. Cache reads such as `cache.readFragment` can refer to registered fragments by name too. Fragments can be registered up front or later with `fragmentRegistry.register(...)`. The risks: a fragment registered inside a lazily loaded module may not be registered when a query using it runs; a definition written in the query itself takes precedence over the registered one; and precompiled, typed documents from a code generator already carry their definitions, so Apollo does not recommend the registry there.

code

ts · 22 lines
ts
import { gql, InMemoryCache } from "@apollo/client";
import { createFragmentRegistry } from "@apollo/client/cache";

export const fragmentRegistry = createFragmentRegistry(gql`
  fragment PriceTagFragment on Product {
    price
    compareAtPrice
  }
`);

export const cache = new InMemoryCache({ fragments: fragmentRegistry });

// No interpolation: the cache appends PriceTagFragment's definition.
export const PRODUCT_LIST = gql`
  query ProductList {
    products {
      id
      name
      ...PriceTagFragment
    }
  }
`;

go deeper

for a junior

Recall that a fragment registry lets queries spread a fragment by name without interpolating its definition, and that it is passed to InMemoryCache.

for a middle

Explain how the cache fills in missing definitions before the request, including nested spreads, and that a document's own definition wins.

for a senior

Anticipate the registry's failure modes: lazy registration order, shadowed definitions and colliding names, and choose it only where they are controlled.

for a principal

Choose between interpolation, a registry and generated typed documents as a codebase convention, weighing explicit imports and type safety against less boilerplate.

## What the fragment registry is Without a registry, every document that spreads a fragment must also contain its definition, which in practice means importing the child's `gql` document and interpolating it: `${PRICE_TAG_FRAGMENT}`. The **fragment registry** moves those definitions into the cache. It is created with `createFragmentRegistry`, imported from `@apollo/client/cache`, and handed to `InMemoryCache` through its `fragments` option. Once `PriceTagFragment` is registered, a query can simply write `...PriceTagFragment` and nothing else. The registry has existed since Apollo Client 3.7 and is still exported in version 4. ## How a spread gets its definition When an operation runs, `InMemoryCache` transforms its document through the registry: 1. It collects the fragment definitions the document already contains. 2. It finds every fragment spread whose name is not defined in the document. 3. For each missing name it looks up the registered definition, and then does the same for the spreads inside that definition, so `ProductCardFragment` can pull in `PriceTagFragment`. 4. It appends the definitions it found to the document. 5. The completed document is what goes over the network and what the cache uses when writing and reading the result. Nothing is sent by reference: the server receives the full definitions on every request, exactly as if they had been interpolated. Cache APIs such as `cache.readFragment` and `cache.readQuery` can also name registered fragments without carrying their definitions. ## Registering up front or lazily | Approach | How | Trade-off | |---|---|---| | Up front | pass the definitions to `createFragmentRegistry(...)` when the cache is created | simple and safe; one central file lists every fragment | | Lazily | export a shared registry, then call `fragmentRegistry.register(PRICE_TAG_FRAGMENT)` at module load in `PriceTag.tsx` | keeps the definition next to the component, but only works once that module has loaded | ## What can go wrong - **Late registration.** If `PriceTag.tsx` is code-split and loaded lazily, the listing query may run before the module executes its `register` call. The spread then has no definition: Apollo's cache reports `No fragment named PriceTagFragment`, and a server would reject the document. Apollo's documentation advises moving such definitions into a shared module that is not lazy-loaded. - **Shadowing.** A query that defines its own `PriceTagFragment` uses that local definition, even when the fragment is referenced only indirectly through another registered fragment. The registry only fills in names the document does not define, so a stale local copy quietly overrides the shared one for that query. - **Name collisions.** Registering a second fragment with an existing name replaces the earlier definition in the registry, so names still have to be unique across the app. - **Lost locality.** A spread by name no longer shows, in the file, where its definition lives or which component owns it; reviewers must know the registry exists. - **Typed documents.** Apollo does not recommend the registry with a code generator's client preset, which produces precompiled documents that already include their fragment definitions; the registry adds nothing there. ## Registry versus interpolation | Question | `gql` interpolation | Fragment registry | |---|---|---| | Where the definition comes from | the imported document, visible in the file | the cache, at transform time | | What a missing definition looks like | a missing import, caught while editing | a runtime `No fragment named ...` error | | A cache read whose document spreads a fragment by name | needs the definition in that document | works, since the registry supplies it | | Order dependence | none | the fragment must be registered before the operation runs | | Fit with generated typed documents | natural | not recommended | The registry does not change the network payload or what the cache stores. It changes only who is responsible for putting definitions into documents, and therefore when a mistake is discovered: at edit time with interpolation, at runtime with a registry. ## When it is worth using The registry pays off when many operations share a few widely used fragments and interpolation chains have become noisy. It is a poor fit when the codebase already generates typed documents, or when fragments live in lazily loaded components. In a product listing page with a handful of components, plain interpolation is usually clearer: each file imports exactly the fragments it composes, and a missing import is a visible error in that file instead of a registration-order bug at runtime.

  • PriceTag.tsx registers its fragment at module load, but PriceTag is loaded lazily. What can happen in Apollo Client?
    The listing query can run before the lazy module has executed `fragmentRegistry.register`, so the registry has no `PriceTagFragment` when the document is transformed. The spread stays undefined, the cache fails with `No fragment named PriceTagFragment`, and a server would reject the document. Register such fragments in a shared module that loads eagerly, or register them when the cache is created.
  • A query defines its own PriceTagFragment while a different PriceTagFragment is registered. Which one does Apollo Client use for that query?
    The query's own definition. The registry only supplies definitions for spreads the document does not define, and a local definition takes precedence even when it is reached indirectly through another registered fragment. Other queries keep using the registered version, which is why a stale local copy is easy to miss.

saying these in an interview costs you the question

  • Registered fragments are sent to the server once and then referenced by name.
  • A registered fragment always overrides a definition written in the query.
  • createFragmentRegistry is imported from @apollo/client/react.
  • With a registry, fragment names no longer need to be unique.
  • Registering inside a lazily loaded module is safe; Apollo waits for it.