How to Lazy Load Images in React to Improve Page Performance
Use React’s native loading="lazy" for offscreen images, while keeping visible and likely LCP images eager. Learn responsive markup, SSR caveats, validation, and troubleshooting.
To lazy load an offscreen image in React, render a normal <img> and set loading="lazy". Keep images visible when the page opens, especially the likely Largest Contentful Paint (LCP) image, eager. Add known width and height so the browser can reserve space before an image arrives.
function ArticleImage() {
return (
<img
src="/images/article-detail.jpg"
alt="A detail from the article"
width={1200}
height={800}
loading="lazy"
/>
);
}
React passes loading to the browser as an image loading hint. The browser can defer an offscreen image request until it approaches the viewport. This is usually all a React page needs for ordinary below-the-fold images; no image-loading package or custom scroll handler is required. See the official React <img> reference and MDN lazy-loading guide.
1. Choose which images to defer
Lazy loading is a per-image decision. Use it for images the visitor does not need immediately, such as later article illustrations, gallery items below the fold, or images in content farther down a long page.
- Initially visible image: leave the default eager behavior unless measurements on your page support another choice.
- Likely LCP image: keep it eager so the browser can discover and request it promptly. Lazy-loading it may delay the most prominent content.
- Below-the-fold content image: use
loading="lazy". - Unknown or conditional source: do not render an image with an empty string for
src. Omit the element until a usable source exists, or passnull.
A blanket rule that adds lazy loading to every image can defer content people can already see. In server-rendered React, this distinction also matters for preload hints: React documents that it generates a preload hint for an image by default, while loading="lazy" prevents that automatic preload. If an image should load immediately at lower priority, React documents fetchPriority="low" as a separate option; it is not a replacement for deciding whether the image belongs in the initial view. See React’s preload reference.
2. Add dimensions and responsive sources
When dimensions are known, supply both width and height. The browser can reserve the image’s space before the bytes arrive, reducing layout shifts as content loads. The values describe the image’s intrinsic dimensions; CSS can still control the displayed size.
function ProductGalleryImage() {
return (
<img
src="/images/product-960.jpg"
srcSet="/images/product-480.jpg 480w, /images/product-960.jpg 960w, /images/product-1440.jpg 1440w"
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 480px"
alt="A blue ceramic cup on a wooden table"
width={1440}
height={960}
loading="lazy"
/>
);
}
srcSet and sizes help the browser select a suitable image resource for the rendered slot and viewport. They work alongside loading: responsive attributes affect which file is selected, while loading="lazy" affects when an offscreen image is fetched. Refer to the React image reference for the supported props and MDN’s image element reference for HTML behavior.
3. Use native loading before custom JavaScript
For normal offscreen images, start with the browser’s native loading hint. It avoids maintaining custom visibility and request logic, and modern browsers support browser-level image lazy loading for common cases.
Use Intersection Observer when you need a behavior the native hint does not provide, such as a custom visibility-triggered transition or tightly controlled request timing. A custom implementation must decide what markup appears before the request, when to assign the real source, how failures are shown, and how to handle target browsers. Compare it with native loading against your audience’s browser support, required fetch threshold, placeholder behavior, amount of custom code, failure behavior, and measured impact. The documentation cited here does not establish a need for a third-party library for a typical image list.
Do not confuse image loading with React.lazy(). React’s lazy API defers loading a component’s JavaScript code until that component is first rendered. The image prop loading="lazy" asks the browser to defer an image request. See React’s lazy reference.
4. Validate the behavior on your page
- Open the page at representative mobile and desktop sizes. Identify images visible without scrolling and the likely LCP image.
- Keep those initial images eager. Add
loading="lazy"to images farther down the page. - Add intrinsic width and height where known. Preserve suitable
srcSetandsizesfor responsive images. - Inspect the rendered HTML to confirm the intended
loading, dimensions, and responsive attributes made it through any React or framework image component. - Use the browser network panel while loading and scrolling. Confirm the visible image starts promptly and offscreen image requests are deferred as expected.
- Compare page performance before and after using a consistent test setup. Do not claim a numeric speedup without measuring the actual page under comparable conditions.
Lazy loading defers requests; it does not reduce the bytes in an image file. If transfers are still large, handle image dimensions, formats, and compression as a separate optimization. MDN’s guide to authoring fast-loading HTML pages discusses image optimization as well as making image dimensions knowable to the browser.
5. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
| The hero image appears late | An initially visible or likely LCP image has loading="lazy". |
Remove the lazy hint for that image and verify its request begins promptly. Avoid applying lazy loading indiscriminately. |
| An image has no reserved space and content jumps | Intrinsic dimensions are missing or do not match the asset’s aspect ratio. | Set accurate width and height attributes. Check CSS sizing and the selected responsive variant. |
| An image never appears | The URL may be invalid, the source may be empty, or a custom loader may not assign the real source. | Inspect the rendered src and network request. Omit the element or use null when there is no source; do not use an empty string. |
| Every image request starts immediately | The rendered element may not have the lazy prop, or a framework component may use different defaults or options. | Inspect the final HTML and that component’s documentation. Check the network panel after a fresh load. |
| The page is slower after adding lazy loading | An important initial image may have been deferred, or the comparison may not use consistent conditions. | Keep visible and likely LCP images eager, then compare with the same viewport and test setup. |
| Images load at the right time but still transfer too much data | Lazy loading changes request timing, not file size. | Optimize dimensions, responsive variants, formats, and compression separately. |
6. Performance, reliability, and cost considerations
- Performance: deferring offscreen requests can reduce work and network use during initial page loading. The result depends on the page and its images; measure rather than assuming a particular speedup.
- Reliability: native loading leaves request scheduling to the browser. Custom visibility code adds placeholder, timing, and failure handling that your application must maintain.
- Image weight: choose appropriately sized responsive files and compress them as needed. Lazy loading does not compress or resize image bytes.
- Cost: the HTML loading hint and browser behavior do not require a separate image-loading library. Image hosting, transformation, or delivery costs are separate choices; select those based on your assets and delivery needs.
7. Capture the finished page with ScreenshotNeo
After changing image-loading behavior, you may want a saved screenshot of the page at a particular viewport or scroll state for a review. ScreenshotNeo is a website screenshot API and MCP server for developers. It can capture PNG, JPEG, WebP, or PDF output, and its options include viewport and device presets, full-page capture with lazy images loaded, waiting for a selector, delay or network idle, and custom CSS and JavaScript. See the ScreenshotNeo API documentation for parameters.
Or skip the browser setup
One GET request captures a page. The response format can be set to WebP as shown here; replace the example URL with your page and provide your API key.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with response headers indicating the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. The same features are available on every plan. Read the API docs and sign up for 1,000 free screenshots a month, no card required.
8. Frequently asked questions
Does loading="lazy" work on a React image component?
Yes. React’s built-in <img> accepts loading as a prop and passes the hint to the browser. For a framework-specific image component, check that component’s API and rendered markup.
Should I lazy load every image in a long article?
Lazy-load images that are initially offscreen. Leave images visible at first paint, especially likely LCP content, eager unless measurement supports another choice.
Does lazy loading improve image quality or reduce file size?
No. It defers a request. Use suitable image dimensions, responsive variants, formats, and compression to address quality and transfer size.
Is React.lazy() the way to lazy load an image?
No. React.lazy() defers component code. Use the image element’s loading="lazy" prop to request browser-level image deferral.


