Image Processing Strategies for Web Performance
Drop a PDF report onto your company blog as-is and your LCP jumps to three seconds. You converted one page to PNG and shipped it — and now half your mobile first-paint budget is locked up in that single image. This article walks through how PNGs extracted from PDFs interact with Core Web Vitals (LCP, CLS, INP), and the six strategies that keep your score green.
How PNGs affect Core Web Vitals
| Metric | Why it's affected | Target |
|---|---|---|
| LCP | A large PNG often is the largest above-the-fold element | ≤ 2.5s |
| CLS | Layout shifts as the PNG paints in | ≤ 0.1 |
| INP | Heavy decoding work blocks the main thread | ≤ 200ms |
Strategy 1: render at the right size up front
The single biggest lever. Convert a PDF to a 2000px-long-edge PNG and display it at 700px? You ship 1.3 MB to render at one-third of that. Start from 1200–1600px (or downscale right after conversion) and you get the same visual at half the bytes.
# ImageMagick — resize to 1600px wide and strip metadata magick page-001.png -resize 1600x -strip web/page-001.png # sips on macOS sips -Z 1600 page-001.png --out web/page-001.png
Strategy 2: always set width/height
Without width/height the browser waits for the image to download before allocating space — and text reflows when it lands. That's a CLS hit.
<!-- Bad -->
<img src="/page-001.png" alt="Report page 1" />
<!-- Good -->
<img src="/page-001.png" alt="Report page 1"
width="1600" height="2263" />Next.js's <Image> enforces this and defends CLS automatically.
Strategy 3: lazy load below the fold
Thirty PDF pages in a gallery? They shouldn't all fetch at first paint. loading="lazy" tells the browser to wait until the image is near the viewport.
<img src="/page-005.png" alt="Page 5" width="1600" height="2263" loading="lazy" decoding="async" />
But never lazy-load the LCP image above the fold. If anything, mark it fetchpriority="high".
Strategy 4: re-encode to WebP / AVIF
Ship PNG as-is and photo-heavy pages cross 1 MB easily. At matched quality, WebP is ~25% smaller, AVIF ~40%.
# PNG → WebP magick page-001.png -quality 85 web/page-001.webp # PNG → AVIF (slower encode, smallest output) magick page-001.png -quality 70 web/page-001.avif
Need compatibility fallbacks? Wrap them in <picture> with AVIF → WebP → PNG:
<picture> <source srcSet="/page-001.avif" type="image/avif" /> <source srcSet="/page-001.webp" type="image/webp" /> <img src="/page-001.png" alt="..." width="1600" height="2263" /> </picture>
Strategy 5: responsive srcset
Mobile needs 800px, desktop maybe 1200px, Retina desktop 2400px. Sending the same 2400px PNG to everyone burns mobile bytes; sending 800px to everyone betrays desktop Retina users.
<img
src="/page-001-1200.png"
srcSet="/page-001-800.png 800w,
/page-001-1200.png 1200w,
/page-001-2400.png 2400w"
sizes="(max-width: 768px) 100vw, 1200px"
alt="..."
width="1200" height="1697"
/>Next.js <Image> generates srcset and sizes for you.
Strategy 6: CDN caching with immutable headers
PNGs almost never change once rendered. Hash the file name (page-001.a8f3.png) and serve with Cache-Control: public, max-age=31536000, immutable. Repeat visitors skip the network entirely, cutting LCP roughly in half.
Before / after measurements
A marketing page embedding one PDF report page above the fold, measured on a mid-tier mobile:
| Setup | Bytes | Mobile LCP |
|---|---|---|
| 2000px PNG, no cache | 1.3 MB | 3.4s ❌ |
| 1200px PNG + lazy | 450 KB | 2.1s ⚠️ |
| 1200px AVIF + cache + preload | 180 KB | 1.4s ✅ |
Pull the source PNG at our converter's 2000px default, then bake the six steps above (downscale, re-encode, lazy, srcset, cache headers) into a build step. After that, every new PDF gets optimized for free.