Apollo Client fetchPolicies, React, and Pre-Rendering
Tags: graphql, apollo, react
Published: 2019-07-18 16:09:57

## Background

My team at [FireEye](https://www.fireeye.com/) built the [FireEye Market](https://fireeye.market/) as a React app with a GraphQL _(Apollo Server Lambda)_ backend. It's a marketplace to _"Discover apps, extensions, and add-ons that integrate with and extend your FireEye experience."_ One of the things we discovered early on was that we needed to work to improve the [Time to First Meaning Paint](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint) _(TTFMP)_. We couldn't really reduce our bundle size further, and we already do code splitting. So we looked instead to generate static HTML, with the Apollo Client cache data serialized into the markup. This allows the client to quickly download a fully rendered HTML page, to begin interacting with immediately, while the React app scripts are downloaded and evaluated by the browser. When the React app [hydrates](https://reactjs.org/docs/react-dom.html#hydrate), it has been configured  to read the serialized data into the Apollo Client cache, which then makes the data instantly available to the React app to update the component tree. However, there is a catch...

## Enter `fetchPolicy`

Apollo Client, and the corresponding React components _(`Query`, `Mutation`, `Subscription`, and `graphql` HOC that encapsulates them)_ that consume the client, have an option called `fetchPolicy`. What this does is control how the components interact with the Apollo Client cache. This is very powerful, but the documentation for it is spread out in a couple places in the Apollo docs. My aim here is to consolidate that information, and hopefully, clarify it a bit.

The valid values for `cachePolicy` are:

### `cache-first`

This is the default if you don't explicitly specify an option. What this means is that the client will look in its cache, and if it finds **all** of the data it needs to fulfill the query, it will use that and **not make a network request for the data**. Each of the queries you make, along with the arguments, are stored in the cache. If the query is cached, then it will use the data from this query. _I believe_ that the selection set of the query is also considered, so if that differs, a network request _will_ be made. 

I'm admittedly unsure on this last point. The FireEye Market app has a known set of queries that the client executes, which differ only in the parameters passed at runtime.

### `cache-and-network`

This policy will look in the cache first, and use that data if available. It will **always make a network request**, updating the cache and returning the fresh data when available. This may result in an additional update to your components when the fresh data comes in. This policy optimizes for getting cached data to the client quickly, while also ensuring that fresh data is always fetched. 

_This is the policy that we have found to work best for most cases when dealing with pre-rendering._

### `network-only`

This policy skips reading from the cache altogether and goes straight to the network for data. Queries using this option will **never read from the cache**. It will, however, write the results to the cache. This is for the situation where you always want to go to the backend for data, and are willing to pay for it in response time.

### `cache-only`

This policy _exclusively_ reads from the cache, and will **never go to network**. If the data doesn't exist in the cache, then an error is thrown. This is useful for scenarios where you want the client to operate in offline mode only, where the entirety of the data exists on the client.

_I've never used this policy myself, so take that assertion with a giant grain of salt._

### `no-cache`

This policy will never read data from, nor write data to, the cache.

## Configuration

Armed with this knowledge of `fetchPolicy`, how do you configure it? There's two places: in the client config, and in the request config.

### Client Configuration

When you [configure the Apollo Client instance](https://www.apollographql.com/docs/react/api/apollo-client/), you can provide it with a `defaultOptions` key, which specifies the policy each type of query should use unless specifically provided by the request.

```javascript
const defaultOptions = {
  watchQuery: {
    fetchPolicy: 'cache-and-network',
    errorPolicy: 'ignore',
  },
  query: {
    fetchPolicy: 'network-only',
    errorPolicy: 'all',
  },
  mutate: {
    errorPolicy: 'all'
  }
}
```

_From the docs:_

> **Note:** The React Apollo `<Query />` component uses Apollo Client's `watchQuery` functionality, so if you would like to set `defaultOptions` when using `<Query />`, be sure to set them under the defaultOptions.watchQuery property.

Also note that the `graphql` HOC, which given a document that is a `query`, ends up wrapping an instance of the `<Query />` component.

### Request Configuration

You can also specify the `fetchPolicy` per request. One of the props that you can provide to the `<Query />` component is `fetchPolicy`. This will override whatever is configured in the client for this query only.

```jsx
<Query query={QUERY_DOCUMENT} fetchPolicy="network-only">
  {(data) => { /* render prop! */ }}
</Query>

```

Similarly for the `graphql` HOC, you can specify a `fetchPolicy` in the config object:

```javascript
const listAppsForNotificatonSettings = graphql(APPS_FOR_NOTIFICATION_SETTINGS_QUERY, {
  options: {
    fetchPolicy: 'cache-first' 
  }
});

```

## Conclusion

As I mentioned, we found that this `cache-and-network` policy ended up being the best option for providing the best experience for our customers when serving up pre-rendered pages for various entry points into the application. In a few cases, we found that using `cache-first` was a better option, but this are few. As always, this is what worked for my team. Your mileage may vary.