web-frontendcore-web-vitalsweb-vitalsfront-end-optimization

LCP result is totally opposite from performance API than the pagespeed insights or webpagetest


I am trying to optimise LCP for this page. I read an article on LCP optimisation where I also found a script which can help to determine which part of the LCP most time is spent on. Script:

const LCP_SUB_PARTS = [
  'Time to first byte',
  'Resource load delay',
  'Resource load time',
  'Element render delay',
];

new PerformanceObserver((list) => {
  const lcpEntry = list.getEntries().at(-1);
  const navEntry = performance.getEntriesByType('navigation')[0];
  const lcpResEntry = performance
    .getEntriesByType('resource')
    .filter((e) => e.name === lcpEntry.url)[0];

  // Ignore LCP entries that aren't images to reduce DevTools noise.
  // Comment this line out if you want to include text entries.
  if (!lcpEntry.url) return;

  // Compute the start and end times of each LCP sub-part.
  // WARNING! If your LCP resource is loaded cross-origin, make sure to add
  // the `Timing-Allow-Origin` (TAO) header to get the most accurate results.
  const ttfb = navEntry.responseStart;
  const lcpRequestStart = Math.max(
    ttfb,
    // Prefer `requestStart` (if TOA is set), otherwise use `startTime`.
    lcpResEntry ? lcpResEntry.requestStart || lcpResEntry.startTime : 0
  );
  const lcpResponseEnd = Math.max(
    lcpRequestStart,
    lcpResEntry ? lcpResEntry.responseEnd : 0
  );
  const lcpRenderTime = Math.max(
    lcpResponseEnd,
    // Prefer `renderTime` (if TOA is set), otherwise use `loadTime`.
    lcpEntry ? lcpEntry.renderTime || lcpEntry.loadTime : 0
  );

  // Clear previous measures before making new ones.
  // Note: due to a bug this does not work in Chrome DevTools.
  // LCP_SUB_PARTS.forEach(performance.clearMeasures);

  // Create measures for each LCP sub-part for easier
  // visualization in the Chrome DevTools Performance panel.
  const lcpSubPartMeasures = [
    performance.measure(LCP_SUB_PARTS[0], {
      start: 0,
      end: ttfb,
    }),
    performance.measure(LCP_SUB_PARTS[1], {
      start: ttfb,
      end: lcpRequestStart,
    }),
    performance.measure(LCP_SUB_PARTS[2], {
      start: lcpRequestStart,
      end: lcpResponseEnd,
    }),
    performance.measure(LCP_SUB_PARTS[3], {
      start: lcpResponseEnd,
      end: lcpRenderTime,
    }),
  ];

  // Log helpful debug information to the console.
  console.log('LCP value: ', lcpRenderTime);
  console.log('LCP element: ', lcpEntry.element);
  console.table(
    lcpSubPartMeasures.map((measure) => ({
      'LCP sub-part': measure.name,
      'Time (ms)': measure.duration,
      '% of LCP': `${
        Math.round((1000 * measure.duration) / lcpRenderTime) / 10
      }%`,
    }))
  );
}).observe({type: 'largest-contentful-paint', buffered: true});

For me, this was the result at the start in 4x CPU slowdown and Fast3G connection. enter image description here

After that, since render delay was the area where I should focus on, I moved some of the scripts to the footer and also made the "deferred" scripts "async". This is the result:

enter image description here

We can see there is a clear improvement in LCP after the change but, when I test with lighthouse the result is different.

Before: enter image description here

After: enter image description here

I am in dilemma now about what step to take. Please suggest!!


Solution

  • I ran a trace of the URL you linked in your question, and the first thing I noticed is that your LCP resource finishes loading pretty early in the page, but it isn't able to render until a file called mirage2.min.js finishes loading.

    This explains why your "Element render delay" portion of LCP is so long, and moving your scripts to the bottom of the page or seeing defer of them is not going to solve that problem. The solution is to make it so your LCP image can render without needing to wait until that JavaScript file finishes loading.

    Another thing I noticed is this mirage2.min.js file is loaded from ajax.cloudflare.com, which made me think it's a "feature" offered by Cloudflare and not something you set up yourself.

    Based on what I see here, I'm assuming that's true: https://support.cloudflare.com/hc/en-us/articles/219178057

    So my recommendation for you is to turn off this feature, because it's clearly not helping your LCP, as you can see in this trace:

    Performance trace using Chrome DevTools

    There's one more thing you said that I think is worth clarifying:

    After that, since render delay was the area where I should focus on, I moved some of the scripts to the footer and also made the "deferred" scripts "async". This is the result:

    When I look at your "result" screenshot, I still see that the "element render delay" portion is still > 50%, so while you were correct when you said that "render delay was the area where I should focus on", the fact that it was still high after you made your changes (e.g. moving the scripts and using defer/async) was an indication that the changes you tried didn't fix the problem.

    In this case, I believe that if you turn off the "Mirage" feature in your Cloudflare dashboard, you should see a big improvement.


    Oh, one more thing, I noticed that you're using importance="high" on your image. This is old syntax that does not work anymore. You should replace that with fetchpriority="high" instead. See this post for details: https://web.dev/priority-hints/