[GH-ISSUE #363] feat(image): build-time image optimization via Sharp #83

Closed
opened 2026-05-06 12:37:05 +02:00 by BreizhHardware · 1 comment

Originally created by @Divkix on GitHub (Mar 9, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/363

Summary

Add build-time WebP generation via sharp (optional peer dep) and real-time dev optimization for local images. Closes the gap for local image optimization without requiring paid services.

Current Behavior

Scenario Current
Local images, build Served as-is
Local images, dev 302 redirect to original
Local images, Node.js prod Passthrough
Local images, CF Workers CF Images binding or passthrough
Remote images (CDN) @unpic/react

Proposed Behavior

Scenario After
Local images, build Pre-generated .webp at multiple widths
Local images, dev Sharp resize+webp on-the-fly (cached)
Local images, Node.js prod Sharp on-demand + filesystem cache
Local images, CF Workers Build-time .webp served from ASSETS (no change)
Remote images (CDN) No change

Implementation

Sharp is an optional peer dependency. If not installed, everything works exactly as today.

Files Changed

  • NEW: packages/vinext/src/utils/sharp.ts — Sharp availability detection
  • MODIFY: packages/vinext/src/shims/image.tsx — Extend StaticImageData, update rendering
  • MODIFY: packages/vinext/src/index.ts — Build-time ?vinext-opt plugin + dev server handler
  • MODIFY: packages/vinext/src/server/app-dev-server.ts — App Router dev optimization
  • MODIFY: packages/vinext/src/server/prod-server.ts — Node.js prod optimization
  • MODIFY: packages/vinext/package.json — Optional peer dep
  • NEW: tests/image-optimization.test.ts — New test file
  • MODIFY: tests/image-component.test.ts — Extended tests

Edge Cases Handled

  • SVG files skipped (vector, no benefit)
  • Animated GIF detection via metadata pages
  • Upscale prevention (no widths > 2x original)
  • Memory cache capped at 500 entries in dev
  • Filesystem cache in prod (/.vinext-image-cache/)
  • Graceful fallback when sharp unavailable
  • Build concurrency limited to CPU count

Test Plan

  • Unit tests: sharp detection, build-time transform, component rendering with optimizedSrcSet
  • Dev mode: verify /_vinext/image returns WebP with correct Content-Type
  • Build: verify .webp files in dist/client/assets/
  • Built HTML: srcSet points to static .webp assets
  • Without sharp: build works, original images served
  • pnpm run typecheck and pnpm run lint pass
Originally created by @Divkix on GitHub (Mar 9, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/363 ## Summary Add build-time WebP generation via sharp (optional peer dep) and real-time dev optimization for local images. Closes the gap for local image optimization without requiring paid services. ## Current Behavior | Scenario | Current | |----------|---------| | Local images, build | Served as-is | | Local images, dev | 302 redirect to original | | Local images, Node.js prod | Passthrough | | Local images, CF Workers | CF Images binding or passthrough | | Remote images (CDN) | @unpic/react | ## Proposed Behavior | Scenario | After | |----------|-------| | Local images, build | Pre-generated .webp at multiple widths | | Local images, dev | Sharp resize+webp on-the-fly (cached) | | Local images, Node.js prod | Sharp on-demand + filesystem cache | | Local images, CF Workers | Build-time .webp served from ASSETS (no change) | | Remote images (CDN) | No change | ## Implementation Sharp is an **optional peer dependency**. If not installed, everything works exactly as today. ### Files Changed - **NEW**: `packages/vinext/src/utils/sharp.ts` — Sharp availability detection - **MODIFY**: `packages/vinext/src/shims/image.tsx` — Extend StaticImageData, update rendering - **MODIFY**: `packages/vinext/src/index.ts` — Build-time `?vinext-opt` plugin + dev server handler - **MODIFY**: `packages/vinext/src/server/app-dev-server.ts` — App Router dev optimization - **MODIFY**: `packages/vinext/src/server/prod-server.ts` — Node.js prod optimization - **MODIFY**: `packages/vinext/package.json` — Optional peer dep - **NEW**: `tests/image-optimization.test.ts` — New test file - **MODIFY**: `tests/image-component.test.ts` — Extended tests ### Edge Cases Handled - SVG files skipped (vector, no benefit) - Animated GIF detection via metadata pages - Upscale prevention (no widths > 2x original) - Memory cache capped at 500 entries in dev - Filesystem cache in prod (`/.vinext-image-cache/`) - Graceful fallback when sharp unavailable - Build concurrency limited to CPU count ## Test Plan - [ ] Unit tests: sharp detection, build-time transform, component rendering with optimizedSrcSet - [ ] Dev mode: verify `/_vinext/image` returns WebP with correct Content-Type - [ ] Build: verify `.webp` files in `dist/client/assets/` - [ ] Built HTML: srcSet points to static `.webp` assets - [ ] Without sharp: build works, original images served - [ ] `pnpm run typecheck` and `pnpm run lint` pass
Author
Owner

@Divkix commented on GitHub (Mar 10, 2026):

Deferring this work — PR #364 is closed. Will revisit image optimization parity in a future iteration.

<!-- gh-comment-id:4029397877 --> @Divkix commented on GitHub (Mar 10, 2026): Deferring this work — PR #364 is closed. Will revisit image optimization parity in a future iteration.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#83
No description provided.