[{"data":1,"prerenderedAt":1497},["ShallowReactive",2],{"content:\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F":3,"surroundings:\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F":1488},{"id":4,"title":5,"body":6,"description":1471,"extension":1472,"meta":1473,"navigation":936,"path":1483,"seo":1484,"stem":1486,"__hash__":1487},"content\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Findex.md","Lazy Loading Images Without Hurting LCP",{"type":7,"value":8,"toc":1458},"minimark",[9,14,29,41,145,150,164,167,171,191,211,215,222,238,381,385,395,398,402,411,430,538,542,570,582,827,831,860,866,1037,1041,1063,1073,1077,1097,1111,1247,1251,1254,1269,1388,1398,1402,1443,1448,1451,1454],[10,11,13],"h1",{"id":12},"lazy-loading-images-without-hurting-lcp-a-workflow-for-safe-deferral","Lazy Loading Images Without Hurting LCP: A Workflow for Safe Deferral",[15,16,17,18,23,24,28],"p",{},"This guide extends the ",[19,20,22],"a",{"href":21},"\u002Fimage-media-optimization\u002F","Image & Media Optimization"," discipline into the technique most likely to backfire when applied bluntly: deferring image and media downloads until they are needed. Lazy loading is unambiguously good for the many offscreen images far down the page — it trims initial bytes, shortens the request queue, and frees bandwidth for content the user can actually see. But the moment a deferral rule touches the one image the user sees first, it stops being an optimization and becomes a regression: the ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002F","Largest Contentful Paint"," element gets pulled out of the preload scanner's early-discovery path, its request slips behind layout and script work, and the loading metric you were trying to protect blows past its 2.5s budget.",[15,30,31,32,36,37,40],{},"The discipline here is surgical, not blanket. The correct mental model is a single hard rule plus a deferral strategy for everything below it: the above-the-fold candidate loads eagerly with high priority, and every image, iframe, and video element outside the initial viewport defers. This workflow walks the full deferral toolkit — the native ",[33,34,35],"code",{},"loading=\"lazy\""," attribute, the ",[33,38,39],{},"decoding=\"async\""," hint, a custom IntersectionObserver implementation for the cases native loading cannot express, deferral of iframes and video, and the placeholder\u002Fspace-reservation techniques that keep deferral from trading an LCP win for a CLS loss.",[15,42,43],{},[44,45,52,53,52,57,52,61,52,71,52,78,52,87,52,93,52,98,52,103,52,107,52,110,52,114,52,118,52,121,52,127,52,132,52,136,52,140,52],"svg",{"xmlns":46,"viewBox":47,"width":48,"role":49,"ariaLabel":50,"style":51},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 320","100%","img","Decision flow showing the above-the-fold image loads eagerly while offscreen media defers via native lazy loading or IntersectionObserver","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[54,55,56],"title",{},"Eager vs lazy deferral decision",[58,59,60],"desc",{},"The LCP and above-the-fold images load eagerly with high priority; offscreen media defers via loading lazy or IntersectionObserver, with reserved space to protect layout stability.",[62,63],"rect",{"x":64,"y":64,"width":65,"height":66,"rx":67,"fill":68,"stroke":69,"style":70},"1","758","318","10","none","currentColor","stroke-opacity:0.18",[72,73,77],"text",{"x":74,"y":75,"fill":69,"style":76},"24","38","font-size:18px;font-weight:700","Defer everything except the first paint",[62,79],{"x":74,"y":80,"width":81,"height":82,"rx":83,"fill":84,"stroke":85,"style":86},"62","230","64","6","#ffc300","#b8860b","fill-opacity:0.22",[72,88,92],{"x":89,"y":90,"fill":69,"style":91},"139","88","font-size:13px;font-weight:600;text-anchor:middle","Above the fold",[72,94,97],{"x":89,"y":95,"fill":69,"style":96},"108","font-size:12px;text-anchor:middle","eager + fetchpriority",[62,99],{"x":100,"y":80,"width":81,"height":82,"rx":83,"fill":101,"stroke":101,"style":102},"280","#0466c8","fill-opacity:0.14",[72,104,106],{"x":105,"y":90,"fill":69,"style":91},"395","Native lazy",[72,108,109],{"x":105,"y":95,"fill":69,"style":96},"loading=lazy",[62,111],{"x":112,"y":80,"width":113,"height":82,"rx":83,"fill":101,"stroke":101,"style":102},"536","200",[72,115,117],{"x":116,"y":90,"fill":69,"style":91},"636","Custom defer",[72,119,120],{"x":116,"y":95,"fill":69,"style":96},"IntersectionObserver",[122,123],"line",{"x1":74,"y1":124,"x2":125,"y2":124,"stroke":69,"style":126},"156","736","stroke-opacity:0.3",[72,128,131],{"x":74,"y":129,"fill":69,"style":130},"186","font-size:13px","Rule: the LCP candidate must NEVER carry loading=lazy.",[72,133,135],{"x":74,"y":134,"fill":69,"style":130},"216","Every deferred slot reserves its box via width\u002Fheight or aspect-ratio.",[72,137,139],{"x":74,"y":138,"fill":69,"style":130},"246","Native lazy is free; reach for the observer only when you need control.",[72,141,144],{"x":74,"y":142,"fill":69,"style":143},"284","font-size:13px;font-weight:600","Bytes saved below the fold must not cost layout shift above it.",[146,147,149],"h2",{"id":148},"problem-framing-when-deferral-becomes-a-regression","Problem Framing: When Deferral Becomes a Regression",[15,151,152,153,155,156,158,159,163],{},"The failure pattern is consistent and easy to reproduce. A team enables a global lazy-loading rule — often a single line in an image component or a ",[33,154,35],{}," default applied across a template — and ships it. Initial transfer bytes drop, the team celebrates, and a week later field data shows LCP regressed from 2.1s to 3.4s on mobile. The cause is mechanical: when an image carries ",[33,157,35],{},", the browser's preload scanner deliberately ",[160,161,162],"em",{},"skips"," it during early HTML parsing, because the whole point is to avoid fetching offscreen resources. If the LCP element is in that set, its request is deferred until layout determines it is near the viewport — which, for an above-the-fold hero, means it waits behind CSS, fonts, and the main document instead of racing alongside them.",[15,165,166],{},"The numbers that bound this problem are the standard loading thresholds: LCP must land under 2.5s at the field p75, and the LCP image's discovery-to-request delay is the single phase most sensitive to deferral. A correctly eager hero is discovered by the preload scanner within the first few hundred milliseconds of HTML arriving; a lazily-loaded one can slip 500–1500ms later, which is exactly the regression magnitude teams report. The fix is not to abandon lazy loading but to scope it precisely, and the rest of this workflow is that scoping discipline.",[146,168,170],{"id":169},"prerequisites-versions-attributes-and-feature-support","Prerequisites: Versions, Attributes, and Feature Support",[15,172,173,174,176,177,180,181,184,185,187,188,190],{},"Native ",[33,175,35],{}," on ",[33,178,179],{},"\u003Cimg>"," is supported in all current Chromium, Firefox, and Safari versions (Safari shipped it in 16.4), so it is a safe baseline with graceful degradation — older browsers simply load eagerly, which is never a correctness problem, only a bytes one. Native lazy loading on ",[33,182,183],{},"\u003Ciframe>"," is also broadly supported. The ",[33,186,39],{}," attribute is universally supported and orthogonal to loading. For the custom path you need ",[33,189,120],{},", which is available everywhere you care about; no polyfill is warranted in 2026.",[15,192,193,194,197,198,201,202,205,206,210],{},"You also need the layout primitives that make deferral safe: either explicit ",[33,195,196],{},"width"," and ",[33,199,200],{},"height"," attributes on every media element or a CSS ",[33,203,204],{},"aspect-ratio"," on the container, so the browser can reserve the box before the resource arrives. Without reserved space, deferred images snap content downward when they finally load — trading an LCP win for a ",[19,207,209],{"href":208},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F","Cumulative Layout Shift"," loss. Treat reserved dimensions as a hard prerequisite, not a nicety.",[146,212,214],{"id":213},"_1-environment-setup-mark-the-fold","1. Environment Setup: Mark the Fold",[15,216,217,218,221],{},"The first concrete step is identifying which elements are above the fold, because that boundary drives every subsequent decision. \"Above the fold\" is not a fixed pixel value — it depends on viewport height, which varies wildly across devices. The practical definition for deferral purposes is: any element whose box intersects the initial viewport on your ",[160,219,220],{},"most constrained"," common device (typically a short mobile viewport around 640–700px tall) must load eagerly. Everything reliably below that line is a deferral candidate.",[15,223,224,225,228,229,232,233,237],{},"In a component-driven codebase, encode this as an explicit prop rather than a heuristic. Give your image component a ",[33,226,227],{},"priority"," or ",[33,230,231],{},"eager"," flag, default it to lazy, and have the page author opt the hero (and any other guaranteed-visible media) into eager loading. This inverts the dangerous default: instead of \"everything is eager and we forgot to defer,\" you get \"everything defers unless a human decided it is visible.\" The hero opt-in then pairs naturally with priority hints covered in ",[19,234,236],{"href":235},"\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002Fusing-fetchpriority-to-prioritize-the-lcp-image\u002F","using fetchpriority to prioritize the LCP image",".",[239,240,245],"pre",{"className":241,"code":242,"language":243,"meta":244,"style":244},"language-html shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u003C!-- The hero \u002F LCP candidate: eager, high priority, never deferred -->\n\u003Cimg\n  src=\"hero-1200.jpg\"\n  srcset=\"hero-800.jpg 800w, hero-1200.jpg 1200w, hero-1600.jpg 1600w\"\n  sizes=\"(max-width: 768px) 100vw, 1100px\"\n  width=\"1600\" height=\"900\"\n  alt=\"Annual conference main stage\"\n  fetchpriority=\"high\"\n  decoding=\"async\">\n\u003C!-- trade-off: fetchpriority=high on the hero is correct, but apply it to exactly\n     ONE element. Marking several images high priority dilutes the signal and the\n     browser falls back to its default heuristics, leaving the real LCP image\n     competing for bandwidth again. -->\n","html","",[33,246,247,255,266,280,291,302,321,332,343,357,363,369,375],{"__ignoreMap":244},[248,249,251],"span",{"class":122,"line":250},1,[248,252,254],{"class":253},"sIIH1","\u003C!-- The hero \u002F LCP candidate: eager, high priority, never deferred -->\n",[248,256,258,262],{"class":122,"line":257},2,[248,259,261],{"class":260},"syybb","\u003C",[248,263,265],{"class":264},"s-fAs","img\n",[248,267,269,273,276],{"class":122,"line":268},3,[248,270,272],{"class":271},"sf6mN","  src",[248,274,275],{"class":260},"=",[248,277,279],{"class":278},"s-_DF","\"hero-1200.jpg\"\n",[248,281,283,286,288],{"class":122,"line":282},4,[248,284,285],{"class":271},"  srcset",[248,287,275],{"class":260},[248,289,290],{"class":278},"\"hero-800.jpg 800w, hero-1200.jpg 1200w, hero-1600.jpg 1600w\"\n",[248,292,294,297,299],{"class":122,"line":293},5,[248,295,296],{"class":271},"  sizes",[248,298,275],{"class":260},[248,300,301],{"class":278},"\"(max-width: 768px) 100vw, 1100px\"\n",[248,303,305,308,310,313,316,318],{"class":122,"line":304},6,[248,306,307],{"class":271},"  width",[248,309,275],{"class":260},[248,311,312],{"class":278},"\"1600\"",[248,314,315],{"class":271}," height",[248,317,275],{"class":260},[248,319,320],{"class":278},"\"900\"\n",[248,322,324,327,329],{"class":122,"line":323},7,[248,325,326],{"class":271},"  alt",[248,328,275],{"class":260},[248,330,331],{"class":278},"\"Annual conference main stage\"\n",[248,333,335,338,340],{"class":122,"line":334},8,[248,336,337],{"class":271},"  fetchpriority",[248,339,275],{"class":260},[248,341,342],{"class":278},"\"high\"\n",[248,344,346,349,351,354],{"class":122,"line":345},9,[248,347,348],{"class":271},"  decoding",[248,350,275],{"class":260},[248,352,353],{"class":278},"\"async\"",[248,355,356],{"class":260},">\n",[248,358,360],{"class":122,"line":359},10,[248,361,362],{"class":253},"\u003C!-- trade-off: fetchpriority=high on the hero is correct, but apply it to exactly\n",[248,364,366],{"class":122,"line":365},11,[248,367,368],{"class":253},"     ONE element. Marking several images high priority dilutes the signal and the\n",[248,370,372],{"class":122,"line":371},12,[248,373,374],{"class":253},"     browser falls back to its default heuristics, leaving the real LCP image\n",[248,376,378],{"class":122,"line":377},13,[248,379,380],{"class":253},"     competing for bandwidth again. -->\n",[146,382,384],{"id":383},"_2-capture-a-baseline-confirm-the-lcp-element-is-eager","2. Capture a Baseline: Confirm the LCP Element Is Eager",[15,386,387,388,391,392,394],{},"Before deferring anything, verify the current state. Open Chrome DevTools, run a Performance trace, and identify the LCP element from the Timings track — DevTools labels it directly. Then inspect that element in the Elements panel and check its ",[33,389,390],{},"loading"," attribute. If the LCP element shows ",[33,393,35],{},", you have already found the regression; if it is eager, record the LCP value as your baseline so you can prove the deferral work below the fold does not move it.",[15,396,397],{},"Cross-check with the Network panel. Filter to images, sort by start time, and confirm the LCP image is among the earliest requests — ideally initiated by the preload scanner before the main parser reaches it. An LCP image that starts late, after scripts or fonts, is being discovered too slowly even if it is technically eager, which is the discovery problem that priority hints solve. Capture three numbers as your scorecard: LCP value, the LCP image request start time, and total image bytes transferred on initial load. The deferral work should drive the third number down while leaving the first two flat or improved.",[146,399,401],{"id":400},"_3-isolate-the-bottleneck-native-lazy-loading-for-the-long-list","3. Isolate the Bottleneck: Native Lazy Loading for the Long List",[15,403,404,405,407,408,410],{},"For the large set of below-the-fold images — article body images, grid thumbnails, footer logos, anything the user scrolls to — native ",[33,406,35],{}," is the correct first tool. It costs nothing in JavaScript, the browser tunes the load distance based on connection speed and viewport, and it degrades gracefully. Apply it to every image that is reliably offscreen at first paint, and pair it with ",[33,409,39],{}," so image decode work happens off the critical rendering path rather than blocking the main thread during scroll.",[15,412,413,414,416,417,420,421,416,423,426,427,429],{},"The two attributes do different jobs and should usually travel together on deferred images. ",[33,415,35],{}," controls ",[160,418,419],{},"when the bytes are fetched"," — the browser waits until the image approaches the viewport. ",[33,422,39],{},[160,424,425],{},"when the decoded bitmap is prepared"," — it tells the browser it may decode off the main thread and present the image without blocking other rendering. On the eager hero, ",[33,428,39],{}," is also generally safe and often beneficial, but the load timing is what matters there. Below the fold, both attributes apply to every element.",[239,431,433],{"className":241,"code":432,"language":243,"meta":244,"style":244},"\u003C!-- Below-the-fold content images: defer fetch AND async-decode -->\n\u003Cimg\n  src=\"figure-800.jpg\"\n  srcset=\"figure-400.jpg 400w, figure-800.jpg 800w, figure-1200.jpg 1200w\"\n  sizes=\"(max-width: 768px) 100vw, 720px\"\n  width=\"1200\" height=\"675\"\n  alt=\"Throughput by region, Q3\"\n  loading=\"lazy\"\n  decoding=\"async\">\n\u003C!-- trade-off: do NOT put loading=lazy on anything that may be the LCP element or\n     sits at the top of the viewport. The browser skips lazy images in the preload\n     scan, so a lazily-loaded hero is fetched late and LCP regresses. If you are\n     unsure whether an element is above the fold, default it to eager. -->\n",[33,434,435,440,446,455,464,473,489,498,508,518,523,528,533],{"__ignoreMap":244},[248,436,437],{"class":122,"line":250},[248,438,439],{"class":253},"\u003C!-- Below-the-fold content images: defer fetch AND async-decode -->\n",[248,441,442,444],{"class":122,"line":257},[248,443,261],{"class":260},[248,445,265],{"class":264},[248,447,448,450,452],{"class":122,"line":268},[248,449,272],{"class":271},[248,451,275],{"class":260},[248,453,454],{"class":278},"\"figure-800.jpg\"\n",[248,456,457,459,461],{"class":122,"line":282},[248,458,285],{"class":271},[248,460,275],{"class":260},[248,462,463],{"class":278},"\"figure-400.jpg 400w, figure-800.jpg 800w, figure-1200.jpg 1200w\"\n",[248,465,466,468,470],{"class":122,"line":293},[248,467,296],{"class":271},[248,469,275],{"class":260},[248,471,472],{"class":278},"\"(max-width: 768px) 100vw, 720px\"\n",[248,474,475,477,479,482,484,486],{"class":122,"line":304},[248,476,307],{"class":271},[248,478,275],{"class":260},[248,480,481],{"class":278},"\"1200\"",[248,483,315],{"class":271},[248,485,275],{"class":260},[248,487,488],{"class":278},"\"675\"\n",[248,490,491,493,495],{"class":122,"line":323},[248,492,326],{"class":271},[248,494,275],{"class":260},[248,496,497],{"class":278},"\"Throughput by region, Q3\"\n",[248,499,500,503,505],{"class":122,"line":334},[248,501,502],{"class":271},"  loading",[248,504,275],{"class":260},[248,506,507],{"class":278},"\"lazy\"\n",[248,509,510,512,514,516],{"class":122,"line":345},[248,511,348],{"class":271},[248,513,275],{"class":260},[248,515,353],{"class":278},[248,517,356],{"class":260},[248,519,520],{"class":122,"line":359},[248,521,522],{"class":253},"\u003C!-- trade-off: do NOT put loading=lazy on anything that may be the LCP element or\n",[248,524,525],{"class":122,"line":365},[248,526,527],{"class":253},"     sits at the top of the viewport. The browser skips lazy images in the preload\n",[248,529,530],{"class":122,"line":371},[248,531,532],{"class":253},"     scan, so a lazily-loaded hero is fetched late and LCP regresses. If you are\n",[248,534,535],{"class":122,"line":377},[248,536,537],{"class":253},"     unsure whether an element is above the fold, default it to eager. -->\n",[146,539,541],{"id":540},"_4-apply-custom-deferral-intersectionobserver-when-native-falls-short","4. Apply Custom Deferral: IntersectionObserver When Native Falls Short",[15,543,544,545,548,549,551,552,555,556,558,559,562,563,566,567,569],{},"Native lazy loading is a black box: you cannot tune ",[160,546,547],{},"how far"," before the viewport it triggers, you cannot animate or fade images in cleanly tied to entry, and you cannot defer arbitrary work (analytics impressions, expensive widgets, background images set via CSS) on the same trigger. When you need that control, ",[33,550,120],{}," is the precise tool. The pattern: render the element with the real source held in a ",[33,553,554],{},"data-src"," attribute, observe it, and swap ",[33,557,554],{}," into ",[33,560,561],{},"src"," when the observer reports the element entering a ",[33,564,565],{},"rootMargin","-expanded viewport. The ",[33,568,565],{}," is the knob native loading hides — set it to a few hundred pixels to start the fetch before the element is visible so it is decoded by the time the user reaches it.",[15,571,572,573,575,576,578,579,581],{},"The critical correctness detail is disconnecting the observer per element once it fires, so each image loads exactly once and you do not leak observers across a long list. Also keep the reserved box: the element still needs ",[33,574,196],{},"\u002F",[33,577,200],{}," or an ",[33,580,204],{}," so the placeholder occupies layout space and the swap does not shift content. The trade-off versus native is real — you ship and maintain JavaScript, and a render-blocking or late-executing script means images that never load if the observer never runs — so reserve this for the cases where native loading genuinely cannot express what you need.",[239,583,587],{"className":584,"code":585,"language":586,"meta":244,"style":244},"language-javascript shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F Custom deferral with tunable trigger distance\nconst io = new IntersectionObserver((entries, observer) => {\n  for (const entry of entries) {\n    if (!entry.isIntersecting) continue;\n    const img = entry.target;\n    img.src = img.dataset.src;\n    if (img.dataset.srcset) img.srcset = img.dataset.srcset;\n    observer.unobserve(img);          \u002F\u002F load once, then stop watching this element\n  }\n}, {\n  rootMargin: '300px 0px',            \u002F\u002F start fetching 300px before entry\n  threshold: 0,                       \u002F\u002F fire as soon as any part enters the margin\n});\ndocument.querySelectorAll('img[data-src]').forEach((img) => io.observe(img));\n\u002F\u002F trade-off: this hides the real URL behind data-src, so an image with NO src is\n\u002F\u002F invisible to the preload scanner and never loads if the script fails to run.\n\u002F\u002F Prefer native loading=lazy unless you specifically need rootMargin tuning,\n\u002F\u002F fade-in tied to entry, or deferral of non-image work on the same trigger.\n","javascript",[33,588,589,594,635,654,673,686,696,708,722,727,732,746,760,765,803,809,815,821],{"__ignoreMap":244},[248,590,591],{"class":122,"line":250},[248,592,593],{"class":253},"\u002F\u002F Custom deferral with tunable trigger distance\n",[248,595,596,600,603,606,609,613,616,620,623,626,629,632],{"class":122,"line":257},[248,597,599],{"class":598},"sP5qI","const",[248,601,602],{"class":271}," io",[248,604,605],{"class":598}," =",[248,607,608],{"class":598}," new",[248,610,612],{"class":611},"ssM3C"," IntersectionObserver",[248,614,615],{"class":260},"((",[248,617,619],{"class":618},"seIZK","entries",[248,621,622],{"class":260},", ",[248,624,625],{"class":618},"observer",[248,627,628],{"class":260},") ",[248,630,631],{"class":598},"=>",[248,633,634],{"class":260}," {\n",[248,636,637,640,643,645,648,651],{"class":122,"line":268},[248,638,639],{"class":598},"  for",[248,641,642],{"class":260}," (",[248,644,599],{"class":598},[248,646,647],{"class":271}," entry",[248,649,650],{"class":598}," of",[248,652,653],{"class":260}," entries) {\n",[248,655,656,659,661,664,667,670],{"class":122,"line":282},[248,657,658],{"class":598},"    if",[248,660,642],{"class":260},[248,662,663],{"class":598},"!",[248,665,666],{"class":260},"entry.isIntersecting) ",[248,668,669],{"class":598},"continue",[248,671,672],{"class":260},";\n",[248,674,675,678,681,683],{"class":122,"line":293},[248,676,677],{"class":598},"    const",[248,679,680],{"class":271}," img",[248,682,605],{"class":598},[248,684,685],{"class":260}," entry.target;\n",[248,687,688,691,693],{"class":122,"line":304},[248,689,690],{"class":260},"    img.src ",[248,692,275],{"class":598},[248,694,695],{"class":260}," img.dataset.src;\n",[248,697,698,700,703,705],{"class":122,"line":323},[248,699,658],{"class":598},[248,701,702],{"class":260}," (img.dataset.srcset) img.srcset ",[248,704,275],{"class":598},[248,706,707],{"class":260}," img.dataset.srcset;\n",[248,709,710,713,716,719],{"class":122,"line":334},[248,711,712],{"class":260},"    observer.",[248,714,715],{"class":611},"unobserve",[248,717,718],{"class":260},"(img);          ",[248,720,721],{"class":253},"\u002F\u002F load once, then stop watching this element\n",[248,723,724],{"class":122,"line":345},[248,725,726],{"class":260},"  }\n",[248,728,729],{"class":122,"line":359},[248,730,731],{"class":260},"}, {\n",[248,733,734,737,740,743],{"class":122,"line":365},[248,735,736],{"class":260},"  rootMargin: ",[248,738,739],{"class":278},"'300px 0px'",[248,741,742],{"class":260},",            ",[248,744,745],{"class":253},"\u002F\u002F start fetching 300px before entry\n",[248,747,748,751,754,757],{"class":122,"line":371},[248,749,750],{"class":260},"  threshold: ",[248,752,753],{"class":271},"0",[248,755,756],{"class":260},",                       ",[248,758,759],{"class":253},"\u002F\u002F fire as soon as any part enters the margin\n",[248,761,762],{"class":122,"line":377},[248,763,764],{"class":260},"});\n",[248,766,768,771,774,777,780,783,786,788,790,792,794,797,800],{"class":122,"line":767},14,[248,769,770],{"class":260},"document.",[248,772,773],{"class":611},"querySelectorAll",[248,775,776],{"class":260},"(",[248,778,779],{"class":278},"'img[data-src]'",[248,781,782],{"class":260},").",[248,784,785],{"class":611},"forEach",[248,787,615],{"class":260},[248,789,49],{"class":618},[248,791,628],{"class":260},[248,793,631],{"class":598},[248,795,796],{"class":260}," io.",[248,798,799],{"class":611},"observe",[248,801,802],{"class":260},"(img));\n",[248,804,806],{"class":122,"line":805},15,[248,807,808],{"class":253},"\u002F\u002F trade-off: this hides the real URL behind data-src, so an image with NO src is\n",[248,810,812],{"class":122,"line":811},16,[248,813,814],{"class":253},"\u002F\u002F invisible to the preload scanner and never loads if the script fails to run.\n",[248,816,818],{"class":122,"line":817},17,[248,819,820],{"class":253},"\u002F\u002F Prefer native loading=lazy unless you specifically need rootMargin tuning,\n",[248,822,824],{"class":122,"line":823},18,[248,825,826],{"class":253},"\u002F\u002F fade-in tied to entry, or deferral of non-image work on the same trigger.\n",[146,828,830],{"id":829},"_5-defer-iframes-and-video-without-stalling-playback","5. Defer Iframes and Video Without Stalling Playback",[15,832,833,834,836,837,840,841,843,844,847,848,851,852,855,856,859],{},"Embedded media is heavier than images and benefits even more from deferral. Iframes — YouTube embeds, maps, third-party widgets — support native ",[33,835,35],{}," and should always carry it when offscreen, because an eager iframe pulls in an entire subdocument's worth of scripts and requests during initial load. For video, the ",[33,838,839],{},"\u003Cvideo>"," element does not honor ",[33,842,35],{},", so you control deferral through ",[33,845,846],{},"preload",". Set ",[33,849,850],{},"preload=\"none\""," to fetch nothing until the user interacts, or ",[33,853,854],{},"preload=\"metadata\""," to fetch only enough to know duration and dimensions while deferring the media payload. Pair video with a lightweight ",[33,857,858],{},"poster"," image so the slot shows something immediately and reserves its box.",[15,861,862,863,865],{},"The expensive anti-pattern is the autoplaying or eagerly-preloaded hero video, which can dwarf every other initial request combined. Below the fold, ",[33,864,850],{}," plus a poster is almost always right. For third-party iframe embeds, the strongest pattern is a \"facade\" — render a static poster image that looks like the embed and only inject the real iframe on click, deferring the third party's entire script cost until the user actually engages.",[239,867,869],{"className":241,"code":868,"language":243,"meta":244,"style":244},"\u003C!-- Offscreen iframe: native lazy. Video: defer payload via preload + poster -->\n\u003Ciframe src=\"https:\u002F\u002Fmaps.example.com\u002Fembed?id=42\"\n        width=\"640\" height=\"360\" loading=\"lazy\"\n        title=\"Venue location map\">\u003C\u002Fiframe>\n\n\u003Cvideo width=\"1280\" height=\"720\" controls\n       preload=\"none\" poster=\"video-poster-800.jpg\">\n  \u003Csource src=\"walkthrough.mp4\" type=\"video\u002Fmp4\">\n\u003C\u002Fvideo>\n\u003C!-- trade-off: preload=none means the first play has a startup delay while the\n     browser fetches metadata and the initial segment. For short, likely-to-play\n     clips above the fold, preload=metadata trades a little eager bandwidth for an\n     instant-feeling play; reserve preload=none for clearly optional media. -->\n",[33,870,871,876,891,915,932,938,963,983,1008,1017,1022,1027,1032],{"__ignoreMap":244},[248,872,873],{"class":122,"line":250},[248,874,875],{"class":253},"\u003C!-- Offscreen iframe: native lazy. Video: defer payload via preload + poster -->\n",[248,877,878,880,883,886,888],{"class":122,"line":257},[248,879,261],{"class":260},[248,881,882],{"class":264},"iframe",[248,884,885],{"class":271}," src",[248,887,275],{"class":260},[248,889,890],{"class":278},"\"https:\u002F\u002Fmaps.example.com\u002Fembed?id=42\"\n",[248,892,893,896,898,901,903,905,908,911,913],{"class":122,"line":268},[248,894,895],{"class":271},"        width",[248,897,275],{"class":260},[248,899,900],{"class":278},"\"640\"",[248,902,315],{"class":271},[248,904,275],{"class":260},[248,906,907],{"class":278},"\"360\"",[248,909,910],{"class":271}," loading",[248,912,275],{"class":260},[248,914,507],{"class":278},[248,916,917,920,922,925,928,930],{"class":122,"line":282},[248,918,919],{"class":271},"        title",[248,921,275],{"class":260},[248,923,924],{"class":278},"\"Venue location map\"",[248,926,927],{"class":260},">\u003C\u002F",[248,929,882],{"class":264},[248,931,356],{"class":260},[248,933,934],{"class":122,"line":293},[248,935,937],{"emptyLinePlaceholder":936},true,"\n",[248,939,940,942,945,948,950,953,955,957,960],{"class":122,"line":304},[248,941,261],{"class":260},[248,943,944],{"class":264},"video",[248,946,947],{"class":271}," width",[248,949,275],{"class":260},[248,951,952],{"class":278},"\"1280\"",[248,954,315],{"class":271},[248,956,275],{"class":260},[248,958,959],{"class":278},"\"720\"",[248,961,962],{"class":271}," controls\n",[248,964,965,968,970,973,976,978,981],{"class":122,"line":323},[248,966,967],{"class":271},"       preload",[248,969,275],{"class":260},[248,971,972],{"class":278},"\"none\"",[248,974,975],{"class":271}," poster",[248,977,275],{"class":260},[248,979,980],{"class":278},"\"video-poster-800.jpg\"",[248,982,356],{"class":260},[248,984,985,988,991,993,995,998,1001,1003,1006],{"class":122,"line":334},[248,986,987],{"class":260},"  \u003C",[248,989,990],{"class":264},"source",[248,992,885],{"class":271},[248,994,275],{"class":260},[248,996,997],{"class":278},"\"walkthrough.mp4\"",[248,999,1000],{"class":271}," type",[248,1002,275],{"class":260},[248,1004,1005],{"class":278},"\"video\u002Fmp4\"",[248,1007,356],{"class":260},[248,1009,1010,1013,1015],{"class":122,"line":345},[248,1011,1012],{"class":260},"\u003C\u002F",[248,1014,944],{"class":264},[248,1016,356],{"class":260},[248,1018,1019],{"class":122,"line":359},[248,1020,1021],{"class":253},"\u003C!-- trade-off: preload=none means the first play has a startup delay while the\n",[248,1023,1024],{"class":122,"line":365},[248,1025,1026],{"class":253},"     browser fetches metadata and the initial segment. For short, likely-to-play\n",[248,1028,1029],{"class":122,"line":371},[248,1030,1031],{"class":253},"     clips above the fold, preload=metadata trades a little eager bandwidth for an\n",[248,1033,1034],{"class":122,"line":377},[248,1035,1036],{"class":253},"     instant-feeling play; reserve preload=none for clearly optional media. -->\n",[146,1038,1040],{"id":1039},"deconstructing-the-deferral-decision-into-timing-phases","Deconstructing the Deferral Decision Into Timing Phases",[15,1042,1043,1044,1048,1049,1051,1052,1055,1056,1059,1060,1062],{},"Whether deferral helps or hurts depends entirely on which phase of the load it touches. Break the LCP image's journey into three phases and the rule becomes obvious. ",[1045,1046,1047],"strong",{},"Discovery"," is when the preload scanner finds the image URL in the raw HTML; for the LCP element this should happen in the first parse pass, within the first few hundred milliseconds, and ",[33,1050,35],{}," sabotages exactly this phase by making the scanner skip the element. ",[1045,1053,1054],{},"Request and transfer"," is the fetch itself, where priority determines queue position — the eager LCP image should be high priority and unblocked. ",[1045,1057,1058],{},"Decode and paint"," is where ",[33,1061,39],{}," keeps the bitmap preparation off the main thread so it does not contend with hydration or scroll handlers.",[15,1064,1065,1066,1069,1070,1072],{},"For ",[160,1067,1068],{},"below-the-fold"," images the calculus inverts: deferring discovery is the entire benefit, because skipping those URLs in the preload scan keeps bandwidth and connections free for the above-the-fold content that drives the metric. So the same ",[33,1071,35],{}," attribute is a regression on one element and a win on a hundred others, purely as a function of whether the element participates in the first paint. This is why a blanket policy fails and a fold-aware policy succeeds: the attribute is correct or incorrect only relative to an element's position, and the per-element discovery phase is where that correctness is decided. Once you internalize that the LCP image's discovery time is the dominant, most fragile phase, every deferral decision reduces to a single question — is this element part of the first paint?",[146,1074,1076],{"id":1075},"advanced-diagnostics-framework-defaults-and-placeholder-strategy","Advanced Diagnostics: Framework Defaults and Placeholder Strategy",[15,1078,1079,1080,1082,1083,575,1085,1087,1088,1090,1091,1093,1094,237],{},"Framework image components are the most common source of accidental LCP regressions. Some default to lazy loading every image, so a hero rendered through the default component silently carries ",[33,1081,35],{}," and regresses LCP until an author flips a ",[33,1084,227],{},[33,1086,231],{}," prop. Others default to eager, which over-fetches below the fold. Either way, never trust the default — inspect the rendered ",[33,1089,179],{}," in DevTools and confirm the ",[33,1092,390],{}," attribute matches the element's fold position. For the hero, also confirm the component did not strip ",[33,1095,1096],{},"fetchpriority",[15,1098,1099,1100,575,1102,1104,1105,1107,1108,1110],{},"Placeholders close the perceptual gap and protect layout. A low-quality image placeholder (LQIP) — a tiny, heavily-compressed or blurred version inlined as a data URI or a CSS background — gives the reserved box visible content while the real image loads, so the user perceives progress and the box never collapses. The non-negotiable companion is space reservation: set ",[33,1101,196],{},[33,1103,200],{}," attributes (the browser derives ",[33,1106,204],{}," from them automatically in modern engines) or an explicit CSS ",[33,1109,204],{}," so the placeholder occupies the final dimensions. A deferred image without reserved space is the textbook cause of layout shift, so any lazy-loading rollout that omits dimensions converts an LCP improvement into a CLS regression. The placeholder is optional polish; the reserved box is mandatory.",[239,1112,1116],{"className":1113,"code":1114,"language":1115,"meta":244,"style":244},"language-css shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F* Reserve the box so deferred images never shift content *\u002F\n.media {\n  aspect-ratio: 16 \u002F 9;        \u002F* matches the intrinsic ratio of the source *\u002F\n  width: 100%;\n  background: #e9edf2;          \u002F* visible LQIP-style fill while bytes arrive *\u002F\n}\n.media > img { width: 100%; height: 100%; object-fit: cover; }\n\u002F* trade-off: a fixed aspect-ratio assumes every image in this slot shares one\n   ratio. If sources vary, set width\u002Fheight per image instead, or the reserved\n   box will letterbox or crop unexpectedly via object-fit. *\u002F\n","css",[33,1117,1118,1123,1130,1153,1167,1183,1188,1232,1237,1242],{"__ignoreMap":244},[248,1119,1120],{"class":122,"line":250},[248,1121,1122],{"class":253},"\u002F* Reserve the box so deferred images never shift content *\u002F\n",[248,1124,1125,1128],{"class":122,"line":257},[248,1126,1127],{"class":271},".media",[248,1129,634],{"class":260},[248,1131,1132,1135,1138,1141,1144,1147,1150],{"class":122,"line":268},[248,1133,1134],{"class":271},"  aspect-ratio",[248,1136,1137],{"class":260},": ",[248,1139,1140],{"class":271},"16",[248,1142,1143],{"class":260}," \u002F ",[248,1145,1146],{"class":271},"9",[248,1148,1149],{"class":260},";        ",[248,1151,1152],{"class":253},"\u002F* matches the intrinsic ratio of the source *\u002F\n",[248,1154,1155,1157,1159,1162,1165],{"class":122,"line":282},[248,1156,307],{"class":271},[248,1158,1137],{"class":260},[248,1160,1161],{"class":271},"100",[248,1163,1164],{"class":598},"%",[248,1166,672],{"class":260},[248,1168,1169,1172,1174,1177,1180],{"class":122,"line":293},[248,1170,1171],{"class":271},"  background",[248,1173,1137],{"class":260},[248,1175,1176],{"class":271},"#e9edf2",[248,1178,1179],{"class":260},";          ",[248,1181,1182],{"class":253},"\u002F* visible LQIP-style fill while bytes arrive *\u002F\n",[248,1184,1185],{"class":122,"line":304},[248,1186,1187],{"class":260},"}\n",[248,1189,1190,1192,1195,1197,1200,1202,1204,1206,1208,1211,1213,1215,1217,1219,1221,1224,1226,1229],{"class":122,"line":323},[248,1191,1127],{"class":271},[248,1193,1194],{"class":598}," >",[248,1196,680],{"class":264},[248,1198,1199],{"class":260}," { ",[248,1201,196],{"class":271},[248,1203,1137],{"class":260},[248,1205,1161],{"class":271},[248,1207,1164],{"class":598},[248,1209,1210],{"class":260},"; ",[248,1212,200],{"class":271},[248,1214,1137],{"class":260},[248,1216,1161],{"class":271},[248,1218,1164],{"class":598},[248,1220,1210],{"class":260},[248,1222,1223],{"class":271},"object-fit",[248,1225,1137],{"class":260},[248,1227,1228],{"class":271},"cover",[248,1230,1231],{"class":260},"; }\n",[248,1233,1234],{"class":122,"line":334},[248,1235,1236],{"class":253},"\u002F* trade-off: a fixed aspect-ratio assumes every image in this slot shares one\n",[248,1238,1239],{"class":122,"line":345},[248,1240,1241],{"class":253},"   ratio. If sources vary, set width\u002Fheight per image instead, or the reserved\n",[248,1243,1244],{"class":122,"line":359},[248,1245,1246],{"class":253},"   box will letterbox or crop unexpectedly via object-fit. *\u002F\n",[146,1248,1250],{"id":1249},"validation-and-performance-budgeting","Validation and Performance Budgeting",[15,1252,1253],{},"Validation re-runs the baseline from step 2 and proves the two-sided outcome: below-the-fold bytes dropped while LCP held or improved. Confirm in the Network panel that initial image transfer fell — the deferred images should no longer appear in the first wave of requests — and confirm in a fresh Performance trace that the LCP element is still discovered early and its value is at or under your baseline. Then scroll and watch the deferred images request on approach; if any never load, the observer or native trigger is misconfigured.",[15,1255,1256,1257,1260,1261,1264,1265,1268],{},"Enforce both sides in CI so a future change cannot quietly regress either. Lighthouse exposes ",[33,1258,1259],{},"offscreen-images"," (catches images that should be deferred but are not) and ",[33,1262,1263],{},"largest-contentful-paint"," (catches a hero accidentally pushed late), and a ",[33,1266,1267],{},"cumulative-layout-shift"," budget catches missing reserved space. Asserting all three together is what prevents the classic regression where someone \"optimizes\" by lazy-loading the hero or removes the dimensions that were holding the box.",[239,1270,1274],{"className":1271,"code":1272,"language":1273,"meta":244,"style":244},"language-json shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","{\n  \"ci\": {\n    \"assert\": {\n      \"assertions\": {\n        \"offscreen-images\": [\"error\", { \"minScore\": 0.9 }],\n        \"largest-contentful-paint\": [\"error\", { \"maxNumericValue\": 2500 }],\n        \"cumulative-layout-shift\": [\"error\", { \"maxNumericValue\": 0.1 }]\n      }\n    }\n  }\n}\n","json",[33,1275,1276,1281,1289,1296,1303,1328,1349,1370,1375,1380,1384],{"__ignoreMap":244},[248,1277,1278],{"class":122,"line":250},[248,1279,1280],{"class":260},"{\n",[248,1282,1283,1286],{"class":122,"line":257},[248,1284,1285],{"class":264},"  \"ci\"",[248,1287,1288],{"class":260},": {\n",[248,1290,1291,1294],{"class":122,"line":268},[248,1292,1293],{"class":264},"    \"assert\"",[248,1295,1288],{"class":260},[248,1297,1298,1301],{"class":122,"line":282},[248,1299,1300],{"class":264},"      \"assertions\"",[248,1302,1288],{"class":260},[248,1304,1305,1308,1311,1314,1317,1320,1322,1325],{"class":122,"line":293},[248,1306,1307],{"class":264},"        \"offscreen-images\"",[248,1309,1310],{"class":260},": [",[248,1312,1313],{"class":278},"\"error\"",[248,1315,1316],{"class":260},", { ",[248,1318,1319],{"class":264},"\"minScore\"",[248,1321,1137],{"class":260},[248,1323,1324],{"class":271},"0.9",[248,1326,1327],{"class":260}," }],\n",[248,1329,1330,1333,1335,1337,1339,1342,1344,1347],{"class":122,"line":304},[248,1331,1332],{"class":264},"        \"largest-contentful-paint\"",[248,1334,1310],{"class":260},[248,1336,1313],{"class":278},[248,1338,1316],{"class":260},[248,1340,1341],{"class":264},"\"maxNumericValue\"",[248,1343,1137],{"class":260},[248,1345,1346],{"class":271},"2500",[248,1348,1327],{"class":260},[248,1350,1351,1354,1356,1358,1360,1362,1364,1367],{"class":122,"line":323},[248,1352,1353],{"class":264},"        \"cumulative-layout-shift\"",[248,1355,1310],{"class":260},[248,1357,1313],{"class":278},[248,1359,1316],{"class":260},[248,1361,1341],{"class":264},[248,1363,1137],{"class":260},[248,1365,1366],{"class":271},"0.1",[248,1368,1369],{"class":260}," }]\n",[248,1371,1372],{"class":122,"line":334},[248,1373,1374],{"class":260},"      }\n",[248,1376,1377],{"class":122,"line":345},[248,1378,1379],{"class":260},"    }\n",[248,1381,1382],{"class":122,"line":359},[248,1383,726],{"class":260},[248,1385,1386],{"class":122,"line":365},[248,1387,1187],{"class":260},[15,1389,1390],{},[160,1391,1392,1393,197,1395,1397],{},"Assert ",[33,1394,1263],{},[33,1396,1259],{}," together so neither side of the trade-off can regress unnoticed.",[146,1399,1401],{"id":1400},"related","Related",[1403,1404,1405,1412,1418,1424,1431,1438],"ul",{},[1406,1407,1408,1411],"li",{},[19,1409,1410],{"href":235},"Using fetchpriority to prioritize the LCP image"," — the other half of keeping the hero fast once you have ensured it is not deferred.",[1406,1413,1414,1417],{},[19,1415,1416],{"href":26},"Measuring LCP with Chrome DevTools"," — identify the LCP element and confirm deferral did not move it.",[1406,1419,1420,1423],{},[19,1421,1422],{"href":208},"Reducing Cumulative Layout Shift"," — reserve space so deferred media never shifts content as it loads.",[1406,1425,1426,1430],{},[19,1427,1429],{"href":1428},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Ffixing-lazy-loaded-images-that-delay-lcp\u002F","Fixing lazy-loaded images that delay LCP"," — the step-by-step recovery when a hero was wrongly deferred.",[1406,1432,1433,1437],{},[19,1434,1436],{"href":1435},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F","Native lazy loading vs IntersectionObserver"," — choose between the zero-cost attribute and a custom observer.",[1406,1439,1440,1442],{},[19,1441,22],{"href":21}," — the broader media-weight strategy this workflow plugs into.",[1444,1445,1447],"script",{"type":1446},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Lazy load images without hurting LCP\",\n  \"description\": \"A workflow to defer offscreen images, iframes, and video while keeping the LCP element eager and reserving layout space to protect CLS.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Mark the fold\", \"text\": \"Identify above-the-fold media and give your image component an eager opt-in that defaults to lazy.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F#1-environment-setup-mark-the-fold\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Capture a baseline\", \"text\": \"Confirm in DevTools that the LCP element is eager and record LCP, its request start, and initial image bytes.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F#2-capture-a-baseline-confirm-the-lcp-element-is-eager\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Apply native lazy loading\", \"text\": \"Add loading=lazy and decoding=async to every below-the-fold image.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F#3-isolate-the-bottleneck-native-lazy-loading-for-the-long-list\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Add custom deferral where needed\", \"text\": \"Use IntersectionObserver with rootMargin when you need to tune trigger distance or defer non-image work.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F#4-apply-custom-deferral-intersectionobserver-when-native-falls-short\" },\n    { \"@type\": \"HowToStep\", \"position\": 5, \"name\": \"Defer iframes and video\", \"text\": \"Use loading=lazy on offscreen iframes and preload=none with a poster on video.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F#5-defer-iframes-and-video-without-stalling-playback\" },\n    { \"@type\": \"HowToStep\", \"position\": 6, \"name\": \"Validate and budget\", \"text\": \"Re-measure to confirm bytes dropped and LCP held, then assert offscreen-images, LCP, and CLS budgets in CI.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F#validation-and-performance-budgeting\" }\n  ]\n}\n",[1444,1449,1450],{"type":1446},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Lazy Loading Images Without Hurting LCP: A Workflow for Safe Deferral\",\n  \"description\": \"How to defer offscreen images, iframes, and video with loading=lazy and IntersectionObserver while keeping the LCP element eager and protecting CLS.\",\n  \"datePublished\": \"2026-06-18\",\n  \"dateModified\": \"2026-06-18\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F\" }\n}\n",[1444,1452,1453],{"type":1446},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Image & Media Optimization\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Lazy Loading Images Without Hurting LCP\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F\" }\n  ]\n}\n",[1455,1456,1457],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .s-fAs, html code.shiki .s-fAs{--shiki-default:#024C1A;--shiki-dark:#024C1A;--shiki-light:#024C1A}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .ssM3C, html code.shiki .ssM3C{--shiki-default:#622CBC;--shiki-dark:#622CBC;--shiki-light:#622CBC}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}",{"title":244,"searchDepth":257,"depth":257,"links":1459},[1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470],{"id":148,"depth":257,"text":149},{"id":169,"depth":257,"text":170},{"id":213,"depth":257,"text":214},{"id":383,"depth":257,"text":384},{"id":400,"depth":257,"text":401},{"id":540,"depth":257,"text":541},{"id":829,"depth":257,"text":830},{"id":1039,"depth":257,"text":1040},{"id":1075,"depth":257,"text":1076},{"id":1249,"depth":257,"text":1250},{"id":1400,"depth":257,"text":1401},"A diagnostic workflow for deferring offscreen media without regressing LCP: native lazy loading, IntersectionObserver, iframe\u002Fvideo deferral, and LQIP.","md",{"slug":1474,"type":1475,"breadcrumb":1476,"datePublished":1482,"dateModified":1482},"lazy-loading-images-without-hurting-lcp","cluster",[1477,1479,1480],{"name":1478,"url":575},"Home",{"name":22,"url":21},{"name":5,"url":1481},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F","2026-06-18","\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp",{"title":5,"description":1485},"Use loading=lazy and decoding=async correctly, keep the LCP image eager, build an IntersectionObserver fallback, and reserve space to protect CLS.","image-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Findex","8bHRvah-fGyhj7EygEMf9XgsfROQkyapo3Ta_CbTvY8",[1489,1493],{"title":1490,"path":1491,"stem":1492,"children":-1},"Using fetchpriority to Prioritize the LCP Image","\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002Fusing-fetchpriority-to-prioritize-the-lcp-image","image-media-optimization\u002Fimage-cdns-and-fetchpriority\u002Fusing-fetchpriority-to-prioritize-the-lcp-image\u002Findex",{"title":1494,"path":1495,"stem":1496,"children":-1},"Fixing Lazy-Loaded Images That Delay LCP","\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Ffixing-lazy-loaded-images-that-delay-lcp","image-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Ffixing-lazy-loaded-images-that-delay-lcp\u002Findex",1782237170881]