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 topublic/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
useStaticQueryhook or the legacy<StaticQuery>component. - Execution: Run once per build and cached globally in
StaticQueryContext. - Access: Retrieved via
useStaticQueryor<StaticQuery>, which read from the React context populated byquery-result-store.js. - Limitations: Cannot reference
$variablesdependent 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 throughprops.data. - Static queries execute once globally, cache results in
StaticQueryContext, and provide site-wide data viauseStaticQueryor<StaticQuery>. - The
file-parser.jsmodule extracts all GraphQL documents using Babel AST parsing. - The
query-compiler.jsvalidates and optimizes queries before execution. - The
query-runner.tshandles 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →