Organizing GraphQL files with components using near-operation-file pattern
Now that you know how to write GraphQL operations, let's discuss where to organize them. Colocation keeps your GraphQL files next to the components that use them, improving discoverability and maintainability.
What Colocation Means
In our GraphQL setup, "colocation" refers to the near-operation-file preset pattern where:
.graphql files are placed in the same folder as the components that use them
Generated TypeScript files (.graphql.ts) are created alongside the .graphql files
This is NOT about embedding GraphQL strings directly in component files
Discoverability: GraphQL operations are right next to the components using them
Maintainability: Easy to update operations when modifying components
Type Safety: Generated types are imported from the same directory
Modularity: Components are self-contained with their data requirements
Refactoring: Moving components automatically moves their GraphQL files
Before and After Examples
❌ Don't do this - Centralized Operations
Old Pattern: All operations in a central location
Problems:
Operations disconnected from components
Hard to find what queries a component uses
Refactoring requires hunting down imports
No clear ownership of operations
Leads to bloated operations files that request too much data and query too many levels deep.
Types become difficult to reason about and maintain.
✅ Do this - Colocated Operations
New Pattern: Operations next to components
Benefits:
Clear ownership and organization
Easy to see component's data needs
Self-contained components
Natural code splitting
Types are scoped to the component and are easy to reason about
How Generated Files Work
When codegen runs (at the start of pnpm dev, or manually with pnpm turbo run codegen), the near-operation-file preset:
Finds all .graphql files in your app
Generates a .graphql.ts file next to each one
Includes typed document nodes and operation types
Example Generated Output
For this operation:
Generates this TypeScript file:
Important: NOT Inline GraphQL
❌ Don't do this - Inline GraphQL Strings
✅ Do this - Separate .graphql Files
Organizing Complex Components
For components with multiple operations:
Best Practices
✅ Do
Place .graphql files in the same folder as components, under src/ of an app, remote, or package
Use descriptive names for operation files
Group related operations in the same file
Give each component its own fragment, named Component_prop, in a fragments.graphql next to it
❌ Don't
Put GraphQL strings directly in component files (pnpm lint:graphql rejects gql imports and tags outside tests)
Put a .graphql file anywhere codegen does not read, such as a remote's exposes/ directory
Name a document anything but queries.graphql, mutations.graphql, subscriptions.graphql, or fragments.graphql
Create deeply nested GraphQL folders
Share component-specific operations
Manually edit generated .graphql.ts files
Share one fragment between components: each component declares its own, even when the selections match today
Spread a grandchild's fragment into a parent: spread the child's fragment, and let the child spread its own
Cross-Feature Contracts
A fragment belongs to one component. It can appear in more than one place only through its parent:
Composition (✅ correct): A parent spreads its direct child's fragment (ChatroomListItem_chatroom spreads ...ChatroomLabel_chatroom). The route's query spreads only the fragment of the component it renders. The route fetches everything in one request, and Apollo's data masking gives each component only the fields in its own fragment.
Sharing (❌ avoid): Two components import one fragment, or a query spreads a grandchild's fragment. The components are then coupled: a field that one of them stops reading can't be removed, and a field that one needs is fetched for both.
If two components need the same fields, each declares its own fragment. Duplicated field selections cost nothing: the server merges them.
Next, we'll learn how fragments and data masking work.