The LCP Element That Wasn’t Content

Largest Contentful Paint measures, per its name, the largest piece of content that paints on screen. Most of the time that’s exactly what you’d guess — a hero image, a headline, a big above-the-fold photo. So when LCP on a network of content sites stayed stubbornly bad despite genuinely fixing hero image sizing, format, and preload hints, the obvious next move was to optimize harder. Smaller images. Better srcset. Explicit width/height. None of it moved the number.

It didn’t move because none of it was the thing actually being measured.

TL;DR: LCP stayed bad despite genuine hero-image optimization because the LCP element wasn’t the hero image at all — it was a cookie-consent banner’s logo, loading unconditionally on every page view. The fix lived entirely in a third-party vendor’s dashboard setting, not in any repository a code review would ever touch.

What Did the Trace Actually Say?

The instinct, when LCP won’t improve, is to keep tuning the thing you’re already looking at. The move that actually broke the case open was simpler: stop assuming, and read the Lighthouse trace’s element path literally, instead of skimming past it to the number.

The flagged LCP element wasn’t inside the page’s article markup at all. Its selector path looked something like:

body.home > div#consent-overlay > div.cmp-logo > img.cmp-logo-img

That’s not a hero image. That’s a logo, inside a consent-management overlay, inside a div that has nothing to do with page content. The moment that selector path is actually read instead of glossed over, the whole shape of the investigation changes — this stopped being "why is our hero image slow" and became "why is a cookie-banner logo being measured as our largest content at all."

Why Can a Consent Banner Win LCP?

A consent-management platform (CMP) — the kind of third-party SDK responsible for the cookie/privacy overlay on basically every European site — typically renders early, deliberately, so it can gate everything else behind a consent decision. If that overlay renders a full-viewport-width logo before the page’s real content has painted, and that logo is larger in rendered pixel area than anything else on screen at that moment, Lighthouse and Chrome’s own CrUX field data will both correctly report it as the LCP element. They’re not wrong — it genuinely was the largest thing that painted. It’s just not what anyone means by "content."

On this particular network, the CMP SDK — about 55KB of JS plus half a dozen additional network requests — was loading unconditionally on every single page view, for every visitor, regardless of whether they’d already made a consent choice. That unconditional load was the precondition; the oversized logo inside the resulting overlay was the trigger.

Where Did the Bug Actually Live?

Here’s the part worth sitting with: there was no application code to fix. Grepping the entire site codebase for anything related to the CMP integration, and searching the deploy history for any commit that touched it, turned up nothing — because there was nothing there to find. The unconditional loading behavior was controlled entirely by a setting inside the CMP vendor’s own admin dashboard, not in the theme, not in a plugin, not in any file under version control.

Specifically: a dashboard toggle (something like "gate script loading behind consent") was switched off, and a manually-authored custom-JavaScript block was configured directly in that same dashboard, containing an unconditional loader — a queue-stub pattern that’s extremely common for this kind of SDK:

window.cmp = window.cmp || function () {
  (window.cmp.q = window.cmp.q || []).push(arguments);
};
cmp('create', 'YOUR-ACCOUNT-ID');
cmp('authenticate');
cmp('render');

(function () {
  var script = document.createElement('script');
  script.async = true;
  script.src = 'https://cdn.example-cmp-vendor.com/sdk.js';
  document.head.appendChild(script);
})();

Nothing about that snippet is wrong on its own — it’s a standard loader pattern. The bug was entirely in the configuration surrounding it: the gate that should have prevented this from running before a consent decision (or at minimum, deferred it out of the critical rendering path) was simply off.

This is worth internalizing as its own lesson, independent of the specific fix: not every performance bug lives in a repository you can git blame. A meaningful and growing share of what actually renders on a modern page — consent platforms, tag managers, A/B testing tools, analytics — is configured in a vendor’s own dashboard, invisible to a code review, and just as capable of tanking your Core Web Vitals as anything in your own stack. If your investigation stays confined to "our codebase," you’ll eventually chase a bug that was never there.

There was also a smaller, quieter finding worth mentioning: an earlier note in the team’s tracking system claimed this exact issue had already been reported and a ticket filed. It hadn’t. A search by reporter and by full text turned up nothing. Someone had spotted the problem once, said as much out loud, and it evaporated without ever becoming an actionable, assigned piece of work — a reminder that "someone probably already knows about this" is worth verifying, not assuming.

What Was the Fix (and an Honest Result)?

The fix itself was almost anticlimactic once found: flip the gating setting in the CMP dashboard so script loading actually waits for a consent decision (or at minimum defer it off the critical path), and reduce the overlay’s own logo asset to something that can’t plausibly out-size real page content.

What’s more interesting than the fix is what happened when it was measured — properly, with repeated warm runs rather than a single before/after — across four sites on the same network that all shared the same CMP configuration:

Site LCP before LCP after
A 7.5s 3.0s
B 4.5s ~3.0s
C 5.8s 5.8s (no change)
D 2.7s ~3.0s (no change)

Two of the four sites improved meaningfully. The other two didn’t move — because on those sites, the page’s own real hero image was already larger in rendered area than the CMP logo, so the overlay was never the actual bottleneck there in the first place; it was present but not load-bearing. That’s an important, easy-to-skip detail: the same misconfigured third-party script produced a large win on some pages and nothing measurable on others, purely as a function of what else happened to be competing for "largest" on that particular page. A post-fix report that only showed the two improved sites would have overstated the fix; showing all four is what makes the result trustworthy.

What’s the Generalizable Lesson Here?

  • When a metric refuses to move despite genuine optimization work, stop tuning what you’re already looking at and go re-read exactly what the tool says is being measured. Lighthouse and CrUX will tell you the specific element — read the selector, don’t just read the number.
  • Consent platforms, tag managers, and similar vendor-configured tooling are common, high-leverage, and structurally invisible to a code-only investigation. If your app-level grep comes up empty, that’s a signal to look at vendor dashboards next, not a dead end.
  • Measure the fix across every affected surface, not just the one that shows the best number. A fix that helps two of four sites and does nothing measurable on the other two is a more honest — and more useful — result than cherry-picking the win.
  • "Someone probably already reported this" is a claim, not a fact. Check.