Skip to content

Conversation

@busbyk
Copy link
Collaborator

@busbyk busbyk commented Jan 15, 2026

Description

This PR removes unused sizes generated by Payload, only keeping the thumbnail size for a more performant Payload admin panel experience. It then adds appropriate sizes (i.e. srcset) attributes for every usage of next/image in our codebase (let me know if I missed any!).

Related Issues

Fixes #563

Key Changes

  • Added context-aware sizes prop to all ImageMedia usages across the codebase
  • Centralized breakpoints and container widths in cssVariables.js as single source of truth
  • Reduced image quality from 100 to 80 (reduces bandwidth with minimal visual difference)
  • Removed unused Payload image sizes (square, small, medium, large, xlarge, og)
  • Fixed sizes prop formula in ImageMedia/AvatarImageMedia (was using w units incorrectly)
  • Added getImageWidthFromMaxHeight utility for height-constrained images (Header, Footer, MobileNav, Carousel)

How to test

  • Upload a new image and see that only a thumbnail size is generated (along with the original)
  • Inspect the <img> srcset attributes for various components to see the size the browser loads

Screenshots / Demo video

Migration Explanation

Removed image sizes from Media collection. These were already unused - we were already only using next.js image optimization.

busbyk and others added 7 commits January 14, 2026 17:19
Phase 1 of image optimization strategy (#563):
- Fix sizes prop in ImageMedia: change from invalid `w` unit to valid `px`
- Add 100vw fallback for viewports larger than breakpoints
- Reduce quality from 100 to 80 (industry standard compression)
- Add optional quality prop for per-component overrides
- Apply same fixes to AvatarImageMedia component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Only keep thumbnail size (300px) for admin panel and site icons.
Removes 6 unused sizes (square, small, medium, large, xlarge, og)
that were being generated but never used - Next.js handles
responsive images via its Image component instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit size hints to ImageMedia components so browsers request
appropriately sized images. Components updated:
- PostPreview: (max-width: 768px) 100vw, 320px
- ImageLinkGrid: (max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw
- ImageText: (max-width: 768px) 100vw, 33vw
- MediaBlock: dynamic sizes based on imageSize prop
- Header banner/logo: 200px
- Footer logo: 200px

Phase 3 of image optimization strategy (gh-563).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rate image sizing

- Add container max-widths to cssVariables.js as single source of truth
- Update tailwind.config.mjs to use shared breakpoints and container sizes
- Calculate precise sizes attributes in ImageLinkGrid based on column count
- Calculate sizes for ImageText based on 4/12 column ratio
- Use breakpoint variables in MediaBlock for consistency
- Add aspect-ratio-based size calculation for Header/Footer logos
- Add proper type guard for logo in Footer component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…e loading

- Add sizes prop to landing page tenant logos (192px fixed)
- Add getImageSizes() helper for ImageQuote block (handles md breakpoint and 4/12 column ratio)
- Add getImageSizes() helper for ImageTextList block (handles full vs side layout, column count)
- Add sizes to SponsorsBlock components (Banner, Carousel, Static)
- Add sizes to EventPreview and EventPreviewSmallRow components
- Add sizes to PostPreviewSmallRow component
- Create getImageWidthFromMaxHeight utility for height-constrained images
- Refactor Header, MobileNav, and Footer to use new utility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link

Migration Safety Check

Found 78 potential issues:

20260115_234107_remove_image_sizes.ts

Warning (line 5): DROP keyword detected - review for data loss

await db.run(sql`DROP INDEX \`media_sizes_square_sizes_square_filename_idx\`;`)

Warning (line 6): DROP keyword detected - review for data loss

await db.run(sql`DROP INDEX \`media_sizes_small_sizes_small_filename_idx\`;`)

Warning (line 7): DROP keyword detected - review for data loss

await db.run(sql`DROP INDEX \`media_sizes_medium_sizes_medium_filename_idx\`;`)

Warning (line 8): DROP keyword detected - review for data loss

await db.run(sql`DROP INDEX \`media_sizes_large_sizes_large_filename_idx\`;`)

Warning (line 9): DROP keyword detected - review for data loss

await db.run(sql`DROP INDEX \`media_sizes_xlarge_sizes_xlarge_filename_idx\`;`)

Warning (line 10): DROP keyword detected - review for data loss

await db.run(sql`DROP INDEX \`media_sizes_og_sizes_og_filename_idx\`;`)

Warning (line 11): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_url\`;`)

Warning (line 11): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_url\`;`)

Warning (line 12): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_width\`;`)

Warning (line 12): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_width\`;`)

Warning (line 13): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_height\`;`)

