WordPress Plugins
Free Tools
Pricing Blog Case Studies Switch to Royal Plugin Graveyard Support My Account Cart
Support / ForgeCache / Lazy Load Broke LCP Image

Lazy Load Broke My LCP Image — Hero Loading Too Late

Your hero image (the Largest Contentful Paint candidate) is getting loading="lazy" from ForgeCache when it should be eager. Symptom: hero image fades in after a 200–500ms delay; PageSpeed Insights flags “Defer offscreen images” incorrectly OR LCP score in the lab drops below the field-data baseline. Three root causes account for nearly all of these reports.

How ForgeCache Decides Which Images Get Loading=Eager

When lazy load is enabled, ForgeCache walks every <img> tag in the HTML output in document order. For each image:

The three causes below are: budget exhausted (more than 3 non-decorative images before the LCP), heuristic mis-detecting the LCP as decorative, and the LCP is a kind of element ForgeCache doesn’t handle (background image, iframe, etc.).

First — Identify the Actual LCP Element

Before fixing anything, confirm which element Lighthouse / Chrome flags as the LCP candidate. This isn’t always your visual hero image — sometimes it’s a large block of text, a button background image, or a video poster.

Open Lighthouse in DevTools

DevTools → Lighthouse tab → choose Mobile or Desktop → click Analyze page load. Wait ~10 seconds.

Find “Largest Contentful Paint element” in the audit results

Under the Performance section, expand Largest Contentful Paint element. Lighthouse highlights the exact element it considers the LCP candidate. Hover the element in the side panel to highlight it on the page so you can see what it is visually.

Note the element type

If the LCP is a <img> tag, continue with this doc — one of the three causes below applies. If the LCP is a text block (paragraph, heading), lazy load isn’t the cause — you need to look at Critical CSS / font loading instead. If the LCP is a <video> or a div with a CSS background-image, jump to cause 3 below.

Cause 1 — The Eager Budget Was Exhausted Before the LCP

Most common cause. Your template emits multiple <img> tags before the LCP candidate — site logo, social-link icons in the header, a small profile thumbnail, a sponsor banner, etc. None of those trigger the is_likely_decorative() heuristic (they’re not hidden, not display:none, and they have width/height attributes ≥ 100px). So they consume the eager budget (3 slots) before the loop reaches your actual hero image.

How to confirm

In DevTools, switch to the Elements tab and view the page source (or right-click the LCP image → Inspect). Count the <img> tags between <body> and your LCP image. If there are 3 or more img tags before the LCP, the budget was used up.

Three ways to fix it

Option A (recommended): Add loading="eager" manually to the LCP image

ForgeCache always respects an existing loading= attribute. Find where your hero image is output (usually a theme template file like header.php, a page template, or a block pattern), and add loading="eager" + fetchpriority="high" directly:

<img src="/wp-content/uploads/2026/05/hero.jpg"
     alt="..."
     width="1200" height="600"
     loading="eager"
     fetchpriority="high">

This is the cleanest fix — explicitly declares your intent in the markup. The Web Vitals optimization in ForgeCache 2.1.17+ also adds fetchpriority="high" to the first image automatically via the wp_get_attachment_image_attributes filter, but adding it manually guarantees it regardless of how the image is rendered.

Option B: Demote the decorative-looking images that consumed the budget

Your header logo and social icons don’t need eager loading. If they’re emitted by your theme without size hints, add a width + height attribute < 100 to your logo / icon images in the theme template — that triggers the is_likely_decorative() heuristic and ForgeCache will lazy-load them, freeing eager budget slots for content images.

For images emitted by a plugin (where you don’t want to edit plugin code), the cleaner path is Option A: just mark the hero explicitly.

Option C: Increase the eager budget via a filter (advanced)

If your template legitimately needs more than 3 eager images (e.g. a hero carousel with 4 above-the-fold slides), there’s no built-in setting for this in 2.1.19, but the budget can be raised via a small mu-plugin if you’re hitting this on multiple page types. Email support — we’ll send the snippet that monkey-patches the budget for your install.

Cause 2 — The Decorative Heuristic Is Mis-Flagging the LCP

The is_likely_decorative() heuristic looks for three signals: a hidden boolean attribute, an inline style with display:none or visibility:hidden, or a width / height attribute under 100px. False positives are bounded (you just lose one eager slot) but can hit specific patterns:

Pattern 1: Hero image with width="" or height="" attribute

