Skip to content

Serve resized featured images through srcset - #130

Merged
ssavutu merged 1 commit into
mainfrom
feat/responsive-featured-images
Oct 1, 2026
Merged

ssavutu merged 1 commit into
mainfrom
feat/responsive-featured-images

Conversation

@ssavutu

@ssavutu ssavutu commented Oct 1, 2026

Copy link
Copy Markdown
Member

Why

Every card on the homepage and the section pages loads the full-size original as its image. Those are often camera JPEGs (6000×4000, 4–5 MB) drawn at about 400px. The live homepage downloads 32.2 MB of images.

triangle-cms now makes resized WebP copies and returns them as featured_image_variants (DrexelTriangle/triangle-cms#239, deployed 2026-10-01).

Change

  • ImageVariant type, and an optional featured_image_variants on ArticleSummary, RelatedArticle, Article and SectionCardArticle.
  • imageSrcset() in utils/data.js turns variants into a srcset string, or undefined when there are none, so Astro drops the attribute.
  • srcset + sizes on:
    • PhotoArticle: grid cards and the related-article row
    • PhotoArticleLong: list cards
    • the infinite-scroll cards in Scroll.astro, using an inline copy of the helper because that script is is:inline
    • the article lead <figure>
  • Every Section-* component passes variants={article.featured_image_variants}.
  • The sizes values come from the grid each image sits in: a 1200px container (900px at lg, 90vw below), the 8fr main column, and the section splits inside it. They lean slightly large, because guessing too small makes images blurry.
  • src is still the original. A not-yet-rendered image, a placeholder, or a CMS without the sidecar renders exactly as before.

Verified

Ran astro dev locally against the public CMS API:

  • Homepage: 21 of 21 images have a srcset.
  • /news: 15 srcsets.
  • /api/sections/news?page=2: passes featured_image_variants through to the scroll script.

Homepage image bytes, picking the way browsers do (the smallest candidate ≥ slot width × DPR):

Viewport Before After
1440px @1x 32.2 MB 1.4 MB
1440px @2x 32.2 MB 3.5 MB
390px @3x phone 32.2 MB 4.5 MB

eslint is clean. astro check shows 0 errors, 0 warnings, and the same 8 hints as main.

Not covered: article bodies

Article pages still load originals. CMS-authored articles put the lead photo inline in the body, not in featured_image; that holds for all 30 of the latest. stripDimensions deliberately removes every body srcset, because the WordPress ones point at legacy derivatives. Fixing this needs the CMS to return the variants for each inline image, and this page to add them back after stripping. That's a follow-up across both repos.

🤖 Generated with Claude Code

https://claude.ai/code/session_019EcFyy8aqNnUd75CDPY6Bx

Every homepage and section card loaded the full-size original as its image:
camera JPEGs up to 6000px and 5MB, drawn about 400px wide. The homepage
alone downloaded ~32MB of images.

triangle-cms now renders 480/960/1600/2400px WebP copies of library images
and lists them as `featured_image_variants`. The cards, the long list cards,
the infinite-scroll cards, the related-article row and the article lead
figure now pass these as a srcset, with `sizes` worked out from the grid
they sit in. The original stays as `src`, so an image the CMS hasn't
rendered yet, or a CMS without the sidecar, behaves exactly as before.

Measured on the live homepage, total image bytes go from 32.2MB to 1.4MB on a
1440px desktop, 3.5MB on a 2x laptop and 4.5MB on a 3x phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EcFyy8aqNnUd75CDPY6Bx
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.

1 participant