
I started this 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, and small changes in one place could quietly break expectations elsewhere. 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
This was an important constraint for me: 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, reliability and performance improved with it. 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 durable fix was maintainability: fewer scattered conventions, one source of truth, and clearer build/runtime boundaries. For this site, performance improvements became much easier once the image system became easier to reason about.
Get new posts by email
Subscribe for occasional updates when I publish something new.
Related posts
From Writing Code to Orchestrating Agents
February 3, 2026
How my AI workflow changed from quick snippets to a practical plan/implement/verify loop.
The 'Boring' Architecture Behind This Blog
January 31, 2026
How I added a fully static, markdown-based blog to my Next.js portfolio.
Relearning Through Small Interactive Tools
April 10, 2026
Using a coding agent to build small browser tools turned out to be a good way to revisit concepts I had not touched in a while.