Claude
Skills
Sign in
Back

image-optimization-audit

Included with Lifetime
$97 forever

Analyzes every image on a page: format efficiency (WebP/AVIF vs legacy), dimension vs display size ratio, lazy loading correctness, responsive images (srcset/sizes/picture), above-fold images that should not be lazy, total weight with savings estimate, missing width/height (CLS risk), and broken images. Produces an image-by-image report with actionable recommendations.

Image & Video

What this skill does


# Image Optimization Audit

Perform a comprehensive audit of every image on the page. Checks format
efficiency, oversized dimensions, lazy loading correctness, responsive markup,
CLS risk from missing dimensions, and broken images. Calculates potential byte
savings from format conversion and dimension optimization.

## When to Use

- Diagnosing slow page loads where images are the primary bottleneck.
- Checking that lazy loading is correctly applied (not on above-fold images).
- Verifying responsive image markup (srcset, sizes, picture) is present.
- Estimating byte savings from converting JPEG/PNG to WebP/AVIF.
- Identifying missing width/height attributes that cause layout shifts.

## Prerequisites

- **Playwright MCP server** connected and responding (all `mcp__playwright__browser_*` tools available).
- **Chromium-based browser** required for CDP Network domain and full image introspection.
- Target page must be reachable from the browser instance.

## Workflow

### Step 1 -- Set Up Network Monitoring via CDP

Enable CDP Network monitoring before navigation to capture image transfer
sizes and content types.

```javascript
browser_run_code({
  code: `async (page) => {
    const client = await page.context().newCDPSession(page);
    await client.send('Network.enable');

    const imageRequests = {};

    client.on('Network.responseReceived', (params) => {
      const url = params.response.url;
      const mimeType = params.response.mimeType || '';
      if (mimeType.startsWith('image/') || params.type === 'Image') {
        imageRequests[params.requestId] = {
          url,
          mimeType,
          status: params.response.status,
          protocol: params.response.protocol,
          headers: {
            contentLength: params.response.headers['content-length'] || null,
            contentType: params.response.headers['content-type'] || null,
            cacheControl: params.response.headers['cache-control'] || null
          }
        };
      }
    });

    client.on('Network.loadingFinished', (params) => {
      if (imageRequests[params.requestId]) {
        imageRequests[params.requestId].encodedDataLength = params.encodedDataLength;
      }
    });

    client.on('Network.loadingFailed', (params) => {
      if (imageRequests[params.requestId]) {
        imageRequests[params.requestId].failed = true;
        imageRequests[params.requestId].errorText = params.errorText;
      }
    });

    page.__imageRequests = imageRequests;
    page.__cdpClient = client;

    return 'Image network monitoring enabled';
  }`
})
```

### Step 2 -- Navigate to the Target Page

```
browser_navigate({ url: "<target_url>" })
```

Wait for images to load (including lazy-loaded ones triggered by scroll):

```
browser_wait_for({ time: 3 })
```

### Step 3 -- Scroll to Trigger Lazy-Loaded Images

Scroll through the page to trigger lazy-loaded images, then wait for them
to load.

```javascript
browser_evaluate({
  function: `() => {
    return new Promise((resolve) => {
      const totalHeight = document.documentElement.scrollHeight;
      const viewportHeight = window.innerHeight;
      let currentPosition = 0;
      const step = viewportHeight * 0.8;

      const scrollInterval = setInterval(() => {
        currentPosition += step;
        window.scrollTo(0, currentPosition);

        if (currentPosition >= totalHeight) {
          clearInterval(scrollInterval);
          // Scroll back to top
          window.scrollTo(0, 0);
          resolve('Scrolled through entire page, height: ' + totalHeight + 'px');
        }
      }, 200);
    });
  }`
})
```

```
browser_wait_for({ time: 3 })
```

### Step 4 -- Enumerate All Images with Full Attributes

Collect comprehensive data for every image element on the page.

