Catch the highlights of GraphQLConf 2023! Click for recordings. Or check out our recap blog post.

Using @defer Directive with GraphQL Code Generator

Aleksandra Sikora

Optimization is crucial for the good performance of GraphQL applications, which can grow in complexity and size. The @defer directive in GraphQL is a feature that allows fragment fields to be resolved separately from the rest of the query. This can be useful when dealing with large queries or slow-to-resolve fields.

And now, we have some exciting news to share: support for @defer is available in GraphQL Code Generator! 🥳

You can start using GraphQL Code Generator to generate code for queries that include deferred fragments, and the generated code will automatically include support for the @defer directive.

Let's take a closer look at how the directive works and how you can use it in your GraphQL applications.

How Does @defer Work?

When a query includes a deferred fragment field, the server will return a partial response with the non-deferred fields first, followed by the remaining fields once they have been resolved.

For example, consider the following query:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    ...OrdersFragment @defer
  }
}
 
fragment OrdersFragment on User {
  orders {
    id
    total
  }
}

Let's say you have a query that fetches information about a user, but the orders field takes more time to resolve. You can use the @defer directive to first return a partial response with the id and name and then later return the orders field. This can improve the performance of your application by reducing the amount of time it takes to load initial data.

Using @defer with GraphQL Codegen

Once you update the GraphQL Code Generator to the latest version and start using the @defer directive in your queries, the generated code will automatically include support for the directive. Below, we’re going to see an example of usage.

As the @defer directive is now supported in Apollo, we’re going explore how it works with Apollo Client. You need to use Apollo Client version 3.7.0 or higher for the defer support to work.

Note: you could also use the @defer directive with other GraphQL clients, such as URQL — the example usage in Hive with URQL is described below.

Here’s an example codegen.ts config:

// condegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: '<PATH_TO_YOUR_SCHEMA>',
  documents: ['src/**/*.tsx'],
  generates: {
    './gql/': {
      preset: 'client'
    }
  },
  hooks: { afterAllFileWrite: ['prettier --write'] }
}
 
export default config

Let’s consider the same example with user and orders. Once you declare a fragment and a query in your application, you can run the codegen, and it will have the information about deferred fields.

// src/index.tsx
import { graphql } from './gql'
 
const OrdersFragment = graphql(`
  fragment OrdersFragment on User {
    orders {
      id
      total
    }
  }
`)
 
const GetUserQuery = graphql(`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      ...OrdersFragment @defer
    }
  }
`)

The generated type for GetUserQuery will have information that the fragment is incremental, meaning it may not be available right away.

// gql/graphql.ts
 
export type GetUserQuery = { __typename?: 'Query'; id: string; name: string } & ({
  __typename?: 'Query'
} & {
  ' $fragmentRefs'?: { OrdersFragment: Incremental<OrdersFragment> }
})

Apart from generating code that includes support for the @defer directive, the Codegen also exports a utility function called isFragmentReady. This function can be used to determine whether the data for a deferred fragment field has been resolved or not. The isFragmentReady function takes three arguments: the query document, the fragment definition, and the data returned by the query. You can use it to conditionally render components based on whether the data for a deferred fragment is available, as shown in the example below:

// index.tsx
import { useQuery } from '@apollo/client';
 
import { useFragment, graphql, FragmentType, isFragmentReady } from './gql';
 
const OrdersFragment = graphql(`...`)
 
const GetUserQuery = graphql(`...`);
 
const OrdersList = (props: { data: FragmentType<typeof OrdersFragment> }) => {
  const data = useFragment(OrdersFragment, props.data);
  return (
    // render orders list
  )
};
 
function App() {
  const { data } = useQuery(GetUserQuery);
 
  return (
    <div className="App">
      {data && (
        <>
          <span>Name: {data.name}</span>
          <span>Id: {data.name}</span>
          {isFragmentReady(GetUserQuery, OrdersFragment, data)
						&& <OrdersList data={data} />}
        </>
      )}
    </div>
  );
}
 
export default App;

With the above code, the orders field is rendered only if it's available in the data object.

Example with GraphQL Yoga and Apollo Client

If you want to see the @defer directive in action, you can check out the example in the GraphQL Code Generator repository.

How Does It Compare with Relay?

Relay allows for incremental delivery of data from the server to the client without an extra logic on the client because it uses Suspense. It enables the client to render parts of the UI as soon as the corresponding data is available.

The same can be achieved in other GraphQL clients with the @defer directive and the isFragmentReady utility function.

As the Suspense usage requires deep integration with a GraphQL client code, the GraphQL Code Generator does not support it out-of-the-box. We are looking forward to collaborate with the maintainers of clients like Apollo Client and Urql, and go for a more generic suspense-backed implementation in the future.

Example Usage in GraphQL Hive

To evaluate the codegen support for the @defer directive, we integrated it into the GraphQL Hive application. GraphQL Hive is a schema registry for GraphQL.

With Hive you manage and collaborate on all your GraphQL schemas and GraphQL workflows, regardless of the underlying strategy, engine or framework you're using: this includes Schema Stitching (opens in a new tab), Apollo Federation, or just a traditional monolith approach.

The Hive application uses URQL as its GraphQL client, which supports the @defer directive starting from version 4.0.0.

In Hive, the operations page displays all server-executed operations, providing valuable insights into operation performance, end-user consumption, and operation success rates.

Given the substantial amount of data to be loaded, we decided to apply the @defer directive to the operation stats query fragments:

query generalOperationsStats($selector: OperationsStatsSelectorInput!, $resolution: Int!) {
  operationsStats(selector: $selector) {
    ... on OperationsStats {
      # ...
    }
    ... on OperationsStats @defer {
      failuresOverTime(resolution: $resolution) {
        # ...
      }
      requestsOverTime(resolution: $resolution) {
        # ...
      }
      durationOverTime(resolution: $resolution) {
        # ...
      }
    }
    ... on OperationsStats @defer {
      clients {
        # ...
      }
    }
  }
}

You can check out a PR to Hive that adds @defer in the operations page.

Throughout the process, we collaborated a bit with the URQL team to enhance URQL's graphcache exchange, resulting in improved support for the @defer directive in the new version. You can check out the relevant PRs:


To summarize, the @defer directive in GraphQL is a feature that allows fragment fields in a query to be resolved separately from the rest of the query. With support for @defer now available in GraphQL Code Generator, you can easily add deferred resolution to your GraphQL applications and optimize their performance. We hope that this new feature will help you build even more powerful and performant GraphQL apps!

Let us know your thoughts. We’d love your feedback!

Join our newsletter

Want to hear from us when there's something new? Sign up and stay up to date!

By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.

Recent issues of our newsletter

Similar articles