@siteswarm/media
@siteswarm/media
Section titled “@siteswarm/media”Headless digital asset ingestion, Cloudflare Images edge transformation URL builder, responsive srcsets, and R2 storage key management for SiteSwarm applications.
Adheres strictly to the Headless Invariant: pure TypeScript logic with 0 HTML/CSS rendering.
📋 Table of Contents
Section titled “📋 Table of Contents”- Overview
- Commercial & Architectural Context
- Key Features
- Cloudflare Images URL Transformation Grammar
- Responsive Breakpoints & Srcset Generation
- R2 Storage Key Management & Multi-Tenant Prefixing
- Edge Caching & Performance (0 CLS)
- Usage in Astro Templates
- API Reference
- Development & Testing
Overview
Section titled “Overview”@siteswarm/media provides the foundational headless utilities for image transformation, responsive srcset construction, content-addressed storage key generation, and asset metadata validation across the SiteSwarm monorepo. It powers @siteswarm/cms-media, @siteswarm/components, and client-facing storefronts (such as Greenleaf Bakery, Auto Repair, and marketing applications).
Commercial & Architectural Context
Section titled “Commercial & Architectural Context”- Edge Image Transformations: Cloudflare Images transformations cost approximately $0.50 per 1,000 unique transformations. To protect client unit economics, image transformations and heavy asset storage are encapsulated behind clean capability boundaries.
- Decoupled Retainers: Low-overhead sites run with static/D1 assets near $0/month, whereas clients opting into dynamic media catalogs and high-res asset pipelines leverage this capability under a commercial retainer ($29–$99/month).
- Headless Invariant: This package produces pure URLs, srcset strings, structured definitions, and key paths. Visual rendering and layout markup belong in application templates or
@siteswarm/components.
Key Features
Section titled “Key Features”- ⚡ Cloudflare Images Grammar: Formats option strings (
width=...,format=auto,quality=80) and generates normalized edge transformation paths. - 📐 Zero Cumulative Layout Shift (CLS): Calculates reduced mathematical aspect ratios (e.g.
16 / 9,4 / 3,1 / 1) to eliminate layout shift during image loading. - 🖼️ Modern Format Delivery: Automatically generates multi-source picture definitions for AVIF and WebP formats with responsive widths.
- 🔒 R2 Content-Addressed Storage Keys: Generates collision-free, sanitized keys with tenant isolation and cryptographic SHA-256 hashes.
- 🛡️ Edge Cache Optimization: Exports immutable caching headers (
IMMUTABLE_MEDIA_CACHE_CONTROL) guaranteeing 1-year browser and edge caching for content-addressed assets. - 🧪 Type Safety with Zod: Validates media asset payloads with runtime schema enforcement (
MediaAssetSchema).
Cloudflare Images URL Transformation Grammar
Section titled “Cloudflare Images URL Transformation Grammar”Cloudflare Images supports on-the-fly transformations via the /cdn-cgi/image/<options>/<sourcePath> path grammar on Cloudflare-proxied zones.
URL Structure
Section titled “URL Structure”-
Relative Transformation URL (same domain):
/cdn-cgi/image/<options>/<sourcePath>Example:
/cdn-cgi/image/width=640,quality=80,format=auto/uploads/bakery/croissant.jpg -
Absolute Transformation URL (custom zone domain):
https://<zoneDomain>/cdn-cgi/image/<options>/<sourcePath>Example:
https://greenleafbakery.com/cdn-cgi/image/width=1024,format=avif/uploads/hero.jpg -
Remote External Source URL:
/cdn-cgi/image/<options>/https://<remoteDomain>/<path>Example:
/cdn-cgi/image/width=800,format=webp/https://r2.siteswarm.io/assets/pastry.png
Supported Transformation Options
Section titled “Supported Transformation Options”| Parameter | Type | Default | Description |
|---|---|---|---|
width |
number |
– | Target output width in pixels (positive integer). |
height |
number |
– | Target output height in pixels (positive integer). |
quality |
number |
– | Compression quality integer between 1 and 100. |
format |
"auto" | "avif" | "webp" | "jpeg" | "png" |
"auto" |
Target output encoding. "auto" serves AVIF/WebP based on Accept header. |
fit |
"scale-down" | "contain" | "cover" | "crop" | "pad" |
– | Scaling mode relative to target bounding box. |
metadata |
"keep" | "copyright" | "none" |
– | EXIF metadata retention policy. |
blur |
number |
– | Blur radius (1 to 250). Useful for progressive blur placeholders. |
sharpen |
number |
– | Unsharp masking strength (0.0 to 10.0). |
rotate |
number |
– | Rotation angle in degrees (90, 180, 270). |
gravity |
"auto" | "left" | "right" | "top" | "bottom" | "center" |
– | Crop focal point alignment or face/saliency detection ("auto"). |
trim |
string |
– | Trim borders ("<top>;<right>;<bottom>;<left>"). |
onerror |
"redirect" |
– | Fallback behavior when image transformation fails; redirects to original image. |
Note: Cloudflare requires at least one parameter in
/cdn-cgi/image/<options>/.... If an empty options object is supplied,formatTransformOptions()automatically injectsformat=auto.
Responsive Breakpoints & Srcset Generation
Section titled “Responsive Breakpoints & Srcset Generation”Standardized breakpoints ensure consistent browser selection without creating duplicate cache artifacts:
- Default Breakpoints:
[360, 640, 1024, 1920](exported viaDEFAULT_RESPONSIVE_WIDTHS). - Standard Alternate Breakpoints:
[320, 640, 1024, 1920]for mobile-first fluid designs.
Example: Generating a Responsive srcset
Section titled “Example: Generating a Responsive srcset”import { generateResponsiveSrcSet } from "@siteswarm/media";
const srcset = generateResponsiveSrcSet( "/uploads/pastries/sourdough.jpg", [320, 640, 1024, 1920], { quality: 80, fit: "scale-down" });
// Result:// "/cdn-cgi/image/width=320,quality=80,fit=scale-down/uploads/pastries/sourdough.jpg 320w, /cdn-cgi/image/width=640,quality=80,fit=scale-down/uploads/pastries/sourdough.jpg 640w, /cdn-cgi/image/width=1024,quality=80,fit=scale-down/uploads/pastries/sourdough.jpg 1024w, /cdn-cgi/image/width=1920,quality=80,fit=scale-down/uploads/pastries/sourdough.jpg 1920w"R2 Storage Key Management & Multi-Tenant Prefixing
Section titled “R2 Storage Key Management & Multi-Tenant Prefixing”SiteSwarm utilizes Cloudflare R2 object storage with strict key formatting to avoid key collisions, guarantee multi-tenant data isolation, and enable immutable edge caching.
1. Multi-Tenant Key Hierarchy
Section titled “1. Multi-Tenant Key Hierarchy”To guarantee security and tenant isolation across SaaS deployments:
assets/<tenant_id>/<sha256>.<ext>tenant_id: Unique identifier for the client tenant (e.g.greenleaf-bakery).sha256: Cryptographic content hash of the original raw upload.ext: Lowercase sanitized file extension (e.g.jpg,png,webp).
2. Collection & Content-Addressed Key Generator (generateMediaKey)
Section titled “2. Collection & Content-Addressed Key Generator (generateMediaKey)”For headless CMS and collection uploads:
import { generateMediaKey } from "@siteswarm/media";
const key = generateMediaKey( "Menu Items", "Sourdough Boule! (Special).JPG", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
// Result:// "uploads/menu-items/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/sourdough-boule-special.jpg"The function sanitizes folder names and filenames into URL-safe kebab-case strings while preserving lowercase extensions.
Edge Caching & Performance (0 CLS)
Section titled “Edge Caching & Performance (0 CLS)”Immutable Cache-Control
Section titled “Immutable Cache-Control”All assets managed under content-addressed keys should be served with the immutable caching header:
import { IMMUTABLE_MEDIA_CACHE_CONTROL } from "@siteswarm/media";
console.log(IMMUTABLE_MEDIA_CACHE_CONTROL);// => "public, max-age=31536000, immutable"Because content-addressed keys change whenever the underlying image bytes change, cache invalidation is never required.
Cumulative Layout Shift (CLS) Elimination
Section titled “Cumulative Layout Shift (CLS) Elimination”To eliminate Cumulative Layout Shift, Astro components calculate and apply explicit aspect ratios to container elements prior to image loading:
import { calculateAspectRatio } from "@siteswarm/media";
calculateAspectRatio(1920, 1080); // => "16 / 9"calculateAspectRatio(800, 600); // => "4 / 3"calculateAspectRatio(1200, 800); // => "3 / 2"calculateAspectRatio(1000, 1000); // => "1 / 1"calculateAspectRatio(0, 500); // => "auto"Usage in Astro Templates
Section titled “Usage in Astro Templates”Native <picture> Tag with Modern Formats
Section titled “Native <picture> Tag with Modern Formats”---import { buildCloudflareImageUrl, generatePictureSources, calculateAspectRatio, DEFAULT_RESPONSIVE_WIDTHS,} from "@siteswarm/media";
interface Props { src: string; alt: string; width: number; height: number; priority?: boolean;}
const { src, alt, width, height, priority = false } = Astro.props;
const aspectRatio = calculateAspectRatio(width, height);const sources = generatePictureSources( src, DEFAULT_RESPONSIVE_WIDTHS, { quality: 80, fit: "scale-down" }, "(max-width: 640px) 100vw, (max-width: 1024px) 75vw, 50vw");
const fallbackSrc = buildCloudflareImageUrl(src, { width: 640, quality: 80, format: "auto", fit: "scale-down",});---
<picture class="media-container" style={`aspect-ratio: ${aspectRatio};`}> {sources.map((source) => ( <source type={source.type} srcset={source.srcset} sizes={source.sizes} /> ))} <img src={fallbackSrc} alt={alt} width={width} height={height} loading={priority ? "eager" : "lazy"} decoding={priority ? "sync" : "async"} fetchpriority={priority ? "high" : undefined} class="h-auto w-full object-cover" /></picture>
<style> .media-container { display: block; position: relative; overflow: hidden; contain: layout paint; }</style>Component Usage with @siteswarm/components
Section titled “Component Usage with @siteswarm/components”In SiteSwarm applications, you can also import the pre-built <Picture /> component:
---import { Picture } from "@siteswarm/components";---
<Picture src="/uploads/bakery/hero.jpg" alt="Fresh artisan bread loaf" width={1920} height={1080} priority={true} sizes="(max-width: 768px) 100vw, 1200px"/>API Reference
Section titled “API Reference”Functions
Section titled “Functions”buildCloudflareImageUrl(src, options?, zoneDomain?): string
Section titled “buildCloudflareImageUrl(src, options?, zoneDomain?): string”Constructs a Cloudflare Images edge transformation URL.
- Parameters:
src(string): The source image path (relative or absolute URL).options(CloudflareImageTransformOptions): Transformation options.zoneDomain(string, optional): Optional custom zone domain (e.g.example.com).
- Returns: Formatted URL string.
generateResponsiveSrcSet(src, widths?, options?, zoneDomain?): string
Section titled “generateResponsiveSrcSet(src, widths?, options?, zoneDomain?): string”Generates a responsive comma-separated srcset string.
- Parameters:
src(string): Asset path.widths(readonly number[], optional): Array of widths (defaults to[360, 640, 1024, 1920]).options(Omit<CloudflareImageTransformOptions, "width">, optional): Transformation options.zoneDomain(string, optional): Optional zone domain.
- Returns: Standard
srcsetstring (e.g./url 360w, /url 640w).
generatePictureSources(src, widths?, options?, sizes?, zoneDomain?): PictureSourceDefinition[]
Section titled “generatePictureSources(src, widths?, options?, sizes?, zoneDomain?): PictureSourceDefinition[]”Generates structured <source> definitions for modern formats (image/avif and image/webp).
- Parameters:
src(string): Asset path.widths(readonly number[], optional): Array of target widths.options(Omit<CloudflareImageTransformOptions, "width" | "format">, optional): Base transformation options.sizes(string, optional): HTMLsizesattribute.zoneDomain(string, optional): Optional zone domain.
- Returns: Array of
PictureSourceDefinitionobjects.
generateMediaKey(collection, filename, uuidOrHash?): string
Section titled “generateMediaKey(collection, filename, uuidOrHash?): string”Generates a collision-free, sanitized, content-addressed R2 key.
- Parameters:
collection(string): Target folder or collection name (e.g."Pastries").filename(string): Original filename.uuidOrHash(string, optional): Unique UUID or SHA-256 hash. Defaults tocrypto.randomUUID().
- Returns: Key string (
uploads/<collection>/<uuidOrHash>/<sanitized-filename>).
calculateAspectRatio(width, height): string
Section titled “calculateAspectRatio(width, height): string”Calculates a reduced aspect ratio string (e.g. "16 / 9", "4 / 3", "auto").
- Parameters:
width(number): Image width.height(number): Image height.
- Returns: CSS
aspect-ratiostring.
formatTransformOptions(options?): string
Section titled “formatTransformOptions(options?): string”Serializes CloudflareImageTransformOptions into a comma-separated key-value parameter string. Defaults to "format=auto" if no options are specified.
normalizeSourcePath(src): string
Section titled “normalizeSourcePath(src): string”Normalizes image source paths, strips any existing /cdn-cgi/image/... prefixes to prevent duplicate wrapping, and ensures valid leading slash grammar.
Constants
Section titled “Constants”export const IMMUTABLE_MEDIA_CACHE_CONTROL = "public, max-age=31536000, immutable";export const DEFAULT_RESPONSIVE_WIDTHS: readonly number[] = [360, 640, 1024, 1920] as const;Types & Interfaces
Section titled “Types & Interfaces”export type CloudflareImageFormat = "auto" | "avif" | "webp" | "jpeg" | "png";export type CloudflareImageFit = "scale-down" | "contain" | "cover" | "crop" | "pad";export type CloudflareImageMetadata = "keep" | "copyright" | "none";export type CloudflareImageGravity = "auto" | "left" | "right" | "top" | "bottom" | "center";
export interface CloudflareImageTransformOptions { width?: number; height?: number; quality?: number; format?: CloudflareImageFormat; fit?: CloudflareImageFit; metadata?: CloudflareImageMetadata; blur?: number; sharpen?: number; rotate?: number; gravity?: CloudflareImageGravity; trim?: string; onerror?: "redirect";}
export interface PictureSourceDefinition { type: string; srcset: string; sizes?: string;}Validation Schemas
Section titled “Validation Schemas”import { z } from "zod";
export const MediaAssetSchema = z.object({ id: z.string().min(1), key: z.string().min(1), filename: z.string().min(1), contentType: z.string().min(1), byteSize: z.number().int().nonnegative(), width: z.number().int().positive().optional(), height: z.number().int().positive().optional(), aspectRatio: z.string().optional(), altText: z.string().default(""), uploadedAt: z.string().datetime().optional(),});
export type MediaAsset = z.infer<typeof MediaAssetSchema>;Development & Testing
Section titled “Development & Testing”Run unit tests and type checks from the repository root:
# Run tests for @siteswarm/mediapnpm --filter @siteswarm/media test
# Run TypeScript type checkpnpm --filter @siteswarm/media check
# Run monorepo governance and type checkpnpm run check