```javascript
browser_evaluate({
  function: `() => {
    const images = document.querySelectorAll('img');
    const pictureElements = document.querySelectorAll('picture');
    const results = [];

    for (const img of images) {
      const rect = img.getBoundingClientRect();
      const style = window.getComputedStyle(img);

      // Check if image is in a <picture> element
      const inPicture = img.parentElement && img.parentElement.tagName === 'PICTURE';
      let pictureSources = [];
      if (inPicture) {
        const sources = img.parentElement.querySelectorAll('source');
        for (const source of sources) {
          pictureSources.push({
            srcset: source.srcset || null,
            sizes: source.sizes || null,
            type: source.type || null,
            media: source.media || null
          });
        }
      }

      results.push({
        src: img.src || null,
        currentSrc: img.currentSrc || null,
        srcset: img.srcset || null,
        sizes: img.sizes || null,
        alt: img.alt,
        hasAlt: img.hasAttribute('alt'),
        loading: img.loading || 'auto',
        decoding: img.decoding || 'auto',
        fetchpriority: img.fetchPriority || null,
        hasWidthAttr: img.hasAttribute('width'),
        hasHeightAttr: img.hasAttribute('height'),
        widthAttr: img.getAttribute('width'),
        heightAttr: img.getAttribute('height'),
        naturalWidth: img.naturalWidth,
        naturalHeight: img.naturalHeight,
        displayWidth: Math.round(rect.width),
        displayHeight: Math.round(rect.height),
        isVisible: style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0,
        isComplete: img.complete,
        isBroken: img.complete && img.naturalWidth === 0 && img.src,
        inPicture,
        pictureSources,
        cssObjectFit: style.objectFit,
        cssAspectRatio: style.aspectRatio,
        top: Math.round(rect.top + window.scrollY),
        left: Math.round(rect.left)
      });
    }

    // Also check CSS background images on key elements
    const bgImages = [];
    const elements = document.querySelectorAll('[style*="background-image"], .hero, .banner, .jumbotron, header, section');
    for (const el of elements) {
      const bg = window.getComputedStyle(el).backgroundImage;
      if (bg && bg !== 'none') {
        const urls = bg.match(/url\(["']?([^"')]+)["']?\)/g);
        if (urls) {
          for (const u of urls) {
            const match = u.match(/url\(["']?([^"')]+)["']?\)/);
            if (match) {
              bgImages.push({
                element: el.tagName.toLowerCase() + (el.id ? '#' + el.id : '') + (el.className && typeof el.className === 'string' ? '.' + el.className.trim().split(/\\s+/)[0] : ''),
                url: match[1],
                elementWidth: Math.round(el.getBoundingClientRect().width),
                elementHeight: Math.round(el.getBoundingClientRect().height)
              });
            }
          }
        }
      }
    }

    return {
      totalImages: images.length,
      totalPictureElements: pictureElements.length,
      images: results,
      cssBackgroundImages: bgImages.slice(0, 20)
    };
  }`
})
```

### Step 5 -- Detect Above-Fold Images

Identify which images are above the fold (visible in the initial viewport
without scrolling) to check lazy loading correctness.

```javascript
browser_evaluate({
  function: `() => {
    const viewportHeight = window.innerHeight;
    const images = document.querySelectorAll('img');
    const aboveFold = [];
    const belowFold = [];

    for (const img of images) {
      const rect = img.getBoundingClientRect();
      if (rect.width === 0 && rect.height === 0) continue;

      const entry = {
        src: (img.src || '').split('/').pop().substring(0, 50),
        top: Math.round(rect.top),
        loading: img.loading || 'auto',
        fetchpriority: img.fetchPriority || null,
        displayWidth: Math.round(rect.width),
        displayHeight: Math.round(rect.height)
      };

      // Image is above fold if its top edge is within the viewport
      if (rect.top < viewportHeight && rect.bottom > 0) {
        aboveFold.push(entry);
      } else {

Related in Image & Video