How Gatsby's GraphQL Data Layer Works: Static Queries vs Page Queries Explained

Gatsby's GraphQL data layer extracts, compiles, and executes GraphQL queries at build time, with page queries running once per page and injecting data via props.data, while static queries run once globally and share results through React context.

Gatsby's GraphQL data layer serves as the central nervous system of the gatsbyjs/gatsby repository, transforming disparate data sources into a unified schema that powers both static sites and server-side rendering. During the build process, Gatsby scans your source code to extract GraphQL documents, validates them against the central schema, and executes them through a sophisticated pipeline involving the query-compiler and file-parser modules. Understanding how page queries and static queries differ in scope, execution, and runtime consumption is essential for optimizing site architecture.

The Query Extraction and Compilation Pipeline

Gatsby's data layer begins with file scanning and AST parsing. The parseQueries function in packages/gatsby/src/query/query-compiler.js glob-matches all .js, .tsx, and .mdx files that import graphql, then parseToAst in file-parser.js uses Babel to parse each file into an AST. The findGraphQLTags walker locates tagged template literals (graphql`...`) and extracts three document types: page queries, static queries, and config queries.

Anonymous queries receive deterministic names via generateQueryName using the pattern <type>-<slug>-<hash>. The query-compiler.js module then validates these queries against the compiled schema, resolves fragments, and deduplicates requests. Finally, the GraphQLRunner executes validated queries and caches results using SHA-1 hashes stored in LMDB to skip redundant work on subsequent builds.

How Page Queries Work

Page queries are scoped to individual page components and support dynamic variables derived from URL context.

  • Scope: Exported as export const query = graphql\...`from files insrc/pagesor templates created viacreatePage`.
  • Execution: Run once per page instance by query-runner.ts, with results written to public/page-data/<path>/page-data.json.
  • Access: Injected directly into the page component as props.data.

The query-runner.ts module handles the execution lifecycle, distinguishing page queries via queryJob.queryType === 'page'. These queries can reference $variables passed through the pageContext property in gatsby-node.js, enabling dynamic filtering and pagination.

// src/pages/blog.js
import React from "react"
import { graphql } from "gatsby"

export const query = graphql`
  query BlogPage($skip: Int!, $limit: Int!) {
    allMarkdownRemark(skip: $skip, limit: $limit) {
      edges {
        node {
          frontmatter { title }
          excerpt
        }
      }
    }
  }
`

export default function BlogPage({ data, pageContext }) {
  const posts = data.allMarkdownRemark.edges
  return (
    <ul>
      {posts.map(({ node }) => (
        <li key={node.frontmatter.title}>{node.frontmatter.title}</li>
      ))}
    </ul>
  )
}

How Static Queries Work

Static queries provide global data access for components that are not page components.

  • Scope: Defined inside any React component using the useStaticQuery hook or the legacy <StaticQuery> component.
  • Execution: Run once per build and cached globally in StaticQueryContext.
  • Access: Retrieved via useStaticQuery or <StaticQuery>, which read from the React context populated by query-result-store.js.
  • Limitations: Cannot reference $variables dependent on page URLs.

The runtime implementation lives in packages/gatsby/cache-dir/static-query.js. The useStaticQuery hook reads from StaticQueryContext, throwing an error if the query result is missing from the global cache.

// packages/gatsby/cache-dir/static-query.js
export const useStaticQuery = query => {
  const context = React.useContext(StaticQueryContext)
  if (context[query]?.data) return context[query].data
  throw new Error(`The result of this StaticQuery could not be fetched…`)
}

Static Query Example with Hooks

// src/components/site-metadata.js
import React from "react"
import { graphql, useStaticQuery } from "gatsby"

export default function SiteMetadata() {
  const data = useStaticQuery(graphql`
    query SiteInfo {
      site {
        siteMetadata {
          title
          description
        }
      }
    }
  `)

  return (
    <header>
      <h1>{data.site.siteMetadata.title}</h1>
      <p>{data.site.siteMetadata.description}</p>
    </header>
  )
}

Legacy StaticQuery Component

import React from "react"
import { StaticQuery, graphql } from "gatsby"

export default function Footer() {
  return (
    <StaticQuery
      query={graphql`
        query FooterInfo {
          site {
            siteMetadata { author }
          }
        }
      `}
      render={data => <footer>© {data.site.siteMetadata.author}</footer>}
    />
  )
}

Key Implementation Files

The following source files constitute the core of Gatsby's GraphQL data layer:

File Role
packages/gatsby/src/query/file-parser.js Parses source files, extracts static, page, and config queries, auto-generates names via findGraphQLTags and generateQueryName.
packages/gatsby/src/query/query-compiler.js Validates extracted queries, resolves fragments, deduplicates, and returns processed query maps.
packages/gatsby/src/query/query-runner.ts Executes compiled queries, hashes results with SHA-1, writes JSON to public/page-data, and updates the Redux store.
packages/gatsby/cache-dir/static-query.js Runtime implementation of useStaticQuery and <StaticQuery>; supplies data via StaticQueryContext.
packages/gatsby/cache-dir/query-result-store.js Holds the in-memory map of static-query results for browser and SSR bundles.
packages/gatsby/src/query/query-watcher.ts Watches for file changes during development and triggers re-compilation of queries.

Summary

  • Page queries execute per-page, support URL-based variables via pageContext, and inject data through props.data.
  • Static queries execute once globally, cache results in StaticQueryContext, and provide site-wide data via useStaticQuery or <StaticQuery>.
  • The file-parser.js module extracts all GraphQL documents using Babel AST parsing.
  • The query-compiler.js validates and optimizes queries before execution.
  • The query-runner.ts handles execution, SHA-1 hashing, and LMDB caching to avoid redundant processing.
  • All query results are written to public/page-data/ for runtime consumption.

Frequently Asked Questions

Can static queries accept variables from page context?

No, static queries cannot reference $variables that depend on a page's URL or pageContext because they are executed once per build and shared globally across all components. Page queries, by contrast, accept variables passed through createPage's context property and execute individually for each page instance. This architectural distinction makes static queries ideal for global site metadata and page queries essential for dynamic content listings.

Where does Gatsby store query results during the build process?

The query-runner.ts module writes all query results to JSON files under public/page-data/ and maintains an in-memory cache using LMDB with SHA-1 hashed results. Static query results populate the StaticQueryContext React context available via useStaticQuery, while page query results are injected directly into page components as props.data at runtime.

How does Gatsby handle anonymous GraphQL queries?

If a query lacks an explicit name, the generateQueryName function in file-parser.js automatically creates a deterministic identifier using the pattern <type>-<slug>-<hash>. This ensures every extracted document has a unique reference for caching and execution purposes while maintaining consistent builds across environments.

How does the development server watch for query changes?

The query-watcher.ts file monitors source files for modifications during development, triggering re-extraction via file-parser.js and re-compilation through query-compiler.js whenever GraphQL documents change. This hot-reloading capability ensures the GraphQL data layer reflects code changes without requiring a full rebuild.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →