Switch GraphQL Codegen to client-preset with Inline Operations
Date: 2026-03-11 Status: accepted
Context
The frontend (apps/web) used three separate @graphql-codegen plugins (typescript, typescript-operations, typed-document-node) with a workflow that required maintaining separate .graphql files for every query, mutation, and fragment in src/graphql/{queries,mutations,fragments}/. Each new GraphQL operation required:
- A
.graphqlfile defining the operation - Running codegen to generate
TypedDocumentNodeexports - A custom hook importing the generated document and passing explicit generic type parameters to
useQuery/useMutation
This created significant boilerplate — three files per operation — and made it tedious to add or modify GraphQL operations. The fragment files added another layer of indirection that didn’t provide meaningful value at the current scale.
Decision
Switch to @graphql-codegen/client-preset with fragmentMasking: false. This preset generates a typed graphql() function that provides full type inference from inline operation strings.
Operations are now written inline inside hook files using graphql() from @/generated:
import { useQuery } from '@apollo/client/react';
import { graphql } from '@/generated';
const GetItemsQuery = graphql(`
query GetItems($page: Float = 1, $limit: Float = 10) {
getItems(page: $page, limit: $limit) {
items { id name description createdAt }
total
}
}
`);
export function useItems(variables?: { page?: number; limit?: number }) {
return useQuery(GetItemsQuery, { variables });
}
Key changes:
- Removed
src/graphql/directory (all.graphqlfiles and fragments) - Replaced three codegen plugins with
client-preset - Codegen output changed from a single
src/generated/graphql.tsto asrc/generated/directory - Apollo Client configured with
dataMasking: falseto matchfragmentMasking: false - Hooks no longer need explicit generic type parameters —
graphql()provides inference viaTypedDocumentNode
Consequences
What becomes easier:
- Adding a new query/mutation is a single file change (the hook file)
- No need to manage separate
.graphqlfiles or fragment dependencies - Type inference is automatic — less boilerplate, fewer places for type mismatches
- Codegen scans
.ts/.tsxfiles directly, so operations and their types stay co-located
What becomes harder:
- Operations are no longer centralized in one directory — they’re spread across hook files (mitigated by keeping all hooks in
src/hooks/) - Fragment reuse across operations requires more deliberate coordination (not currently needed at this scale)
- The
graphql()function uses string literal type matching — re-run codegen after any operation change
Trade-offs:
- Disabled fragment masking to keep Apollo Client’s
useQuery/useMutationreturn types simple. If fragment masking becomes valuable at larger scale, this can be revisited. - The generated
gql.tsincludes a string map of all operations, which is not tree-shakeable without the babel/swc plugin (acceptable at current scale).