From content-management-system
Designs responsive image pipelines with srcset, AVIF/WebP formats, lazy loading, and sizes queries. Generates code for Next.js, Nuxt, Blazor, vanilla HTML/CSS.
npx claudepluginhub melodic-software/claude-code-plugins --plugin content-management-systemThis skill is limited to using the following tools:
Design a responsive image optimization pipeline for web delivery.
Implements responsive images with srcset/picture, WebP/AVIF conversion, focal point cropping, lazy loading, and C# image processing pipelines for headless CMS.
Optimizes web images using WebP/AVIF formats, responsive srcset/picture elements, lazy loading, and Sharp for Node.js. Improves page loads, responsive images, production assets.
Implements responsive images using srcset for resolution switching, sizes for viewport selection, picture for art direction, and strategies to serve optimal sizes, fixing mobile bandwidth waste, retina blur, and layout shifts.
Share bugs, ideas, or general feedback.
Design a responsive image optimization pipeline for web delivery.
/cms:optimize-images --framework blazor
/cms:optimize-images --framework next --format avif
/cms:optimize-images --framework vanilla --format both
Extract framework and format preferences from command.
Invoke relevant skills:
image-optimization - Responsive patternscdn-media-delivery - CDN integrationBreakpoint Configuration:
responsive:
breakpoints:
mobile: 320
mobile_lg: 480
tablet: 768
desktop: 1024
desktop_lg: 1280
wide: 1920
device_pixel_ratios: [1, 2, 3]
sizes_presets:
thumbnail:
default: 150px
card:
mobile: 100vw
tablet: 50vw
desktop: 33vw
hero:
default: 100vw
content:
mobile: 100vw
tablet: 80vw
desktop: 800px
formats:
priority: [avif, webp, jpg]
fallback: jpg
Blazor Component:
@* ResponsiveImage.razor *@
<picture>
@foreach (var format in Formats)
{
<source type="@GetMimeType(format)"
srcset="@GenerateSrcset(format)"
sizes="@Sizes" />
}
<img src="@FallbackSrc"
alt="@Alt"
loading="@(Lazy ? "lazy" : "eager")"
decoding="async"
width="@Width"
height="@Height"
class="@CssClass"
@attributes="AdditionalAttributes" />
</picture>
@code {
[Parameter] public string Src { get; set; }
[Parameter] public string Alt { get; set; }
[Parameter] public int Width { get; set; }
[Parameter] public int Height { get; set; }
[Parameter] public string Sizes { get; set; } = "100vw";
[Parameter] public bool Lazy { get; set; } = true;
[Parameter] public string CssClass { get; set; }
[Parameter] public string[] Formats { get; set; } = ["avif", "webp"];
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> AdditionalAttributes { get; set; }
private int[] Widths => [320, 640, 768, 1024, 1280, 1920];
private string FallbackSrc => GetTransformUrl(Src, Width, "jpg");
private string GenerateSrcset(string format)
{
return string.Join(", ",
Widths.Select(w => $"{GetTransformUrl(Src, w, format)} {w}w"));
}
private string GetTransformUrl(string src, int width, string format)
{
return $"/media/transform/{GetAssetId(src)}?w={width}&format={format}";
}
private string GetMimeType(string format) => format switch
{
"avif" => "image/avif",
"webp" => "image/webp",
"jpg" => "image/jpeg",
_ => "image/jpeg"
};
}
Next.js Implementation:
// components/ResponsiveImage.tsx
import Image from 'next/image';
interface ResponsiveImageProps {
src: string;
alt: string;
width: number;
height: number;
sizes?: string;
priority?: boolean;
className?: string;
}
export function ResponsiveImage({
src,
alt,
width,
height,
sizes = '100vw',
priority = false,
className,
}: ResponsiveImageProps) {
return (
<Image
src={src}
alt={alt}
width={width}
height={height}
sizes={sizes}
priority={priority}
className={className}
placeholder="blur"
blurDataURL={`/api/placeholder?w=10&h=${Math.round(10 * height / width)}`}
/>
);
}
// next.config.js
module.exports = {
images: {
domains: ['cdn.example.com'],
deviceSizes: [640, 768, 1024, 1280, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60 * 60 * 24 * 7, // 7 days
loader: 'custom',
loaderFile: './lib/imageLoader.ts',
},
};
// lib/imageLoader.ts
export default function cmsImageLoader({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) {
const q = quality || 85;
return `${process.env.CDN_URL}/transform/${src}?w=${width}&q=${q}`;
}
Vanilla HTML:
<!-- Responsive image with art direction -->
<picture>
<!-- Art direction: different crop for mobile -->
<source
media="(max-width: 767px)"
type="image/avif"
srcset="
/media/transform/hero-mobile?w=320&format=avif 320w,
/media/transform/hero-mobile?w=640&format=avif 640w
"
sizes="100vw"
/>
<source
media="(max-width: 767px)"
type="image/webp"
srcset="
/media/transform/hero-mobile?w=320&format=webp 320w,
/media/transform/hero-mobile?w=640&format=webp 640w
"
sizes="100vw"
/>
<!-- Desktop sources -->
<source
type="image/avif"
srcset="
/media/transform/hero?w=768&format=avif 768w,
/media/transform/hero?w=1024&format=avif 1024w,
/media/transform/hero?w=1920&format=avif 1920w
"
sizes="100vw"
/>
<source
type="image/webp"
srcset="
/media/transform/hero?w=768&format=webp 768w,
/media/transform/hero?w=1024&format=webp 1024w,
/media/transform/hero?w=1920&format=webp 1920w
"
sizes="100vw"
/>
<!-- Fallback -->
<img
src="/media/transform/hero?w=1024&format=jpg"
alt="Hero image description"
loading="lazy"
decoding="async"
width="1920"
height="1080"
/>
</picture>
Transform Service:
public class ImageTransformService
{
public async Task<Stream> TransformAsync(
Guid assetId,
ImageTransformOptions options)
{
var asset = await _mediaRepository.GetAsync(assetId);
var cacheKey = GenerateCacheKey(assetId, options);
// Check cache first
if (await _cache.ExistsAsync(cacheKey))
{
return await _cache.GetStreamAsync(cacheKey);
}
// Get original
var original = await _storage.GetAsync(asset.StoragePath);
// Transform using ImageSharp
using var image = await Image.LoadAsync(original);
// Apply focal point aware resize
if (options.FocalPoint != null)
{
image.Mutate(x => x.Resize(new ResizeOptions
{
Size = new Size(options.Width, options.Height),
Mode = ResizeMode.Crop,
CenterCoordinates = new PointF(
options.FocalPoint.X * image.Width,
options.FocalPoint.Y * image.Height)
}));
}
else
{
image.Mutate(x => x.Resize(options.Width, options.Height));
}
// Encode in requested format
var output = new MemoryStream();
await EncodeAsync(image, output, options.Format, options.Quality);
// Cache result
await _cache.SetAsync(cacheKey, output, TimeSpan.FromDays(7));
output.Position = 0;
return output;
}
}
public record ImageTransformOptions
{
public int Width { get; init; }
public int? Height { get; init; }
public string Format { get; init; } = "webp";
public int Quality { get; init; } = 85;
public string Fit { get; init; } = "contain";
public FocalPoint FocalPoint { get; init; }
}
Loading Strategies:
loading:
# Above the fold
critical:
loading: eager
fetchpriority: high
decoding: sync
# Below the fold
lazy:
loading: lazy
fetchpriority: auto
decoding: async
# Low priority
deferred:
loading: lazy
fetchpriority: low
decoding: async
# Placeholder strategies
placeholders:
blur: true # LQIP (Low Quality Image Placeholder)
dominant_color: true
skeleton: false
Cache Configuration:
caching:
# Browser cache
browser:
max_age: 31536000 # 1 year
immutable: true
stale_while_revalidate: 86400
# CDN cache
cdn:
ttl: 604800 # 7 days
vary: [Accept] # Format negotiation
# Transform cache
transform:
storage: redis
ttl: 604800
max_size: 10GB
eviction: lru
Track image performance:
metrics:
- largest_contentful_paint
- cumulative_layout_shift
- time_to_first_byte
- cache_hit_ratio
- format_distribution
- bandwidth_saved
image-optimization - Responsive patternscdn-media-delivery - CDN configurationmedia-asset-management - Asset storage