# Lazy Load Broke My LCP Image — Hero Loading Too Late

> ForgeCache lazy load marks the LCP hero image lazy instead of eager: it is outside the 3-image eager budget, misdetected as decorative, or an untracked image.

- Canonical: <https://royalplugins.com/support/forgecache/lazy-load-broke-my-lcp/>
- Last updated: 2026-06-01
- HTML version: <https://royalplugins.com/support/forgecache/lazy-load-broke-my-lcp/>

---

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:

- If the image already has a `loading=` attribute (you set it manually), ForgeCache **respects it** — no change.
- If the image looks decorative (the `is_likely_decorative()` heuristic in 2.1.17+ returns true), ForgeCache marks it `loading="lazy"` and **doesn’t count it toward the eager budget**.
- Otherwise, the first 3 non-decorative images get `loading="eager"` (the “eager budget”). Everything after gets `loading="lazy"`.

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](mailto: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

- [**Site got slower after activation**](https://royalplugins.com/support/forgecache/site-got-slower-after-activation/) — if the LCP regression is part of a broader speed regression.
- [**JS/CSS optimization broke my site**](https://royalplugins.com/support/forgecache/css-js-optimization-broke-my-site/) — if the issue is broken-not-slow rather than slow-LCP.
- [ForgeCache Documentation](https://royalplugins.com/support/forgecache/) — back to the main support page.
