feat(images): add image variant generator library (BE-2)#473
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
BE-2: pure server-side library for the image preprocessing pipeline.
server/lib/image-variants.ts provides:
- VARIANT_TIER_WIDTHS [320..2560] kept in sync with next.config
imageSizes/deviceSizes so every loader-requested width maps to a
generated tier.
- generateImageVariants(): sharp-based generation of avif + webp at every
tier <= source width (never upscales), ascending, plus the base64
thumbhash placeholder. EXIF auto-orientation; 100 MP decompression-bomb
guard; failOn:'none' for resilience on slightly malformed inputs.
- computeImageKey(): content-addressed base key (sha256) so variants are
safe to serve with an immutable cache header and never need invalidation.
- buildVariantKey(): single source of truth for the `{key}_{w}.{fmt}`
naming convention the custom next/image loader also uses.
- putVariantObject(): S3/R2 upload primitive that stamps the immutable
Cache-Control header at write time.
Pure and side-effect-free except the explicit upload primitive: the
preprocessing queue (BE-3) orchestrates fetch -> generate -> upload ->
advance ready_max_width. OpenList variant upload is handled in BE-3
(it owns the OpenList put specifics).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sharp's .metadata() returns the stored (pre-rotation) width/height, but variants are generated via .rotate() (EXIF auto-orientation). For orientation 5-8 (rotate 90/270/transpose/transverse — common on phone photos) the displayed dimensions are swapped, so the previous code returned width/height that disagreed with both the generated pixels and the browser-reported dimensions of existing rows, which would reintroduce the portrait-as-landscape gallery layout bug. Read metadata.orientation and swap width/height for orientation 5-8. Zero extra decode. Verified against sharp 0.34.5 with a synthetic orientation=6 image (200x100 stored -> 100x200 displayed): swap-based dims now match the actual .rotate() output. Addresses BE-2 review feedback from @Picimpact-Review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7d2a1f4 to
4bb480e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BE-2 — image variant generator library
Part of the image-performance overhaul (parent task #1 in #picimpact). This is the pure generation engine the preprocessing queue (BE-3) will drive. No wiring into the request path here — that comes with BE-3.
What
New
server/lib/image-variants.ts:VARIANT_TIER_WIDTHS[320,480,640,800,1080,1280,1920,2560]— kept in sync with the plannednext.configimageSizes(320,480)/deviceSizes(640..2560), so every width the custom image loader requests maps onto a generated tier.generateImageVariants(input)— sharp generation of avif + webp at every tier ≤ source width (never upscales), ascending, plus the base64 thumbhash placeholder. EXIF auto-orientation (.rotate()), a 100 MP decompression-bomb guard (limitInputPixels), andfailOn: 'none'for resilience on slightly malformed inputs.computeImageKey(input)— content-addressed base key (sha256, 32 hex) → variants are safe to serveimmutableand never need invalidation.buildVariantKey(key, width, fmt)— single source of truth for the{key}_{w}.{fmt}naming the next/image loader (FE-3) also uses.putVariantObject(...)— S3/R2 upload primitive that stampsCache-Control: public, max-age=31536000, immutableat write time.Scope boundaries
putVariantObjectprimitive — reads no external state, produces in-memory buffers. This lets BE-3 advanceready_max_widthafter each successfully-uploaded tier (ascending = monotonic watermark).fs/putspecifics); BE-2 covers the S3/R2 common path.Verification
tsc --noEmit: no errors in the new file.eslint --fix: clean (conforms to repo no-semicolons/single-quote style).Interface lock (with @Picimpact-Design FE-3)
buildVariantKeynaming + tier ladder +thumbhash→blurhashcolumn are the agreed contract.🤖 Generated with Claude Code