All topics
Frontend · Learning hub

Gatsby notes for developers

Master Gatsby with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallTest yourself — Gatsby quizMore Frontend notes
Gatsby

Gatsby Essentials

Gatsby Essentials Gatsby is a React framework for building fast, content-driven static sites — marketing sites, blogs, docs, e-commerce catalogs. Its defining f

Gatsby Essentials

Gatsby is a React framework for building fast, content-driven static sites — marketing sites, blogs, docs, e-commerce catalogs. Its defining feature is the GraphQL data layer: instead of fetching data at request time, Gatsby pulls data from any number of sources (Markdown/MDX files, a CMS, REST/GraphQL APIs) into a single unified GraphQL schema at build time, then generates static HTML for every page from it. This trades runtime flexibility for build-time speed and reliability — the tradeoff that made it popular for JAMstack sites before Next.js's hybrid rendering ate into that niche.

The GraphQL Data Layer

Source plugins (`gatsby-source-filesystem`, `gatsby-source-contentful`, etc.) pull raw data into Gatsby's internal GraphQL store during the build. Transformer plugins (`gatsby-transformer-remark` for Markdown, `gatsby-plugin-image` for images) reshape that raw data into queryable nodes. You then query it with a `page query` (top-level, in a page component) or a `StaticQuery`/`useStaticQuery` (in any component, including non-page components like layouts).

// gatsby-config.js
module.exports = {
  siteMetadata: {
    title: 'My Gatsby Blog',
    siteUrl: 'https://example.com',
  },
  plugins: [
    'gatsby-plugin-image',
    'gatsby-plugin-sharp',
    {
      resolve: 'gatsby-source-filesystem',
      options: { name: 'posts', path: `${__dirname}/content/posts` },
    },
    'gatsby-transformer-remark', // Markdown -> MarkdownRemark nodes
    {
      resolve: 'gatsby-source-contentful',
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
      },
    },
  ],
}
// src/templates/blog-post.jsx
import { graphql } from 'gatsby'
import { GatsbyImage, getImage } from 'gatsby-plugin-image'

export default function BlogPost({ data }) {
  const { markdownRemark } = data
  const heroImage = getImage(markdownRemark.frontmatter.hero)

  return (
    <article>
      <h1>{markdownRemark.frontmatter.title}</h1>
      <GatsbyImage image={heroImage} alt={markdownRemark.frontmatter.title} />
      <div dangerouslySetInnerHTML={{ __html: markdownRemark.html }} />
    </article>
  )
}

// Page query — runs at build time, result is injected as the `data` prop
export const query = graphql`
  query BlogPostBySlug($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
        date(formatString: "MMMM D, YYYY")
        hero {
          childImageSharp {
            gatsbyImageData(width: 1200, placeholder: BLURRED)
          }
        }
      }
    }
  }
`

Because the query runs at build time, there's no loading state to handle in the component — `data` is always populated before the page is rendered. This is the biggest mental shift coming from client-side data fetching: you describe what data a page needs declaratively, and Gatsby figures out how to get it before render, not after.

gatsby-node.js: Programmatic Page Creation

Pages under `src/pages/` are created automatically by filesystem routing, but for data-driven pages (one page per blog post, per product) you generate them programmatically in `gatsby-node.js` using the `createPages` API, querying your data layer and calling `createPage` for each result.

// gatsby-node.js
const path = require('path')

exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions
  const blogPostTemplate = path.resolve('./src/templates/blog-post.jsx')

  const result = await graphql(`
    query {
      allMarkdownRemark {
        nodes {
          fields { slug }
        }
      }
    }
  `)

  if (result.errors) {
    reporter.panicOnBuild('Error loading markdown for page creation', result.errors)
    return
  }

  result.data.allMarkdownRemark.nodes.forEach((node) => {
    createPage({
      path: node.fields.slug,
      component: blogPostTemplate,
      context: { slug: node.fields.slug }, // passed as $slug to the page query
    })
  })
}

