Serve listing photos with responsive srcset variants
Summary
- Generate 400/800/1600 px sidecar JPEGs at upload time in one Sharp pipeline; canonical 2000 px key unchanged (no DB migration).
-
<img srcset sizes>onListingCard(sizes="(min-width: 640px) 240px, 50vw") andImageCarousel(sizes="100vw"). OG/Twitter meta still uses the canonical URL — scrapers want one big image. -
deleteImageremoves all three sidecars alongside the canonical (best-effort). - One-shot backfill script:
pnpm backfill:image-variants— iteratesimagesrows,HeadObjects to skip already-backfilled rows, downloads canonical, regenerates and uploads missing sidecars. -
ListingCardhover-preloader now setssrcset/sizesonnew Image()so it preloads the thumbnail variant instead of the 2 MB canonical.
Why
Every <img> previously pulled the full 2000 px / ~2 MB JPEG even when rendered inside a ~220 px grid cell. On mobile or slow connections that's significant wasted bandwidth and a visible perf hit.
Test plan
-
pnpm install(newtsxdevDep) thenpnpm check— passes 0 errors / 0 warnings. -
pnpm build— clean. -
pnpm dev, upload a new photo via/admin/new, confirm S3 / RustFS holds four objects: canonical +-w400+-w800+-w1600. -
Load /in DevTools → Network → Img, throttle "Slow 4G". Thumbnails should fetch-w400or-w800, not the canonical. -
Load /sale/{shortId}on a 1920-wide viewport — carousel fetches-w1600; resize to mobile — falls to-w800. -
Delete an image via /admin/edit/{id}— all four S3 objects should be removed. -
pnpm backfill:image-variantsagainst a DB with existing images — sidecars appear; re-running is a no-op. -
curl -s https://<host>/sale/{shortId} | grep og:image— still points at the canonical 2000 px URL.