
The responsive-image cleanup started as a performance task: downscale assets, add srcSet/sizes, and improve LCP. The bigger problem turned out to be maintainability.
Image behavior was spread across scripts and components with repeated conventions:
- variant naming assumptions,
- hardcoded
srcSetstrings, - multiple script aliases and legacy paths.
That made drift easy. A renamed variant or new profile could leave a hardcoded srcSet behind. I replaced that with a simpler pattern: generate one manifest at build time (src/generated/imageVariantManifest.json) and resolve variants from it at runtime.
One manifest instead of scattered assumptions
The main change was replacing repeated conventions with one manifest-driven path. There is now one canonical workflow: yarn image:variants. Rendering logic is less duplicated because a shared ResponsiveImage component replaced repeated <picture> patterns. Static assets also became data-driven, so background and portrait srcSet values now come from manifest-backed metadata rather than JSX literals.
A simpler authoring path
I do not want to manually create -sm, -md, -lg files every time I add an image.
The workflow is now simple: add the source image, reference it in frontmatter or markdown, then run yarn image:variants. That keeps authoring lightweight while still making outputs consistent.
Stricter failure modes
Once maintainability improved, performance work became easier to verify. I also removed silent fallback for required profiles (cover.card, cover.hero, inlineContent). If a required variant is missing, it now fails fast instead of shipping a hidden regression.
What I actually fixed
I started by chasing LCP. The fix that stuck was maintainability: fewer scattered conventions, generated metadata, and a clearer boundary between build-time image work and runtime rendering. For this site, performance work became easier once the image system became easier to reason about.
Get new posts by email
Occasional updates when I publish something new.
No spam. Follow.it handles delivery and unsubscribe links.
Related posts
Static Markdown Inside This Next.js Site
January 31, 2026
How I added a repo-backed Markdown blog to my static Next.js portfolio without adding a CMS or runtime admin surface.
Coding Agents for Inspectable Browser Tools
April 10, 2026
Building frontend-only tools for load flow and Mandelbrot zooming gave me concrete ways to inspect older technical models.
Planning and Verification in AI-Assisted Coding
February 3, 2026
How my AI-assisted coding workflow shifted from quick snippets to clearer briefs, review checkpoints, and build/lint/test verification.