GitHub Packages · MIT · Node ≥ 18

image-pulse

Find oversized, unoptimised and wrongly formatted images in your built site, before they slow down your Largest Contentful Paint.

Find oversized and unoptimised images in a built static site: heavy files, images far wider than displayed, JPEG/PNG without AVIF/WebP, missing width/height, srcset and sizes, animated GIFs, opaque PNG photos, bloated SVGs. Reads image headers, no network. SARIF, GitHub Action. Zero deps.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/image-pulse
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx image-pulse dist --base-url https://example.comimage-pulse — https://example.com1 page(s), 5 image(s), 475 KB MEDIUM   img/anim.gif img/anim.gif is an animated GIF of 98 KB [image-animated-gif]MEDIUM   img/photo.jpg img/photo.jpg is 140 KB (400×300 jpeg), over the 100 KB budget [image-heavy]MEDIUM   img/shot.png img/shot.png is 169 KB (240×240 png), over the 100 KB budget [image-heavy]MEDIUM   index.html:3 <img src="/img/photo.jpg"> has no width/height [image-missing-dimensions]MEDIUM   index.html:3 img/photo.jpg (JPEG, 140 KB) has no AVIF/WebP alternative [image-legacy-format]MEDIUM   index.html:4 /img/banner.jpg is 2400px wide but displayed at 600px (4.0×, 49 KB) [image-oversized-dimensions]
Sample output from the image-pulse README.

Why it exists

Images are usually the heaviest part of a page and the most common LCP element. image-pulse scans the HTML and image files in your static build output (Astro, Hugo, Eleventy, Jekyll, Next.js export, Docusaurus, plain HTML), reads each image's real dimensions from its file header, and compares them with how the page uses it: a 2400px photo shown at 600px, a 400 kB JPEG with no AVIF or WebP alternative, a hero without width and height, a 2 MB animated GIF.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. Works on any static build output (Astro, Next.js “output: 'export'”, Eleventy, Hugo, Jekyll, Docusaurus, VitePress, plain HTML) and runs offline: point it at the folder your build writes. By Louis Wright.

Features

Bytes

per-image budget (--max-kb, default 250 kB; above 1 MB is high severity).

Dimensions

intrinsic width vs displayed width (from width or sizes), allowing 2.5× for high-DPI screens; aspect-ratio mismatches between attributes and the file.

Formats

JPEG/PNG without an AVIF or WebP alternative (<picture><source type> or srcset), opaque PNG photos, animated GIFs, baseline JPEGs, SVGs carrying editor metadata.

Responsive markup

large images without srcset, w-descriptor srcsets without sizes, <img> without width/height (layout shift).

Local and fast

dimension parsing for PNG, JPEG, GIF, WebP (VP8/VP8L/VP8X), AVIF/HEIF and SVG with no native modules; external images get markup checks only.

Usage

# Example

 npx image-pulse dist

# Example

 npx image-pulse public --max-kb 150 --fail-on medium

# Example

 npx image-pulse _site --format sarif > images.sarif

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  image-pulse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - run: npm ci && npm run build   # produce the static site first
      - uses: MrLouisWright/image-pulse@v1
        with:
          path: dist
          fail-on: high
          sarif: image-pulse.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: image-pulse.sarif }

Options & flags

FlagDescription
Site
--base-url <url>Public URL of the site, e.g. https://example.com (default: inferred from canonical/og:url)
--include <globs>Only check these pages (file or URL path globs, comma-separated)
--exclude <globs>Skip these pages
--clean-urlsPages are served without .html (about.html -> /about)
Images
--max-kb <n>Byte budget per image file in KB (default 250)
--max-scale <n>Allowed intrinsic/display width ratio (default 2.5)
Common
-c, --config <file>JSON config (default: .image-pulse.json or "imagePulse" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20