Uncompressed images are the single most common cause of poor Core Web Vitals scores on directory sites. A listing page that loads 10 unresized user-uploaded photos — each 3–5 MB at 4000px width — produces a Largest Contentful Paint well above Google's 4-second failure threshold, directly lowering the page's performance score and search ranking. iso-25010:2011 performance-efficiency treats resource utilization as a quality attribute: serving a 4.5 MB JPEG when a 180 KB WebP would be equivalent is a measurable resource waste paid by every visitor. Mobile users on constrained bandwidth experience this as a broken product.
High because oversized images measurably degrade Core Web Vitals, reducing search ranking and user retention across every listing page that displays unconstrained uploads.
Process images with sharp before writing to storage. Resize to max 1200px on the long edge, encode as JPEG at quality 75 (or WebP for better compression), and reject files that exceed 200 KB after processing.
// lib/image-upload.ts
import sharp from 'sharp';
export async function processAndUploadImage(raw: Buffer): Promise<string> {
const optimized = await sharp(raw)
.resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 75 })
.toBuffer();
if (optimized.byteLength > 200 * 1024) {
throw new Error('Image exceeds 200 KB after compression');
}
return uploadToStorage(optimized, 'image/webp');
}
Apply this in the upload API route (src/app/api/listings/[id]/images/route.ts) before any call to S3 or Supabase Storage.
ID: directory-listing-schema.structured-data.image-optimization
Severity: high
What to look for: Count all listing images and for each, check the format and file size. check image upload or processing logic. Look for sharp, jimp, imagemin, or native image resizing code. Verify that images are resized before storage (ideally to max 1200px width) and compressed to a reasonable file size (<200KB). Sample a few stored images and check their dimensions and file size.
Pass criteria: Images are automatically resized to max 1200px width and compressed to <200KB before being stored in the CDN or object storage — 100% of images must use modern formats (WebP, AVIF) and be under 500 KB. Report: "X images found, all Y use modern formats and are under size limit."
Fail criteria: Images are stored without resizing or compression, resulting in multi-MB files or dimensions >2000px.
Skip (N/A) when: The listing type does not use images.
Detail on fail: Example: "Uploaded images not resized. Sampled image is 4500x3000px and 3.2MB" or "Images compressed but not resized; landscape images exceed 1200px width"
Remediation: Add image resizing and compression before upload. Example with sharp:
import sharp from 'sharp'
async function processAndUploadImage(file: Buffer) {
const resized = await sharp(file)
.resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 75 })
.toBuffer()
return s3Upload(resized)
}