How to pull image src from Sanity using React
To pull image src URLs from Sanity, you'll want to use the @sanity/image-url package. This is the standard way to generate optimized image URLs from your Sanity image assets.
Quick Setup:
First, install the package:
npm install @sanity/image-url
# or
pnpm add @sanity/image-urlThen create a helper function (usually in a utils file):
import imageUrlBuilder from "@sanity/image-url";
import { client } from "./client"; // your Sanity client
const builder = imageUrlBuilder(client);
export function urlFor(source) {
return builder.image(source);
}Usage:
Now you can use it to generate image URLs from your Sanity image fields:
import { urlFor } from './utils/imageUrl';
// In your component
const imageUrl = urlFor(sanityImageObject).url();
// With transformations
const optimizedUrl = urlFor(sanityImageObject)
.width(800)
.height(600)
.format('webp')
.url();The sanityImageObject is what you get from your GROQ query - it's typically an object with an asset reference, not just a string.
With Next.js Image:
If you're using Next.js, you'll need to configure the domain in your next.config.js:
module.exports = {
images: {
remotePatterns: [
{ protocol: "https", hostname: "cdn.sanity.io" },
],
},
};Then use it like:
<Image
src={urlFor(image).url()}
alt="Description"
width={800}
height={600}
/>Pro tip: The image URL builder supports tons of transformations like quality settings, cropping, format conversion (including WebP and AVIF with .auto('format')), and more. Sanity's CDN automatically caches these transformed images globally for fast delivery.
import React, { useEffect, useState, useLayoutEffect } from 'react';import { Link } from "react-router-dom";import Header from "../components/Header";
import "./Home.css";
import {Carousel, Card, Image} from 'antd';import {client} from "../lib/client";import imageUrlBuilder from '@sanity/image-url'
const Home = () => { const [products,setProducts]= useState([]);
const [bannerData,setBannerData]= useState([])
const builder = imageUrlBuilder(client)
function urlFor(source) { const url= builder.image(source)
return url
}
useEffect(() => { `client.fetch(*[_type == "featured"]{ name,
genre,
image
}).then((data)=>setProducts(data))`
}, [])
useLayoutEffect(() => { `client.fetch(*[_type == "banner"]{ image
}).then((data)=>setBannerData(data))`
}, [])
return(
<>
<div className="container">
<Header/>
<Carousel autoplay className="carousel">
{bannerData?.map((data) => { return <img src={urlFor(data.image.asset).url()} className="carousel-img" alt="carousel"></img>;})}
</Carousel>
<div className="cards">
{products.map((product)=>{ return(
<Card className="card" key={product._id}><Link to="/categories" state={product.genre} className="link" style={{textDecoration:"none"}}><img src={urlFor(product.image.asset).url()} alt={product.name} className="card-content"/><h4>{product.name}</h4> </Link>
</Card>
)
})}
{/*<Card className="card">*/} {/* <h1>Shop By Category</h1>*/}{/*<div className="card-content">*/}{/* {catCard.map((e) => {*/} {/* return (*/} {/* <img*/} {/* src={e}*/} {/* alt="category"*/} {/* className="card-category"*/}{/* onClick={() => console.log("beauty")} key={e}*/}{/* ></img>*/} {/* );*/} {/* })}*/} {/* <br />*/} {/* <Link to="/" className="link">*/}{/* Shop All*/} {/* </Link>*/} {/*</div>*/} {/*</Card>*/} </div>
</div>
</>
)
}
export default Home;
Query looks like client.fetch(`*[_type == "banner"]{
image:image.asset->url
}`).then((data)=>setBannerData(data))
}, [])*[_type == "banner"] {
"image": image.asset->url
} { name: 'image',
title: 'Image',
type: 'array',
of: [{ type: 'image' }],
options: {
hotspot: true,
}
},import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Header from "../components/Header";
import "./Home.css";
import { Carousel, Card, Image } from "antd";
import { client } from "../lib/client";
import imageUrlBuilder from "@sanity/image-url";
const urlFor = (source) => imageUrlBuilder(client).image(source);
const query = `{
"featured": *[_type == "featured"]{name, genre, image},
"banner": *[_type == "banner"]{image}
}`;
const Home = () => {
const [products, setProducts] = useState([]);
const [bannerData, setBannerData] = useState([]);
useEffect(() => {
client
.fetch(query)
.then(({ featured, banner }) => {
setProducts(featured);
setBannerData(banner);
})
.catch((error) => console.log(error));
}, []);
return (
<>
<div className="container">
<Header />
<Carousel autoplay className="carousel">
{bannerData?.map((data) => {
return (
<img
src={urlFor(data.image).url()}
className="carousel-img"
alt="carousel"
></img>
);
})}
</Carousel>
<div className="cards">
{products.map((product) => {
return (
<Card className="card" key={product._id}>
<Link
to="/categories"
state={product.genre}
className="link"
style={{ textDecoration: "none" }}
>
<img
src={urlFor(product.image).url()}
alt={product.name}
className="card-content"
/>
<h4>{product.name}</h4>
</Link>
</Card>
);
})}
</div>
</div>
</>
);
};
export default Home;The image-url library only needs the top-level
imagefield to generate the urls. That will also make hotspot and crop work if you have that enabled. And you can use its methods to control dimensions and other things. • I’ve combined the queries into one, so that you don’t need to fetch 2 times. Also,
useLayoutEffectwill block your DOM rendering. Not sure if that’s the intention, but I’d generally stay clear of it (also, you should probably look into a framework that’s able to server render this if you’re planning to run this on the web. Next.js is a good choice)• I haven’t actually run this code, but let me know if it works, or what errors you get.
:43:1) at ImageUrlBuilder.url (builder.ts
:229:1) at Home.js
:56:1 at Array.map (<anonymous>)
at Home (Home.js
:45:1) at renderWithHooks (react-dom.development.js
:14985:1) at updateFunctionComponent (react-dom.development.js
:17356:1) at beginWork (react-dom.development.js
:19063:1) at HTMLUnknownElement.callCallback (react-dom.development.js
:3945:1) at Object.invokeGuardedCallbackDev (react-dom.development.js
:3994:1)
image
<Carousel autoplay className="carousel">
{bannerData?.map((data) => {
return data.image.map(img => (
<img
key={img._key}
src={urlFor(img).url()}
className="carousel-img"
alt="carousel"
></img>
));
})}
</Carousel> { name: 'image',
title: 'Image',
type: 'array',
of: [{ type: 'image' }],
options: {
hotspot: true,
}
},{
name: 'images',
title: 'Images',
type: 'array',
of: [{
type: 'image',
options: {
hotspot: true,
}
}],
},import React, { useEffect, useState } from "react";
import imageUrlBuilder from "@sanity/image-url";
import { client } from "../lib/client";
const urlFor = (source) => imageUrlBuilder(client).image(source);
const query = `{
"featured": *[_type == "featured"]{name, genre, image},
"banner": *[_type == "banner"][0]{image}
}`;
const Home = () => {
const [products, setProducts] = useState([]);
const [bannerData, setBannerData] = useState([]);
useEffect(() => {
client
.fetch(query)
.then(({ featured, banner }) => {
console.log({ featured, banner });
setProducts(featured);
setBannerData(banner);
})
.catch((error) => console.log(error));
}, []);
return (
<>
<div className="container">
<div autoplay className="carousel">
<img
src={urlFor(bannerData.image).url()}
className="carousel-img"
alt="carousel"
></img>
</div>
<div className="cards">
{products.map((product) => {
return (
<div className="card" key={product._id}>
{
product.image &&
product.image.map(img => (
<img
key={img._key}
src={urlFor(img).url()}
alt={product.name}
className="card-content"
/>
))
}
<h4>{product.name}</h4>
</div>
);
})}
</div>
</div>
</>
);
};
export default Home;:43:1) at ImageUrlBuilder.url (builder.ts
:229:1) at Home (Home.js
:43:1) at renderWithHooks (react-dom.development.js
:14985:1) at mountIndeterminateComponent (react-dom.development.js
:17811:1) at beginWork (react-dom.development.js
:19049:1) at HTMLUnknownElement.callCallback (react-dom.development.js
:3945:1) at Object.invokeGuardedCallbackDev (react-dom.development.js
:3994:1) at invokeGuardedCallback (react-dom.development.js
:4056:1) at beginWork$1 (react-dom.development.js
:23964:1)
:43:1) at ImageUrlBuilder.url (builder.ts
:229:1) at Home (Home.js
:36:1) at renderWithHooks (react-dom.development.js
:14985:1) at mountIndeterminateComponent (react-dom.development.js
:17811:1) at beginWork (react-dom.development.js
:19049:1) at HTMLUnknownElement.callCallback (react-dom.development.js
:3945:1) at Object.invokeGuardedCallbackDev (react-dom.development.js
:3994:1) at invokeGuardedCallback (react-dom.development.js
:4056:1) at beginWork$1 (react-dom.development.js
:23964:1)
import React, { useEffect, useState } from "react";import { Link } from "react-router-dom";import Header from "../components/Header";
import "./Home.css";
import { Carousel, Card, Image } from "antd";import { client } from "../lib/client";import imageUrlBuilder from "@sanity/image-url";
const urlFor = (source) => imageUrlBuilder(client).image(source);
`const query =
{ "featured": *[_type == "featured"]{name, genre, image},"banner": *[_type == "banner"][0]{image}`}`;`const Home = () => { const [products, setProducts] = useState([]);
const [bannerData, setBannerData] = useState([]);
useEffect(() => { client
.fetch(query)
.then(({ featured, banner }) => { console.log({ featured, banner }); setProducts(featured);
setBannerData(banner);
})
.catch((error) => console.log(error));
}, []);
return (
<>
<Header/>
<div className="container">
<Carousel autoplay className="carousel">
<img
src={urlFor(bannerData.image).url()} className="carousel-img"
alt="carousel"
></img>
</Carousel>
<div className="cards">
{products.map((product) => { return (
<Card className="card" key={product._id}><Link>
{ product.image &&
product.image.map(img => (
<img
key={img._key} src={urlFor(img).url()} alt={product.name} className="card-content"
/>
))
}
<h4>{product.name}</h4> </Link>
</Card>
);
})}
</div>
</div>
</>
);
};
export default Home;
This is a multiline code snippet
bannerDatato be an empty array by default. Looking through your code it seems to me that maybe you want to rethink your data modelling a bit as well? Since you have a
Carouselyou probably want
bannerto have an array of images, and not just the one image it has now? Anyways, given the data structures you have now, I think this code should work.
const query = `{
"featured": *[_type == "featured"]{name, genre, image},
"banner": *[_type == "banner"][0]{image} // [0] will get the banner document with the last update
}`;
const Home = () => {
const [products, setProducts] = useState([]);
const [bannerData, setBannerData] = useState({}); // CHANGE THIS
useEffect(() => {
client
.fetch(query)
.then(({ featured, banner }) => {
setProducts(featured);
setBannerData(banner);
//console.log({ featured, banner });
})
.catch((error) => console.log(error));
}, []);
return (
<>
<Header />
<div className="container">
{/* CHANGE FROM HERE */}
{bannerData?.image && <Carousel autoplay className="carousel">
<img
src={urlFor(bannerData.image).url()}
className="carousel-img"
alt="carousel"
></img>
</Carousel>}
{/* …TO HERE */}
<div className="cards">
{products.length > 0 && products.map((product) => {
return (
<Card className="card" key={product._id}>
<Link>
{
product.image &&
product.image.map(img => (
<img
key={img._key}
src={urlFor(img).url()}
alt={product.name}
className="card-content"
/>
))
}
<h4>{product.name}</h4>
</Link>
</Card>
);
})}
</div>
</div>
</>
);
};bannerI’d probably make a
siteSettingsor a
pagetype with an object called
bannerthat has an array of
imagecalled
imagesin it.
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.