The Image CDN provides on-the-fly image optimization via CloudFront + Lambda@Edge. All images served through the CDN are automatically compressed and converted to modern formats (WebP/AVIF) based on browser support.
Default Behavior
Images are returned at original dimensions (capped at 2048px max) with format conversion. Smaller images are served at original size. Use presets for specific sizes:
- Listings:
?preset=medium(600×600)- Thumbnails:
?preset=thumb(150×150)- Full size (up to 2048): no params needed
| Environment | CDN Base URL |
|---|---|
| Development | https://d3bznwycx2s7pk.cloudfront.net |
| Production | https://d949acbs6mj2e.cloudfront.net |
# CDN URL - original dimensions (max 2048px), WebP if supported
https://d3bznwycx2s7pk.cloudfront.net/images/photo.png
# Listings/galleries (600×600)
https://d3bznwycx2s7pk.cloudfront.net/images/photo.png?preset=medium
# Thumbnail (150×150)
https://d3bznwycx2s7pk.cloudfront.net/images/photo.png?preset=thumb
# High quality large (1200×1200)
https://d3bznwycx2s7pk.cloudfront.net/images/photo.png?preset=large&q=95Use presets for consistent image sizes across the application:
| Preset | Dimensions | Use Case |
|---|---|---|
thumb | 150×150 | List thumbnails, small icons |
medium | 600×600 | Listings, card images |
large | 1200×1200 | Detail views, full-screen galleries |
hero | 1920×800 | Hero banners, headers |
avatar | 200×200 | User avatars, profile pics |
card | 400×300 | Product cards, previews |
Note: Without query params, images keep original dimensions (capped at 2048px max).
# Usage
https://cdn.example.com/path/image.jpg?preset=thumb
https://cdn.example.com/path/image.jpg?preset=hero| Parameter | Type | Default | Description |
|---|---|---|---|
preset | string | - | Only way to resize - see presets table above |
q | number | 80 | Quality percentage (1-100) |
f | string | auto | Output format |
fit | string | inside | Resize fit mode (used with presets) |
Security: Custom
wandhparameters are disabled to prevent cache pollution attacks. Use presets only.
| Value | Description |
|---|---|
auto | Auto-detect best format (WebP/AVIF if browser supports) |
jpeg | Force JPEG output |
webp | Force WebP output |
png | Force PNG output (preserves transparency) |
avif | Force AVIF output (best compression, newer browsers) |
| Value | Description |
|---|---|
inside | Resize to fit within dimensions (default, maintains aspect ratio) |
cover | Resize to cover dimensions (crop overflow) |
contain | Same as inside, with padding if needed |
outside | Resize to cover, allowing overhang |
fill | Stretch to exact dimensions (ignores aspect ratio) |
# 150x150 thumbnail, high quality
?preset=thumb&q=90
# Custom small square
?w=100&h=100&fit=cover&q=80# Standard card size
?preset=card
# Custom card with WebP
?w=400&h=300&f=webp&q=85# Full-width hero
?preset=hero
# Custom hero dimensions
?w=1920&h=600&fit=cover&q=85# Standard avatar
?preset=avatar
# Circular crop
?w=200&h=200&fit=cover&q=80# Large, high-quality (for downloads/printing)
?preset=large&q=95
# Original quality, just format conversion
?q=100&f=webpWhen IMAGE_CDN_URL is configured, the ImageCdnTransformInterceptor automatically transforms S3 URLs in API responses to CDN URLs.
Note: Transformed URLs return images at original dimensions (max 2048px) with format conversion. Use presets for smaller sizes (e.g.,
?preset=mediumfor listings).
{
"id": "123",
"imageUrl": "https://pers.assets.dev.s3.eu-west-1.amazonaws.com/products/photo.jpg"
}{
"id": "123",
"imageUrl": "https://d3bznwycx2s7pk.cloudfront.net/products/photo.jpg"
}To disable automatic transformation, don't set IMAGE_CDN_URL in your environment.
import { ImageCdnUrlBuilder } from '@pers/serverless-functions/infrastructure/interceptors';
const cdn = new ImageCdnUrlBuilder('https://d3bznwycx2s7pk.cloudfront.net');
// Using presets
const thumb = cdn.thumbnail('products/photo.jpg');
const large = cdn.large('products/photo.jpg');
// Custom options
const custom = cdn.build('products/photo.jpg', {
width: 800,
height: 600,
quality: 85,
format: 'webp',
fit: 'cover',
});// iOS Example
let cdnUrl = "https://d949acbs6mj2e.cloudfront.net"
let imagePath = "products/photo.jpg"
let thumbnailUrl = "\(cdnUrl)/\(imagePath)?preset=thumb"// Android Example
val cdnUrl = "https://d949acbs6mj2e.cloudfront.net"
val imagePath = "products/photo.jpg"
val thumbnailUrl = "$cdnUrl/$imagePath?preset=thumb"Add to your .env file:
# Enable CDN URL transformation
IMAGE_CDN_URL=https://d3bznwycx2s7pk.cloudfront.net
# Optional: Default preset for transformed URLs
IMAGE_CDN_DEFAULT_PRESET=medium| Variable | Description | Example |
|---|---|---|
IMAGE_CDN_URL | CDN base URL (enables transformation) | https://d3bznwycx2s7pk.cloudfront.net |
IMAGE_CDN_DEFAULT_PRESET | Default preset for auto-transformation | medium |
AWS_S3_BUCKET_NAME | S3 bucket name (for URL matching) | pers.assets.dev |
| Metric | Value |
|---|---|
| First request | ~200-500ms (Lambda cold start + processing) |
| Cached requests | ~10-50ms (CloudFront edge cache) |
| Cache duration | 1 year (immutable images) |
| Compression | Up to 90%+ reduction |
Cache-Control: public, max-age=31536000, immutable
X-Image-Optimized: true| HTTP Code | Description |
|---|---|
| 200 | Success - optimized image returned |
| 404 | Image not found in S3 |
| 5xx | Processing error - falls back to original image from S3 |
Note: Invalid parameters are automatically clamped to valid ranges (e.g.,
w=5becomesw=10,q=150becomesq=100).
┌──────────────┐ ┌─────────────────┐ ┌───────────────┐
│ Client │────▶│ CloudFront │────▶│ Lambda@Edge │
│ (Browser) │ │ (CDN Edge) │ │ (us-east-1) │
└──────────────┘ └─────────────────┘ └───────┬───────┘
│ cache │
▼ ▼
┌─────────────────┐ ┌───────────────┐
│ Edge Cache │ │ S3 Bucket │
│ (31536000s) │ │ (eu-west-1) │
└─────────────────┘ └───────────────┘- JPEG (.jpg, .jpeg)
- PNG (.png)
- WebP (.webp)
- GIF (.gif) - animations preserved
- AVIF (.avif)
- JPEG (default for photos)
- WebP (auto-detected for Chrome, Edge, Firefox)
- AVIF (auto-detected for Chrome 85+, Firefox 93+)
- PNG (when transparency required)
- Check
IMAGE_CDN_URLis set in environment - Verify image exists in S3 bucket
- Check CloudFront distribution is enabled
- CloudFront caches by full URL including query params
- Use consistent query param order
- Invalidate cache:
aws cloudfront create-invalidation --distribution-id XXXXX --paths "/path/*"
Check CloudWatch logs in us-east-1:
aws logs tail /aws/lambda/us-east-1.pers-image-optimizer-prod-imageOptimizer --follow