Some theme templates emit images with empty width/height attributes (width="") when the size isn’t known at PHP-render time. Empty string evaluates to (int) "" = 0 in the heuristic’s integer comparison, so the image gets flagged as < 100 and gets lazy-loaded. Fix: don’t emit empty size attributes — either omit them or set real values.

Pattern 2: Documented gotcha — data-width="50" false-positive

The 2.1.17 heuristic uses \bwidth\s*= (a word-boundary regex) to find the width attribute. Word boundaries match between letters and non-letters — including the dash and the ‘w’ in data-width. So an image with data-width="50" (a custom data attribute, not the standard width attribute) will be misread as having width="50" and flagged as decorative.

This is a known false-positive logged in ForgeCache’s internal gotcha index. Real-world impact is low (rare HTML pattern), and the failure mode is bounded (one eager slot wasted). If you’re hitting it specifically, fixing the heuristic regex is queued for a future release. Workaround: add loading="eager" manually to the affected image (Option A from cause 1) — that takes priority over the heuristic.

Pattern 3: Hero image inside a wrapper with style="display:none" initially

Some carousel libraries server-render all slides with the inactive ones hidden via inline style, then JS activates the first one on load. If the LCP candidate is a slide that ships with style="display:none", the heuristic correctly flags it as decorative — but Lighthouse sees it as the LCP because JS unhides it during page load.

Fix: server-render the first slide WITHOUT the inline display:none (only hide slides 2+). The first slide should not have the hidden inline style — the carousel JS removes display:none AFTER ForgeCache has already processed the HTML.

Cause 3 — The LCP Isn’t a Plain <img> Tag

ForgeCache’s lazy-load processes <img> tags and <iframe> tags. It does NOT process CSS-injected background images, <video> poster images, or images set via JS after page load. If the LCP is any of those, ForgeCache isn’t doing anything wrong — the optimization simply doesn’t apply.

If the LCP is a CSS background-image

Browsers don’t support a built-in loading="eager" equivalent for background images. The standard fix is to preload the image via a <link rel="preload" as="image"> tag in <head>. ForgeCache’s Web Vitals optimization preloads the post thumbnail on singular pages automatically (when Core Web Vitals is enabled and the page is is_singular() with a featured image set). For other layouts — landing pages, custom hero blocks — you need to add a preload tag manually in your theme:

<link rel="preload" as="image"
      href="/wp-content/uploads/hero-background.jpg"
      fetchpriority="high">

Add this in <head>, ideally via wp_head action hooked in your theme’s functions.php with conditional logic for the page types where this hero pattern exists.

If the LCP is a <video> poster image

<video> poster="..." doesn’t have a loading= attribute. Preload the poster URL the same way as a CSS background image (above). Once preloaded, the poster fetches in parallel with the page’s critical resources.

If the LCP is injected by JS

If your hero image is added to the DOM by JS (React component, dynamic insertion, lightbox library, etc.), neither ForgeCache lazy-load nor browser preload help — the image URL isn’t known at HTML render time. Fix: either server-render the first image so it’s in the initial HTML (best for LCP), or accept the trade-off.

Verify the Fix

After applying one of the fixes above:

Clear cache + force-refresh

ForgeCache → Settings → Tools → Clear All Cache, then Ctrl/Cmd-Shift-R on the affected page.

Re-inspect the LCP image in DevTools

Right-click the LCP image → Inspect. Confirm the rendered HTML now has loading="eager" on the <img> tag (and ideally fetchpriority="high" too).

Re-run Lighthouse

DevTools → Lighthouse → Analyze. LCP timing should drop. “Defer offscreen images” should no longer flag your hero. “Preload Largest Contentful Paint image” should pass.

Still Stuck? Email Priority Support

If the LCP image is still loading late after applying the fix:

Email support@royalplugins.com with the diagnostic info below. Priority email support is included with your ForgeCache Pro license — typical response time is within 24 hours.

Information to include in your email

  • Your ForgeCache version + WordPress version + active theme.
  • The URL of the affected page + the specific LCP element Lighthouse identified (screenshot of the Lighthouse audit panel is ideal).
  • The rendered HTML of the LCP element from DevTools (right-click element → Copy → Copy outerHTML).
  • Whether the LCP is a plain <img>, an iframe, a CSS background, a video poster, or JS-injected.
  • Whether you tried adding loading="eager" manually to the markup (cause 1 / Option A).
Related ForgeCache docs