Building an image optimization pipeline with a CDN
An image pipeline combines storage, transformations, responsive markup, and caching. A CDN can reduce delivery work, but transformation URLs and signing rules are provider-specific. This guide uses Cloudflare Images' hosted-image interface and keeps those contracts explicit.
Understanding image CDNs
Image services can create variants at different sizes, negotiate supported formats, and cache the results. Verify which operations happen at upload time or delivery time, which formats are supported, and how cache keys and access controls interact. Not every CDN performs image processing without a separately configured transformation service.
Choosing an image optimization provider
Compare supported formats, resizing behavior, private delivery, operational limits, and integration with your storage. The following examples use Cloudflare Images predefined variants, not its separate remote-image transformation API. Use the documented URL and signature contracts of your chosen provider rather than treating image-CDN query parameters as interchangeable.
Leveraging Azure Front Door for image delivery
Azure Front Door can deliver content from an image-processing origin, but putting images behind it does not by itself create resized variants. Keep the transformation service, origin access policy, and cache configuration separate. See the Azure Front Door documentation for that provider's origin and delivery setup.
Setting up image optimization
Upload an image to Cloudflare Images and obtain its account hash and image ID. In its dashboard,
create predefined variants named w320, w640, and w1280 with the matching widths, preserved aspect
ratio, and scale-down fit. See the
variant setup documentation.
For this example, use a source image at least 1,280 pixels wide, so every srcset width descriptor
matches an actual output width.
Save this shared URL builder as images.ts. The variant configuration is defined once and reused
for responsive delivery and signing. This example deliberately supports opaque image IDs without
custom path segments.
export const imageVariants = [
{ name: 'w320', width: 320 },
{ name: 'w640', width: 640 },
{ name: 'w1280', width: 1280 },
]
export function getOptimizedImageUrl(accountHash: string, imageId: string, variant: string): string {
const segment = /^[A-Za-z0-9_-]{1,128}$/
if (!segment.test(accountHash) || !segment.test(imageId)) {
throw new Error('Use an account hash and opaque image ID, not a URL or path')
}
if (!imageVariants.some(({ name }) => name === variant)) {
throw new Error('Unknown image variant')
}
return new URL(`https://imagedelivery.net/${accountHash}/${imageId}/${variant}`).href
}
Hosted-image delivery URLs have the account hash, image ID, and variant in the path. Appending
arbitrary width or format query parameters to an incomplete URL is not an equivalent API.
Cloudflare can negotiate delivery formats based on the browser request; keep the complete
hosted-image delivery contract
in mind when adding another proxy or cache.
Implementing responsive images
Keep the React component independent of whether URLs are public or signed. Pass it the generated
sources, intrinsic dimensions, and a sizes value that matches the actual layout:
import type { ReactNode } from 'react'
interface ResponsiveImageProps {
sources: { url: string; width: number }[]
alt: string
width: number
height: number
sizes: string
loading?: 'lazy' | 'eager'
}
export function ResponsiveImage({
sources, alt, width, height, sizes, loading = 'lazy',
}: ResponsiveImageProps): ReactNode {
const fallback = sources[sources.length - 1]
if (!fallback || !Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
throw new Error('Supply image sources and positive intrinsic dimensions')
}
return (
<img
src={fallback.url}
srcSet={sources.map((source) => `${source.url} ${source.width}w`).join(', ')}
width={width}
height={height}
alt={alt}
sizes={sizes}
loading={loading}
className="responsive-image"
/>
)
}
Apply a responsive CSS rule in your application's stylesheet:
.responsive-image {
display: block;
max-width: 100%;
height: auto;
}
For public images, build the sources prop from the shared variant list:
import { getOptimizedImageUrl, imageVariants } from './images.ts'
export function publicImageSources(accountHash: string, imageId: string) {
return imageVariants.map(({ name, width }) => ({
width,
url: getOptimizedImageUrl(accountHash, imageId, name),
}))
}
The width and height props describe the source aspect ratio and reserve layout space. Use eager
loading for a likely LCP image rather than lazily loading every image. Keep meaningful alternative
text even when delivery fails; do not retry a nonexistent fallback URL indefinitely.
Monitoring performance
Use the current web-vitals package and record LCP, INP, and CLS. INP replaces the retired FID metric.
Call this browser-only initializer once, after satisfying your application's analytics consent
policy. Implement the same-origin /analytics endpoint before enabling delivery.
import { onCLS, onINP, onLCP, type Metric } from 'web-vitals'
function sendToAnalytics({ name, value, id }: Pick<Metric, 'name' | 'value' | 'id'>): void {
const body = JSON.stringify({ name, value, id })
if (typeof navigator.sendBeacon === 'function' && navigator.sendBeacon('/analytics', body)) return
void fetch('/analytics', { body, method: 'POST', keepalive: true })
.then((response) => {
if (!response.ok) throw new Error('Analytics request failed')
})
.catch(() => console.warn('Could not deliver performance metric'))
}
export function initializePerformanceMonitoring(): void {
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
}
A queued beacon is not proof of server-side receipt. Compare field metrics with controlled browser measurements and failed-image request rates. Do not send complete signed image URLs or user content to analytics as metric identifiers.
Security considerations
Signing happens only on the server, after authenticating the caller and authorizing access to the image. Store the Images signing key as a server secret; it is not the account hash or an API token, and must never enter browser bundles. Private images must require signed URLs, and their variants must not be configured to bypass that requirement.
Save this Node.js-only helper as images.server.ts. It reuses the same path builder, adds the
provider's exp parameter, and signs the complete path and query before adding sig:
import { createHmac } from 'node:crypto'
import { getOptimizedImageUrl } from './images.ts'
export function getSecureImageUrl(
accountHash: string,
imageId: string,
variant: string,
signingKey: string,
expiresIn = 3600,
): string {
if (!signingKey || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 86400) {
throw new Error('A signing key and bounded expiry are required')
}
const url = new URL(getOptimizedImageUrl(accountHash, imageId, variant))
url.searchParams.set('exp', String(Math.floor(Date.now() / 1000) + expiresIn))
const payload = `${url.pathname}?${url.searchParams.toString()}`
url.searchParams.set('sig', createHmac('sha256', signingKey).update(payload).digest('hex'))
return url.href
}
Generate every responsive source URL server-side for private images, then pass only those signed URLs to the component. Changing the variant or expiration changes the signed payload. Follow the private-image signing documentation and do not expose a public signer that accepts arbitrary image IDs without ownership checks. A signature is a bearer capability until expiry, not a replacement for authorization.
Configure a page-level Content Security Policy that permits the actual delivery origin, such as
https://imagedelivery.net. A wildcard for its subdomains does not include the bare hostname. Merge
that directive into the application's complete policy rather than replacing unrelated directives.
Only enable HSTS policies that your entire affected domain scope can support over HTTPS.
Best practices
- Derive responsive URLs from one verified variant configuration.
- Match
sizesto the layout and width descriptors to actual output widths. - Preserve intrinsic aspect ratio to reduce layout shifts.
- Keep signing keys server-side and authorize access before issuing private URLs.
- Test cache behavior and expiry; adding a signature does not make a publicly configured image private.
- Compare actual file sizes, visual quality, and page metrics instead of promising a universal speedup.
Conclusion
A reliable image pipeline uses the provider's real URL and signing contracts, responsive markup, and observable delivery behavior. Keep shared variant configuration central, and test public and private flows separately. For managed processing and delivery, explore Transloadit's Smart CDN.
