Share GraphQL fragments across queries

How the template keeps shared fragments DRY – one fragment per file, pulled into queries and other fragments with #import.

A fragment is a named bundle of fields you can spread into more than one query. In the example template each shared fragment lives in its own <Name>.fragment.graphql file under src/queries/, and every query – or fragment – that spreads it pulls it in with an #import comment. This page covers how that’s organized and how to add a fragment of your own.

Why fragments live in their own files

fetchGraphql loads a single .graphql file by name and posts its raw text to the server, so the fragments a query spreads have to be present in what’s sent – otherwise the server rejects the request with an unknown-fragment error. Defining a shared fragment inline in every query that uses it would satisfy that, at the cost of copies that drift the moment you edit one and forget another.

Keeping each fragment in one file and #importing it avoids both: the definition exists once, and an import step splices it back into each request so the wire payload stays self-contained. This is purely a runtime concern – codegen reads every file matched by its documents glob and shares fragments across all of them, so for type generation a fragment defined once is already visible everywhere.

Add a shared fragment

Put the fragment in its own <Name>.fragment.graphql file. A leaf fragment – one that spreads no others – is plain:

# src/queries/FontDetailCollection.fragment.graphql
fragment FontDetailCollection on FontCollection {
id
name
fontStyles {
name
webfontSources {
url
format
}
}
}

A fragment can build on another by #importing its file and spreading it – imports nest and resolve transitively:

# src/queries/FontDetail.fragment.graphql
#import "./FontDetailCollection.fragment.graphql"
fragment FontDetail on FontCollection {
...FontDetailCollection
description
images {
url
description
}
children(collectionTypes: [FAMILY]) {
...FontDetailCollection
}
}

Then, in each query that spreads it, add an #import pointing at the fragment file. Import only the top-level fragment you spread – its own imports come along transitively, so Font.graphql gets FontDetailCollection through FontDetail without naming it:

# src/queries/Font.graphql
#import "./FontDetail.fragment.graphql"
query Font($slug: String!) {
viewer {
slug(name: $slug) {
fontCollection {
...FontDetail
}
}
}
}

#import is a GraphQL comment, so codegen ignores it and resolves the fragments from their files via the glob – types are unaffected. Codegen emits a <Name>Fragment type for each fragment, which your components can use to type the data they receive.

How the helper inlines imports

getStaticQuery resolves the #import statements before a query is sent, with @graphql-tools/import’s processImport. That returns a parsed document, so the helper prints it back to the string fetchGraphql posts:

const getStaticQuery = async (queryName: string) => {
// Resolve #import statements so the query sent over the wire is self-contained.
const document = processImport(
path.resolve(process.cwd(), "src", "queries", queryName),
process.cwd(),
);
return print(document);
};

A query with no #import passes through unchanged, so the helper works the same for every file. graphql and @graphql-tools/import are runtime dependencies, because print and processImport run at request time.11Wiring this up in a project of your own rather than cloning? npm install @graphql-tools/import, move graphql into dependencies, and drop the snippet above into your getStaticQuery (importing processImport from @graphql-tools/import and print from graphql).

The whole #import step exists only because queries are loaded as raw files at request time. Codegen’s client preset takes a different path – it compiles each operation into a TypedDocumentNode with its fragments already inlined, so there’s nothing to resolve at runtime. It’s a larger change than #import; see Set up GraphQL codegen.

1 Wiring this up in a project of your own rather than cloning? npm install @graphql-tools/import, move graphql into dependencies, and drop the snippet above into your getStaticQuery (importing processImport from @graphql-tools/import and print from graphql).