Skip to content

13 — Media and Image Pipeline

PRD Document · Savoy Signature Hotels — Multi-Site Headless Platform
Version: 1.0 · Date: 2026-03-04
Related docs: 06_Content_Modeling_Umbraco.md, 09_Cache_and_Performance.md


This document details the complete lifecycle of media assets (images, videos, documents) from editor upload to end-user delivery. It covers Azure Blob Storage integration, Umbraco Media Library, focal points, and Next.js Image Optimization with Cloudflare Polish.


5. Next.js Delivery

4. Edge Network

3. Frontend API

2. Storage (Azure)

1. CMS (Umbraco)

Original file

Metadata

JSON + Original URL

Translate to CFI URL

Pull & Cache Origin

WebP/AVIF Optimized

Editor Uploads Image

Set Focal Point / Crop

Azure Blob Storage

Umbraco Content API

Cloudflare Images

next/image custom loader

Browser


All media is entirely decoupled from the Umbraco web server’s local file system.

  • Provider: Azure Blob Storage (Standard tier, Hot access).
  • Integration: Umbraco.StorageProviders.AzureBlob NuGet package.
  • Path structure: https://{account}.blob.core.windows.net/media/{id}/{filename}
  • Security: Blob container is public for read access (managed via Cloudflare rules). Umbraco handles write access via SAS tokens or Managed Identity.
  • Stateless Web App: The Azure Web App hosting Umbraco can scale out or tear down instantly without losing media.
  • Lower Costs: Blob storage is significantly cheaper than premium SSDs required by App Services.
  • Origin for Cloudflare: Cloudflare Images will pull original assets directly from Blob Storage, bypassing the Umbraco server entirely.

We do not use Umbraco.ImageSharp for frontend delivery. Image processing is offloaded to Cloudflare Images to serve optimized, properly sized WebP/AVIF formats at the edge.

Umbraco’s Image Cropper property editor is still used, but exclusively for defining the Focal Point, not for hardcoded server-side crops.

ConceptImplementation
Focal PointEditors click a spot on the image (e.g., a person’s face).
API ResponseContains focalPoint: { top: 0.3, left: 0.5 }.
Frontend RenderTranslated to CSS: object-position: 50% 30%.
Why not crops?Hardcoded crops break responsive design boundaries. <picture> and object-fit: cover with focal points is the modern standard.

The ResponsiveImage component uses next/image to generate responsive srcset output.

// Resulting HTML output
<picture>
<source
media="(min-width: 1024px)"
srcset="/_next/image?url=https%3A%2F%2Fstorage...&w=1080&q=75 1080w,
/_next/image?url=https%3A%2F%2Fstorage...&w=1920&q=75 1920w"
/>
<img
src="/_next/image?url=https%3A%2F%2Fstorage...&w=640&q=75"
srcset="/_next/image?url=https%3A%2F%2Fstorage...&w=320&q=75 320w,
/_next/image?url=https%3A%2F%2Fstorage...&w=640&q=75 640w"
alt="Pool view"
loading="lazy"
decoding="async"
style="object-position: 50% 30%; object-fit: cover; width: 100%; height: auto;"
/>
</picture>

To avoid routing image requests through the Next.js Node.js server (which increases bandwidth costs and latency), we configure a custom loader that points directly to Cloudflare Images (using the URL format). Cloudflare Images will fetch the origin Blob Storage URL, transform it, and cache it.

src/lib/cloudflare-image-loader.ts
export default function cloudflareLoader({ src, width, quality }) {
// src is the Azure Blob Storage original URL
// We reformat it to use the Cloudflare Images URL format
// https://developers.cloudflare.com/images/url-format/
const params = [
`width=${width}`,
`quality=${quality || 75}`,
'format=auto'
].join(',');
return `https://savoysignature.com/cdn-cgi/image/${params}/${src}`;
}
// next.config.ts
const nextConfig = {
images: {
loader: 'custom',
loaderFile: './src/lib/cloudflare-image-loader.ts',
},
};

As outlined in 06_Content_Modeling_Umbraco.md, the media library enforces strict organization.

  • Client handover condition: The media folder structure must be created by WYcreative before the client receives access.
  • Top-level folders: _Shared, Savoy Signature, Savoy Palace, Royal Savoy, etc.
  • Sub-folders: Homepage, Rooms, Restaurants, Gallery -> Desktop / Mobile.
File TypeMax SizeAllowed ExtensionsValidation
ImagesDesktop: 1MB<br>Mobile: 500KB.jpg, .jpeg, .png, .webp, .svgReject larger files. Auto-convert format via CDN.
Videos10MB.mp4, .webmUsed ONLY for short, looping, mute backgrounds.
Docs5MB.pdf, .docxMenus, brochures, policies.

[!CAUTION] Video Policy: Large videos (e.g., 2-minute promotional videos with audio) MUST be hosted on YouTube or Vimeo and embedded via the videoBlock module. Umbraco/Blob storage is solely for background ambient videos (< 10MB).


SVGs are heavily used for logos, icons, and illustrations.

AssetUsage MethodRationale
Theme UI Icons (arrows, close, menu, social)Inline SCSS / React IconsHardcoded in the Next.js ui package. Extremely fast, cacheable, zero HTTP requests, skinnable via CSS currentColor.
Hotel LogosMedia Library -> <img src=".svg">Uploaded to Umbraco siteRoot. Resolves via ResponsiveImage.
Content Illustrations (e.g., map vectors)Media Library -> <img src=".svg">Managed by editors.

To prevent XSS (Cross-Site Scripting), SVGs uploaded to Umbraco MUST be sanitized via an event handler before saving to Blob Storage, stripping all &lt;script&gt; tags and on* attributes.


  • Azure Blob Storage provider configured in Umbraco.
  • Media URLs in Content API correctly resolve to the Blob Storage domain.
  • next/image configured with a custom loader pointing to Cloudflare/Blob (bypassing Next.js image optimization API).
  • Media Library strictly organized by hotel and section prior to Go-Live.
  • File size restrictions enforced on upload (Images < 1MB, Videos < 10MB, Docs < 5MB).
  • Focal Point editor enabled on all Image properties; crops disabled.
  • Frontend translates Focal Point API data into CSS object-position.
  • &lt;picture&gt; tag renders both Desktop and Mobile sources properly based on viewport.
  • SVGs sanitized on upload to strip malicious scripts.
  • No image processed by Umbraco ImageSharp for frontend delivery to prevent backend CPU drain.

Next document: 14_Accessibility_and_Compliance.md