// onCreateNode — runs per raw node, used to derive the slug field above
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions
  if (node.internal.type === 'MarkdownRemark') {
    const slug = `/blog/${path.basename(node.fileAbsolutePath, '.md')}/`
    createNodeField({ node, name: 'slug', value: slug })
  }
}

// createSchemaCustomization — lock down types so queries fail fast on typos
exports.createSchemaCustomization = ({ actions }) => {
  actions.createTypes(`
    type MarkdownRemark implements Node {
      frontmatter: Frontmatter
    }
    type Frontmatter {
      title: String!
      date: Date @dateformat
      hero: File @fileByRelativePath
    }
  `)
}

SSG vs DSG vs SSR

Gatsby is best known for pure SSG (Static Site Generation — every page pre-rendered at build time), but per-page rendering options let you opt individual pages out of that when the dataset is too large to build in full or needs to be fresh on every request.

// src/templates/product.jsx
import { graphql } from 'gatsby'

export default function Product({ data }) { /* ... */ }

export const query = graphql`
  query ($id: String!) {
    product(id: { eq: $id }) { name price description }
  }
`

// SSG (default) — pre-rendered at build time, fastest, needs a full rebuild to update

// DSG — Deferred Static Generation: page is generated on first request after
// deploy, then cached like a normal static page. Good for thousands of rarely
// visited pages (long-tail product pages) where building all of them up front
// would blow up build time.
export async function config() {
  return { defer: true }
}

// SSR — Server-Side Rendering: renders on every request, for data that must be
// fresh (stock levels, personalized content). Use getServerData in the page module.
export async function getServerData({ params }) {
  const res = await fetch(`https://api.example.com/products/${params.id}`)
  return { props: await res.json() }
}
  • SSG — build-time HTML for every page. Best performance, but a single content change triggers a full rebuild/redeploy.

  • DSG — generated on first request post-deploy, then served statically. Keeps huge sites' build times manageable.

  • SSR — rendered per request via `getServerData`. Use sparingly — it forfeits Gatsby's static-hosting speed advantage for that page.

Image Optimization with gatsby-plugin-image

`gatsby-plugin-image` (replacing the older `gatsby-image`) processes images at build time — resizing, format conversion (WebP/AVIF), lazy loading, and blur-up placeholders — and exposes them through the `gatsbyImageData` GraphQL resolver, avoiding hand-written `<img>` tags and layout shift.

import { StaticImage } from 'gatsby-plugin-image'

// StaticImage — for images whose path is known at build time, no GraphQL query needed
function Hero() {
  return (
    <StaticImage
      src="../images/hero.jpg"
      alt="Team working together"
      layout="fullWidth"
      placeholder="blurred"
      formats={['auto', 'webp', 'avif']}
    />
  )
}

// GatsbyImage — for images that come from a GraphQL query (CMS, dynamic content)
import { GatsbyImage, getImage } from 'gatsby-plugin-image'

function ProductCard({ product }) {
  const image = getImage(product.thumbnail)
  return <GatsbyImage image={image} alt={product.name} />
}

Common Gotchas

  • Expecting page queries to work in nested components — `graphql` page queries only run on page/template components exported by `gatsby-node.js` or in `src/pages/`; use `useStaticQuery` for non-page components like a Header.

  • Forgetting the data layer is build-time — a CMS content edit doesn't appear until the next build (or DSG regeneration on next request); this surprises people expecting live updates like a traditional SSR app.

  • Using `<img>` instead of gatsby-plugin-image — loses automatic resizing, lazy loading, and format negotiation; also a common cause of Cumulative Layout Shift regressions.

  • Build times ballooning with thousands of pages — pure SSG rebuilds every page on every deploy; switch large, rarely-changing sections to DSG instead of accepting ever-growing CI times.

  • Mutating gatsby-node context incorrectly — `context` values passed to `createPage` must match the `$variable` names in the page query exactly, or the query silently returns null data instead of erroring.

Keep your Gatsby knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever