Building an image optimization pipeline with a CDN

Image optimization is vital for modern web performance. In this post, we explore how to set up an image optimization pipeline using a Content Delivery Network (CDN) to ensure your images are automatically optimized and delivered quickly. The techniques demonstrated here apply regardless of the image optimization provider you choose.
Understanding image CDNs
An Image CDN is a specialized content delivery network that serves images from edge servers close to end users while performing real-time optimizations. Modern Image CDNs automatically detect browser support and serve next-generation formats such as WebP and AVIF. Key features include:
- Next-gen format delivery with automatic browser support detection
- Responsive image generation for various device sizes and pixel densities
- Quality optimization using perceptual quality algorithms
- Smart compression based on image content and format
- Edge caching with instant purge capabilities
- DDoS protection and enhanced security
Choosing an image optimization provider
Multiple specialized providers offer modern image optimization solutions. Here is a brief comparison of popular options:
- Cloudflare Images: Leverages Cloudflare's global network and edge computing.
- Cloudinary: Offers advanced image processing with AI-based optimizations.
- ImageKit: Features a developer-friendly API for real-time image transformations.
- Bunny CDN: Provides cost-effective image delivery with widespread geographic coverage.
- Fastly Image Optimizer: Targets enterprise-grade image processing with robust performance.
For this guide, we use Cloudflare Images as an example, although the concepts apply to all providers.
Leveraging Azure front door for image delivery
Azure Front Door is a modern alternative to traditional CDNs, offering global load balancing and dynamic site acceleration. Integrating Azure Front Door into your image optimization pipeline can provide enhanced security, faster content delivery, and seamless integration with other Azure services. For more details, refer to the Azure Front Door documentation and the migration guide.
Setting up image optimization
Below is a TypeScript interface for image optimization options along with a synchronous function to generate an optimized image URL. In more advanced scenarios, you might fetch image metadata, but this example focuses on URL construction.
interface ImageOptimizationOptions {
width?: number
height?: number
quality?: number
format?: 'auto' | 'webp' | 'avif' | 'jpeg' | 'png'
fit?: 'cover' | 'contain' | 'fill'
dpr?: number
}
function getOptimizedImageUrl(path: string, options: ImageOptimizationOptions = {}): string {
try {
const cdnEndpoint = 'https://imagedelivery.net/your-account'
const params = new URLSearchParams()
if (options.width) params.append('width', options.width.toString())
if (options.height) params.append('height', options.height.toString())
if (options.quality) params.append('quality', options.quality.toString())
if (options.format) params.append('format', options.format)
if (options.fit) params.append('fit', options.fit)
if (options.dpr) params.append('dpr', options.dpr.toString())
const url = `${cdnEndpoint}${path}?${params.toString()}`
new URL(url) // Validate URL
return url
} catch (error) {
console.error('Error generating optimized image URL:', error)
throw error
}
}
Implementing responsive images
Create a React component that handles responsive images by generating source sets for multiple formats. This example uses the synchronous URL generator from above:
interface ResponsiveImageProps {
src: string;
alt: string;
sizes: string;
className?: string;
loading?: 'lazy' | 'eager';
widths: number[];
}
function ResponsiveImage({
src,
alt,
sizes,
className,
loading = 'lazy',
widths,
}: ResponsiveImageProps) {
const generateSrcSet = (format: string) => {
return widths
.map((width) => {
const url = getOptimizedImageUrl(src, { width, format });
return `${url} ${width}w`;
})
.join(', ');
};
return (
<picture>
<source type="image/avif" srcSet={generateSrcSet('avif')} sizes={sizes} />
<source type="image/webp" srcSet={generateSrcSet('webp')} sizes={sizes} />
<img
src={getOptimizedImageUrl(src, {
width: Math.max(...widths),
format: 'jpeg',
})}
alt={alt}
className={className}
loading={loading}
onError={(e) => {
const img = e.currentTarget;
img.onerror = null;
img.src = '/path/to/fallback-image.jpg';
}}
/>
</picture>
);
}
Monitoring performance
Implement comprehensive performance monitoring using the Web Vitals API along with a robust PerformanceObserver implementation that checks for browser support and handles errors gracefully:
import { onLCP, onFID, onCLS } from 'web-vitals'
function sendToAnalytics({ name, value, id }: { name: string; value: number; id: string }) {
const body = JSON.stringify({ name, value, id })
if (navigator.sendBeacon) {
navigator.sendBeacon('/analytics', body)
} else {
fetch('/analytics', { body, method: 'POST', keepalive: true })
}
}
function initializePerformanceMonitoring() {
try {
onLCP(sendToAnalytics)
onFID(sendToAnalytics)
onCLS(sendToAnalytics)
} catch (error) {
console.error('Error initializing web vitals:', error)
}
if ('PerformanceObserver' in window) {
try {
const observer = new PerformanceObserver((list) => {
list.getEntriesByType('resource').forEach((entry) => {
if (entry.initiatorType === 'img') {
sendToAnalytics({
name: 'image-load',
value: entry.duration,
id: entry.name,
})
}
})
})
observer.observe({ entryTypes: ['resource'] })
} catch (error) {
console.error('PerformanceObserver error:', error)
}
}
}
Security considerations
Secure image delivery is paramount. Below is an example demonstrating how to generate signed URLs
and set appropriate security headers. Note that functions such as getSigningKey()
and
getCdnEndpoint()
are assumed to be defined elsewhere in your environment.
interface SecurityOptions {
expiresIn?: number
allowedDomains?: string[]
}
async function getSecureImageUrl(
path: string,
options: ImageOptimizationOptions & SecurityOptions = {},
): Promise<string> {
const { expiresIn = 3600 } = options
const timestamp = Math.floor(Date.now() / 1000) + expiresIn
// Generate HMAC signature using the Web Crypto API
const key = await getSigningKey()
const message = `${path}${timestamp}`
const signatureBuffer = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(message))
const hexSignature = Array.from(new Uint8Array(signatureBuffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
const params = new URLSearchParams()
if (options.width) params.append('width', options.width.toString())
if (options.height) params.append('height', options.height.toString())
if (options.quality) params.append('quality', options.quality.toString())
if (options.format) params.append('format', options.format)
if (options.fit) params.append('fit', options.fit)
if (options.dpr) params.append('dpr', options.dpr.toString())
params.append('expires', timestamp.toString())
params.append('signature', hexSignature)
return `${getCdnEndpoint()}${path}?${params.toString()}`
}
// Set security headers for server responses
const securityHeaders = {
'Content-Security-Policy': "img-src 'self' *.imagedelivery.net",
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
}
Best practices
-
Optimize for Core Web Vitals
- Set explicit width and height attributes on images to prevent cumulative layout shift (CLS) and improve Largest Contentful Paint (LCP).
- Preload critical images and use responsive techniques to ensure fast rendering.
- Leverage modern monitoring tools to track web vitals.
-
Select Modern Image Formats
- Serve AVIF images with a WebP fallback using the
<picture>
element. - Consider progressive JPEGs for larger images and adjust quality settings based on content.
- Serve AVIF images with a WebP fallback using the
-
Implement Effective Caching
- Use stale-while-revalidate caching to deliver up-to-date images while refreshing in the background.
- Version your assets and set appropriate cache Time-To-Live (TTL) values.
-
Enhance Security
- Use signed URLs to protect your images.
- Set strict Content Security Policy (CSP) headers and implement DDoS protection measures.
- Enforce rate limiting on critical endpoints.
-
Ensure Robust Error Handling
- Provide graceful fallbacks for image loading errors.
- Monitor error rates and utilize placeholder images during loading or failure events.
Conclusion
Implementing an image optimization pipeline with a CDN significantly enhances web performance and user experience. By leveraging modern image formats, integrating robust performance monitoring, and enforcing stringent security measures, you can deliver optimized images efficiently and securely. For advanced file processing and transformation needs, consider exploring Transloadit's capabilities with integrations such as Uppy and Tus.