Gatsby Image Optimization with gatsby-plugin-image and Sharp
Learn how Gatsby's build-time image pipeline works, how to configure gatsby-plugin-image with StaticImage and GraphQL, and which mistakes break builds.
31 Oct 2025, 01:23 UTC

Shipping a hero image at full camera resolution is one of the fastest ways to make a Gatsby page feel slow. The useful answer is that gatsby-plugin-image moves that work out of the browser and into gatsby build: it uses Sharp to generate multiple widths and modern formats ahead of time, then emits standard <picture> markup that references static files. This article explains the mechanism, shows one working configuration, and covers the limits that most often break builds.
The three-plugin pipeline
Gatsby image optimization is not a single plugin. It is a small chain declared in gatsby-config.js:
gatsby-plugin-imageexports theStaticImageandGatsbyImageReact components and handles lazy loading, placeholders, and layout.gatsby-plugin-sharpconnects Gatsby to the Sharp image library, which performs the actual resizing and format conversion.gatsby-transformer-sharpadds GraphQL fields—notablychildImageSharp—toFilenodes so queried images can requestgatsbyImageData.
All three must be present. If gatsby-transformer-sharp is missing, GraphQL queries for image data will fail even though StaticImage might still work for local files.
Worked configuration
Add the plugins to gatsby-config.js. List gatsby-transformer-sharp after the plugin that creates File nodes, such as gatsby-source-filesystem.
// gatsby-config.js
module.exports = {
plugins: [
'gatsby-plugin-image',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'images',
path: `${__dirname}/src/images`,
},
},
],
}
For a static asset whose path is known when you write the component, use StaticImage. Gatsby resolves the src string at compile time, runs it through Sharp, and injects the generated <picture> element.
// src/components/hero.js
import React from 'react'
import { StaticImage } from 'gatsby-plugin-image'
export default function Hero() {
return (
<StaticImage
src="../images/hero.jpg"
alt="A mountain ridge at sunrise"
width={800}
height={400}
formats={["auto", "webp"]}
placeholder="blurred"
/>
)
}
For images that arrive through the data layer—Markdown frontmatter, CMS exports, or filesystem queries—use GatsbyImage. The query runs during sourceNodes and createPages, so the image variants are fixed before HTML is written.
// src/components/blog-image.js
import React from 'react'
import { GatsbyImage, getImage } from 'gatsby-plugin-image'
export default function BlogImage({ data }) {
const image = getImage(data.markdownRemark.frontmatter.hero)
return <GatsbyImage image={image} alt="Blog hero" />
}
// GraphQL fragment used in a page query
export const query = graphql`
query {
markdownRemark {
frontmatter {
hero {
childImageSharp {
gatsbyImageData(
width: 800
layout: CONSTRAINED
placeholder: BLURRED
formats: [AUTO, WEBP]
)
}
}
}
}
}
`
The formats array controls output. "auto" lets Gatsby choose the best supported formats for the current browser, typically including WebP and AVIF where the underlying Sharp build supports them. Exact availability depends on the installed plugin and native library versions, so pinning the array explicitly is safer than relying on defaults.
What the build actually produces
After gatsby build, the optimized files live under public/static/. The component markup in the generated HTML contains a <picture> element with <source> tags for each format and a srcset listing the generated widths. No JavaScript runs in the browser to resize or re-encode images; the CDN serves pre-built variants.
Limits and common mistakes
StaticImage requires a literal path
StaticImage cannot accept a dynamic variable, prop, or remote URL. The following will fail during static analysis:
// BROKEN
<StaticImage src={imagePath} alt="Dynamic" />
<StaticImage src="https://example.com/photo.jpg" alt="Remote" />
If the path is only known at runtime, query the file through GraphQL and render it with GatsbyImage instead.
Old gatsby-image API
The gatsby-plugin-image API described here applies to Gatsby v4 and v5. Earlier projects used a package named gatsby-image with different components and query fields. That package is deprecated. Mixing the two—importing Img from gatsby-image while configuring gatsby-plugin-image—produces runtime errors or missing styles. Check package.json to confirm which package is installed, remove gatsby-image if present, and rewrite components to use StaticImage or GatsbyImage.
Native dependencies and build memory
Sharp is a native module. Build environments that lack the required system libraries, or that run with constrained memory, can fail during image processing. This is most visible when a repository contains many large source images. If a build dies mid-image-task with an out-of-memory error or a missing-library error, the cause is usually the environment, not the Gatsby configuration.
Build-time cost
Because every variant is generated during gatsby build, adding images increases build duration and the size of the public directory. A site with thousands of high-resolution photos may need longer build timeouts or a smaller set of requested widths.
Verifying the result
You do not need to guess whether the pipeline worked. Three checks confirm it:
- Run
gatsby buildin your project root and inspectpublic/static/. You should see multiple files per source image, named with width and format suffixes. - Open the generated HTML for a page containing a
StaticImage. Look for a<picture>tag withsrcsetand<source type="image/webp">entries. - During development, open the GraphQL explorer (typically at
http://localhost:8000/___graphql) and confirm thatFilenodes exposechildImageSharp.gatsbyImageData. If the field is absent,gatsby-transformer-sharpis not wired correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.