Warning (line 13): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_height\`;`)

Warning (line 14): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_mime_type\`;`)

Warning (line 14): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_mime_type\`;`)

Warning (line 15): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_filesize\`;`)

Warning (line 15): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_filesize\`;`)

Warning (line 16): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_filename\`;`)

Warning (line 16): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_square_filename\`;`)

Warning (line 17): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_url\`;`)

Warning (line 17): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_url\`;`)

Warning (line 18): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_width\`;`)

Warning (line 18): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_width\`;`)

Warning (line 19): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_height\`;`)

Warning (line 19): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_height\`;`)

Warning (line 20): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_mime_type\`;`)

Warning (line 20): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_mime_type\`;`)

Warning (line 21): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_filesize\`;`)

Warning (line 21): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_filesize\`;`)

Warning (line 22): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_filename\`;`)

Warning (line 22): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_small_filename\`;`)

Warning (line 23): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_url\`;`)

Warning (line 23): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_url\`;`)

Warning (line 24): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_width\`;`)

Warning (line 24): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_width\`;`)

Warning (line 25): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_height\`;`)

Warning (line 25): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_height\`;`)

Warning (line 26): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_mime_type\`;`)

Warning (line 26): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_mime_type\`;`)

Warning (line 27): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_filesize\`;`)

Warning (line 27): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_filesize\`;`)

Warning (line 28): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_filename\`;`)

Warning (line 28): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_medium_filename\`;`)

Warning (line 29): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_url\`;`)

Warning (line 29): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_url\`;`)

Warning (line 30): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_width\`;`)

Warning (line 30): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_width\`;`)

Warning (line 31): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_height\`;`)

Warning (line 31): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_height\`;`)

Warning (line 32): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_mime_type\`;`)

Warning (line 32): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_mime_type\`;`)

Warning (line 33): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_filesize\`;`)

Warning (line 33): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_filesize\`;`)

Warning (line 34): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_filename\`;`)

Warning (line 34): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_large_filename\`;`)

Warning (line 35): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_url\`;`)

Warning (line 35): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_url\`;`)

Warning (line 36): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_width\`;`)

Warning (line 36): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_width\`;`)

Warning (line 37): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_height\`;`)

Warning (line 37): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_height\`;`)

Warning (line 38): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_mime_type\`;`)

Warning (line 38): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_mime_type\`;`)

Warning (line 39): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_filesize\`;`)

Warning (line 39): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_filesize\`;`)

Warning (line 40): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_filename\`;`)

Warning (line 40): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_xlarge_filename\`;`)

Warning (line 41): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_url\`;`)

Warning (line 41): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_url\`;`)

Warning (line 42): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_width\`;`)

Warning (line 42): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_width\`;`)

Warning (line 43): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_height\`;`)

Warning (line 43): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_height\`;`)

Warning (line 44): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_mime_type\`;`)

Warning (line 44): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_mime_type\`;`)

Warning (line 45): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_filesize\`;`)

Warning (line 45): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_filesize\`;`)

Warning (line 46): DROP keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_filename\`;`)

Warning (line 46): ALTER keyword detected - review for data loss

await db.run(sql`ALTER TABLE \`media\` DROP COLUMN \`sizes_og_filename\`;`)

Review these patterns and add backup/restore logic if needed. See docs/migration-safety.md for guidance.

@github-actions
Copy link

Preview deployment: https://image-optimization-updates.preview.avy-fx.org

@busbyk busbyk marked this pull request as ready for review January 16, 2026 19:54
Copy link
Collaborator

@rchlfryn rchlfryn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good! This will be wonderful to have cleaned up. Approving but have a few comments that I am curious what you think

>
<div className="aspect-square flex items-center justify-center w-48 mx-auto">
<ImageMedia resource={logo} imgClassName="w-48" />
<ImageMedia resource={logo} imgClassName="w-48" size="192px" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused what size is actually doing here.. I would have expected it to define one size for next to return, but it still looks like it returns a srcset of image sizes

Image

I guess I am learning no matter the size defined (single or an array), next image will always produce srcset.

I wonder if it is worth it to update ImageMedia to use a regular img tag if we define a size.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea great question. And I had to double check myself on this.

Firstly, size is confusing so I just changed that to reflect the actual attribute name: sizes. 6fba3b0

Next.js always generates the srcset which is basically a list of possible images for the browser to download. The sizes attribute indicates which one of those to pick from.

Some good reading:

Re: using a regular img tag - we still want to use NextImage to get optimized images.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate appropriate sizes/srcset to utilize Payload-generated image sizes

3 participants