First CH Apps / Page Image Audit
Enter a URL and every img / picture on that page is audited one by one: the format (still JPEG, or already WebP / AVIF), whether loading="lazy" is set, whether width / height are declared, and what the alt says. For the first 30 images we send a HEAD request and measure the real file size and the format actually served.
Converting the images themselves belongs to the WebP / AVIF converters on tools.first-ch.com, which run entirely in your browser with no upload. This page decides which images need fixing; the fixing happens in those browser tools.
Only the single page you enter is fetched (no site-wide crawling). File sizes are measured for the first 30 images; anything beyond that is marked as not measured.
Convert JPEG / PNG to WebP or AVIF. AVIF suits photographs, WebP handles transparency and illustrations well, and both are supported by every major browser. If you need a fallback for old clients, wrap them in <picture> with source type="image/avif" and type="image/webp" and keep the original as the final img.
The converters on tools.first-ch.com (Batch Image to WebP / to AVIF) do the job entirely inside your browser — the images are never uploaded — and the AVIF one also prints the <picture> snippet for you.
Put the intrinsic width / height of the original file in the HTML. Even when CSS sets width:100%, those attributes let the browser reserve space from the aspect ratio, which removes the jump (CLS) when the image appears.
Use loading="lazy" only for images that are not visible on first paint. Leave it off the large image at the top of the page — that one is usually the LCP element — and consider fetchpriority="high" instead.
Do not ship images far larger than they are displayed. Serving a 3000px-wide original into a 600px slot wastes most of the transfer. srcset and sizes let the browser pick the one candidate that fits the device.
Write alt as the information the image conveys, kept short. Purely decorative images should have alt="" so screen readers skip them — omitting the attribute entirely makes them read the file name instead.
Only the HTML of the single URL you enter is fetched. There is no site-wide crawl, and CSS background images or images injected later by JavaScript are invisible.
File sizes are measured with HEAD requests for the first 30 images (identical URLs are merged into one row). The limit keeps us inside the free-tier subrequest budget; anything beyond it is labelled "not measured" rather than silently dropped.
Servers that reject HEAD — and servers that answer HEAD without a Content-Length (Cloudflare Pages, for one) — are asked once more with GET (Range: bytes=0-0). The body is discarded unread, so the image itself is never downloaded. If the size still cannot be determined we show it as unknown.
The format comes from Content-Type when available and from the file extension otherwise. A setup where .jpg URLs return WebP (content negotiation on the Accept header) counts as already converted.
An image is flagged for review at 200 KB and for fixing at 500 KB.
width / height count as declared only when both are plain numbers (% and auto reserve no space). An aspect-ratio in the style attribute also counts; declarations in CSS files are invisible to us.
loading="lazy" is expected on every image except the first, and flagged when it is on the first one (it delays LCP). Images that only have data-src (JavaScript lazy loading) are exempt.
An img inside a <picture> counts as already converted when the same picture offers a source with type="image/webp" or type="image/avif".
At most 120 images are listed and only the first 512KB of HTML is scanned (to stay inside the free-tier CPU limit).
Sent: only the URL you type. Our server (Cloudflare Workers) fetches it once with a public GET and sends a HEAD request to the first 30 images found (the image data itself is never downloaded). No cookies and no credentials are sent.
Stored: nothing. The audit lives entirely inside one request and writes neither the URL nor the result to any database (no KV, no D1). It leaves memory when the request ends.
Returned: the image URLs, their attributes, the measured size and format, and our verdicts — nothing else. Neither the fetched page nor the images are ever re-served (this app cannot be used as an image proxy).
Note: public GET / HEAD only. URLs pointing at internal networks, localhost or private IPs are rejected.
POST /image-audit/api/inspect with JSON ({"url": "https://example.com/"}) or a form body and you get the same audit back as JSON. /image-audit/api/extract returns the extracted images without measuring them, and /image-audit/api/head measures a list of image URLs (up to 10 per request). The page itself uses those two plus /image-audit/api/notes, which rebuilds the findings text from the per-row verdicts.