skip to content

What does the relay-compiler do with the graphql-tagged queries and fragments in a Relay app, and why must it run before the app works?

level: juniorimportance: must knowfreq 38%

answer

  1. ahead of time, not at runtime
  2. checked against the schema file
  3. one artifact per operation or fragment
  4. the Babel plugin swaps the tag

basics

~20 s

The relay-compiler validates every graphql tag against the schema ahead of time and writes a generated artifact per query or fragment: runtime metadata, query text and types. A Babel plugin swaps each tag for its artifact, so uncompiled tags throw.

solid answer

~50 s

Relay does no GraphQL parsing in the browser. `relay-compiler`, configured by `relay.config.json` (`src`, `schema`, `language`), finds every `graphql` template literal, validates it against the schema file and fails the build on an unknown field, a bad argument or a naming violation: a fragment name must start with its module name, and an operation name must also end in `Query`, `Mutation` or `Subscription`. For each document it writes `__generated__/<Name>.graphql.ts` holding the metadata the runtime walks to read and normalize data, the query text sent to the server, and TypeScript types such as `$data`, `$key` and `$variables`. It also adds selections the runtime needs, like `id` and a connection's `cursor` and `pageInfo`, and generates the queries `@refetchable` asks for. The Babel plugin then replaces each tag with an import of its artifact; without it the tag throws "graphql: Unexpected invocation at runtime".

code

json · 5 lines
json
{
  "src": "./src",
  "schema": "./schema.graphql",
  "language": "typescript"
}

go deeper

for a junior

Recall the pipeline: the compiler validates graphql tags against the schema and writes artifacts, and the Babel plugin swaps each tag for its artifact. Know the naming rule and the generated folder.

for a middle

Explain what an artifact contains: reader and normalization metadata, the operation text and the $data, $key and $variables types, plus selections the compiler adds such as id and connection cursors.

for a senior

Show how you keep the pipeline honest across teams: --watch locally, --validate in CI, and why static documents and compile-time schema checks move failures from readers' browsers into builds.

for a principal

Frame the compiler as the price of Relay's guarantees: a mandatory build step and naming discipline, in exchange for schema-checked documents and generated types every team can rely on.

## Why Relay compiles GraphQL at all Many GraphQL clients accept a document at runtime, parse it in the browser and send it. **Relay moves that work to build time.** A command-line tool, `relay-compiler`, reads every `graphql` template literal in the source tree before the app is bundled, checks it, and writes **generated artifacts** that the runtime loads instead of the original text. On a news site where a dozen teams each own a slice of the article page, this means a broken selection fails in the build of the team that wrote it, not in a reader's browser. The compiler finds its settings in `relay.config.json`, in `relay.config.js`/`.mjs`/`.ts`, or under a `"relay"` key in `package.json`. A minimal config names three things: `src` (where to look for tags), `schema` (the SDL file to validate against) and `language` (`typescript` or `flow`). ## What the compiler checks Every document is validated before anything is written: - **Against the schema.** A misspelled field such as `article { heroImge }`, a missing required argument or a wrong variable type is a compile error, with the file and line. - **Naming.** Fragment names must start with the **module name**, derived from the file name: in `CommentThread.tsx` a fragment is `CommentThread_article`. Queries, mutations and subscriptions must also start with the module name and end in `Query`, `Mutation` or `Subscription`. This keeps names globally unique and traceable to a file. - **Static documents.** A tag may not contain `${...}` substitutions; the Babel plugin rejects them ("Substitutions are not allowed in graphql fragments"). Dynamic behaviour goes through variables, `@include`/`@skip` and fragment arguments. - **Conditional fragment spreads.** Since Relay 19, a spread under `@include`/`@skip`, or on a type that might not match, must carry `@alias`, so the parent reads the fragment reference as a nullable property. An unaliased one is a compile error. - **Directive rules**, for example that a `@connection` key ends with `_` plus the field name. ## What it writes By default each artifact lands in a `__generated__` folder next to the source file (an `artifactDirectory` setting can centralise them). For TypeScript the file is `<Name>.graphql.ts`. | Part of the artifact | Used for | |---|---| | Reader metadata | how `useFragment` and the query hooks read a selection out of the store | | Normalization metadata | how a response is split into records keyed by `id` | | Operation text (or a persisted id) | what the network layer sends | | Types: `$data`, `$key`, `$variables` | typing hook results, fragment-reference props and variables | | Generated operations | the pagination or refetch query that `@refetchable` requests | The compiler also **adds selections the runtime depends on**: it selects `id` on types that have one, so records can be merged, and adds `cursor`, `pageInfo.endCursor` and `hasNextPage` to a `@connection` field so pagination works without you writing them. ## How the artifacts reach the running app 1. `relay-compiler` runs (once, in `--watch` mode during development, or in the build) and writes the artifacts. 2. The bundler runs `babel-plugin-relay` (or a framework's equivalent, such as the SWC plugin) and replaces each `graphql` tag with an import of its artifact. Generated artifacts use ES module imports by default since Relay 19. 3. At runtime, hooks such as `useLazyLoadQuery` and `useFragment` receive the artifact, not text. If step 2 is missing, the `graphql` function that ships in `relay-runtime` executes for real, and it only throws: "graphql: Unexpected invocation at runtime. Either the Babel transform was not set up, or it failed to identify this call site." If step 1 is stale, the import points at an artifact that no longer matches the tag. ## Working with it day to day - Run `relay-compiler --watch` while editing, so artifacts follow the code. - In CI, run `relay-compiler --validate`: it writes nothing and exits non-zero if any artifact is out of date, which catches a teammate who forgot to recompile. - Import the generated types rather than hand-writing prop types: a child's prop is `CommentRow_comment$key`, and `useFragment` then returns the exactly typed `$data`. - On Relay 21 the packages ship their own TypeScript definitions; `@types/relay-runtime` is no longer needed (the definitions are present from 21.0.1). ## Common mistakes - Treating the generated folder as an optional cache. The app imports it; deleting it breaks the build. - Building a document string at runtime to "make it dynamic". Relay cannot compile what it cannot see; use variables or `@argumentDefinitions`. - Naming a fragment after the data instead of the file (`ArticleComments_article` in `CommentThread.tsx`), which the naming rule rejects.

  • Why does Relay reject a graphql tag that interpolates a string into the document?
    Relay compiles documents ahead of time, so every document the app can send must exist in the source as static text. The Babel plugin refuses substitutions outright. Variation belongs in GraphQL itself: operation variables, `@include`/`@skip`, and fragment arguments declared with `@argumentDefinitions` and passed with `@arguments`.
  • Since Relay 19, what does the compiler require of a fragment spread under @include or @skip?
    An `@alias` on the spread. The parent then reads the fragment reference as a named, nullable property, so code has to handle the case where the fragment was not fetched. An unaliased conditional spread is a compile error; `@dangerously_unaliased_fixme` exists only as a migration escape hatch.
  • How do you stop out-of-date Relay artifacts from being merged?
    Run `relay-compiler --validate` in CI. It computes what it would write, writes nothing, and exits non-zero if any artifact differs, so a change to a `graphql` tag without a recompile fails the pipeline instead of shipping a mismatched artifact.

saying these in an interview costs you the question

  • Relay parses the graphql template literal in the browser on first render.
  • A misspelled field only shows up as a null value at runtime.
  • The __generated__ files are an optional cache the app can run without.
  • Any globally unique fragment name compiles, whatever file it is in.
  • Relay 21 with TypeScript still needs @types/relay-runtime installed.