Architecture of Gatsby's image plugins
This is a technical document for anyone who is interested in how images are implemented in Gatsby. This is not for learning how to use images in Gatsby, or even how to add image support to a plugin. This is for if you are working on Gatsby, or if you’re curious about how it’s implemented.
Image processing in Gatsby has a large number of parts, with
gatsby-plugin-image serving as a core, but using several other plugins in order to perform its job. We have tried to simplify this for end-users by documenting most of the features as if they were all part of
gatsby-plugin-image, rather than trying to document the borders of responsibility between
StaticImage is presented to the user as a React component, and they only need to interact with it as one. However it is in fact the front end to an image processing pipeline that includes three Gatsby plugins, two Babel plugins and several hooks into the Gatsby build process. This document will show exactly what each plugin does, and describe how the main parts are implemented. These are the plugins used for image processing and display.
This is the main plugin that users interact with. It includes two components for displaying images, as well as helper functions and a toolkit for plugin developers.
This is a React component, and is the actual component used to display all images. If somebody uses
StaticImage, or an image component from a CMS provider, they all use
GatsbyImage under the hood for the actual image display.
A lightweight wrapper around
GatsbyImage, this component is detected during the build process and the props are extracted to enable images to be downloaded, and processed by
gatsby-plugin-sharp without needing GraphQL.
gatsby-plugin-image includes several functions that are used by third-party source plugins to enable them to generate image data objects. This includes helpers in
gatsby-plugin-image/graphql-utils, which help plugin authors create
gatsbyImageData resolvers, as well as the
getImageData function used to generate image data at runtime by plugins with URL-based image resizing APIs. It does not perform any actual image processing (and doesn’t require sharp), but does ensure that the correct object is generated with all of the correct image sizes. It includes the
getLowResolutionImageURL function which helps when generating blurred placeholders.
The plugin exports a number of other helper functions designed to help end-users work with image data objects at runtime. These include the
getSrcSet utilities, as well as the
This includes the actual image processing functions from sharp and potrace. It includes the functions that generate the image data object, including calculating which srcset sizes to generate. It exports
generateImageData, which is used by
gatsby-plugin-image. It takes a
File node and the image processing arguments, calculates which images to generate, processes these images and returns an image data object suitable for passing to
GatsbyImage. It also exports helper functions for third party plugins to use, such as
This plugin attaches a
childImageSharp node to all image
File nodes. This includes the
gatsbyImageData resolver, which uses
gatsby-plugin-sharp to process images and return an image data object suitable for passing to
GatsbyImage. The node also includes legacy
fluid resolvers for the old
Third party plugin authors can use the
fetchRemoteFile function to download files and make use of Gatsby’s caching without needing to create a file node. This is used for low-resolution placeholder images.
The image plugin uses
createRemoteFileNode when a
StaticImage component has a remote URL as
src. It does not currently use
generateImageData requires a
Many source plugins now support
GatsbyImage. They do this by generating image data objects that can be passed to the
GatsbyImage component, or by providing their own components that wrap it. This data is either returned from a custom
gatsbyImageData resolver on the plugin’s nodes, or via a runtime helper that accepts data from elsewhere. An example of the latter would be the new Shopify plugin, which includes a
getShopifyImage function that can be used to generate images from the Shopify search or cart APIs. These plugins all use the plugin toolkit from
gatsby-plugin-image to ensure that the object they create are compatible. These do not require sharp, as the CMS or CDN provider handles the resizing. The API for each plugin’s
gatsbyImageData resolver will depend on the individual plugin, but authors are encouraged to provide an API similar to the one from
GatsbyImage is a React component used to display performant, responsive images in Gatsby. It is used under the hood by all other compatible components such as
StaticImage and components from CMS source plugins. It uses several performance tricks, and deserves most of the credit for improved performance scores when switching to the image plugin.
GatsbyImage component wraps several other components, which are all exported by the plugin. It was originally designed to allow users to compose their own custom image components, but we have not documented this, so it should currently be considered unsupported. It is something that could be looked at in the future, but until that point
StaticImage should be considered the only public components.
Throughout the component there are different versions delivered for browser and server. The reason for this is that the component performs lazy hydration: the image is loaded as soon as the SSR HTML is loaded in the browser, including blur-up and lazy-loaded images. Hydration is usually the slowest part of a React app’s page load.
GatsbyImage avoids this by skipping React for all initial image loads, leading to faster LCP. The plugin uses
gatsby-ssr to inject inline script and CSS tags to enable this. The JS attaches a
load event listener to the body, which hides the placeholder and shows the main image when any
GatsbyImage has loaded. It uses
<noscript> tags to ensure that images still work with scripts disabled.
GatsbyImage browser component, this means the load handling is skipped for any component that was rendered in SSR (which is indicated by adding a
data-gatsby-image-ssr prop in the server component). However for any images that do not have this prop, this load handling happens in React. This will be the case for any image rendered after initial load, such as after page navigation or conditional rendering.
The browser component is a class component, which uses
shouldComponentUpdate to ensure that it is never hydrated as part of the normal rendering process in the browser. Instead, the image hydrates or renders itself manually, using the
lazyHydrate function, which is itself loaded asynchronously using
import(). This takes the
ref of the wrapper’s HTML element, and uses
react-dom to render or hydrate the inner components as their own React tree. This ensures that the hydration of the images never blocks page loading, contributing to faster TTI.
GatsbyImage component supports three types of layout, which define the resizing behavior of the images. The component uses a
Sizer component inside
layout-wrapper.tsx to ensure that the wrapper is the correct size, even before any image has loaded. This avoids the page needing to re-layout, which looks bad for the user and is a serious problem for performance. For fixed layout components the size is just set via CSS. For full-width images,
Sizer uses a
padding-top hack to maintain aspect ratio as the image scales infinitely. For constrained layouts,
Sizer uses an
<img> tag with an inline, empty SVG
src to make the browser use its native
<img> resizing behaviour, even though the main image will not have loaded at this point.
GatsbyImage supports displaying a placeholder while the main image loads. There are two kinds of placeholder that are currently supported: flat colors and images. The type of placeholder is set via the image data object, and will either be a data URI for the image, or a CSS color value. The image will either be a base64-encoded low resolution raster image (called
BLURRED when using sharp) or a URI-encoded SVG (called
TRACED_SVG). The raster image will by default be 20px wide, and the same aspect ratio as the main image. This will be resized to fill the full container, giving a blurred effect. The SVG image is expected to be a single-color, simplified SVG generated using potrace. While these are the defaults produced by sharp, and also used by many third-party source plugins, we do not enforce this, and it can be any URI. We strongly encourage the use of inline data URIs, as any placeholder that needs to make a network request will defeat much of the purpose of using a placeholder. The actual placeholder element is a regular
<img> tag, even for SVGs.
The alternative placeholder is a flat color. This is expected to be calculated from the dominant color of the source image. sharp supports performing this calculation, and some CMSs provide it in the image metadata. This color is applied as a background color to a placeholder
When the main image is loaded, the placeholder is faded-out using a 250ms CSS opacity transition. We do not currently support disabling or changing this fade-out time, but it is a common request so could be a useful addition.
The main image component displays the actual image, as defined in the image data object. There is a lot of flexibility in how this is rendered, depending on which formats are provided.
In most cases, the object will include multiple sources in next-gen format such as WebP or AVIF, plus one fallback in JPEG or PNG format. In these cases, the component will render a
<picture> tag, with multiple
<source> elements and an
<img> tag for the fallback. The
<img> tags will always include a
srcset prop, with multiple image resolutions according to the layout and size of the source and input images. We strongly recommend that users and source plugin authors allow the image plugin to generate these automatically. We use
w (pixel width) units for the
srcset rather than pixel density values, as this offers the browser the most flexibility in choosing the source to download. If there are not multiple sources provided, then an
<img> tag will be rendered without an enclosing
We pass through
media props to the
<source> elements, allowing art direction to be used. However we do not attempt to do any handling of changing the aspect ratio of the container in these cases, so the user must do this themselves using CSS.
The image plugin performs a number of tricks so that the
StaticImage component appears to work like a regular React component, while being able to process images at build time.
In order to process images at build time, Gatsby needs to know which images will be needed and how they will need to be sized. It needs to do this in the scope of the build process so it has access to the sharp plugin (so it can’t be done in e.g. the Babel plugin) and it can’t be done by actually rendering the page, because that will miss images that are conditionally rendered and has similar issues with access to sharp and the Node APIs. The solution we have used is to use static analysis of the source files. This is similar to how static queries are extracted from source files.
The static analysis happens in
gatsby-plugin-image, during the
preprocessSource lifecycle. This runs immediately before query extraction. The plugin uses Babel to find references to
StaticImage imported from
gatsby-plugin-image. It then uses babel-jsx-utils to extract the value of the props. This calls
evaluate() on the AST nodes, so is able to extract values and evaluate expressions in the local scope as well as inline literals. If any of these props fails to be evaluated, it generates a structured error that includes the location of the error and a link to the docs.
If all is well, all the props have been extracted. The only required prop is
src: all the others can use default values. These props are then passed to
gatsby-plugin-sharp. This handles the actual image processing, and returns an
IGatsbyImageData object. If the
src is a remote URL then the image is downloaded and cached locally. This image data object includes all of the data needed to display an image, including a list of sources, dimensions, layout, placeholder etc (though no actual image data except for data URIs of placeholders).
This object is then written to disk as a JSON file. The filename is generated using a hash of the props, and it is saved in
.cache/caches/gatsby-plugin-image. If there have been any errors, an object with the error data is written instead. This is so the errors can be displayed in the browser console too.
The rest of the build process then happens, and the next step where the image plugin is involved is the rendering stage. The image plugin adds a local Babel plugin
babel-plugin-parse-static-images which once again finds the
StaticImage components and extracts the props. However this time, the props are only used to calculate the JSON filename hash. The plugin then adds a new
__imageData prop to the component, which is a
require() of the image data JSON file. If there are errors either during the parsing, or passed through in the JSON file, these are inserted as an
At runtime, the
StaticImage component behaves as a regular React component, but with the special
__imageData prop injected by Babel. The component itself is a lightweight wrapper around
GatsbyImage, and aside from error handling it just passes
__imageData to the wrapped