Displaying images correctly is as important as uploading them. Responsive images served at appropriate sizes, lazy loading for off-screen images, and proper layout dimensions prevent layout shifts and reduce bandwidth. A CDN in front of Azure Blob Storage adds global edge caching — users in Asia receive images from an edge server near them rather than from a data center in the US, reducing latency from 300ms to 30ms.
Responsive Images and CDN Integration
// ── Angular image URL pipe — transform blob URL to CDN URL ─────────────────
@Pipe({ name: 'cdnUrl', standalone: true, pure: true })
export class CdnUrlPipe implements PipeTransform {
private config = inject(APP_CONFIG);
transform(url: string | null | undefined, size?: 'thumb' | 'medium' | 'large'): string {
if (!url) return '/assets/images/placeholder.webp';
// Transform Azure Blob URL to CDN URL
// From: https://blogappstorage.blob.core.windows.net/images/abc.webp
// To: https://cdn.blogapp.com/images/abc.webp
if (url.includes('blob.core.windows.net') && this.config.cdnBaseUrl) {
const path = url.split('.net').pop(); // /images/abc.webp
return `${this.config.cdnBaseUrl}${path}`;
}
return url;
}
}
// ── Usage in templates ────────────────────────────────────────────────────
// <img [src]="post.coverImageUrl | cdnUrl" alt="Cover"
// loading="lazy"
// width="1200" height="630"
// (error)="onImageError($event)">
//
// <img [src]="user.avatarUrl | cdnUrl" alt="Avatar"
// loading="lazy"
// width="200" height="200">
// ── PostCardComponent — optimised image display ────────────────────────────
@Component({
selector: 'app-post-card',
standalone: true,
imports: [CdnUrlPipe, RouterLink, DatePipe],
template: `
<article class="post-card">
<!-- Cover image with responsive sizing and lazy loading ─────────────── -->
@if (post.coverImageUrl) {
<a [routerLink]="['/posts', post.slug]" class="cover-link">
<img
[src]="post.coverImageUrl | cdnUrl"
[alt]="post.title + ' cover image'"
loading="lazy" <!-- native lazy loading ──────────────── -->
width="800" <!-- prevents layout shift (CLS) ────────── -->
height="450"
class="cover-image"
(error)="onImageError($event)">
</a>
}
<div class="card-content">
<!-- Author avatar ───────────────────────────────────────────────── -->
<div class="author-row">
<img
[src]="post.authorAvatarUrl | cdnUrl"
[alt]="post.authorName + ' avatar'"
loading="lazy"
width="32" height="32"
class="avatar"
(error)="onAvatarError($event)">
<span>{{ post.authorName }}</span>
<time [dateTime]="post.publishedAt">
{{ post.publishedAt | date:'mediumDate' }}
</time>
</div>
<h2><a [routerLink]="['/posts', post.slug]">{{ post.title }}</a></h2>
<p>{{ post.excerpt }}</p>
<div class="meta">
<span>👁 {{ post.viewCount | number }}</span>
<span>💬 {{ post.commentCount }}</span>
</div>
</div>
</article>
`,
})
export class PostCardComponent {
@Input({ required: true }) post!: PostSummaryDto;
onImageError(event: Event): void {
(event.target as HTMLImageElement).src = '/assets/images/post-placeholder.webp';
}
onAvatarError(event: Event): void {
(event.target as HTMLImageElement).src = '/assets/images/avatar-default.webp';
}
}
width and height attributes on <img> elements are critical for preventing Cumulative Layout Shift (CLS) — a Core Web Vitals metric. When the browser knows the image dimensions before it downloads the image, it reserves the correct space in the layout. Without these attributes, the page reflows (shifts layout) when the image loads, which degrades user experience and hurts search engine rankings. Set width and height to the image’s natural dimensions (or the dimensions it will be displayed at).loading="lazy" attribute defers loading images until they are near the viewport. Do NOT add it to images that are visible on page load (above the fold) — particularly the largest image on the page (the LCP — Largest Contentful Paint). Lazy loading the LCP image delays it from starting to load, which hurts the LCP score (another Core Web Vitals metric). Use loading="eager" (or omit the attribute) for the first visible image, and loading="lazy" for everything below the fold.Common Mistakes
Mistake 1 — No width/height on images (layout shift on load)
❌ Wrong — <img [src]="..."> without width/height; page reflows when image loads; poor CLS score.
✅ Correct — always specify width and height matching the displayed dimensions; browser reserves space before download.
Mistake 2 — loading=”lazy” on above-the-fold LCP image (hurts Core Web Vitals)
❌ Wrong — hero image with loading=”lazy”; browser defers it; LCP score degrades significantly.
✅ Correct — first visible image uses loading="eager" or omits the attribute; only below-fold images get loading="lazy".