Lazy Loading Images with Claude Code: A Beginner-Safe Implementation Guide
Implement lazy image loading with Claude Code without hurting LCP, CLS, responsive images, SEO, or UX.
Lazy loading images is not the same as adding loading="lazy" everywhere. If Claude Code lazily loads the hero image, the page can get a worse Largest Contentful Paint, which is the moment the main visible content appears. If it loads every gallery, product thumbnail, and related-article image up front, mobile readers pay for images they may never see.
This refreshed guide gives you a safe beginner pattern: native lazy loading first, eager loading for the LCP or hero image, decoding, fetchpriority, dimensions to prevent CLS, responsive srcset and sizes, IntersectionObserver for advanced cases, placeholders, SEO pitfalls, measurement, and safer Claude Code prompts. I checked the recommendations against MDN’s img element reference, Intersection Observer API, web.dev’s browser-level image lazy loading, Core Web Vitals, and Chrome’s LCP request discovery.
For adjacent work, pair this with image optimization, skeleton loading, and performance optimization. Lazy loading is only one part of a serious image strategy.
The Rule of Thumb
The first rule is simple: do not lazy-load what the user can see immediately. Use native lazy loading for images below the first viewport, and reserve layout space for every image with width and height or an equivalent aspect-ratio.
| Image location | loading | fetchpriority | decoding | Required guardrail |
|---|---|---|---|---|
| Hero image or LCP candidate | eager or omitted | Consider high | sync or async | Make it discoverable in initial HTML |
| Mid-article diagram | lazy | auto | async | Keep dimensions |
| Product grid after the first screen | lazy | auto | async | Use srcset and sizes |
| Carousel or infinite scroll | Depends | auto | async | Use IntersectionObserver when needed |
The common beginner mistake is thinking “large image” means “lazy image.” The real question is whether the image is needed for the first meaningful view. Chrome’s LCP guidance is explicit: if the LCP element is an image, keep it discoverable, prioritize it, and avoid loading=lazy.
Copy-Paste HTML
Start with framework-free HTML. The first image is the hero, so it uses eager loading and a high fetch priority. The second image is lower in the article, so it uses native lazy loading and asynchronous decoding.
<img
class="hero-image"
src="/images/hero/product-dashboard-1200.webp"
srcset="
/images/hero/product-dashboard-640.webp 640w,
/images/hero/product-dashboard-1200.webp 1200w
"
sizes="100vw"
alt="First view of a product dashboard"
width="1200"
height="675"
loading="eager"
fetchpriority="high"
decoding="sync"
/>
<img
class="article-image"
src="/images/articles/setup-step-800.webp"
srcset="
/images/articles/setup-step-400.webp 400w,
/images/articles/setup-step-800.webp 800w
"
sizes="(max-width: 720px) 100vw, 720px"
alt="Screenshot of the setup step"
width="800"
height="450"
loading="lazy"
decoding="async"
/>
decoding="async" is a browser hint that lets image decoding happen without blocking other rendering work as tightly. It is not a magic speed switch, but it is a sensible default for article images and thumbnails. For a hero image, test sync versus async instead of copying one value forever.
Prevent CLS With Reserved Space
CLS means Cumulative Layout Shift. In plain language, it measures how much the page jumps while a reader is trying to read or tap. If an image loads with no reserved height, the paragraph, ad slot, newsletter form, or purchase CTA below it moves. That is a UX bug, not just a Lighthouse warning.
.image-frame {
aspect-ratio: 16 / 9;
background: #f3f4f6;
overflow: hidden;
}
.image-frame > img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
@media (prefers-reduced-motion: no-preference) {
.image-frame > img {
transition: opacity 180ms ease-out;
}
}
If your CMS knows the original image dimensions, store them and pass them into the template. If you must handle unknown third-party images, reserve a stable container with aspect-ratio, then let the real image fit inside it. This is less precise than real dimensions, but it is much better than a zero-height placeholder.
A Small React Component
When you ask Claude Code to update a React codebase, put the rules inside a small component. That keeps the policy consistent: priority images are eager and high priority, normal content images are lazy, and all images keep dimensions.
type SmartImageProps = {
src: string;
srcSet?: string;
sizes?: string;
alt: string;
width: number;
height: number;
priority?: boolean;
className?: string;
};
export function SmartImage({
src,
srcSet,
sizes,
alt,
width,
height,
priority = false,
className,
}: SmartImageProps) {
const loading = priority ? "eager" : "lazy";
const fetchPriority = priority ? "high" : "auto";
const decoding = priority ? "sync" : "async";
return (
<span
className={`image-frame ${className ?? ""}`}
style={{ aspectRatio: `${width} / ${height}` }}
>
<img
src={src}
srcSet={srcSet}
sizes={sizes}
alt={alt}
width={width}
height={height}
loading={loading}
fetchPriority={fetchPriority}
decoding={decoding}
/>
</span>
);
}
This component is intentionally boring. It does not hide SEO-critical images behind JavaScript, and it does not invent a new image pipeline. In Next.js, Astro, or a CMS-driven site, you can adapt the same policy to the local image component.
Three Product Use Cases
The first use case is an ecommerce product grid. The first few product images may be visible immediately, so do not blindly mark every item lazy. The second screen, recommendations, reviews, and recently viewed products are better candidates. A concrete failure mode is adding fetchpriority="high" to every product thumbnail. That forces the browser to treat everything as urgent and can delay CSS, fonts, or the real hero image.
The second use case is a tutorial or media article. The cover image is often the LCP candidate, so keep it eager. Screenshots lower in the article, diagrams after code blocks, and related post cards can be lazy. A concrete failure mode is using a blurred background placeholder but leaving the real image with an empty alt. Search engines and screen readers need the real image text when the image has meaning.
The third use case is a SaaS dashboard. Avatars, customer logos, report thumbnails, and audit screenshots can appear in large lists. Lazy loading offscreen rows helps, but do not delay the top chart, onboarding illustration, or CTA area that helps the user take the first action. For measurement planning across CTAs and revenue events, connect this with Claude Code analytics implementation.
A fourth case is a gallery or horizontal carousel. Native lazy loading is often enough now, but IntersectionObserver still helps when images live inside scroll containers, when you need a custom preload distance, or when a carousel hides slides in a way that native heuristics cannot handle cleanly.
When to Use IntersectionObserver
Use native lazy loading first. Reach for IntersectionObserver only when you need more control: loading 300px before an image appears, replacing data-src, removing placeholder classes, or observing images inside a custom scroll container.
<img
class="js-lazy-image"
src="/images/placeholders/report-thumb.svg"
data-src="/images/reports/report-2026.webp"
data-srcset="/images/reports/report-2026.webp 1x"
alt="Monthly report thumbnail"
width="640"
height="360"
/>
const lazyImages = document.querySelectorAll("img[data-src]");
function loadImage(img) {
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
img.removeAttribute("data-src");
img.removeAttribute("data-srcset");
}
if ("IntersectionObserver" in window) {
const observer = new IntersectionObserver((entries, currentObserver) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
loadImage(entry.target);
currentObserver.unobserve(entry.target);
});
}, {
rootMargin: "300px 0px",
threshold: 0.01,
});
lazyImages.forEach((image) => observer.observe(image));
} else {
lazyImages.forEach(loadImage);
}
Keep dimensions even in the JavaScript path. Also remember that a data-src image may never load if JavaScript fails. Do not use this pattern for the main product image, the article hero, or any image that must be visible to crawlers and assistive technology from the initial HTML.
Measure Before and After
Core Web Vitals currently focus on LCP for loading, INP for interaction, and CLS for visual stability. The common good thresholds are LCP within 2.5 seconds, INP at 200 milliseconds or less, and CLS at 0.1 or less. For lazy images, LCP and CLS are the two metrics most likely to move.
npm install web-vitals
import { onCLS, onINP, onLCP } from "web-vitals";
onLCP((metric) => {
const lastEntry = metric.entries.at(-1);
console.log("LCP", metric.value, lastEntry?.element);
});
onCLS((metric) => {
console.log("CLS", metric.value, metric.entries);
});
onINP((metric) => {
console.log("INP", metric.value, metric.entries);
});
In Chrome DevTools, check whether the LCP element is the expected hero image, whether the request is discoverable from HTML, and whether any image causes layout shift. After publishing, compare PageSpeed Insights, Search Console, and your own analytics. Lab tools are useful, but real mobile readers can expose a different bottleneck.
A Safer Claude Code Prompt
Claude Code is very good at broad replacements. That is useful and risky. Give it scope, non-goals, and verification up front.
{
"goal": "Add safe lazy loading for images",
"scope": [
"Only article-body and product-list images are in scope",
"Do not lazy-load first-viewport or LCP candidate images"
],
"rules": [
"Keep alt, width, and height on every img",
"Use loading=\"lazy\" and decoding=\"async\" below the fold",
"Use loading=\"eager\" or omit loading for hero images",
"Use fetchpriority=\"high\" on at most one LCP candidate"
],
"verification": [
"Check MDX and code fences",
"Use DevTools to inspect LCP and CLS",
"Check mobile width for image, text, and CTA overlap"
]
}
Ask Claude Code to report the images it did not lazy-load and why. That explanation is useful in review because the best lazy-loading implementation often contains deliberate non-changes.
Failure Checklist
- The hero image, main product image, or top CTA background was lazy-loaded.
widthandheightwere removed during a refactor.- Every image received
fetchpriority="high". srcsetcandidates have different aspect ratios without art direction.- A CSS background image was treated as if
loadingapplies to it. - Meaningful diagrams were given empty
alttext. - Important images depend entirely on JavaScript-inserted
src. - The placeholder is more visually dominant than the real content.
CTA and Verification Note
Lazy image loading is not just a performance score trick. It affects reading depth, CTA visibility, product browsing, and the first action inside an app. For personal workflows, start with the free Claude Code cheatsheet. For reusable implementation prompts and review templates, use the products page. For team rollout across performance, SEO, analytics, and monetization paths, use Claude Code training and consultation.
For this refresh, I tested the article logic against the official docs listed above and rebuilt the examples around a practical rule: decide which images must not be lazy before adding lazy loading to the rest. In Masa’s workflow, the stable pattern was eager hero, lazy lower screenshots, explicit dimensions everywhere, responsive srcset for mobile, and a Claude Code prompt that includes failure modes and measurement tasks from the start.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Safety Ladder: Expand Access Without Losing Control
A beginner-friendly ladder for moving Claude Code from read-only to limited edits, proof commands, and deploy checks.
Claude Code Small PR Proof Pack: Make Tiny Changes Reviewable
A practical proof pack for Claude Code PRs: diff, checks, public URL, CTA path, and rollback note.
Claude Code Review Gate Before Commit: Diff, Tests, Public URL, and CTA Checks
A commit-time review gate for Claude Code work: diff scope, build, public URL, revenue CTA links, missing tests, and unrelated files.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.