Preventing image jank with Sanity images in Gatsby (gatsby-image vs @sanity/image-url)
Looking at the CSS Tricks article you linked, it's about preventing content layout shift (CLS) when images load by reserving the proper space with aspect ratio. The good news is that gatsby-plugin-image handles this automatically when you use it with Sanity images through gatsby-source-sanity!
The modern approach with gatsby-source-sanity and gatsby-plugin-image gives you lazy loading, proper aspect ratios, and prevents content jank out of the box. Here's how to set it up:
The Modern Solution (Recommended)
Use the gatsbyImageData field in your GraphQL queries. This automatically includes dimensions and generates the proper placeholder:
import {GatsbyImage} from 'gatsby-plugin-image'
export const query = graphql`
query {
sanityPost {
mainImage {
asset {
gatsbyImageData(
width: 800
placeholder: BLURRED
fit: FILLMAX
)
}
}
}
}
`
function BlogPost({data}) {
return (
<GatsbyImage
image={data.sanityPost.mainImage.asset.gatsbyImageData}
alt="Blog post image"
/>
)
}The GatsbyImage component automatically:
- Reserves space using the image's aspect ratio (preventing layout shift)
- Lazy loads images as they enter the viewport
- Shows a blurred placeholder while loading
- Generates responsive srcsets
If You're Using image-url.js Directly
If you need to use @sanity/image-url outside of GraphQL (like in your image-url.js file), you can still prevent jank by:
- Query the image dimensions from Sanity and use them to set aspect ratio CSS:
// In your GraphQL query, get dimensions
mainImage {
asset {
_id
metadata {
dimensions {
width
height
aspectRatio
}
}
}
}- Apply aspect ratio styling to reserve space:
<div style={{
aspectRatio: `${width} / ${height}`,
position: 'relative'
}}>
<img
src={imageUrlBuilder.image(imageRef).width(800).url()}
loading="lazy"
style={{
position: 'absolute',
width: '100%',
height: '100%',
objectFit: 'cover'
}}
/>
</div>For Portable Text / Block Content
If you're rendering images inside Portable Text, use the @portabletext/react serializer with GatsbyImage:
import {PortableText} from '@portabletext/react'
import {GatsbyImage} from 'gatsby-plugin-image'
import {getGatsbyImageData} from 'gatsby-source-sanity'
const sanityConfig = {projectId: 'your-id', dataset: 'production'}
const components = {
types: {
image: ({value}) => {
const imageData = getGatsbyImageData(
value.asset._ref,
{maxWidth: 800},
sanityConfig
)
return <GatsbyImage image={imageData} alt={value.alt || ''} />
}
}
}
<PortableText value={portableTextContent} components={components} />Don't Modify image-url.js - Use gatsbyImageData Instead
The key insight is that you don't need to combine gatsby-image and @sanity/image-url. The gatsby-source-sanity plugin already integrates with gatsby-plugin-image through the gatsbyImageData field, giving you all the performance benefits including preventing layout shift automatically.
Your image-url.js file should only be used for cases where you need direct URL manipulation (like social media meta tags), not for rendering images in your components. For component rendering, always prefer the gatsbyImageData approach which handles aspect ratios, lazy loading, and placeholder generation automatically.
Show original thread9 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.