google / eleventy-high-performance-blog

A high performance blog template for the 11ty static site generator.
https://www.industrialempathy.com/posts/eleventy-high-performance-blog/
MIT License
4.01k stars 283 forks source link

Largest Contentful Paint image was lazily loaded #146

Open rdallaire opened 2 years ago

rdallaire commented 2 years ago

I'm trying to get perfect lighthouse scores.

When having an image in the Largest Contentful Paint (LCP) you get this warning when running Google Lighthouse Tests

⚠️ Largest Contentful Paint image was lazily loaded

My example https://rossdallaire.com/notes/tv-interfaces-can-be-hard-to-find-active-selection/

https://pagespeed.web.dev/report?url=https%3A%2F%2Frossdallaire.com%2Fnotes%2Ftv-interfaces-can-be-hard-to-find-active-selection%2F&form_factor=mobile

When I look at the Industrial Empathy post that has an LCP image it has loading="eager" on the first image. https://www.industrialempathy.com/posts/viral-software-deadlines/

I'm wondering if I did something incorrectly or it looks like there needs to be an updated way to detect LCP image and load it differently.


I haven't tried anything but I'm assuming it has something to do in img-dim.js https://github.com/google/eleventy-high-performance-blog/blob/main/_11ty/img-dim.js#L91

May explore further if no one else takes a look.

cramforce commented 2 years ago

Good catch. I have a pretty aggressive optimization for my own site that renders pages in puppeteer and stores the information as to whether they appear above the fold.

const fs = require("fs-extra");
const puppeteer = require("puppeteer");

const updateHeroImages = async (content, outputPath) => {
  if (
    !outputPath.endsWith(".html") ||
    isAmp(content) ||
    // Must have an image element to be interesting.
    !/<img/.test(content)
  ) {
    return content;
  }
  const images = {};
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.emulate(puppeteer.devices["Pixel 2"]);
    await page.setContent(content, {
      waitUntil: "domcontentloaded",
    });
    for (let i of await page.$$("img")) {
      const img = await page.evaluate((i) => {
        const { top, left, bottom, right } = i.getBoundingClientRect();
        return {
          src: i.getAttribute("src"),
          rect: {
            top: Math.round(top),
            left: Math.round(left),
            bottom: Math.round(bottom),
            right: Math.round(right),
          },
        };
      }, i);
      if (!images[img.src]) {
        images[img.src] = {
          isInFirstMobileViewport: img.rect.top < 830,
          rect: img.rect,
        };
      }
    }
  } finally {
    await browser.close();
  }
  if (Object.keys(images).length) {
    const filename = `_data/images.json`;
    const allImages = JSON.parse(fs.readFileSync(filename));
    allImages[outputPath] = images;
    fs.writeFileSync(filename, JSON.stringify(allImages, null, "  "));
  }
  return content;
};

module.exports = {
  initArguments: {},
  configFunction: async (eleventyConfig, pluginOptions = {}) => {
    if (!process.env.UPDATE_HERO_IMAGES) {
      return;
    }
    eleventyConfig.addTransform("updateHeroImages", updateHeroImages);
  },
};

function isAmp(content) {
  return /\<html[^>]* amp/i.test(content);
}

I didn't put this into the open-source code because it is too slow to run on every render and I wanted to avoid complicating the template too much. See how it is guarded with UPDATE_HERO_IMAGES. I run this in a Github Action as a cron a couple times a day to gather the data.

Maybe the right solution is to simply default to eager for the first N images by default.

rdallaire commented 2 years ago

Ok great! I knew this would be fairly complex or heavy to do properly. I was also thinking about first N images by default even though it's not the perfect solution.

indcoder commented 1 year ago

Ideally I wish we could label an image for eager or lazy loading....because its a matter of perspective of blog owner /post author but after explaining the various cavears...so as to which image needs to be rendered immediately or not. Obviously hero images need to; above the fold would depend on UA's form factor. Also an important factor, UA's internet speed, an important consideration here in India and other such similar regions.

On Sun, Aug 21, 2022 at 8:26 PM Malte Ubl @.***> wrote:

Good catch. I have a pretty aggressive optimization for my own site that renders pages in puppeteer and stores the information as to whether they appear above the fold.

const fs = require("fs-extra");const puppeteer = require("puppeteer"); const updateHeroImages = async (content, outputPath) => { if ( !outputPath.endsWith(".html") || isAmp(content) || // Must have an image element to be interesting. !/<img/.test(content) ) { return content; } const images = {}; const browser = await puppeteer.launch(); try { const page = await browser.newPage(); await page.emulate(puppeteer.devices["Pixel 2"]); await page.setContent(content, { waitUntil: "domcontentloaded", }); for (let i of await page.$$("img")) { const img = await page.evaluate((i) => { const { top, left, bottom, right } = i.getBoundingClientRect(); return { src: i.getAttribute("src"), rect: { top: Math.round(top), left: Math.round(left), bottom: Math.round(bottom), right: Math.round(right), }, }; }, i); if (!images[img.src]) { images[img.src] = { isInFirstMobileViewport: img.rect.top < 830, rect: img.rect, }; } } } finally { await browser.close(); } if (Object.keys(images).length) { const filename = _data/images.json; const allImages = JSON.parse(fs.readFileSync(filename)); allImages[outputPath] = images; fs.writeFileSync(filename, JSON.stringify(allImages, null, " ")); } return content;}; module.exports = { initArguments: {}, configFunction: async (eleventyConfig, pluginOptions = {}) => { if (!process.env.UPDATE_HERO_IMAGES) { return; } eleventyConfig.addTransform("updateHeroImages", updateHeroImages); },}; function isAmp(content) { return /\<html[^>]* amp/i.test(content);}

I didn't put this into the open-source code because it is too slow to run on every render and I wanted to avoid complicating the template too much. See how it is guarded with UPDATE_HERO_IMAGES. I run this in a Github Action as a cron a couple times a day to gather the data.

Maybe the right solution is to simply default to eager for the first N images by default.

— Reply to this email directly, view it on GitHub https://github.com/google/eleventy-high-performance-blog/issues/146#issuecomment-1221561598, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAQAQZD7ERZ4FA4CFHIDTBDV2I7TJANCNFSM57EBNAJA . You are receiving this because you are subscribed to this thread.Message ID: @.*** com>