<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Cloudinary Blog</title>
	<atom:link href="https://cloudinary.com/blog/feed" rel="self" type="application/rss+xml" />
	<link>https://cloudinary.com/blog/</link>
	<description></description>
	<lastBuildDate>Tue, 24 Mar 2026 07:24:47 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.5</generator>

<image>
	<url>https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1645214625/website-2021/blog/favicon-og_1937735151/favicon-og_1937735151.png?_i=AA&w=32</url>
	<title>Cloudinary Blog</title>
	<link>https://cloudinary.com/blog/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">201755607</site>	<item>
		<title>Introducing Cloudinary Moderation: AI-Powered Brand Protection for Every Visual Asset</title>
		<link>https://cloudinary.com/blog/introducing-cloudinary-moderation</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Tue, 24 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Moderation]]></category>
		<category><![CDATA[User-Generated Content]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39867</guid>

					<description><![CDATA[<p>Today we&#8217;re launching Cloudinary Moderation — a new AI-powered solution that automatically evaluates and enforces your brand standards across every asset entering your pipeline, whether it comes from marketplace sellers, agency partners, or end users. Built natively into Cloudinary, Moderation reviews assets in real time and determines whether they should be approved, flagged, or rejected [&#8230;]</p>
<p>The post <a href="https://cloudinary.com/blog/introducing-cloudinary-moderation">Introducing Cloudinary Moderation: AI-Powered Brand Protection for Every Visual Asset</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Today we&#8217;re launching <a href="https://cloudinary.com/products/moderation" type="link" id="https://cloudinary.com/products/moderation">Cloudinary Moderation</a> — a new AI-powered solution that automatically evaluates and enforces your brand standards across every asset entering your pipeline, whether it comes from marketplace sellers, agency partners, or end users.</p>



<p>Built natively into Cloudinary, Moderation reviews assets in real time and determines whether they should be approved, flagged, or rejected based on your unique brand guidelines. No manual review queues. No generic moderation rules. Just consistent, scalable brand control.</p>



<p>Because as visual content volume grows, the risk of off-brand, noncompliant, or low-quality imagery grows with it — and manual review simply doesn&#8217;t scale.</p>



<p>Here&#8217;s what Cloudinary Moderation does, and why we built it.</p>



<h2 class="wp-block-heading">Automatically Align Every Asset With Your Standards</h2>



<p>Most brands have clear visual guidelines. Few can enforce them consistently at scale.</p>



<p>As content flows in from sellers, partners, creators, and customers, review bottlenecks grow. Standards drift. Risk increases.</p>



<p>Cloudinary Moderation transforms static brand guidelines into automated standards.</p>



<p>You can define brand rules such as:</p>



<ul class="wp-block-list">
<li>Quality, resolution, and size.</li>



<li>Composition requirements like centered products or background standards.</li>



<li>Logo placement and spacing consistency.</li>



<li>Tone, lighting, and stylistic alignment.</li>



<li>Blocking overlays, watermarks, or promotional badges.</li>



<li>Detecting AI-generated or web-sourced content.</li>



<li>Compliance and safety parameters.</li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1773336715/blog-Introducing_Cloudinary_Moderation-1.png" alt="A section to create new rules in Cloudinary Moderation"/></figure>



<p>Every asset receives a clear outcome:</p>



<ul class="wp-block-list">
<li>Approved</li>



<li>Needs Review</li>



<li>Rejected</li>
</ul>



<p>And every decision is explainable, transparent, and fully auditable. Teams can override when necessary, ensuring humans remain in control where it matters most.</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1774024443/blog-Introducing_Cloudinary_Moderation-2.png" alt="Images include descriptions underneath of whether or not they pass the custom rules, and the reasons why"/></figure>



<p>This is brand governance in action.</p>



<h2 class="wp-block-heading">Accelerate Marketplace and Seller Growth</h2>



<p>For marketplaces and e-commerce platforms, inconsistent imagery slows catalog expansion and undermines customer trust.</p>



<p>Sellers upload thousands of assets daily. Manual moderation becomes expensive and unpredictable. Standards vary. Listings stall.</p>



<p>Cloudinary Moderation checks marketplace requirements automatically, including:</p>



<ul class="wp-block-list">
<li>White background.</li>



<li>Proper product framing and padding.</li>



<li>Minimum resolution and sharpness.</li>



<li>No unauthorized text overlays or watermarks.</li>
</ul>



<figure class="wp-block-image size-large"><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1773337164/blog-Introducing_Cloudinary_Moderation-3.png" alt="Rule preview for Centered Object"/></figure>



<p>Assets that fall within defined sensitivity thresholds are routed for review, balancing automation with precision.</p>



<p>The result:</p>



<ul class="wp-block-list">
<li>Faster listing publication.</li>



<li>Reduced operational costs.</li>



<li>Cleaner, more cohesive product pages.</li>



<li>Improved browsing confidence and conversion.</li>
</ul>



<p>When growth depends on onboarding speed, moderation becomes a growth engine.</p>



<h2 class="wp-block-heading">Power UGC and Partner Content Workflows in Near Real Time</h2>



<p>User-generated and partner-uploaded content introduces even greater variability.</p>



<p>Low-quality photography. Screenshots instead of original images. Misclassified categories. Off-brand aesthetics.</p>



<p>Cloudinary Moderation evaluates content at upload, delivering near real-time decisions with mapped rejection reasons. This enables:</p>



<ul class="wp-block-list">
<li>Faster partner and contributor onboarding.</li>



<li>Clear feedback loops for users and contributors.</li>



<li>Reduced moderation queues.</li>



<li>Improved content quality from day one.</li>
</ul>



<p>Instead of slowing growth, moderation accelerates it.</p>



<h2 class="wp-block-heading">Reduce Compliance and Reputation Risk</h2>



<p>Brand risk rarely originates from your internal creative teams. It emerges from external content sources, like unlicensed images, AI-generated assets, sensitive or unsafe visuals, even unauthorized logos.</p>



<p>Cloudinary Moderation proactively identifies these risks before they reach production.</p>



<p>With explainable decisions and override capabilities, organizations gain defensible governance embedded directly in the content lifecycle.</p>



<p>Moderation shifts from reactive cleanup to proactive protection.</p>



<h2 class="wp-block-heading">Native to Cloudinary. Faster Time to Market.</h2>



<p>Cloudinary has long empowered teams to manage, transform, and deliver visual content at scale.</p>



<p>With Moderation, Cloudinary now helps determine whether content should be used at all, and gives you the tools to fix it when it falls short.</p>



<p>Because Moderation is built directly into Cloudinary, assets can be evaluated, fixed, or enhanced using Cloudinary’s transformation and GenAI capabilities, and approved within the same platform. <strong>This turns moderation from a gate into an optimization loop.</strong></p>



<p>Instead of rejecting content and sending it back through fragmented processes, teams can correct, re-evaluate, and publish faster — all within Cloudinary.</p>



<p>Embedding intelligent standards directly into your visual workflows marks a shift from media management to full lifecycle brand governance.</p>



<p>You can:</p>



<ul class="wp-block-list">
<li>Scale content without losing control.</li>



<li>Reduce manual review and subjectivity.</li>



<li>Shorten time to market.</li>



<li>Fix and improve assets instead of discarding them.</li>



<li>Maintain consistency across regions and teams.</li>



<li>Protect customer trust before issues go live.</li>
</ul>



<p>Because in a world driven by visual experiences, brand integrity isn’t optional.</p>



<p>It’s foundational.</p>



<h2 class="wp-block-heading">Ready to Scale Your Brand With Confidence?</h2>



<p>Cloudinary Moderation is now available within the Cloudinary Console.</p>



<p><a href="https://cloudinary.com/documentation/cloudinary_moderation">Learn more</a> about Cloudinary Moderation, <a href="https://console.cloudinary.com/app/moderation/">get started for free</a>, or <a href="http://cloudinary.com/products/moderation#contact-form">contact us</a> to see how AI-powered, brand-aligned moderation can transform your visual reviews.</p>
<p>The post <a href="https://cloudinary.com/blog/introducing-cloudinary-moderation">Introducing Cloudinary Moderation: AI-Powered Brand Protection for Every Visual Asset</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39867</post-id>	</item>
		<item>
		<title>2026, the Year of JPEG XL</title>
		<link>https://cloudinary.com/blog/2026-the-year-of-jpeg-xl</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 14:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[JPEG XL]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39926</guid>

					<description><![CDATA[<p>🤖 AI disclaimer: This article uses quite a few em dashes and emojis — but it was written by a human. The saga of JXL adoption has been a surprisingly dramatic one. While Google was one of the main proponents of the format — it was created in a collaboration between Google Research and Cloudinary, [&#8230;]</p>
<p>The post <a href="https://cloudinary.com/blog/2026-the-year-of-jpeg-xl">2026, the Year of JPEG XL</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>AI disclaimer: This article uses quite a few </em><a href="https://www.nightwater.email/em-dash-ai/"><em>em dashes</em></a><em> and </em><a href="https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#Emoji"><em>emojis</em></a><em> — but it was written by a human.</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>The saga of JXL adoption has been a surprisingly dramatic one. While Google was one of the main proponents of the format — it was created in a collaboration between Google Research and <a href="https://cloudinary.com/labs/jpeg-xl">Cloudinary</a>, with guidance from the broader JPEG committee — this didn’t imply that Google Chrome would become an early adopter. But now it looks like in 2026 all pieces of the puzzle might finally come together.</p>



<p>In <a href="https://cloudinary.com/blog/how_jpeg_xl_compares_to_other_image_codecs">past blog posts</a>, I have made <a href="https://cloudinary.com/blog/the-case-for-jpeg-xl">the case for JPEG XL</a> several times already. In a nutshell: <a href="https://cloudinary.com/blog/contemplating-codec-comparisons">superior</a> <a href="https://cloudinary.com/blog/jpeg-xl-and-the-pareto-front">image compression performance</a> while ensuring a <a href="https://cloudinary.com/blog/what_to_focus_on_in_image_compression_fidelity_or_appeal">high fidelity</a> (in particular for <a href="https://cloudinary.com/labs/aic-3-and-hdr">HDR images</a>), <a href="https://cloudinary.com/blog/legacy_and_transition_creating_a_new_universal_image_codec">legacy-friendly</a> reversible JPEG recompression, and a <a href="https://cloudinary.com/blog/time_for_next_gen_codecs_to_dethrone_jpeg">very complete feature set</a> including <a href="https://cloudinary.com/blog/improve_the_web_experience_with_progressive_image_decoding">progressive decoding</a> make it the most promising modern image format.</p>



<p><a href="https://cloudinary.com/blog/cloudinary_supports_jpeg_xl">Cloudinary supports JXL</a> since November 2020 already. But of course server-side support alone is not sufficient to be able to actually use it on the Web…</p>



<h2 class="wp-block-heading">Browser Support, a Rollercoaster Ride <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f3a2.png" alt="🎢" class="wp-smiley" style="height: 1em; max-height: 1em;" /></h2>



<p>The story of JXL browser support has been one with quite some ups and downs. Let me give a brief summary of the main turning points.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Promising Start</h3>



<p>In a promising start, experimental Chrome support for JXL was implemented in May 2021, followed by Firefox in July 2021. This was very early&nbsp;—&nbsp;the standard itself wasn’t officially published until March 2022, though the “final draft” (FDIS) of the file format specification was approved by the committee in April 2021, so the format was effectively frozen already.</p>



<p>In both browsers it was disabled by default and a configuration flag had to be manually changed to actually get JXL support. The general expectation was that this was temporary, and once the standard would be published and the implementation would be mature and well-tested, the feature would be enabled by default.</p>



<p>The libjxl implementation improved, bugs were squashed, the standard was finalized. Things were looking good.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f383.png" alt="🎃" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Halloween Announcement</h3>



<p>But then, exactly on Halloween 2022, a <a href="https://issues.chromium.org/issues/40168998#comment85">controversial announcement</a> was made by the Chrome developers, saying they were planning to remove JXL support, citing “not enough interest from the entire ecosystem.&#8221;</p>



<p>Shortly afterwards, <a href="https://github.com/mozilla/standards-positions/issues/522#issuecomment-1409539985">Mozilla announced</a> in January 2023 that their position on JXL was “neutral,&#8221; i.e., they wouldn’t add support for it unless the other browsers would do that.</p>



<p>In February 2023, all JXL code was removed from Chrome.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/26b0.png" alt="⚰" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> JXL is Dead</h3>



<p>At this point, it looked like the story was basically over for JXL support in browsers, and thus probably for JXL in general as a widely-supported image format. After all, browsers are such a critical part of so many workflows and use cases, that no image format can be considered ubiquitous and interoperable if it doesn’t have universal browser support.</p>



<p>Considering the market share of Chrome and Chromium-derived browsers such as Edge, a <a href="https://www.fsf.org/blogs/community/googles-decision-to-deprecate-jpeg-xl-emphasizes-the-need-for-browser-choice-and-free-formats">veto from Chrome essentially is a death sentence</a> for any web feature.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f34e.png" alt="🍎" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Resurrection by Apple</h3>



<p>In a plot twist, in June 2023 <a href="https://cloudinary.com/blog/jpeg-xl-how-it-started-how-its-going#then_came_the_apple_announcement">Apple announced</a> that they were planning to add JXL support to their entire ecosystem, including iOS, macOS, and Safari. In September 2023, JXL support was rolled out in Safari.</p>



<p>The following months we saw a rapid increase in the number of image requests coming from browsers with JXL support in Cloudinary’s overall traffic.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f4c8.png" alt="📈" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Safari Rollout</h3>



<p>The proportion of web clients with JXL support jumped from only a fraction of a percent beginning of September 2023 to a peak of around 25% around Christmas. It slowly grew to around 28% and has been fluctuating around that ratio for about two years now, with peaks above 30%.</p>



<p>(As a side note: This 28% browser support number is higher than the 12% reported by <a href="https://caniuse.com/jpegxl">“Can I Use”</a> or as cited in <a href="https://www.corewebvitals.io/pagespeed/jpeg-xl-core-web-vitals-support">Arjen Karel’s excellent recent blog post about JPEG XL and Core Web Vitals<span style="text-decoration: underline;">.</span></a> Likely this is caused by North America and Europe/U.K. being overrepresented in Cloudinary’s traffic — together over 75% of our traffic&nbsp;— which bumps up the proportion of iPhones in our logs.)</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1773848235/blog-2026_the_year_of_jpeg_xl-1.png" alt="JXL adoption over time" title="Chart"/></figure>



<p>(As another side note: In case you’re wondering where those peaks are coming from: on desktop, about 10% of browsers support JXL, while on mobile, it is about 40%. On working days about 60% of our traffic is from mobile devices, while on weekends and holidays it is around 75%. The plot shows the numbers for Tuesdays, so it mostly shows “working day traffic,&#8221; except around holidays. In particular around the end of the year, this causes spikes in the plot.)</p>



<p>In the same period, the number of JXL images Cloudinary delivered per day also increased significantly, from practically none (around 50,000/day) in the summer of 2023 to around one billion per day at the time of writing. The spikes in overall amount of traffic are always very noticeable around Black Friday.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f629.png" alt="😩" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> JXL: A Safari-Only Format?</h3>



<p>Of course Safari’s support for JXL brought back hope for getting JXL support back into Chrome and Firefox. Alas, for both <a href="https://www.theregister.com/2024/02/03/jpeg_xl_interop_2024/">Interop 2024</a> and <a href="https://github.com/web-platform-tests/interop/issues/700">Interop 2025</a>, making JXL an interoperable web format was proposed, but the proposals were not selected by the browser devs.</p>



<p>So it looked like JXL would become a Safari-only image format, similar to J2K and HEIC.</p>



<h2 class="wp-block-heading">Meanwhile, Broad JXL Adoption Grew <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f423.png" alt="🐣" class="wp-smiley" style="height: 1em; max-height: 1em;" /></h2>



<p>JPEG XL was conceived with a diverse range of applications in mind from the start, as outlined in its <a href="https://ds.jpeg.org/documents/wg1n83043-REQ-JPEG_XL_Use_Cases_and_Requirements.pdf">use cases and requirements document</a>. While web delivery is a key area, it is not the sole focus. Unlike formats such as WebP or AVIF, which were created specifically to optimize web bandwidth, JXL&#8217;s intended scope is much broader.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f4f8.png" alt="📸" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> DNG: JPEG XL in Photography Workflows</h3>



<p>Introduced in June 2023, version 1.7 of <a href="https://helpx.adobe.com/camera-raw/digital-negative.html">Adobe’s Digital Negative format</a>, DNG, makes it possible to use JXL as a payload codec for RAW sensor data. In particular, both lossless and lossy JXL can be used. With legacy JPEG, lossy compression wouldn’t make sense for RAW files, since the precision would be too low to allow for adjustments in post-production. But JXL effectively has no precision limits and can do very high-fidelity lossy compression, which is <a href="https://fstoppers.com/lightroom/jpeg-future-photography-results-are-incredible-686945">a very attractive option for photography workflows</a>.</p>



<p>Samsung introduced <a href="https://r2.community.samsung.com/t5/CamCyclopedia/JPEG-XL-Image-Codec/ba-p/15356525">JXL as a payload in Expert RAW</a> in the Galaxy S24 in January 2024, while Apple added <a href="https://petapixel.com/2024/09/18/why-apple-uses-jpeg-xl-in-the-iphone-16-and-what-it-means-for-your-photos/">JXL as a payload in ProRAW in the iPhone 16 Pro</a> in September 2024.</p>



<p>More recently, <a href="https://www.cast-inc.com/compression/jpeg-image-compression/jpeg-xl-e">hardware implementations of a JXL encoder</a> have become available in November 2025, based on a <a href="https://www.shikino.co.jp/eng/products/product-ip_core_products_1.php">design by Shikino High-Tech</a> in collaboration with the JPEG committee. This will likely lead to consumer and professional camera vendors announcing new products that support JXL as a capture format in the near future.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> PDF: Images Inside Documents</h3>



<p>At <a href="https://pdfa.org/event/pdf-days-europe-2025/">PDF Days Europe 2025</a>, Peter Wyatt, CTO of the <a href="https://pdfa.org/">PDF Association</a>, <a href="https://www.youtube.com/watch?v=DjUPSfirHek&amp;t=2100s">announced</a> that JPEG XL was selected as the preferred solution for <a href="https://cloudinary.com/labs/aic-3-and-hdr">HDR images</a> embedded in PDF. He also mentioned other features of JXL that could be useful in PDF: wide gamut, ultra-high resolution, up to 32 bits per channel and up to 4099 channels.</p>



<p>It’s worth pointing out that JXL was designed with printing use cases in mind, including support for CMYK and spot colors. In that respect, there’s an obvious difference between JXL and image formats derived from video codecs, such as WebP, HEIC, and AVIF.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1fa7b.png" alt="🩻" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> DICOM: Medical Imaging</h3>



<p>In healthcare imaging (think MRIs, CTs, etc.),&nbsp; <a href="https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_8.2.15.html">JXL can be used as a payload codec in DICOM</a> since the 2024d release. The defined JPEG XL transfer syntaxes include lossless, lossy, and JPEG recompression variants. <a href="https://www.dicomstandard.org/news-dir/progress/docs/sups/sup232-slides.pdf">According to pathologists at Mayo Clinic</a>, for whole slide imaging (WSI) the file size reduction that can be achieved using JXL is 50-60%. The WSI archives at Mayo alone are growing by about 4000 terabytes per year, so such savings represent a substantial cost reduction.</p>



<p>In September 2025, Philips announced the first <a href="https://www.philips.com/a-w/about/news/archive/standard/news/articles/2025/philips-announces-digital-pathology-scanner-with-native-configurable-dicom-jpeg-and-jpeg-xl-output-in-world-first.html">digital pathology scanner with native DICOM JXL</a> output. Recently <a href="https://aws.amazon.com/about-aws/whats-new/2026/01/aws-healthimaging-adds-jpeg-xl/">AWS HealthImaging also added JXL support</a>.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f5fa.png" alt="🗺" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Geospatial and Other Scientific Imaging <strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f6f0.png" alt="🛰" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f52d.png" alt="🔭" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f52c.png" alt="🔬" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></h3>



<p>The <a href="https://gdal.org/">Geospatial Data Abstraction Library</a> (GDAL), released by the <a href="https://www.osgeo.org/">Open Source Geospatial Foundation</a> (OSGeo), is a translator library for raster and vector geospatial data formats. It’s part of the backbone of many GIS applications, from Google Earth to OpenStreetMap. In November 2022, GDAL 3.6 was released, which <a href="https://gdal.org/en/stable/drivers/raster/jpegxl.html">added support for JXL</a> and JXL payloads in <a href="https://gdal.org/en/stable/drivers/raster/gtiff.html">GeoTIFF</a>.</p>



<p>Also in other scientific applications JXL brings advantages, in particular for lossless or near-lossless compression of high bitdepth images, and for multispectral images for which <a href="https://arstechnica.com/science/2025/03/scientists-are-storing-light-we-cannot-see-in-formats-meant-for-human-eyes/">Spectral JPEG XL</a> was recently introduced.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Image Authoring Tools</h3>



<p>All major image authoring tools support JXL as an import and export format: Adobe Photoshop and Lightroom, Affinity, Acorn, ACDSee Photo Studio, paint.net, GIMP, Krita, Darktable, and so on.</p>



<p>Many of them added support already back in 2022.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Operating System Support</h3>



<p>Linux distributions were the first to add JPEG XL support at the OS-level. The integration of libjxl in both GTK (GNOME) and Qt (KDE) allowed some distributions like KaOS and OpenMandriva to add out-of-the-box JXL support as early as 2021. Popular distributions including <a href="https://www.phoronix.com/news/Ubuntu-25.04-JPEG-XL-Default">Ubuntu</a> now have JXL support included in default installs.</p>



<p>As mentioned before, Apple rolled out full support for JXL in September 2023 — not just in Safari but also at the OS level.</p>



<p>In March 2025, Microsoft added OS-level <a href="https://apps.microsoft.com/detail/9mzprth5c0tb">support for JXL in Windows 11</a>.</p>



<h2 class="wp-block-heading">Back to Browsers: Is JXL Support Finally Landing? <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f6ec.png" alt="🛬" class="wp-smiley" style="height: 1em; max-height: 1em;" /></h2>



<p>For a year or two, there was pretty much no news from Chrome and Firefox, and it looked like Safari would remain the only browser with JXL support. But in September 2024, something started changing. Bobby Holley, the CTO of Firefox, stated that &#8220;<a href="https://github.com/mozilla/standards-positions/pull/1064">Firefox will consider a Rust implementation of JPEG XL</a>. This announcement triggered the development of a <a href="https://github.com/libjxl/jxl-rs">Rust-based JXL implementation jxl-rs</a>.</p>



<p>In November 2025, Rick Byers, Area Tech Lead on Chrome, <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/WjCKcBw219k/m/NmOyvMCCBAAJ">announced</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“Since JPEG XL was last evaluated, Safari <a href="https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes">has shipped support</a> and Firefox has <a href="https://github.com/mozilla/standards-positions/pull/1064">updated their position</a>. We also continue to see developer signals for this in <a href="https://issues.chromium.org/issues/40270698">bug upvotes</a>, <a href="https://github.com/web-platform-tests/interop/issues?q=%22jpeg%20xl%22%20sort%3Areactions-%2B1-desc">Interop proposals</a>, and <a href="https://github.com/web-platform-tests/interop/issues/994#issuecomment-3416932563">survey data</a>. There was also a recent <a href="https://www.youtube.com/watch?v=DjUPSfirHek&amp;t=2284s">announcement</a> that JPEG XL will be added to PDF.&nbsp;</p>



<p>Given these positive signals, we would welcome contributions to integrate a performant and memory-safe JPEG XL decoder in Chromium. In order to enable it by default in Chromium we would need a commitment to long-term maintenance. With those and our usual launch criteria met, we would ship it in Chrome.”</p>
</blockquote>



<p>This was obviously a big turning point. Within weeks, the jxl-rs decoder was integrated in both Chrome and Firefox. Also, JPEG XL was selected as an “<a href="https://github.com/web-platform-tests/interop-jpegxl">investigation effort</a>” for <a href="https://web.dev/blog/interop-2026">Interop 2026</a>.</p>



<p>Right now, chances are that you’re reading this on a browser that can decode JXL images. In Safari or iOS, JXL is already supported for a while now. On Chrome, it is available behind a flag since M145, which was released on February 10th, 2026. The relevant flag is:</p>



<p><code>chrome://flags/#enable-jxl-image-format</code></p>



<p>There’s a <a href="https://jpegxl.info/resources/jpeg-xl-test-page.html">test page</a> on the <a href="https://jpegxl.info/">JPEG XL community website</a> to check if your browser supports JXL.</p>



<p>While experimental JXL support behind a flag has been in Chrome before, it looks like this time it will actually be shipped. At the time of writing, no date is known yet for having JXL enabled by default, but it seems likely that this will happen in 2026.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f4c6.png" alt="📆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Timing and Interoperability</h3>



<p>It’s interesting to compare the timing for the three modern image formats recently introduced. In case of both WebP and AVIF, generally speaking the format could be used on the Web well before it could be used in most other contexts. For JPEG XL, it’s more or less the other way around:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td></td><td class="has-text-align-center" data-align="center"><strong>WebP</strong></td><td class="has-text-align-center" data-align="center"><strong>AVIF</strong></td><td class="has-text-align-center" data-align="center"><strong>JPEG XL</strong></td></tr><tr><td>Standardization</td><td class="has-text-align-center" data-align="center">single company<br>(Google)</td><td class="has-text-align-center" data-align="center">industry consortium (AOMedia)</td><td class="has-text-align-center" data-align="center">standards organization (ISO/IEC)</td></tr><tr><td>Specification finalized</td><td class="has-text-align-center" data-align="center">Aug 2012</td><td class="has-text-align-center" data-align="center">Feb 2019</td><td class="has-text-align-center" data-align="center">Mar 2022</td></tr><tr><td>Chrome</td><td class="has-text-align-center" data-align="center">–1 ½ year&nbsp;<br>Feb 2011</td><td class="has-text-align-center" data-align="center">1 ½ year<br>Aug 2020</td><td class="has-text-align-center" data-align="center">4-5 years<br>2026 (?)</td></tr><tr><td>Edge</td><td class="has-text-align-center" data-align="center">6 years<br>Nov 2018</td><td class="has-text-align-center" data-align="center">5 years<br>Jan 2024</td><td class="has-text-align-center" data-align="center">4-5 years<br>2026 (?)</td></tr><tr><td>Firefox</td><td class="has-text-align-center" data-align="center">7 years<br>Jan 2019</td><td class="has-text-align-center" data-align="center">2 ½ year<br>Oct 2021</td><td class="has-text-align-center" data-align="center">4-5 years<br>2026 (?)</td></tr><tr><td>Safari / iOS</td><td class="has-text-align-center" data-align="center">10 years<br>Sept 2022</td><td class="has-text-align-center" data-align="center">3 ½ year<br>Oct 2022</td><td class="has-text-align-center" data-align="center">1 ½ year<br>Sept 2023</td></tr><tr><td>Adobe Photoshop</td><td class="has-text-align-center" data-align="center">10 years<br>Feb 2022</td><td class="has-text-align-center" data-align="center">6 years<br>June 2025</td><td class="has-text-align-center" data-align="center">3 years<br>June 2025</td></tr><tr><td>Adobe Lightroom</td><td class="has-text-align-center" data-align="center">15 years<br>Feb 2026</td><td class="has-text-align-center" data-align="center">4 ½ year<br>Oct 2023</td><td class="has-text-align-center" data-align="center">½ year<br>Oct 2022</td></tr><tr><td>Darktable</td><td class="has-text-align-center" data-align="center">11 years<br>Dec 2022</td><td class="has-text-align-center" data-align="center">1 ½ year<br>Aug 2020</td><td class="has-text-align-center" data-align="center">½ year<br>Dec 2022</td></tr><tr><td>GIMP</td><td class="has-text-align-center" data-align="center">5 years<br>Aug 2017</td><td class="has-text-align-center" data-align="center">1 ½ year<br>Oct 2020</td><td class="has-text-align-center" data-align="center">¼ year<br>June 2022</td></tr><tr><td>Krita</td><td class="has-text-align-center" data-align="center">9 years<br>Aug 2022</td><td class="has-text-align-center" data-align="center">2 ½ year<br>Dec 2021</td><td class="has-text-align-center" data-align="center">½ year<br>Aug 2022</td></tr><tr><td>Windows</td><td class="has-text-align-center" data-align="center">11 years<br>May 2023</td><td class="has-text-align-center" data-align="center">3 ½ year<br>Sept 2022</td><td class="has-text-align-center" data-align="center">3 years<br>Mar 2025</td></tr><tr><td>MacOS</td><td class="has-text-align-center" data-align="center">8 years<br>Sept 2020</td><td class="has-text-align-center" data-align="center">3 ½ year<br>Sept 2022</td><td class="has-text-align-center" data-align="center">1 ½ year<br>Sept 2023</td></tr></tbody></table><figcaption class="wp-element-caption">Table: How long did it take to add support after the format specification was finalized?</figcaption></figure>



<p>WebP, created by Google, was supported in Chrome even before the format was finalized — lossless mode, alpha transparency and animation didn’t exist yet in the early lossy-only version of WebP that Chrome initially supported. While Google was <a href="https://web.dev/articles/serve-images-webp">actively promoting the use of WebP</a> on websites, for example by making sure that a Lighthouse Performance Audit would list any images that are not being served in WebP, broader ecosystem adoption outside web browsers (or even outside Chrome) was slow. This is perhaps not surprising: the format was not designed to have a broad scope in terms of use cases, and even though it is open source and royalty-free, it is a single-vendor proprietary format &#8220;owned&#8221; by Google, not by an international standards organization.</p>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f92c.png" alt="🤬" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> Format Frustration</h3>



<p>However this lack of broader support for WebP has caused much user frustration, to the extent that it <a href="https://knowyourmeme.com/memes/webp">became</a> a <a href="https://www.memedroid.com/memes/tag/webp">meme</a>. Saving an image from a website and then using it in software other than a browser is something that works effortlessly with JPEG or PNG, but with WebP, it often still doesn’t work, e.g., even at the time of writing, inserting a WebP in a Google Docs results in “unsupported image type.&#8221;</p>



<p>While any new format inevitably causes friction, for JXL the order of adoption is pretty much the opposite of how it went with WebP. After being introduced in Chrome and being promoted as the format of choice for web images, it took a decade for “interest from the entire ecosystem” to emerge. In contrast, JXL is already supported in all image editors and viewers well before it can be used in Chrome. From the point of view of interoperability — which is, after all, the main point of standardization — this might be a good thing, actually.</p>



<h2 class="wp-block-heading">Conclusion <img src="https://s.w.org/images/core/emoji/16.0.1/72x72/1f3c1.png" alt="🏁" class="wp-smiley" style="height: 1em; max-height: 1em;" /></h2>



<p>Maybe it’s not surprising that it is hard to get <em>yet another</em> image format supported, when JPEG and PNG are considered &#8220;good enough&#8221; for most workflows. Both born in the 1990s, they have the undeniable advantage that by now they “just work anywhere”. For any new format, it’s hard to overcome inertia and inevitable initial interoperability issues, which make it attractive to just stick to the old formats.</p>



<p>Yet, as discussed above, JXL adoption in the broad ecosystem — photography, digital art, medical and scientific imaging, and so on&nbsp; — actually was relatively swift. Adoption in browsers, on the other hand, was a roller coaster ride. But it looks like 2026 will be the year in which JXL is fully on track to become a worthy successor to good old JPEG and PNG. Knocking on wood, this dramatic saga will be concluding with a happy ending.</p>
<p>The post <a href="https://cloudinary.com/blog/2026-the-year-of-jpeg-xl">2026, the Year of JPEG XL</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39926</post-id>	</item>
		<item>
		<title>Introducing the New Cloudinary Studio: Enhanced Image Transformation at Your Fingertips</title>
		<link>https://cloudinary.com/blog/introducing-new-cloudinary-studio</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Wed, 18 Mar 2026 14:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[Image Transformation]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39907</guid>

					<description><![CDATA[<p>The post <a href="https://cloudinary.com/blog/introducing-new-cloudinary-studio">Introducing the New Cloudinary Studio: Enhanced Image Transformation at Your Fingertips</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="wp-block-cloudinary-markdown "><p>If you’ve been using Cloudinary to transform images, you’re probably familiar with the <a href="https://cloudinary.com/documentation/dam_editing_and_transformations#original_studio">original Studio</a> and the <a href="https://cloudinary.com/documentation/image_transformations#transformation_builder">Transformation Builder</a>. Both have been valuable tools for applying transformations to your images through an intuitive UI. Now, we’re introducing a new Studio that combines the best of both tools and adds powerful new capabilities.</p>
</div>


<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1773791078/studio_templates.avif" alt=""/><figcaption class="wp-element-caption">Original Studio</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1773791103/construct-mode.avif" alt=""/><figcaption class="wp-element-caption">Transformation Builder</figcaption></figure>
</div>
</div>


<div class="wp-block-cloudinary-markdown "><p>The new Studio is set to replace both the original Studio and the Transformation Builder, providing a unified interface for building transformations, getting the URL or SDK code you need, and saving transformed images directly to your Media Library.</p>
<p><img decoding="async" src="https://cloudinary-marketing-res.cloudinary.com/image/upload/v1773791056/studio_init_open.avif" alt="Construct Mode" loading="lazy" class="c-transformed-asset"  width="2676" height="1378"/></p>
<h2>How to Access the New Studio</h2>
<p>To open Studio for a single image:</p>
<ol>
<li>In the Media Library, locate your image.</li>
<li>Click the (three-dotted) <strong>Options</strong> menu on the image.</li>
<li>Select <strong>Edit</strong>.</li>
</ol>
<p>Alternatively, from an image’s drill-down page, click <strong>Edit</strong> from the Edit drop-down button.</p>
<p>To work with multiple images, select them first, then click the <strong>Edit</strong> icon in the assets toolbar.</p>
<h2>Transforming Transformations</h2>
<p>If you’ve used the original Studio in the past, you’ll find all the core transformations you rely on:</p>
<ul>
<li>Resize and crop</li>
<li>Background removal</li>
<li>Image overlays</li>
<li>Generative AI transformations</li>
<li>Image enhancements</li>
</ul>
<p>…plus, now you have access to many more, such as color adjustments, borders, artistic filters and effects.</p>
</div>


<video src="https://cloudinary-marketing-res.cloudinary.com/video/upload/f_auto,q_auto/v1773790189/studio_toolbar_options.mp4" autoplay muted playsinline loop type="mp4/video"></video>


<div class="wp-block-cloudinary-markdown "><p>Use the search at the top of the vertical toolbar to find transformations quickly. If no results are returned, it’s possible that Studio doesn’t support it yet.  In this case you could create a transformation with <strong>Custom Syntax</strong> (towards the bottom of the toolbar). Enter transformation parameters directly using Cloudinary’s <a href="https://cloudinary.com/documentation/transformation_reference">URL-based transformation syntax</a>. This gives you access to the full range of Cloudinary transformations without leaving Studio.</p>
<h3>The Step Approach</h3>
<p>In the new Studio, each transformation you apply is saved as an individual step. This gives you granular control:</p>
<ul>
<li>
<strong>Reorder steps</strong> to see how the sequence affects the final result.</li>
<li>
<strong>Disable steps temporarily</strong> to test different combinations without deleting them.</li>
<li>
<strong>Delete individual steps</strong> that you no longer need.</li>
<li>
<strong>Edit configuration details</strong> within a step.</li>
<li>
<strong>Undo and redo edits</strong> using the keyboard shortcuts <strong>Cmd+Z/Cmd+Shift+Z</strong>.</li>
<li>
<strong>View the transformation URL or SDK code</strong> at any point in the process.</li>
</ul>
<p>This approach makes it easier to experiment and iterate on your transformations.</p>
<p>In this video you can see a text overlay being added, the image being given rounded corners, then cropped. Notice that when the rounded corners step comes before the crop, only the top two corners remain rounded, but see how easy it is to move the rounded corners step to the end, so that they’re applied to the image after the crop:</p>
</div>

<cld-video-player
      cloud-name='cloudinary-marketing'
      public-id='studio_steps.mp4'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c4030887e'
        data-cld-big-play-button='init'
        data-cld-source-types='["hls","webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<div class="wp-block-cloudinary-markdown "><h3>Canvas Interactions</h3>
<p>The new Studio enhances the image editing experience through direct canvas interaction. You can now intuitively place and resize text and image overlays, as well as precisely define crop areas, directly on the image canvas.</p>
<div class='c-callout  c-callout--inline-title c-callout--tip'><strong class='c-callout__title'>Tip:</strong> <p>See the previous video for text layer placement, and the next one for manually cropping.</p></div>
<p>This drag-and-drop capability and visual feedback make complex layer placement and cropping adjustments much more immediate and easier to manage than relying solely on input fields, leading to a more intuitive and efficient editing workflow.</p>
<h3>From Individual Edits to Edits at Scale</h3>
<p>Studio caters for all your image editing needs, whether you want to tweak one particular image, or set up a transformation that works across multiple images.</p>
<p>Here’s how to manually crop one image:</p>
</div>

<cld-video-player
      cloud-name='cloudinary-marketing'
      public-id='studio_manual_crop.mp4'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c40309420'
        data-cld-big-play-button='init'
        data-cld-source-types='["hls","webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<div class="wp-block-cloudinary-markdown "><p>But this crop is specific to this image.  For multiple images, use smart cropping, which automatically detects the important part of the image and keeps it in the frame:</p>
</div>

<cld-video-player
      cloud-name='cloudinary-marketing'
      public-id='studio_multi_smart_crop.mp4'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c40309ad7'
        data-cld-big-play-button='init'
        data-cld-source-types='["webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<div class="wp-block-cloudinary-markdown "><p>The video shows key aspects of working with multiple images simultaneously:</p>
<ul>
<li>Adding multiple preview images to see how transformations look across different assets.</li>
<li>Toggling between grid view and pinned view.</li>
<li>Applying the same transformations to all images at once.</li>
</ul>
<h3>Transformation Templates</h3>
<p>Transformation templates continue to work as they did in the original Studio.  You can save a set of transformation steps as a template, which you can reuse across other images. When you save a template in the new Studio, it automatically creates a <a href="https://cloudinary.com/documentation/image_transformations#named_transformations">named transformation</a> for easy application to images at scale.</p>
</div>

<cld-video-player
      cloud-name='cloudinary-marketing'
      public-id='studio_template.mp4'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c4030a5f3'
        data-cld-big-play-button='init'
        data-cld-source-types='["hls","webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<div class="wp-block-cloudinary-markdown "><h3>Transformation Refiners</h3>
<p>If you’re not completely happy with AI-generated results, you can fine-tune them using <strong>Refiners</strong>. For example, after using background removal, you can clean up any parts of the background that weren’t removed or restore parts that were removed accidentally. Different refiners are available based on which AI transformations you’ve used.</p>
<p>In this video you can see the Background Removal Refiner in action, followed by the Generative Background Replace Refiner:</p>
</div>

<cld-video-player
      cloud-name='cloudinary-marketing'
      public-id='studio_refiners.mp4'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c4030af37'
        data-cld-big-play-button='init'
        data-cld-source-types='["hls","webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<div class="wp-block-cloudinary-markdown "><h3>Apply Transformations With AI</h3>
<p>The new Studio includes an AI chat interface where you can describe how you want to edit an image using natural language. The AI suggests transformations and shows you a preview. If you like what you see, you can apply those suggestions to your image and they’ll be added as steps automatically.</p>
<p>Keep in mind that AI won’t always interpret requests exactly as you intend, especially for complex or out-of-scope edits, but it can be a quick way to get started.</p>
<h2>Deliver Transformed Images</h2>
<p>If you’re looking to deliver your transformed images straight to your website, head to the <strong>Get Code</strong> tab. Here, you can add optimization and delivery settings, such as format, quality, color space and DPR, which don’t change the preview but impact the delivered image, then copy the URL or the code required for your SDK.</p>
<h2>Download Options</h2>
<p>When working with multiple images, there are different download options to choose from. Either download all the transformed images as a ZIP file, or download a CSV file that contains all the transformation URLs. Otherwise, if you’re editing just one image, the download button simply downloads the transformed version of that image.</p>
<h3>Save Transformed Images to your Media Library</h3>
<p>The new Studio lets you save transformed images to your Media Library.  There are several ways to save images:</p>
<ul>
<li>
<p><strong>Save as a new image.</strong> Create a new asset in your Media Library with the transformations permanently applied. You can specify the display name for the new image (the public ID will be the original public ID with random characters appended).</p>
</li>
<li>
<p><strong>Overwrite the original.</strong> Replace the original image with the transformed version while keeping its metadata. If backups are enabled, you can revert to a previous version if needed.</p>
</li>
<li>
<p><strong>Bulk save operations.</strong> You can save multiple transformed images at once, either as new assets or by overwriting the originals. When saving as new assets, you can provide a suffix to apply to each display name.</p>
</li>
</ul>
<p>Of course, you can still choose not to save the transformed image at all and simply use the transformation URL to deliver transformed variations dynamically, keeping your original intact.</p>
<h2>Making the Transition</h2>
<p>If you’ve been using the original Studio or the Transformation Builder, you’ll find the new Studio feels familiar while offering more flexibility. The core workflows remain the same, you’re still building transformations visually and applying them to your images, but now you have more control over the process and more options for what to do with the results.</p>
<p>The new Studio is available for accounts on Assets Free plans created after December 15, 2025, and is currently being rolled out to all customers.</p>
<h2>Additional Resources</h2>
<ul>
<li>
<a href="https://cloudinary.com/documentation/dam_editing_and_transformations#new_studio">New Studio documentation</a>
</li>
<li>
<a href="https://cloudinary.com/documentation/transformation_reference">Transformation reference</a>
</li>
<li>
<a href="https://cloudinary.com/documentation/image_transformations">Image transformations guide</a>
</li>
<li>
<a href="https://cloudinary.com/documentation/transformation_refiners">Transformation refiners</a>
</li>
</ul>
</div><p>The post <a href="https://cloudinary.com/blog/introducing-new-cloudinary-studio">Introducing the New Cloudinary Studio: Enhanced Image Transformation at Your Fingertips</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39907</post-id>	</item>
		<item>
		<title>The Vibecoder Storefront: Automating Premium UGC With TanStack Start and Cloudinary AI</title>
		<link>https://cloudinary.com/blog/vibecoder-storefront-automating-premium-ugc</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Tue, 10 Mar 2026 14:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39843</guid>

					<description><![CDATA[<p>The post <a href="https://cloudinary.com/blog/vibecoder-storefront-automating-premium-ugc">The Vibecoder Storefront: Automating Premium UGC With TanStack Start and Cloudinary AI</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="wp-block-cloudinary-markdown "><ul>
<li>
<strong>Live Demo</strong>: <a href="https://vibecoder-storefront.vercel.app/">https://vibecoder-storefront.vercel.app</a>
</li>
<li>
<strong>GitHub Repository</strong>: <a href="https://github.com/musebe/vibecoder-storefront">https://github.com/musebe/vibecoder-storefront</a>
</li>
</ul>
<h2>Why Messy Uploads Kill Conversion</h2>
<p>The core problem with user-generated content (UGC) marketplaces is that they can look messy. An uploaded photo of an expensive shoe? Great! But the photo was taken on a dimly lit kitchen table with a cluttered background, and since the buyer can’t see all the intricate details, they’re more likely to click away.</p>
<p>For developers, bridging this gap at scale is a nightmare. There are, unfortunately, three bad options. You can force users to learn photography (impossible). Manually review and edit every asset (<em>unscalable</em>). Build brittle server-side processing queues (expensive <em>and</em> slow!).</p>
<p>A zero-touch “Art Director” can fix uploaded photos so you don’t have to. In this guide, I’ll walk you through how to create one.</p>
<h2>The Virtual Mastering Architecture</h2>
<p>Instead of building a traditional editing pipeline that generates duplicate files for every change, you’ll use a metadata-first architecture with:</p>
<ul>
<li>
<strong>TanStack Start as the interface.</strong> It handles the secure, signed ingestion and enables type-safe server functions.</li>
<li>
<strong>Cloudinary Programmable Media as the intelligence.</strong> It restores, expands, and contextualizes the asset using generative AI.</li>
<li>
<strong>Virtual mastering as the state.</strong> Store the AI transformation recipe in the asset’s metadata, keeping the original file pristine while serving image worthy of being studio-grade.</li>
</ul>
<h2>The Agentic Workflow</h2>
<p>By decoupling the raw asset from its mastered state, you’ll create a self-healing pipeline:</p>
<ol>
<li>
<strong>Ingestion.</strong> The user uploads a raw image via a secure, signed handshake.</li>
<li>
<strong>Restoration.</strong> The agent automatically detects blur and compression artifacts (<code>e_gen_restore</code>).</li>
<li>
<strong>Contextualization.</strong> The agent strips the messy background and generates a professional studio setting (<code>e_gen_background_replace</code>).</li>
<li>
<strong>Hydration.</strong> The gallery fetches the raw asset and metadata recipe to reconstruct the “mastered” view instantly, without waiting for search indexing.</li>
</ol>
<h2>Initializing the TanStack Engine</h2>
<p>Before you build the intelligence, you’ll need a high-performance home for it. You’ll utilize <strong>TanStack Start</strong> because it bridges the gap between client-side interactivity and secure server-side operations without the bloat of traditional frameworks.</p>
<h3>The Project Scaffold</h3>
<p>Run the following command in your terminal to create a fresh, type-safe workspace. This sets up the router, TypeScript, and Tailwind CSS automatically.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-css shcb-wrap-lines"><span class="hljs-selector-tag">npm</span> <span class="hljs-selector-tag">create</span> <span class="hljs-keyword">@tanstack</span>/start<span class="hljs-keyword">@latest</span> vibecoder-storefront
</code></span></pre>
<h3>The ‘Engine’ Architecture</h3>
<p>To keep your AI mastering logic isolated and scalable, enforce a domain-driven folder structure. Don’t scatter files into generic <code>components</code> or <code>utils</code> folders. Instead, group everything related to the “Art Director” agent into a single feature directory.</p>
<p>Create the following structure inside your <code>src</code> folder:</p>
<pre class="js-syntax-highlighted"><code>src/
├── features/
│   └── upload/
│       ├── components/    # The &quot;Control Room&quot; (Client-Side UI)
│       │   ├── AIControlPanel.tsx
│       │   ├── AIPreview.tsx
│       │   └── UploadZone.tsx
│       └── server/        # The &quot;Engine Room&quot; (Server Functions)
│           └── upload.fn.ts
└── lib/
    └── cloudinary.ts      # SDK Configuration
</code></pre>
<ul>
<li>
<strong>Components.</strong> These are your visual controls. They never touch the database or API keys directly.</li>
<li>
<strong>Server.</strong> Files (like <code>upload.fn.ts</code>) run strictly on the server, keeping your API secrets safe from the browser.</li>
</ul>
<h3>Environment Variable Configuration</h3>
<p>The connection between your TanStack server and the Cloudinary media cloud requires a secure bridge.</p>
<p>Create a <code>.env</code> file in your root directory. Note the distinction between the <strong>Public</strong> key (safe for the browser) and the <strong>Secret</strong> key (server-only).</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-php shcb-wrap-lines"><span class="hljs-comment"># Client-Side (Vite)</span>
VITE_CLOUDINARY_CLOUD_NAME=<span class="hljs-string">"your_cloud_name"</span>

<span class="hljs-comment"># Server-Side (TanStack Functions)</span>
CLOUDINARY_API_KEY=<span class="hljs-string">"your_api_key"</span>
CLOUDINARY_API_SECRET=<span class="hljs-string">"your_api_secret"</span>
</code></span></pre>
<blockquote>
<p><strong>Pro Tip:</strong> Never prefix your <code>API_SECRET</code> with <code>VITE_</code>. It must remain invisible to the client to prevent unauthorized access to your media library.</p>
</blockquote>
<h3>The SDK Singleton</h3>
<p>Finally, you’ll create a centralized instance of the Cloudinary SDK to avoid re-initializing it in every file.</p>
<blockquote>
<p>File: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/lib/cloudinary.ts"><code>src/lib/cloudinary.ts</code></a></p>
</blockquote>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-keyword">import</span> { v2 <span class="hljs-keyword">as</span> cloudinary } <span class="hljs-keyword">from</span> <span class="hljs-string">'cloudinary'</span>;

cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.VITE_CLOUDINARY_CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.CLOUDINARY_API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.CLOUDINARY_API_SECRET,
  <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,
});

<span class="hljs-keyword">export</span> { cloudinary };
</code></span></pre>
<p>With the engine initialized, you’re ready to configure the security protocols that will protect your pipeline.</p>
<h2>The Security Gateway: Configuring Cloudinary Presets</h2>
<p>The AI Agent needs a sterile environment to operate. You don’t want it auditing every random upload in your cloud; you want a dedicated pipeline for the Storefront.</p>
<p>To achieve this, configure a <strong>signed upload [=preset</strong>. This acts as a strict “doorman” that enforces your governance rules at the edge.</p>
<h3>The ‘Signed’ Protocol</h3>
<p>In a production environment, “unsigned” presets are risky because they expose your cloud to unauthenticated uploads. For the Vibecoder Storefront, you’ll use <strong>signed mode</strong>.</p>
<ul>
<li>
<strong>The logic.</strong> The frontend can’t upload a file unless it first requests a cryptographic signature from our TanStack server.</li>
<li>
<strong>The benefit.</strong> This allows us to rate-limit users, validate file types on the server, and enforce strict metadata rules <em>before</em> the upload even starts.</li>
</ul>
<h3>Configuring the ‘Doorman’ Preset</h3>
<p>Follow these steps to configure the preset that guards your <code>vibecoder_raw</code> folder.</p>
<ol>
<li>Log in to your <a href="https://cloudinary.com/users/login">Cloudinary Console.</a>.</li>
<li>Navigate to <strong>Settings</strong> (Gear Icon) &gt; <strong>Upload</strong>.</li>
<li>Scroll down to <strong>Upload presets</strong> and click <strong>Add Upload Preset</strong>.</li>
</ol>
<h3>The Engine Configuration</h3>
<p>Configure the preset with these exact settings to match your codebase:</p>
<ul>
<li>Preset Name: <code>vibecoder_signed</code>
<ul>
<li>
<em>Note:</em> This key is hardcoded in our <code>upload.fn.ts</code> server function.</li>
</ul>
</li>
<li>Signing Mode: Select <strong>Signed</strong>.</li>
<li>Folder: Type <code>vibecoder_raw</code>.
<ul>
<li>The Quarantine: By hardcoding this folder, you’ll ensure that all raw user uploads land in one isolated location, keeping your main media library clean.</li>
</ul>
</li>
<li>Unique filename: <strong>On</strong>.
<ul>
<li>Why? It prevents overwrites if two users upload <code>shoe.jpg</code> at the same time.</li>
</ul>
</li>
<li>Delivery type: <strong>Upload</strong>.</li>
</ul>
<h3>Save and Deploy</h3>
<p>Click <strong>Save</strong>. You should now see <code>vibecoder_signed</code> in your list with the mode set to “Signed.”</p>
<h3>The Architecture: Quarantine Zone</h3>
<p>By configuring this preset, you’ve created a quarantine zone.</p>
<ul>
<li>In: <code>vibecoder_raw</code> (raw, messy user uploads).</li>
<li>Out: The gallery (curated, AI-mastered assets).</li>
</ul>
<p>The AI Agent will <em>only</em> listen to assets that land in this folder, ensuring it never accidentally modifies your other marketing assets.</p>
<h2>The Ingestion Engine: Server-Side Signing</h2>
<p>Professional applications do not expose API secrets to the browser. Instead, you’ll implement a <strong>cryptographic handshake</strong>.</p>
<ol>
<li>
<strong>Request:</strong> The client asks: “May I upload?”</li>
<li>
<strong>Sign:</strong> The server verifies the request and uses the <code>CLOUDINARY_API_SECRET</code> to generate a time-limited signature.</li>
<li>
<strong>Upload:</strong> The client attaches this signature to the file and sends it directly to Cloudinary.</li>
</ol>
<h3>The Server Function: <code>getUploadSignature</code></h3>
<p>In <strong>TanStack Start</strong>, you’ll create a Server Function that runs strictly in the backend. This function uses the <a href="https://cloudinary.com/documentation/node_integration">Cloudinary SDK</a> to generate a signature without ever revealing the secret key to the client.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/server/upload.fn.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getUploadSignature = createServerFn({ <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span> })
  .handler(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> timestamp = <span class="hljs-built_in">Math</span>.round(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getTime() / <span class="hljs-number">1000</span>);
    
    <span class="hljs-comment">// The "Engine": Generates the cryptographic token</span>
    <span class="hljs-keyword">const</span> signature = cloudinary.utils.api_sign_request(
      {
        timestamp,
        <span class="hljs-attr">upload_preset</span>: <span class="hljs-string">'vibecoder_signed'</span>, <span class="hljs-comment">// Matches our Cloudinary setting</span>
      },
      process.env.CLOUDINARY_API_SECRET!
    );

    <span class="hljs-keyword">return</span> { signature, timestamp, <span class="hljs-attr">apiKey</span>: process.env.CLOUDINARY_API_KEY! };
  });
</code></span></pre>
<blockquote>
<p>View full source: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/server/upload.fn.ts">src/features/upload/server/upload.fn.ts</a></p>
</blockquote>
<h3>The UI Component: <code>UploadZone</code></h3>
<p>Now, you’ll build the “control room” component. This React component handles the drag-and-drop interaction and initiates the handshake.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-php shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/components/UploadZone.tsx</span>

export <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UploadZone</span><span class="hljs-params">({ onUploadComplete }: UploadZoneProps)</span> </span>{
  <span class="hljs-keyword">return</span> (
    &lt;CldUploadWidget
      <span class="hljs-comment">// The Handshake: We fetch the signature from our server function</span>
      signatureEndpoint={async () =&gt; {
        <span class="hljs-keyword">const</span> { signature, timestamp, apiKey } = await getUploadSignature();
        <span class="hljs-keyword">return</span> { signature, timestamp, apiKey };
      }}
      onSuccess={(result) =&gt; {
        <span class="hljs-comment">// The Handoff: Pass the publicId to the Art Director</span>
        <span class="hljs-keyword">if</span> (result.info &amp;&amp; typeof result.info === <span class="hljs-string">"object"</span>) {
          onUploadComplete(result.info.public_id);
        }
      }}
    &gt;
      {({ open }) =&gt; (
        &lt;button onClick={() =&gt; open()} className=<span class="hljs-string">"..."</span>&gt;
          Upload Raw Asset
        &lt;/button&gt;
      )}
    &lt;/CldUploadWidget&gt;
  );
}
</code></span></pre>
<blockquote>
<p>View full source: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/components/UploadZone.tsx">src/features/upload/components/UploadZone.tsx</a></p>
</blockquote>
<h3>The Result</h3>
<p>When the user drops a file:</p>
<ol>
<li>The <code>UploadZone</code> calls <code>getUploadSignature</code>.</li>
<li>The Server returns a valid token.</li>
<li>The Widget uploads the file to the <code>vibecoder_raw</code> folder.</li>
<li>The <code>public_id</code> is passed to the next stage: <strong>The Art Director</strong>.</li>
</ol>
<h2>Constructing the Logic: Repair and Expand</h2>
<p>The first job of an Art Director isn’t to be creative; it’s to be corrective. User uploads are often blurry, compressed, or tightly cropped. You can fix these issues programmatically using a deterministic chain to repair and expand.</p>
<ul>
<li>
<strong>The restoration layer.</strong> Use <a href="https://ai.cloudinary.com/demos/restore"><code>generativeRestore()</code></a> as a “Digital Polish.” This two-pass restoration filter removes JPEG artifacts and sharpens blurry details, ensuring the base asset is high quality before we modify it.</li>
<li>
<strong>The expansion layer.</strong> Product photos often suffer from tight cropping, where the item touches the edges of the frame. You can use <a href="https://ai.cloudinary.com/demos/fill"><code>generativeFill()</code></a> combined with <code>pad()</code> to intelligently expand the canvas to a 1:1 square ratio. The AI invents realistic surroundings (e.g., extending a table surface) where none existed before.</li>
<li>
<strong>The repair chain.</strong> Combine these two powerful operations into a single, type-safe transformation chain within your <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/components/AIPreview.tsx"><code>AIPreview</code></a> component.</li>
</ul>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/components/AIPreview.tsx</span>

<span class="hljs-keyword">import</span> { pad } <span class="hljs-keyword">from</span> <span class="hljs-string">'@cloudinary/url-gen/actions/resize'</span>;
<span class="hljs-keyword">import</span> { generativeFill } <span class="hljs-keyword">from</span> <span class="hljs-string">'@cloudinary/url-gen/qualifiers/background'</span>;
<span class="hljs-keyword">import</span> { generativeRestore } <span class="hljs-keyword">from</span> <span class="hljs-string">'@cloudinary/url-gen/actions/effect'</span>;

<span class="hljs-comment">// ... inside your component</span>
<span class="hljs-keyword">if</span> (config.fill) {
  <span class="hljs-comment">// 1. Expansion: Force 1:1 ratio and invent missing pixels</span>
  aiImage.resize(
    pad()
      .width(<span class="hljs-number">1000</span>)
      .aspectRatio(<span class="hljs-string">"1:1"</span>)
      .background(generativeFill())
  );
}

<span class="hljs-keyword">if</span> (config.restore) {
  <span class="hljs-comment">// 2. Restoration: Fix blur and compression</span>
  aiImage.effect(generativeRestore()); 
}
</code></span></pre>
<blockquote>
<p>View full source: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/components/AIPreview.tsx">src/features/upload/components/AIPreview.tsx</a></p>
</blockquote>
<h3>The Result</h3>
<p>By chaining these methods, you’ll turn a low-res, rectangular phone photo into a sharp, square, studio-ready asset. This provides the perfect blank canvas for the next step of creative contextualization.</p>
<h2>The Creative Engine: Context and Variants</h2>
<p>The second phase transforms the product from a snapshot into a studio shoot. You’ll use natural language prompts to generate photorealistic environments and create inventory variants without a reshoot.</p>
<h3>The Context Layer</h3>
<p>Use <a href="https://ai.cloudinary.com/demos/replace"><code>generativeBackgroundReplace()</code></a> to strip away the original messy environment (kitchen tables, cluttered desks) and generate a new one based on a text prompt.</p>
<ul>
<li>
<strong>The logic:</strong> The AI automatically segments the foreground object, generates a new background, and blends the lighting and shadows to match.</li>
<li>
<strong>The prompt:</strong> “Studio lighting, minimalist concrete podium, soft shadows.”</li>
</ul>
<h3><strong>The Variant Layer</strong></h3>
<p>To scale your catalog, you need to show different color options. Instead of photographing every SKU, we use <a href="https://ai.cloudinary.com/demos/recolor"><code>generativeRecolor()</code></a>. This allows us to target specific objects (e.g., “Sneakers”) and swap their color values instantly (e.g., to <code>#FF0000</code>).</p>
<h3><strong>The “Aha!” Moment: The Creative Chain</strong></h3>
<p>We implement these creative directives as conditional layers in our transformation pipeline.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/components/AIPreview.tsx</span>

<span class="hljs-keyword">import</span> {
  generativeBackgroundReplace,
  generativeRecolor,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@cloudinary/url-gen/actions/effect"</span>;

<span class="hljs-comment">// ... inside the transformation logic</span>
<span class="hljs-keyword">if</span> (config.bgReplace) {
  <span class="hljs-comment">// 1. Context: "Put it on a marble table"</span>
  aiImage.effect(generativeBackgroundReplace().prompt(config.bgReplace));
}

<span class="hljs-keyword">if</span> (config.recolor?.item &amp;&amp; config.recolor?.color) {
  <span class="hljs-comment">// 2. Variant: "Make the shoes red"</span>
  aiImage.effect(generativeRecolor(config.recolor.item, config.recolor.color));
}
</code></span></pre>
<h3>The Result</h3>
<p>With just a few lines of code, a single uploaded image now serves as the source for unlimited marketing variations, different backgrounds for different campaigns, and different colors for different inventory SKUs.</p>
<h2>The Persistence Engine: Virtual Mastering</h2>
<p>Traditional editors create a mess of <code>image_v1.jpg</code>, <code>image_v2.jpg</code>. This kills storage and breaks the link to the original asset. We solve this by storing the AI transformation string in the asset’s metadata (<code>context</code>).</p>
<h3>The ‘Single Asset’ Philosophy</h3>
<ol>
<li>
<strong>The asset is immutable.</strong> We never overwrite the raw upload.</li>
<li>
<strong>The recipe is dynamic.</strong> We save the Cloudinary transformation string (e.g., <code>e_gen_restore/e_bg_replace:studio</code>) to a custom context key called <code>mastered_chain</code>.</li>
<li>
<strong>The result.</strong> Zero storage cost for variations.</li>
</ol>
<h3>The Server Function: <code>saveMasteredImage</code></h3>
<p>You’ll use the Cloudinary <strong>Admin API</strong> to patch the metadata without re-uploading the image.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/server/upload.fn.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> saveMasteredImage = createServerFn({ <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span> })
  .inputValidator(
    z.object({ <span class="hljs-attr">publicId</span>: z.string(), <span class="hljs-attr">transformation</span>: z.string() })
  )
  .handler(<span class="hljs-keyword">async</span> ({ data }) =&gt; {
    <span class="hljs-comment">// The "Engine": Updates metadata, not the file</span>
    <span class="hljs-keyword">await</span> cloudinary.uploader.explicit(data.publicId, {
      <span class="hljs-attr">type</span>: <span class="hljs-string">"upload"</span>,
      <span class="hljs-attr">context</span>: { <span class="hljs-attr">mastered_chain</span>: data.transformation },
    });
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span> };
  });
</code></span></pre>
<blockquote>
<p>View full source: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/server/upload.fn.ts">src/features/upload/server/upload.fn.ts</a></p>
</blockquote>
<h3>The ‘Hydration’ Logic</h3>
<p>When the gallery loads, it doesn’t look for a new file. It fetches the raw image and the <code>mastered_chain</code> string and reconstructs the “Mastered” view on the fly. This ensures the Art Director can always undo or remix the edit later.</p>
<h2>The Zero-Distortion Interface</h2>
<p>A masterful asset looks amateur if the UI stretches it. A horizontal camera shot looks terrible when forced into a vertical card. To fix this, build a context-aware visualizer.</p>
<h3>The Container Strategy</h3>
<p>Start by enforcing a strict <code>aspect-square</code> grid for the container, but allow the asset to float freely inside it using <code>object-contain</code>.</p>
<ol>
<li>The grid sets a predictable 1:1 rhythm for the gallery.</li>
<li>The asset uses <code>max-w-full</code> and <code>max-h-full</code> to fit perfectly without touching the edges.</li>
<li>The result is a product that centers itself as if it were shot professionally, whether it’s tall (bottle) or wide (shoes).</li>
</ol>
<h3>The Context-Aware Slider</h3>
<p>Next, implement a <code>CompareSlider</code> component that handles the before and after reveal without layout shifts.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/components/CompareSlider.tsx</span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CompareSlider</span>(<span class="hljs-params">{ before, after }: CompareSliderProps</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative w-full aspect-square rounded-3xl overflow-hidden bg-gray-50 border border-gray-100"</span>&gt;</span>
      {/* The After Image (Base) */}
      <span class="hljs-tag">&lt;<span class="hljs-name">AdvancedImage</span> 
        <span class="hljs-attr">cldImg</span>=<span class="hljs-string">{after}</span> 
        <span class="hljs-attr">className</span>=<span class="hljs-string">"absolute inset-0 w-full h-full object-contain p-8"</span> 
      /&gt;</span>
      
      {/* The Before Image (Overlay) */}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> 
        <span class="hljs-attr">className</span>=<span class="hljs-string">"absolute inset-0 w-1/2 overflow-hidden border-r-2 border-white/50"</span>
        <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> `${<span class="hljs-attr">sliderPosition</span>}%` }}
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">AdvancedImage</span> 
          <span class="hljs-attr">cldImg</span>=<span class="hljs-string">{before}</span> 
          <span class="hljs-attr">className</span>=<span class="hljs-string">"absolute inset-0 w-full h-full object-contain p-8"</span> 
          // <span class="hljs-attr">Crucial:</span> <span class="hljs-attr">Counter-translate</span> <span class="hljs-attr">to</span> <span class="hljs-attr">keep</span> <span class="hljs-attr">image</span> <span class="hljs-attr">static</span> <span class="hljs-attr">while</span> <span class="hljs-attr">container</span> <span class="hljs-attr">clips</span>
          <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> '<span class="hljs-attr">200</span>%' }} 
        /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></span></pre>
<blockquote>
<p>View full source: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/components/CompareSlider.tsx">src/features/upload/components/CompareSlider.tsx</a></p>
</blockquote>
<h3>The Layout Logic</h3>
<p>By adding <code>p-8</code> (padding) and <code>object-contain</code>, you can create a “virtual matte” around the product. This ensures that even if the AI expands the background, the core product remains the visual anchor of the card.</p>
<h2>The Retrieval Engine: Instant Sync</h2>
<p>When a user clicks <strong>Save</strong>, they expect the gallery to update <em>immediately</em>. However, standard Search APIs often have a two- to five-second indexing delay. This creates a phantom state during which the user wonders if their edit actually worked.</p>
<p>Solve this by switching to the <strong>Admin API</strong> to read directly from the folder source.</p>
<h3>The Bottleneck: Search vs. Admin</h3>
<p>The Search API is fast for filtering millions of assets, but suffers from index lag. The Admin API, on the other hand, is slower for massive datasets, but offers real-time consistency for specific folders.</p>
<p>Since your <code>vibecoder_raw</code> folder is a curated workspace, you should prioritize consistency over raw query speed.</p>
<h3>The Server Function: <code>getGalleryImages</code></h3>
<p>Implement a direct fetch using <code>cloudinary.api.resources</code>. This bypasses the search index and reads the file list straight from the disk.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// src/features/upload/server/upload.fn.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getGalleryImages = createServerFn({ <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span> }).handler(
  <span class="hljs-keyword">async</span> ({ data }) =&gt; {
    <span class="hljs-comment">// 1. Direct Fetch: Bypasses the Search Index</span>
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> cloudinary.api.resources({
      <span class="hljs-attr">type</span>: <span class="hljs-string">"upload"</span>,
      <span class="hljs-attr">prefix</span>: <span class="hljs-string">"vibecoder_raw"</span>,
      <span class="hljs-attr">max_results</span>: <span class="hljs-number">50</span>,
      <span class="hljs-attr">context</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Crucial: Fetches the 'mastered_chain' metadata</span>
      <span class="hljs-attr">direction</span>: <span class="hljs-string">"desc"</span>,
    });

    <span class="hljs-comment">// 2. Client-Side Sort: Ensures "Recently Saved" pops to the top</span>
    <span class="hljs-keyword">const</span> sorted = result.resources.sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> dateA = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(a.updated_at || a.created_at).getTime();
      <span class="hljs-keyword">const</span> dateB = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(b.updated_at || b.created_at).getTime();
      <span class="hljs-keyword">return</span> dateB - dateA;
    });

    <span class="hljs-keyword">return</span> sorted.map(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> ({
      <span class="hljs-attr">publicId</span>: res.public_id,
      <span class="hljs-attr">masteredChain</span>: res.context?.custom?.mastered_chain || <span class="hljs-literal">null</span>,
    }));
  }
);
</code></span></pre>
<blockquote>
<p>View full source: <a href="https://github.com/musebe/vibecoder-storefront/blob/main/src/features/upload/server/upload.fn.ts">src/features/upload/server/upload.fn.ts</a></p>
</blockquote>
<h3>The Result</h3>
<p>By manually sorting based on <code>updated_at</code>, the gallery becomes self-healing. As soon as the metadata patch is confirmed, the asset jumps to the top of the list with its new AI transformation applied, creating a seamless feedback loop for the Art Director.</p>
<h2>The Future of Media Operations</h2>
<p>You’ve successfully moved from an approach in which every new image requirement meant a new code deployment, to an AI Agent architecture. By decoupling the <strong>asset</strong> (the raw file) from the <strong>intent</strong> (the metadata recipe), you created a system that’s:</p>
<ol>
<li>
<strong>Efficient for massive volumes of storage.</strong> One thousand product variants now cost <strong>0 bytes</strong> of extra storage due to keeping 1,000 text strings in the metadata.</li>
<li>
<strong>Self-healing.</strong> If the AI model improves next month, you don’t need to reupload files. Just rerun the <code>saveMasteredImage</code> function to update the metadata, and the frontend will instantly reflect the better quality.</li>
<li>
<strong>Governance-ready.</strong> Because the mastered state is just data, you can easily add a step for human oversight (e.g., adding an <code>approved: true</code> flag to the metadata) before publishing to the live storefront.</li>
</ol>
<h3>Declarative Media</h3>
<p>The true breakthrough is the shift from imperative editing (e.g., crop to 500px, increase brightness by 10%) to declarative intent (e.g., make this look like a studio shoot). The old method is brittle, pixel-perfect fragility, while the vibecoder method is semantic, resilient, and adaptive.</p>
<h3>Next Steps: Extending the Agent</h3>
<p>This architecture is the foundation. To scale it into a full enterprise digital asset management (DAM) system, your next engineering steps would be:</p>
<ul>
<li>
<strong>Multi-channel variants.</strong> Add a new metadata key for <code>social_story_chain</code> to automatically generate 9:16 vertical video assets from the same static product shot.</li>
<li>
<strong>Webhooks.</strong> Use Cloudinary notification URLs to trigger a Slack alert whenever the AI Agent finishes a batch of 100 restorations.</li>
<li>
<strong>Role-based access.</strong> Lock the <code>saveMasteredImage</code> function behind an admin role so only approved Art Directors can commit changes to the public gallery.</li>
</ul>
<p>You now have the blueprint! <a href="https://cloudinary.com/users/register_free">Sign up for a free Cloudinary account</a> today and build the future of automated media.</p>
<p><strong>Resources:</strong></p>
<ul>
<li>
<strong>Live Demo</strong>: <a href="https://vibecoder-storefront.vercel.app">https://vibecoder.vercel.app</a>
</li>
<li>
<strong>Source Code</strong>: <a href="https://github.com/musebe/vibecoder-storefront">https://github.com/musebe/vibecoder-storefront</a>
</li>
<li>
<strong>Cloudinary Docs</strong>: <a href="https://cloudinary.com/documentation/transformation_reference">Generative AI Transformations</a>
</li>
</ul>
</div><p>The post <a href="https://cloudinary.com/blog/vibecoder-storefront-automating-premium-ugc">The Vibecoder Storefront: Automating Premium UGC With TanStack Start and Cloudinary AI</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39843</post-id>	</item>
		<item>
		<title>Scaling Subject-Aware Layouts: Solving Multi-Device Quality With React and AI</title>
		<link>https://cloudinary.com/blog/scaling-subject-aware-layouts-react-ai</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Thu, 05 Mar 2026 15:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Generative AI]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[Responsive Images]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39839</guid>

					<description><![CDATA[<p>The post <a href="https://cloudinary.com/blog/scaling-subject-aware-layouts-react-ai">Scaling Subject-Aware Layouts: Solving Multi-Device Quality With React and AI</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="wp-block-cloudinary-markdown "><p>In the multi-device era, responsive design often just means cropping an image until it fits.</p>
<p>We’ve all been there. You upload a stunning landscape hero image and it looks great on desktop. But when the layout shifts to a vertical mobile view (9:16) or a square card (1:1), standard CSS takes over.</p>
<p><img decoding="async" src="https://res.cloudinary.com/demo-article-projects/image/upload/v1770941085/Articles-Images/screen-resolution.jpg" alt="screen resolution" loading="lazy" class="c-transformed-asset"  width="3998" height="2666"/></p>
<p><em>The fragmentation of modern screens makes fixed-ratio assets a liability.</em></p>
<p>The industry standard, <code>object-fit: cover</code>, is aggressive. It blindly chops off the edges to fill the container. If your subject isn’t dead center, they get decapitated. This isn’t just a layout bug; it’s a conversion killer.</p>
<ul>
<li>
<strong>E-commerce.</strong> You crop out the heel of the shoe.</li>
<li>
<strong>Portrait.</strong> You crop the forehead of the CEO.</li>
<li>
<strong>Travel.</strong> You crop the landmark out of the skyline.</li>
</ul>
<p>To fix this manually, developers write complex media queries or force content teams to upload three different versions of the same asset. That is unscalable technical debt.</p>
<p>You need a “zero-cut” architecture. With a single upload of a high-quality source image, your application will automatically:</p>
<ul>
<li>
<strong>Detect</strong> the subject.</li>
<li>
<strong>Decide</strong> if it needs to crop (zoom) or expand (pad).</li>
<li>
<strong>Generate</strong> the perfect pixels to fill the gap.</li>
</ul>
<p><img decoding="async" src="https://res.cloudinary.com/demo-article-projects/image/upload/v1770941226/Articles-Images/one%20source%20asset.png" alt="one-source image" loading="lazy" class="c-transformed-asset"  width="1799" height="837"/></p>
<p>The result is one source asset intelligently expanded and focused across Story, Post, and Banner layouts.</p>
<h2>The Architecture: Context-Aware Rendering</h2>
<p>To prevent the subjects in our images from awkward cropping, you’ll need more than just CSS. You’ll need a component that makes a <strong>content-aware decision</strong> before the image even loads.</p>
<p>This logic will be built into a smart component that evaluates the target aspect ratio against the source. It’ll choose between two distinct AI strategies:</p>
<ol>
<li>Strategy A: The Smart Crop (<code>g_auto</code>)</li>
</ol>
<ul>
<li>
<strong>When to use it:</strong> When the aspect ratio change is minor (e.g., Landscape 4:3 to Square 1:1).</li>
<li>
<strong>What it does:</strong> It zooms in slightly but keeps the most interesting part of the image (faces, products) centered using <code>gravity=&quot;auto&quot;</code>.</li>
<li>
<strong>Result:</strong> A tight, focused composition.</li>
</ul>
<ol start="2">
<li>Strategy B: The Generative Pad (<code>gen_fill</code>)</li>
</ol>
<ul>
<li>
<strong>When to use it:</strong> When the change is extreme (e.g., Panoramic 16:9 to Vertical Story 9:16).</li>
<li>
<strong>The problem:</strong> Cropping here would destroy the image. You’d lose the scenery or the subject’s context.</li>
<li>
<strong>The solution:</strong> Don’t crop. Scale the image to fit <strong>inside</strong> the container and ask the AI to “paint” the missing pixels (sky, floor, walls) to fill the empty space.</li>
<li>
<strong>The result:</strong> The original subject is preserved 100%, and the layout is filled seamlessly.</li>
</ul>
<p>Instead of relying on heavy client-side effects, you’ll calculate the aspect ratio delta directly in the render pass.</p>
<p>If the target is significantly taller than it is wide (like a mobile story), you’ll trigger <strong>Strategy B</strong>. For everything else, <strong>Strategy A</strong> usually suffices.</p>
<h2>The Tech Stack (Next.js 16 + Cloudinary)</h2>
<p>To build a “zero-cut” engine that’s fast, type-safe, and scalable, let’s go with a stack that enforces modern industry best practices.</p>
<p>The core components are:</p>
<ol>
<li>
<strong>Next.js 16 (App Router) as the backbone.</strong> You’ll use server components for initial data fetching and client components only where interaction (like this responsive grid) is required.</li>
<li>
<strong>Cloudinary AI as the brain.</strong> Instead of resizing images in CSS, Cloudinary will generate new pixels on the fly.</li>
<li>
<strong><code>@cloudinary/url-gen</code> as the builder.</strong> Instead of prone-to-error string concatenation (e.g., <code>image/upload/w_500...</code>), you’ll use a type-safe SDK to construct transformations.</li>
</ol>
<h2>Building the Core: The <code>SmartImage</code> Component</h2>
<p>At the heart of this solution is <a href="https://github.com/musebe/subject-aware-layouts/blob/main/src/components/features/media/SmartImage.tsx"><code>SmartImage.tsx</code></a>. This component wraps the Cloudinary image component (<code>CldImage</code>) and injects our layout logic.</p>
<h3>The ‘No-Effect’ Paradigm</h3>
<p>A common mistake in React is to use <code>useEffect</code> to calculate derived state, like deciding whether to crop or pad based on props. This causes a “flash of wrong content” and unnecessary re-renders.</p>
<p>Instead, calculate your strategy directly in the render pass. This is faster, cleaner, and ensures the correct layout is sent to the server (or hydration) immediately.</p>
<h3>The Logic: Aspect Ratio Delta</h3>
<p>Let’s determine strategy by comparing the requested <code>width</code> and <code>height</code>. If the image is requested in an “extreme” vertical format (like a Story), it’s time to switch strategies.</p>
<p>Here’s the logic inside your component:</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-php shcb-wrap-lines"><span class="hljs-comment">// src/components/features/media/SmartImage.tsx</span>

export <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SmartImage</span><span class="hljs-params">({ width, height, ...props })</span> </span>{
  <span class="hljs-comment">// 1. Calculate the Aspect Ratio</span>
  <span class="hljs-keyword">const</span> aspectRatio = width / height;

  <span class="hljs-comment">// 2. Define "Extreme" (e.g., Stories are usually 0.56)</span>
  <span class="hljs-keyword">const</span> isExtremeVertical = aspectRatio &lt; <span class="hljs-number">0.6</span>;

  <span class="hljs-keyword">return</span> (
    &lt;CldImage
      {...props}
      width={width}
      height={height}
      <span class="hljs-comment">// 3. Conditional Strategy Switch</span>
      {...(isExtremeVertical
        ? {
            <span class="hljs-comment">// Strategy: Generative Fill (Zero-Cut)</span>
            crop: <span class="hljs-string">"pad"</span>,
            fillBackground: <span class="hljs-keyword">true</span>,
          }
        : {
            <span class="hljs-comment">// Strategy: Smart Crop (Focus)</span>
            crop: <span class="hljs-string">"fill"</span>,
            gravity: <span class="hljs-string">"auto"</span>,
          })}
    /&gt;
  );
}
</code></span></pre>
<p>By simply passing <code>fillBackground: true</code> when <code>crop=&quot;pad&quot;</code> is active, you’ll trigger Cloudinary’s generative AI, which will analyze the image content and extend it naturally to fill the 9:16 container.</p>
<h2>Generative Fill in Action</h2>
<p>Most developers rely on <code>crop=&quot;fill&quot;</code> (CSS <code>cover</code>), which zooms in until the container is full. This is dangerous because it deletes pixels.</p>
<p>With a “zero-cut” strategy, instead of zooming <em>in</em>, you’ll effectively zoom <em>out</em> until the entire image fits inside the container. Then, you’ll ask Cloudinary’s generative AI to paint the empty space.</p>
<p>Here’s how this single strategy solves the three hardest layout challenges:</p>
<h3>Case Study 1: The Vertical Story (9:16)</h3>
<ul>
<li>
<strong>The problem:</strong> You have a landscape photo of a sneaker on a table. If you crop it to 9:16, you lose the toes and the heel. You only see the laces.</li>
<li>
<strong>The solution:</strong> The image is scaled down to fit the width. The AI analyzes the texture of the table and the lighting of the room, then generates more “table” at the bottom and more “room” at the top.</li>
<li>
<strong>The result:</strong> A perfect mobile-first asset where the product is fully visible, surrounded by context that didn’t exist in the original photo.</li>
</ul>
<h3>Case Study 2: The Square Post (1:1)</h3>
<ul>
<li>
<strong>The problem:</strong> A wide group shot of five people. A center crop cuts off the two people on the edges.</li>
<li>
<strong>The solution:</strong> The system fits the width of the group into the square. This leaves empty space above and below (letterboxing).</li>
<li>
<strong>The result:</strong> The AI fills that “letterbox” space with convincing background extensions. You keep all five people in the frame, and the image looks like it was shot natively for Instagram.</li>
</ul>
<h3>Case Study 3: The Cinematic Banner (16:9)</h3>
<ul>
<li>
<strong>The problem:</strong> A tall portrait of a model. Cropping to 16:9 zooms in on just the eyes, losing the outfit.</li>
<li>
<strong>The solution:</strong> The model is centered. The AI extends the background horizontally, turning a portrait studio shot into a cinematic wide shot.</li>
</ul>
<p>By flipping the logic from “Remove Pixels” to “Generate Pixels,” we guarantee that the subject is never lost.</p>
<h2>Live Demo and Source Code</h2>
<p>You can see the “zero-cut” engine in action and explore the implementation details through the links below.</p>
<ul>
<li>
<p><strong>Live demo:</strong> Witness how a single source asset adapts to Story, Post, and Banner formats in real time. <a href="https://subject-aware-layouts.vercel.app/">subject-aware-layouts.vercel.app</a></p>
</li>
<li>
<p><strong>Explore the codebase:</strong> The project is built with a focus on high performance and long-term maintainability, utilizing several modern architectural patterns.</p>
</li>
<li>
<p><strong>GitHub source code:</strong> <a href="https://github.com/musebe/subject-aware-layouts">github.com/musebe/subject-aware-layouts</a></p>
</li>
</ul>
<p>This concludes our guide on scaling subject-aware layouts. By moving away from aggressive cropping and toward generative AI strategies, you can ensure your UI remains professional and your subjects remain the star of the show, regardless of the device.</p>
<p><a href="https://cloudinary.com/users/register_free">Sign up for a free Cloudinary account</a> today and start building your next project.</p>
</div><p>The post <a href="https://cloudinary.com/blog/scaling-subject-aware-layouts-react-ai">Scaling Subject-Aware Layouts: Solving Multi-Device Quality With React and AI</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39839</post-id>	</item>
		<item>
		<title>Crack the Mystery. Master Cloudinary.</title>
		<link>https://cloudinary.com/blog/crack-the-mystery-master-cloudinary</link>
		
		<dc:creator><![CDATA[sharonyelenik]]></dc:creator>
		<pubDate>Tue, 03 Mar 2026 15:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Dev Rel]]></category>
		<category><![CDATA[Image Transformation]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39788</guid>

					<description><![CDATA[<p>Today, writing documentation means writing for AI.  Your IDE, your LLMs, your internal tools… They all read our docs. So yes, we make sure our content is structured, accurate, and optimized for machine consumption. But at Cloudinary, we haven’t forgotten our main reader: you. You still come to our docs to learn, experiment, build, and [&#8230;]</p>
<p>The post <a href="https://cloudinary.com/blog/crack-the-mystery-master-cloudinary">Crack the Mystery. Master Cloudinary.</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Today, writing documentation means writing for AI.  Your IDE, your LLMs, your internal tools… They all read our docs. So yes, we make sure our content is structured, accurate, and optimized for machine consumption.</p>



<p>But at Cloudinary, we haven’t forgotten our main reader: <strong>you</strong>.</p>



<p>You still come to our docs to learn, experiment, build, and engage…and ideally enjoy the process.</p>



<p>That’s why we’ve added something new to the Cloudinary docs: an interactive mystery game that teaches you how things work while you play.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/cloudinary/image/upload/f_auto,q_auto/v1765662594/clara-denari-avatar.png" alt="" style="width:150px;height:auto"/></figure></div>


<h2 class="wp-block-heading">Clara Denari Does It Again</h2>



<p>If you enjoyed learning about Cloudinary through <a href="https://claradenari.com/">Clara Denari and the Mysterious Transformations</a>, you’ll love what comes next.</p>



<p>This time, the mystery isn’t on a standalone site. It’s woven directly into the docs.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/v1763669429/clara-denari.jpg" alt="" style="width:229px;height:auto"/></figure></div>


<h2 class="wp-block-heading">A Case That Starts With a Blurry Photo</h2>



<p>The new mystery begins with a small neighborhood dispute. Clara discovers a pair of sapphire earrings in her backyard. Her neighbor insists they’re a treasured family heirloom and produces an old photograph as proof.</p>



<p>There’s just one issue: <strong>the image is too blurry to confirm anything</strong>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img width="1024" height="1024" fetchpriority="high" decoding="async" src="https://res.cloudinary.com/cloudinary-marketing/images/w_1024,h_1024,c_fill,g_auto/f_auto,q_auto/v1771176711/Web_Assets/blog/very_blurry/very_blurry.jpg?_i=AA" alt="" class="wp-post-39788 wp-image-39794" style="width:650px;height:auto" data-public-id="Web_Assets/blog/very_blurry/very_blurry.jpg" data-format="jpg" data-transformations="f_auto,q_auto" data-version="1771176711" data-seo="1" srcset="https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176711/Web_Assets/blog/very_blurry/very_blurry.jpg?_i=AA 1500w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176711/Web_Assets/blog/very_blurry/very_blurry.jpg?_i=AA 150w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176711/Web_Assets/blog/very_blurry/very_blurry.jpg?_i=AA 300w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176711/Web_Assets/blog/very_blurry/very_blurry.jpg?_i=AA 768w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176711/Web_Assets/blog/very_blurry/very_blurry.jpg?_i=AA 1024w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure></div>


<p>And that’s where Cloudinary comes in.</p>



<p>To advance the story, you don’t just read about uploads. You actually execute one. A multi-parameter upload that applies AI-powered restoration to recover details from the faded photograph. You inspect the response. You check the result. You decide whether the evidence holds up.</p>



<p>The narrative moves forward only when you do.</p>



<h2 class="wp-block-heading">Following the Evidence</h2>



<p>But restoring the photo isn’t enough.</p>



<p>To determine whether the earrings truly match, you’ll need to dig deeper, isolating the relevant details, transforming the image to focus on what matters, and applying analysis to extract meaningful visual information.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/v1763567459/sapphire_earrings.png" alt="" style="width:258px;height:auto"/></figure></div>


<p>Cropping becomes part of the investigation. Color analysis becomes part of the proof. Transformations become your forensic tools.</p>



<p>By the time you reach the end of the case, you’ve worked through a realistic, multi-step media workflow — complex uploads, restoration, targeted transformations, and image analysis — diving deeper into features as the mystery leads you across the docs.</p>



<p>And ideally, without even realizing how much you’ve learned along the way.</p>



<h2 class="wp-block-heading">What You’ll Walk Away With</h2>



<p>By the time you solve Clara Denari’s latest case, you won’t just know whether the earrings match.</p>



<p>You’ll know how to:</p>



<ul class="wp-block-list">
<li><strong>Apply AI-powered restoration during upload</strong> and understand how uploads can be customized by combining multiple parameters in a single request.</li>



<li><strong>Use AI object detection (<code>e_extract:prompt_object</code>) to isolate specific elements</strong> and see how advanced transformations can refine and focus your assets with precision.</li>



<li><strong>Extract dominant colors (<code>colors: true</code>) for analysis</strong> and recognize how upload-time analysis can be layered into more complex media workflows.</li>
</ul>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img width="1024" height="1024" decoding="async" src="https://res.cloudinary.com/cloudinary-marketing/images/w_1024,h_1024,c_fill,g_auto/f_auto,q_auto/v1771176742/Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg?_i=AA" alt="" class="wp-post-39788 wp-image-39795" style="width:660px;height:auto" data-public-id="Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg" data-format="jpg" data-transformations="f_auto,q_auto" data-version="1771176742" data-seo="1" srcset="https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176742/Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg?_i=AA 1500w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176742/Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg?_i=AA 150w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176742/Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg?_i=AA 300w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176742/Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg?_i=AA 768w, https://res.cloudinary.com/cloudinary-marketing/images/f_auto,q_auto/v1771176742/Web_Assets/blog/restored_397958f2b4/restored_397958f2b4.jpg?_i=AA 1024w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure></div>


<p>In other words, you won’t just read about powerful features… You’ll use them.</p>



<p>And because the mystery spans multiple sections of the docs, you’ll naturally move from upload customization to deeper transformation and analysis capabilities, seeing how they connect in a real workflow.</p>



<h2 class="wp-block-heading">Ready to Investigate?</h2>



<p>If you’ve ever skimmed documentation and thought, “I’ll try that later,” this is your chance to try it now.</p>



<p>Start in the Upload guide.</p>



<p>Follow the clues.</p>



<p>See where they lead.</p>



<p>Crack the mystery. Master Cloudinary.</p>
<p>The post <a href="https://cloudinary.com/blog/crack-the-mystery-master-cloudinary">Crack the Mystery. Master Cloudinary.</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39788</post-id>	</item>
		<item>
		<title>From Cloud to Crowd: Introducing the Cloudinary Creators Community</title>
		<link>https://cloudinary.com/blog/cloudinary-creators-community</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Mon, 23 Feb 2026 16:30:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Cloudinary]]></category>
		<category><![CDATA[Dev Rel]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39805</guid>

					<description><![CDATA[<p>The wait is finally over! We’re honored and excited to pull back the curtain on a project that has been fueling our passion for months: the Cloudinary Creators Community (CCC).&#160; At Cloudinary, we know that the leap from finishing a coding tutorial to landing your dream role can feel like a massive chasm. That’s why [&#8230;]</p>
<p>The post <a href="https://cloudinary.com/blog/cloudinary-creators-community">From Cloud to Crowd: Introducing the Cloudinary Creators Community</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The wait is finally over! We’re honored and excited to pull back the curtain on a project that has been fueling our passion for months: the <a href="https://cloudinary.com/developers/community"><strong>Cloudinary Creators Community</strong></a><strong> (CCC)</strong>.&nbsp;</p>



<p>At Cloudinary, we know that the leap from finishing a coding tutorial to landing your dream role can feel like a massive chasm. That’s why we created the CCC. We aren&#8217;t just building a community; we’re building a bridge to help you cross that gap and step into your future as a leader in the tech industry.</p>



<h2 class="wp-block-heading">The Vision: ‘From Cloud to Crowd’</h2>



<p>Our mission is captured in four simple words: <strong>from Cloud to Crowd</strong>,<strong> </strong>a phrase coined by one of our community members, Jerome Hardaway.</p>



<p>This program is specifically designed for the dreamers and the doers, the early-career developers, the brave career-changers, and the media-savvy creators who are ready to shape the future of the visual web. Whether you&#8217;re just starting to understand how images and videos power the internet or you&#8217;re already building complex apps, we’re here to empower you with the tools, mentorship, and global network you need to grow from a learner into a <strong>recognized Cloudinary Creator.</strong></p>



<p>At the heart of everything we do are our three core values:</p>



<ul class="wp-block-list">
<li><strong>Learn.</strong> Master the fundamentals of modern media management and high-performance web development through structured, accessible education.</li>



<li><strong>Build.</strong> Turn that knowledge into action! We provide the platform for you to ship real-world projects that make your portfolio shine.</li>



<li><strong>Connect.</strong> Join a vibrant, global ecosystem of peers and industry experts who are all rooting for your success.</li>
</ul>



<p>We believe that when you combine the right tools with a supportive community, there’s no limit to what you can create. Ready to see how we’re making it happen?</p>



<h2 class="wp-block-heading">Phase 1: Education</h2>



<h3 class="wp-block-heading">Joining the Community: Two Pathways</h3>



<p>We want to make sure the CCC is accessible while maintaining the high-quality mentorship that makes this program special. There are two exciting ways to join us:</p>



<h4 class="wp-block-heading">The Partner Pathway</h4>



<p>We’re incredibly proud to launch in collaboration with global nonprofits that are changing the face of tech. If you’re a member of <a href="https://gssoc.girlscript.org"><strong>GirlScript</strong></a>,<strong> </strong><a href="https://developersinvogue.org"><strong>Developers in Vogue</strong></a>,<strong> </strong><a href="https://vetswhocode.io/"><strong>Vets Who Code</strong></a>,<strong> </strong><a href="https://www.hackyourfuture.dk/"><strong>Hack Your Future</strong></a>, or<a href="https://www.tampadevs.com/"><strong> </strong><strong>Tampa Devs</strong></a>, you have a direct seat at the table! We work closely with these organizations to provide dedicated spots for their communities.</p>



<h4 class="wp-block-heading">The Individual Pathway</h4>



<p>Are you an independent builder ready to level up? You can join, too! Simply dive into our &#8220;<a href="https://training.cloudinary.com/courses/devrel-c2c-next"><strong>Cloud to Crowd</strong></a>&#8221; course at the Cloudinary Academy. Once you’ve mastered the material and passed the exam, you can submit an application to join our priority waitlist for the next available cohort.</p>



<p>Because we keep our cohorts selective to ensure everyone gets the support they need, these spots are highly coveted, so bring your A-game!</p>



<h2 class="wp-block-heading">Phase 2: Activation</h2>



<h3 class="wp-block-heading">Discord: Our Digital Headquarters</h3>



<p>Discord is where Phase 2 happens. Once inside, you get access to private channels, technical support, and our Developer Relations staff. To keep the community vibrant, we have a 30-day rule: <strong>New members must introduce themselves within their first month to keep their seat.</strong> You have 14 days to accept your invitation. We also monitor activity. After 60 days of silence, your status becomes inactive, and after 90 days, your spot is released to a new learner on the waitlist. This ensures our headquarters is full of creators ready to ship.</p>



<h2 class="wp-block-heading">Selectivity and Waitlist Management</h2>



<p>To guarantee quality mentorship, we limit active seats. This keeps our environment high-performing and focused. We operate on a bi-annual model with two intake cycles. If seats are full, you’ll join our priority waitlist. We monitor activity closely and invite waitlisted learners as spots open, ensuring every creator gets the support they deserve. Even if you’re not yet onboarded as an official Cloudinary Creator, however, please join us on Discord to engage with the community and participate in our fun activities such as hackathons and challenges.</p>



<h2 class="wp-block-heading">Membership Benefits and Rewards</h2>



<p>Your growth is rewarded at every stage. As a recognized creator, you gain access to a toolkit designed to power your professional portfolio.</p>



<p>The journey includes a one-year renewable<strong> </strong><strong>Ambassador Plan </strong>to ensure your personal projects perform at their best. We also celebrate your milestones with exclusive <strong>digital badges </strong>via Holopin, giving you a way to showcase your education and project achievements to your network.</p>



<p>Beyond the tech, you’ll join an ecosystem built on mentorship. This includes dedicated office hours for technical Q&amp;A, tailored career advice, and networking opportunities within the broader Cloudinary partner network. And to welcome you properly, once you’ve made your first meaningful engagement on Discord, we’ll ship the official CCC <strong>Swag Pack</strong> directly to your door.</p>



<h2 class="wp-block-heading">The Engagement Path (12 Points/Year)</h2>



<p>We value builders over observers. To keep your creator status active, we use a simple point system with <strong>a goal of 12 points per year</strong>.</p>



<p>You earn <strong>5 points</strong> for shipping a project and <strong>3</strong> for joining a mini-community hackathon. Attending events earns you <strong>2 points</strong>, while smaller acts like helping a peer solve a bug or joining a weekly standup earn <strong>1 point</strong>. This path ensures our creators stay sharp, connected, and consistently moving forward.</p>



<h2 class="wp-block-heading">Community Life and Activities</h2>



<p>The activation phase is where the energy truly shifts. We keep the momentum high with hackathons and mini challenges designed to test your skills and reward your creativity with exciting prizes.</p>



<p>Our regular Demo Days give you a global stage to showcase your projects to the community and beyond. To track your progress, we use gamification through leaderboards and badges to celebrate our most active contributors. When you need help or career advice, our live office hours provide direct access to the experts who can help you solve technical challenges and plan your next big move.</p>



<h2 class="wp-block-heading">Professionalism and Governance</h2>



<p>A community this vibrant thrives on respect. Our Code of Conduct ensures that every member feels included and supported as they grow. We prioritize kindness and helpfulness, creating a professional network where everyone can advance their career safely.</p>



<p>This is a long-term partnership. To renew your spot each year, we look for continued growth through new project submissions and engagement with your peers. Staying connected means dropping into Discord at least once every 60 days to share your progress.</p>



<p>We’re ready to build the future of the visual web with you. Visit our <a href="https://cloudinary.com/developers/community">Community page</a> to find your path and start your journey from learner to creator today. Let&#8217;s go!</p>
<p>The post <a href="https://cloudinary.com/blog/cloudinary-creators-community">From Cloud to Crowd: Introducing the Cloudinary Creators Community</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39805</post-id>	</item>
		<item>
		<title>How to Create a Digital Portfolio That Visually Pops</title>
		<link>https://cloudinary.com/blog/digital-portfolio-visually-pops</link>
		
		<dc:creator><![CDATA[sharonyelenik]]></dc:creator>
		<pubDate>Mon, 23 Feb 2026 13:30:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Generative AI]]></category>
		<category><![CDATA[Image Transformation]]></category>
		<category><![CDATA[Performance Optimization]]></category>
		<category><![CDATA[Video Transformation]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39797</guid>

					<description><![CDATA[<p>Job searching can be tough, and so is standing out among the competition. When putting together job applications, there’s always that question: How should I describe myself? Will potential employers care more about past experience or a list of skills? A digital portfolio answers that question in a way a résumé alone can’t. It shows [&#8230;]</p>
<p>The post <a href="https://cloudinary.com/blog/digital-portfolio-visually-pops">How to Create a Digital Portfolio That Visually Pops</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Job searching can be tough, and so is standing out among the competition.</p>



<p>When putting together job applications, there’s always that question: How should I describe myself? Will potential employers care more about past experience or a list of skills? A digital portfolio answers that question in a way a résumé alone can’t. It shows what you’re actually capable of.</p>



<p>That’s why I put together this digital portfolio demo project.</p>



<p>Instead of talking about performance, polish, and visual quality in theory, I wanted to demonstrate what that looks like in practice. This portfolio is built the way I’d recommend anyone build one today: fast, visually sharp, and optimized from the start.</p>



<p>In this guide, I’ll walk you through how I build a frontend portfolio project using Cloudinary to handle all the image and video magic. No endless hours in Photoshop. No massive file sizes. And, no manual resizing for every device.</p>



<h2 class="wp-block-heading">Live Demo (Optional, but Highly Recommended)</h2>



<p>Before diving into the code, you can check out the live portfolio demo here:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><a href="https://stackblitz.com/github/cloudinary-devs/digital_portfolio?file=README.md"><strong>View the live demo on StackBlitz</strong></a>.</p>
</blockquote>



<p>Feel free to explore it, then come back and see how everything works under the hood.</p>


<cld-video-player
      cloud-name='cloudinary'
      public-id='training/digital_portfolio'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      poster-timestamp='3'
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c4033420d'
        data-cld-big-play-button='init'
        data-cld-source-types='["webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>


<h2 class="wp-block-heading">Keys to Making Your Digital Portfolio Stand Out</h2>



<p>Building a great-looking digital portfolio is a no-brainer. However, the real question is: How do you make yours stand out? One of the biggest differentiators is focusing on performance and visual polish. When your portfolio feels fast, smooth, and thoughtfully built, it immediately comes across as more professional.&nbsp;</p>



<p>And when you build it efficiently, you’re also signaling to future employers that you know how to work efficiently:</p>



<ul class="wp-block-list">
<li>Deliver images that load fast but look crisp.</li>



<li>Make videos that play smoothly without eating bandwidth.</li>



<li>Create responsive layouts that look perfect on every device.</li>



<li>Apply visual effects that make content pop.</li>



<li>Optimize everything without sacrificing quality.</li>
</ul>



<p>So really, building a great portfolio is just practice for the real thing. And that&#8217;s pretty cool.</p>



<h2 class="wp-block-heading">The Tech Stack I Chose</h2>



<p>For my portfolio, I went with tools that are popular in the industry and honestly just fun to work with:</p>



<ul class="wp-block-list">
<li>React 19 with TypeScript for type safety and component architecture.</li>



<li>Vite for lightning-fast development and optimized builds.</li>



<li>CSS for beautiful, responsive styling.</li>



<li>Cloudinary for all image and video transformations.</li>
</ul>



<p>Feel free to clone my code and adapt it to whatever you’re used to working with.</p>



<h2 class="wp-block-heading">Getting Started</h2>



<p>I’m sharing my portfolio with you as a starting point. Once you get a feel for how it works, you can customize the design to match your style and add sections that show off what matters to you.</p>


<pre class="wp-block-code"><span><code class="hljs language-php shcb-wrap-lines"><span class="hljs-comment"># Clone the starter portfolio repo</span>
git <span class="hljs-keyword">clone</span> https:<span class="hljs-comment">//github.com/your-username/digital-portfolio.git</span>

<span class="hljs-comment"># Install dependencies</span>
cd digital-portfolio
npm install

<span class="hljs-comment"># Start the development server</span>
npm run dev</code></span></pre>


<p>I’m excited to see how you make it your own.</p>



<h2 class="wp-block-heading">How Cloudinary Helps Meet Stand-Out Goals</h2>



<p>A portfolio that stands out needs to be fast, visually sharp, and responsive across devices.</p>



<p>Without automation, that usually means resizing images manually, generating multiple breakpoints, compressing files carefully, and managing large video assets.</p>



<p>Cloudinary handles image and video delivery, optimization, and transformations through simple URL parameters. In this project, cropping, resizing, blur effects, format conversion, and quality optimization are all applied directly in the media URLs.</p>



<p>Transformations run on the fly, and the right size and format are delivered automatically for each device and browser.</p>



<p>Instead of maintaining multiple asset versions or editing files manually, I define the transformation once and move on, without sacrificing quality or performance.</p>



<h2 class="wp-block-heading">Swap the Demo Media for Your Own</h2>



<p>This project uses Cloudinary’s demo account (res.cloudinary.com/demo) with sample images and videos, so it works out of the box. When you&#8217;re ready, switch to your own Cloudinary account to display your own images and videos.</p>



<h3 class="wp-block-heading">Step 1: Create a Cloudinary Account</h3>



<p>Sign up for a <a href="https://cloudinary.com/users/register_free">free Cloudinary account</a> (the free tier is more than enough for a portfolio).</p>



<h3 class="wp-block-heading">Step 2: Find Your Cloud Name</h3>



<p>After logging in, copy your <strong>cloud name</strong> from the dashboard. You’ll use it in URLs like this:</p>


<pre class="wp-block-code"><span><code class="hljs language-xml shcb-wrap-lines">https://res.cloudinary.com/<span class="hljs-tag">&lt;<span class="hljs-name">your_cloud_name</span>&gt;</span>/image/upload/<span class="hljs-tag">&lt;<span class="hljs-name">public_id</span>&gt;</span></code></span></pre>


<h3 class="wp-block-heading">Step 3: Update One Line in the Code</h3>



<p>In your project, change this:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-comment">// Set this to your cloud name when you're ready to use your own media</span>
<span class="hljs-keyword">const</span> CLOUDINARY_CLOUD_NAME = <span class="hljs-string">'demo'</span></code></span></pre>


<p>…to your cloud name:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-keyword">const</span> CLOUDINARY_CLOUD_NAME = <span class="hljs-string">'&lt;your_cloud_name&gt;'</span></code></span></pre>


<p>That’s it. Since <code>CLOUDINARY_BASE</code> is built from <code>CLOUDINARY_CLOUD_NAME</code>, all image/video URLs that use <code>CLOUDINARY_BASE</code> will automatically point to your account.</p>



<h3 class="wp-block-heading">Step 4: Upload Your Own Media and Swap the Public IDs</h3>



<p>In your code, you reference assets using public IDs — for example:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-wrap-lines">image: <span class="hljs-string">'docs/profile-pic'</span></code></span></pre>


<p>That means Cloudinary is looking for an asset with the public ID docs/catwalk in your cloud.</p>



<p>After you upload your own images/videos to Cloudinary, replace those image values with your own public IDs, for example:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-wrap-lines">image: <span class="hljs-string">'portfolio/catwalk'</span></code></span></pre>

<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:</strong> <p>You don’t need to change the transformations.
Everything in the URL after <code>/upload/</code> (like <code>c_fill,g_auto,h_400,w_600/f_auto/q_auto</code>) can stay the same.</p>
</div>


<h2 class="wp-block-heading">The Cool Cloudinary Features I Used</h2>



<h3 class="wp-block-heading">1. Smart Cropping With Face Detection</h3>



<p>For the testimonials section, I needed consistent circular profile images that focused tightly on each person’s face.</p>



<p>Here’s the original image:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/docs/profile-pic.jpg" alt="" style="width:314px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>docs/profile-pic.jpg</figcaption></figure></div>


<p>And here’s the transformed version:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg" alt="" style="width:196px;height:auto"/><figcaption class="wp-element-caption"><a href="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg">https://res.cloudinary.com/</a><br><a href="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg">demo/image/upload/</a><br><a href="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg">c_thumb,g_face,h_300,w_300/</a><br><a href="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg">r_max/e_sharpen:80/</a><br><a href="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg">f_auto/q_auto</a><br><a href="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_300,w_300/r_max/e_sharpen:80/f_auto/q_auto/docs/profile-pic.jpg">/docs/profile-pic.jpg</a></figcaption></figure></div>


<h4 class="wp-block-heading"><strong>What the Transformation Does</strong></h4>



<ol class="wp-block-list">
<li><code>c_thumb,g_face</code> automatically detects the face and crops around it.</li>



<li><code>h_300,w_300</code> enforces a fixed square size.</li>



<li><code>r_max</code> makes the image circular.</li>



<li><code>e_sharpen:80</code> restores clarity after resizing.</li>



<li><code>f_auto,q_auto</code> handle format and compression.</li>
</ol>



<p>The result isn’t just a circle. It’s a consistent 300×300 headshot, centered correctly every time — regardless of how the original photo was framed.</p>



<p>That means no manual cropping, guessing focal points, or layout inconsistencies.</p>



<h3 class="wp-block-heading">2. Hero Background Blur</h3>



<p>For the hero section, I wanted a full-width background image that wouldn’t compete with the foreground content.</p>



<p>Original image:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/vieste_italy.jpg" alt="" style="width:408px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/vieste_italy.jpg</figcaption></figure></div>


<p>Transformed version:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/c_fill,g_auto,h_1080,w_1920/e_blur:800/f_auto/q_auto/vieste_italy.jpg" alt="" style="width:408px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>c_fill,g_auto,h_1080,w_1920/e_blur:800/f_auto/q_auto/<br>vieste_italy.jpg</figcaption></figure></div>


<h4 class="wp-block-heading"><strong>What the Transformation Does</strong></h4>



<ul class="wp-block-list">
<li><code>c_fill,g_auto</code> crops intelligently to 1920×1080.</li>



<li><code>e_blur:800</code> applies a strong blur effect.</li>



<li><code>f_auto,q_auto</code> optimize delivery.</li>
</ul>



<p>The original image is detailed and high contrast — great for photography, not ideal for text overlays.</p>



<p>By blurring it at delivery time, I keep the color and atmosphere while removing visual noise. The background supports the content instead of competing with it. No separate “blurred copy” of the file is needed.</p>



<h3 class="wp-block-heading">3. AI-Enhanced Portrait</h3>



<p>For the hero portrait, I wanted a clean, high-quality look — even if the source image wasn’t studio-perfect.</p>



<p>Original:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg" alt="" style="width:354px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>docs/profile-pic1.jpg</figcaption></figure></div>


<p>Transformed:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/c_fill,g_face,h_300,w_300/r_max/e_improve/bo_8px_solid_rgb:0f172a/f_auto/q_auto:best/docs/profile-pic1.jpg" alt="" style="width:250px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>c_fill,g_face,h_300,w_300/r_max/e_improve/<br>bo_8px_solid_rgb:0f172a/<br>f_auto/q_auto:best/docs/profile-pic1.jpg</figcaption></figure></div>


<h4 class="wp-block-heading"><strong>What the Transformation Does</strong></h4>



<ul class="wp-block-list">
<li><code>g_face</code> centers the subject.</li>



<li><code>r_max</code> applies a circular crop.</li>



<li><code>e_improve</code> enhances lighting and contrast using AI.</li>



<li><code>bo_8px_solid_rgb:0f172a</code> adds a clean border.</li>



<li><code>q_auto:best</code> balances compression with quality.</li>
</ul>



<p>The enhancement isn’t dramatic — it’s subtle. Skin tones are more balanced, contrast is cleaner, and the framing is consistent.</p>



<p>It looks like a designed component, not just an uploaded image.</p>



<h3 class="wp-block-heading">4. Responsive Project Images</h3>



<p>In my project grid, the source images came from different industries — fashion, e-commerce, outdoor photography — all with different aspect ratios.</p>



<p>Here’s one of the original images:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/woman_mountain_ledge.jpg" alt="" style="width:316px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>woman_mountain_ledge.jpg</figcaption></figure></div>


<p>And here’s the version used in the grid:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/c_fill,g_auto,h_400,w_600/r_20/e_saturation:20/f_auto/q_auto/woman_mountain_ledge.jpg" alt="" style="width:386px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>c_fill,g_auto,h_400,w_600/r_20/e_saturation:20/<br>f_auto/q_auto/<br>woman_mountain_ledge.jpg</figcaption></figure></div>


<h4 class="wp-block-heading"><strong>What the Transformation Does</strong></h4>



<ul class="wp-block-list">
<li><code>c_fill,h_400,w_600</code> forces a consistent 600×400 frame.</li>



<li><code>g_auto</code> intelligently selects the focal area.</li>



<li><code>r_20</code> adds rounded corners.</li>



<li><code>e_saturation:20</code> slightly boosts color.</li>



<li><code>f_auto,q_auto</code> optimize delivery.</li>
</ul>



<p>The original image has its own natural proportions.</p>



<p>The transformed version guarantees:</p>



<ul class="wp-block-list">
<li>Every card in the grid is exactly the same size.</li>



<li>No distortion.</li>



<li>No manual cropping.</li>



<li>No awkward whitespace.</li>
</ul>



<p>Even though the source images vary wildly, the layout stays predictable and clean. That’s what makes the grid feel cohesive.</p>



<h3 class="wp-block-heading">5. Video Transformations That Actually Work</h3>



<p>Video is usually where portfolios fall apart. Files are large, aspect ratios are inconsistent, and playback isn’t optimized.</p>



<p>Here’s the original full video:</p>


<cld-video-player
      cloud-name='demo'
      public-id='guy_woman_mobile'
      js-config='{"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c4033776c'
        data-cld-big-play-button='init'
        data-cld-source-types='["webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-wrap-lines">https:<span class="hljs-comment">//res.cloudinary.com/demo/video/upload/v1731855790/guy_woman_mobile.mp4</span></code></span></pre>


<p>And here’s the version used in the portfolio:</p>


<cld-video-player
      cloud-name='demo'
      public-id='guy_woman_mobile'
      js-config='{"transformation":{"width":600,"height":400,"startOffset":"133","endOffset":"147","crop":"pad","border":"8px_solid_rgb:d4a520"},"playbackRates":[0.5,1,1.5,2]}'
      style='max-width: ;'
      class='c-video-player'
      
      core-version='2.12.3'
      player-version='1.7.0'
      >
      <video
        id='_video-player69c7c40337c1c'
        data-cld-big-play-button='init'
        data-cld-source-types='["webm\/vp9","mp4\/h265","mp4"]'
        controls
        muted
        class='cld-video-player cld-fluid wp-block-cloudinary-video-player  cld-video-player-skin-dark'
      ></video>
    </cld-video-player>

<pre class="wp-block-code"><span><code class="hljs language-javascript shcb-wrap-lines">https:<span class="hljs-comment">//res.cloudinary.com/demo/video/upload/so_133,eo_147/c_pad,h_400,w_600/b_rgb:d4a520/f_auto/q_auto/v1731855790/guy_woman_mobile.mp4</span></code></span></pre>


<h4 class="wp-block-heading"><strong>What the Transformation Does</strong></h4>



<ul class="wp-block-list">
<li><code>so_133,eo_147</code> trims the clip to a specific 14-second segment.</li>



<li><code>c_pad,h_400,w_600</code> fits it into a 600×400 frame without cutting off content.</li>



<li><code>b_rgb:d4a520</code> fills extra space with a consistent background color.</li>



<li><code>f_auto,q_auto</code> optimize format and compression.</li>
</ul>



<p>Instead of uploading a separately edited clip, I trim and resize at delivery time.</p>



<p>That means:</p>



<ul class="wp-block-list">
<li>The video is shorter and lighter.</li>



<li>The layout dimensions are guaranteed.</li>



<li>There are no black bars.</li>



<li>The browser gets the best possible format automatically.</li>
</ul>



<p>It behaves like a designed component — not a raw media file dropped onto a page.</p>



<h3 class="wp-block-heading">6. Small Details That Make a Difference</h3>



<p>Once layout and performance were handled, I added subtle refinements.</p>



<p>Here’s the original image:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg" alt="" style="width:440px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg</figcaption></figure></div>


<p>And here’s the polished version:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://res.cloudinary.com/demo/image/upload/c_fill,g_auto,h_800,w_700/e_vignette:30/e_sharpen:100/r_20/bo_1px_solid_rgb:e0e0e0/f_auto/q_auto/docs/profile-pic1.jpg" alt="" style="width:392px;height:auto"/><figcaption class="wp-element-caption">https://res.cloudinary.com/demo/image/upload/<br>c_fill,g_auto,h_800,w_700/e_vignette:30/e_sharpen:100/<br>r_20/bo_1px_solid_rgb:e0e0e0/<br>f_auto/q_auto/docs/profile-pic1.jpg</figcaption></figure></div>


<h4 class="wp-block-heading"><strong>What the Transformation Does</strong></h4>



<ul class="wp-block-list">
<li><code>c_fill,g_auto</code> enforces consistent framing.</li>



<li><code>e_vignette:30</code> darkens the edges slightly.</li>



<li><code>e_sharpen:100</code> restores clarity.</li>



<li><code>r_20</code> rounds the corners.</li>



<li><code>bo_1px_solid_rgb:e0e0e0</code> adds a subtle border.</li>



<li><code>f_auto,q_auto</code> optimize delivery.</li>
</ul>



<p>None of these effects are dramatic, but together they:</p>



<ul class="wp-block-list">
<li>Improve edge definition.</li>



<li>Add separation from the background.</li>



<li>Standardize presentation across sections.</li>
</ul>



<p>These are small adjustments, but they’re the difference between “image placed on a page” and “designed component.”</p>



<h2 class="wp-block-heading">What This Means for Performance</h2>



<p>You might be thinking, &#8220;All these effects must slow things down, right?&#8221; Actually, the opposite!&nbsp;</p>



<p>With Cloudinary:</p>



<ul class="wp-block-list">
<li><strong>70-80% smaller file sizes</strong> compared to unoptimized images.</li>



<li><strong>3-5x faster loading</strong> thanks to automatic optimization.</li>



<li><strong>Zero manual editing time</strong>.</li>



<li><strong>Automatic device optimization</strong> — phone users get mobile-sized images, desktop users get high-res.</li>
</ul>



<p>When someone views your portfolio on their phone, they automatically get perfectly-sized images. On a 4K monitor, they get crisp, detailed versions. It just works.</p>



<p>Notice how much this image was optimized and what that means for your website stats and loading time! Reduced from a 21.30 MB JPG to a 18.26 KB AVIF.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" src="https://cloudinary-res.cloudinary.com/image/upload/v1771198908/blog/digital_portfolio_mi_img.png" alt="" style="width:470px;height:auto"/></figure></div>


<h2 class="wp-block-heading">Bringing It Full Circle</h2>



<p>Here&#8217;s what I love about this whole process: The skills you use to build an impressive portfolio are the same skills you&#8217;ll use every day in your job.</p>



<p>When you build this portfolio, you&#8217;re learning how to:</p>



<ul class="wp-block-list">
<li>Build modern React applications with TypeScript.</li>



<li>Create responsive, mobile-first designs.</li>



<li>Optimize images and videos for real-world performance.</li>



<li>Use cloud services to solve practical problems.</li>



<li>Write clean, maintainable code.</li>



<li>Think about user experience.</li>
</ul>



<p>Your portfolio becomes a preview of what you can do. So, you&#8217;ve shown you can build websites that look great, load fast, and feel professional. That&#8217;s exactly what teams are looking for.</p>



<h2 class="wp-block-heading">Wrapping Up</h2>



<p>Making a portfolio that stands out doesn’t have to be complicated or stressful. It’s really about:</p>



<ul class="wp-block-list">
<li><strong>Polish</strong> that shows you care about details.</li>



<li><strong>Performance</strong> that respects people’s time.</li>



<li><strong>Smart visual choices</strong> that guide the eye.</li>



<li><strong>Responsive design</strong> that works everywhere.</li>



<li><strong>Using the right tools</strong> (like Cloudinary) to make your life easier.</li>
</ul>



<p>If you’re job searching right now, I hope this helps. You’ve got this.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>Resources</strong>:</p>



<ul class="wp-block-list">
<li><strong>Live demo:</strong> <a href="https://stackblitz.com/github/cloudinary-devs/digital_portfolio?file=README.md">https://stackblitz.com/github/cloudinary-devs/digital_portfolio?file=README.md</a></li>



<li><strong>Source code:</strong> <a href="https://github.com/cloudinary-devs/digital_portfolio" type="link" id="https://github.com/cloudinary-devs/digital_portfolio">GitHub repository</a></li>



<li><strong>Cloudinary docs:</strong> <a href="https://cloudinary.com/documentation">https://cloudinary.com/documentation</a></li>
</ul>
<p>The post <a href="https://cloudinary.com/blog/digital-portfolio-visually-pops">How to Create a Digital Portfolio That Visually Pops</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39797</post-id>	</item>
		<item>
		<title>Automating Video Governance: AI Moderation and Tagging in Next.js</title>
		<link>https://cloudinary.com/blog/video-governance-ai-moderation-tagging-next-js</link>
		
		<dc:creator><![CDATA[melindapham]]></dc:creator>
		<pubDate>Thu, 19 Feb 2026 15:00:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Agentic]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Moderation]]></category>
		<category><![CDATA[Next.js]]></category>
		<category><![CDATA[Video]]></category>
		<guid isPermaLink="false">https://cloudinary.com/blog/?p=39822</guid>

					<description><![CDATA[<p>The post <a href="https://cloudinary.com/blog/video-governance-ai-moderation-tagging-next-js">Automating Video Governance: AI Moderation and Tagging in Next.js</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="wp-block-cloudinary-markdown "><p>Video moderation creates operational bottlenecks, especially as your platform scales. In this guide, you’ll build an <strong>agentic video governance pipeline</strong> using <strong>Next.js 16</strong> and <strong>Cloudinary</strong>. Instead of manually reviewing every upload, you’ll delegate moderation to AI agents that automatically:</p>
<ol>
<li>Watch every video upon upload.</li>
<li>Flag and block inappropriate content.</li>
<li>Tag content with descriptive metadata for searchability.</li>
</ol>
<h2>Architecture Overview</h2>
<p>You’ll leverage the <strong>Next.js App Router</strong> for the frontend and <strong>Cloudinary’s Admin API</strong> as the backend intelligence. The system uses an architecture where the UI optimistically uploads content but pessimistically hides it until AI verification passes.</p>
<h2>The Agentic Workflow</h2>
<p>The pipeline operates autonomously:</p>
<ul>
<li>
<p><strong>Trigger</strong>: User uploads a video via the client component.</p>
</li>
<li>
<p><strong>Agent 1 (<a href="https://cloudinary.com/documentation/aws_rekognition_video_moderation_addon">Rekognition</a>)</strong>: Scans for moderation labels (safe vs. unsafe).</p>
</li>
<li>
<p><strong>Agent 2 (<a href="https://cloudinary.com/documentation/microsoft_azure_video_indexer_addon">Azure Video Indexer</a>)</strong>: Generates taxonomy and content tags.</p>
</li>
<li>
<p><strong>Decision Engine</strong>: The UI re-fetches the status and decides whether to render the player (approved) or a warning shield (rejected).</p>
</li>
<li>
<p><strong>Live Demo</strong>: <a href="https://video-governance-guide.vercel.app/">video-governance-guide.vercel.app</a></p>
</li>
<li>
<p><strong>Source Code</strong>: <a href="https://github.com/musebe/video-governance-guide">github.com/musebe/video-governance-guide</a></p>
</li>
</ul>
<h2>Project Configuration</h2>
<h3>Spinning Up and Installing Dependencies</h3>
<p>Start by initializing a <strong>Next.js 16</strong> app with <strong>TypeScript</strong> and <strong>Tailwind CSS</strong>. Then, install the <strong>Cloudinary SDK</strong> and the <strong>Shadcn UI</strong> engine, which includes <code>lucide-react</code> for your icons.</p>
<p>Run these commands in your terminal:</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-css shcb-wrap-lines"># 1. <span class="hljs-selector-tag">Create</span> <span class="hljs-selector-tag">the</span> <span class="hljs-selector-tag">Next</span><span class="hljs-selector-class">.js</span> 16 <span class="hljs-selector-tag">App</span>
<span class="hljs-selector-tag">npx</span> <span class="hljs-selector-tag">create-next-app</span><span class="hljs-keyword">@latest</span> video-governance --typescript --tailwind --eslint

# <span class="hljs-number">2</span>. Initialize Shadcn UI (Select <span class="hljs-string">'New York'</span>, <span class="hljs-string">'Slate'</span>, <span class="hljs-string">'Yes'</span> to CSS variables)
npx shadcn@latest init

# <span class="hljs-number">3</span>. Install Core Engines
npm install next-cloudinary lucide-react
</code></span></pre>
<h3>Environment Variables (<code>.env.local</code>)</h3>
<p>Create a <code>.env.local</code> file in your root. This is the <strong>security bridge</strong> between your app and Cloudinary’s AI.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-php shcb-wrap-lines"><span class="hljs-comment"># Public: For the Client-Side Upload Widget</span>
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=<span class="hljs-string">"&lt;YOUR_CLOUD_NAME&gt;"</span>
NEXT_PUBLIC_CLOUDINARY_API_KEY=<span class="hljs-string">"&lt;YOUR_API_KEY&gt;"</span>

<span class="hljs-comment"># Private: For Server Actions &amp; Admin API (NEVER expose this)</span>
CLOUDINARY_API_SECRET=<span class="hljs-string">"&lt;YOUR_API_SECRET&gt;"</span>

<span class="hljs-comment"># Config: The Upload Preset we created</span>
NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=<span class="hljs-string">"video-governance-preset"</span>
</code></span></pre>
<h3>The Singleton Engine (<code>lib/cloudinary.ts</code>)</h3>
<p>This is the <strong>Core Engine #1</strong>. Instead of importing <code>v2</code> everywhere, you’ll configure it <strong>once</strong> here. This prevents the common <em>“Must supply api_key”</em> error by ensuring the SDK is always initialized with your environment variables before any API call is made.</p>
<blockquote>
<p>View the file on <a href="https://github.com/musebe/video-governance-guide/blob/main/lib/cloudinary.ts">GitHub</a>.</p>
</blockquote>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-keyword">import</span> { v2 <span class="hljs-keyword">as</span> cloudinary } <span class="hljs-keyword">from</span> <span class="hljs-string">"cloudinary"</span>;

<span class="hljs-comment">// Global Configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.CLOUDINARY_API_SECRET,
  <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Force HTTPS</span>
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> cloudinary;
</code></span></pre>
<h3>Cloudinary Dashboard Setup</h3>
<p>For the “Agentic” pipeline to work, you must enable two <strong>Add-ons</strong> in your Cloudinary Dashboard:</p>
<ol>
<li>
<strong>AWS Rekognition Video Moderation</strong> for safety checks.</li>
<li>
<strong>Microsoft Azure Video Indexer</strong> for auto-tagging.</li>
</ol>
<h2>Building the Secure Upload Pipeline</h2>
<h3>The Secure Signature Flow</h3>
<p>Security is paramount. You don’t expose your <code>CLOUDINARY_API_SECRET</code> to the client. Instead, you’ll use a <strong>Signed Upload</strong> pattern:</p>
<ol>
<li>
<strong>Client</strong> requests a signature.</li>
<li>
<strong>Server</strong> validates and signs the request using the secret.</li>
<li>
<strong>Client</strong> uploads the video to Cloudinary with that signature.</li>
</ol>
<h3>The Signature Engine (<code>api/sign-cloudinary/route.ts</code>)</h3>
<p>This API route acts as the gatekeeper. It receives parameters from the client, signs them using the SDK’s utility function, and returns the signature.</p>
<blockquote>
<p>View the file on <a href="https://github.com/musebe/video-governance-guide/blob/main/app/api/sign-cloudinary/route.ts">GitHub</a>.</p>
</blockquote>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-keyword">import</span> { v2 <span class="hljs-keyword">as</span> cloudinary } <span class="hljs-keyword">from</span> <span class="hljs-string">"cloudinary"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">request: Request</span>) </span>{
  <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">await</span> request.json();
  <span class="hljs-keyword">const</span> { paramsToSign } = body;

  <span class="hljs-comment">// CORE ENGINE: Generate signature using the Secret</span>
  <span class="hljs-keyword">const</span> signature = cloudinary.utils.api_sign_request(
    paramsToSign,
    process.env.CLOUDINARY_API_SECRET <span class="hljs-keyword">as</span> string
  );

  <span class="hljs-keyword">return</span> Response.json({ signature });
}
</code></span></pre>
<h3>The Client Uploader (<code>InteractivePipeline.tsx</code>)</h3>
<p>You’ll use the <code>CldUploadWidget</code> to handle chunking and retries. The key configuration is pointing <code>signatureEndpoint</code> to our route above.</p>
<blockquote>
<p>View the file on <a href="https://github.com/musebe/video-governance-guide/blob/main/app/_components/InteractivePipeline.tsx">GitHub</a>.</p>
</blockquote>
<pre class="js-syntax-highlighted"><span><code class="hljs language-xml shcb-wrap-lines"><span class="hljs-tag">&lt;<span class="hljs-name">CldUploadWidget</span>
  <span class="hljs-attr">signatureEndpoint</span>=<span class="hljs-string">"/api/sign-cloudinary"</span> // <span class="hljs-attr">Points</span> <span class="hljs-attr">to</span> <span class="hljs-attr">our</span> <span class="hljs-attr">secure</span> <span class="hljs-attr">route</span>
  <span class="hljs-attr">uploadPreset</span>=<span class="hljs-string">{process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET}</span>
  <span class="hljs-attr">onSuccess</span>=<span class="hljs-string">{(result)</span> =&gt;</span> {
    // Trigger Server Action to refresh the gallery immediately
    startTransition(() =&gt; refreshGallery());
  }}
&gt;
  {({ open }) =&gt; (
    <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> open()}&gt;
       Upload Video
    <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
  )}
<span class="hljs-tag">&lt;/<span class="hljs-name">CldUploadWidget</span>&gt;</span>
</code></span></pre>
<h3>The Revalidation Action (<code>actions.ts</code>)</h3>
<p>Using <strong>Next.js 16 Server Actions</strong>, you’ll instantly refresh the UI after an upload without a full page reload.</p>
<blockquote>
<p>View the file on GitHub](<a href="https://github.com/musebe/video-governance-guide/blob/main/app/video-pipeline/actions.ts">https://github.com/musebe/video-governance-guide/blob/main/app/video-pipeline/actions.ts</a>).</p>
</blockquote>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-string">"use server"</span>;
<span class="hljs-keyword">import</span> { revalidatePath } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/cache"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">refreshGallery</span>(<span class="hljs-params"></span>) </span>{
  revalidatePath(<span class="hljs-string">"/"</span>); <span class="hljs-comment">// Purges the cache for the home page</span>
}
</code></span></pre>
<h2>The Data Layer: Fetching and Caching</h2>
<h3>Accessing the Cloudinary Admin API</h3>
<p>To build your governance dashboard, you’ll use the Cloudinary Admin API (<code>cloudinary.api.resources</code>) and fetch the list of uploaded videos along with their <strong>moderation status</strong> and <strong>AI tags</strong>.</p>
<blockquote>
<p>View the file on <a href="https://github.com/musebe/video-governance-guide/blob/main/app/video-pipeline/data.ts">GitHub</a>.</p>
</blockquote>
<h3>Bypassing Cache for Real-Time Updates</h3>
<p>By default, Next.js caches data aggressively. However, moderation happens seconds after upload. If you cache the “Pending” state, the user will never see the “Approved” state.</p>
<p>Use <code>unstable_noStore()</code> to opt out of caching for this specific request, ensuring you’ll always get a fresh status from Cloudinary.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-keyword">import</span> cloudinary <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/cloudinary"</span>;
<span class="hljs-keyword">import</span> { unstable_noStore <span class="hljs-keyword">as</span> noStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/cache"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getGovernedVideos</span>(<span class="hljs-params"></span>) </span>{
  noStore(); <span class="hljs-comment">// CORE ENGINE: Forces fresh data on every request</span>

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> cloudinary.api.resources({
      <span class="hljs-attr">type</span>: <span class="hljs-string">"upload"</span>,
      <span class="hljs-attr">prefix</span>: <span class="hljs-string">"governance/uploads"</span>, <span class="hljs-comment">// Target specific folder</span>
      <span class="hljs-attr">resource_type</span>: <span class="hljs-string">"video"</span>,
      <span class="hljs-attr">moderation</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Fetch AI safety status</span>
      <span class="hljs-attr">tags</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Fetch AI content tags</span>
      <span class="hljs-attr">max_results</span>: <span class="hljs-number">50</span>,
      <span class="hljs-attr">direction</span>: <span class="hljs-string">"desc"</span>,
    });
    <span class="hljs-keyword">return</span> results.resources;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Fetch Error:"</span>, error);
    <span class="hljs-keyword">return</span> &#91;];
  }
}
</code></span></pre>
<h3>Type Safety (<code>VideoAsset</code>)</h3>
<p>To avoid <code>any</code> types and ensure our UI handles the data correctly, define a strict interface that matches the Admin API response structure.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-php shcb-wrap-lines">export <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">VideoAsset</span> </span>{
  public_id: string;
  created_at: string;
  format: string;
  bytes: number;
  moderation_status?: <span class="hljs-string">"approved"</span> | <span class="hljs-string">"rejected"</span> | <span class="hljs-string">"pending"</span>;
  tags?: string&#91;];
}
</code></span></pre>
<h2>The Governance Dashboard (Server Component)</h2>
<h3>UI Strategy: ‘Trust, but Verify’</h3>
<p>This is the <strong>Core Engine #2</strong>. You’ll implement a pessimistic security model where content is hidden by default until explicitly approved by the AI. This protects users from ever seeing harmful uploads while they’re being processed.</p>
<blockquote>
<p>View the file on <a href="https://github.com/musebe/video-governance-guide/blob/main/app/_components/VideoGallery.tsx">GitHub</a>.</p>
</blockquote>
<h3>The Decision Engine</h3>
<p>Inside your server component, map over the video list and switch entirely based on the <code>moderation_status</code>. Note that the <code>&lt;VideoPlayer&gt;</code> is <strong>only</strong> rendered if the status is strictly <code>'approved'</code>.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines">{
  videos.map(<span class="hljs-function">(<span class="hljs-params">video</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> status = video.moderation_status || <span class="hljs-string">"pending"</span>;
    <span class="hljs-keyword">const</span> isSafe = status === <span class="hljs-string">"approved"</span>;
    <span class="hljs-keyword">const</span> isRejected = status === <span class="hljs-string">"rejected"</span>;

    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Card</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{video.public_id}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"aspect-video relative"</span>&gt;</span>
          {/* CASE 1: SAFE -&gt; Render the Player */}
          {isSafe &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">VideoPlayer</span> <span class="hljs-attr">publicId</span>=<span class="hljs-string">{video.public_id}</span> /&gt;</span>}

          {/* CASE 2: REJECTED -&gt; Render the Shield */}
          {isRejected &amp;&amp; (
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col items-center justify-center bg-red-50 text-red-600 h-full"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">ShieldAlert</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"h-10 w-10"</span> /&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-bold"</span>&gt;</span>Content Blocked<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          )}

          {/* CASE 3: PENDING -&gt; Render the Loader */}
          {!isSafe &amp;&amp; !isRejected &amp;&amp; (
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col items-center justify-center h-full"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Loader2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"animate-spin text-blue-500"</span> /&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>AI Moderation in Progress...<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          )}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Card</span>&gt;</span></span>
    );
  });
}
</code></span></pre>
<h3>Visualizing AI Tags</h3>
<p>You’ll also conditionally render the tags. If a video is rejected, make sure to hide the tags as well, since they might contain sensitive descriptions of the blocked content.</p>
<pre class="js-syntax-highlighted"><span><code class="hljs language-xml shcb-wrap-lines">{
  !isRejected &amp;&amp; video.tags &amp;&amp; (
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex gap-2"</span>&gt;</span>
      {video.tags.map((tag) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">Badge</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{tag}</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"secondary"</span>&gt;</span>
          {tag}
        <span class="hljs-tag">&lt;/<span class="hljs-name">Badge</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  );
}
</code></span></pre>
<h2>The Interactive Player (Client Component)</h2>
<h3>Solving Server/Client Conflicts</h3>
<p>The standard Cloudinary video player needs browser APIs (<code>window</code>, <code>document</code>) to attach event listeners and handle media streams. However, your Governance Dashboard is a <strong>Server Component</strong> (to keep API keys secure).</p>
<p>If you try to render the player directly, Next.js will throw a <code>useState only works in Client Components</code> error. Solve this with the <strong>Client Wrapper Pattern</strong>.</p>
<h3>The Wrapper Pattern (<code>VideoPlayer.tsx</code>)</h3>
<p>Create a dedicated component marked with <code>&quot;use client&quot;</code>. This isolates the interactive logic from the server-side fetching logic.</p>
<blockquote>
<p>View the file on <a href="https://github.com/musebe/video-governance-guide/blob/main/app/_components/VideoPlayer.tsx">GitHub</a>.</p>
</blockquote>
<pre class="js-syntax-highlighted"><span><code class="hljs language-javascript shcb-wrap-lines"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { CldVideoPlayer } <span class="hljs-keyword">from</span> <span class="hljs-string">"next-cloudinary"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"next-cloudinary/dist/cld-video-player.css"</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">VideoPlayer</span>(<span class="hljs-params">{ publicId }: { publicId: string }</span>) </span>{
  <span class="hljs-keyword">const</span> &#91;error, setError] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-comment">// CORE ENGINE: Error Handling for HLS Delays</span>
  <span class="hljs-comment">// If the HLS stream isn't ready, we catch the error and show a loader</span>
  <span class="hljs-keyword">if</span> (error) {
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-zinc-500"</span>&gt;</span>Processing Media...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">CldVideoPlayer</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"1920"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"1080"</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">{publicId}</span>
      <span class="hljs-attr">onError</span>=<span class="hljs-string">{()</span> =&gt;</span> setError(true)} // Catch transcoding errors
      controls
    /&gt;</span>
  );
}
</code></span></pre>
<h3>Handling Transcoding Delays</h3>
<p>When a video is first uploaded, Cloudinary generates the adaptive streaming formats (HLS/DASH) in the background. This can take a few seconds.</p>
<ul>
<li>
<strong>The problem.</strong> The player might try to load <code>.m3u8</code> before it exists, causing a “Media Not Supported” error.</li>
<li>
<strong>The fix.</strong> Your <code>onError</code> handler catches this specific failure and swaps the player for a “Processing” state, preventing a broken UI experience.</li>
</ul>
<h2>Final Results: A Self-Governing Video Library</h2>
<p>You’ve built an <strong>Agentic Video Governance Pipeline</strong>. By combining <strong>Next.js 16 Server Actions</strong> with <strong>Cloudinary’s AI</strong>, you eliminated the need for manual moderation. Users get instant feedback, admins get peace of mind that harmful content is blocked automatically, and developers get a clean, type-safe codebase without having to manage complex media servers.</p>
<h3>Future Enhancements</h3>
<p>While this guide covers the loop of uploads and checks, a production-grade system could go further:</p>
<ul>
<li>
<strong>Webhooks.</strong> Instead of polling or refreshing, use Cloudinary Webhooks to push updates to your database the moment AI processing finishes.</li>
<li>
<strong>Notifications.</strong> Connect the “Rejected” state to a Slack bot or Email service to alert a human admin for a final review.</li>
</ul>
<p>You can find the complete source code for this project here: <a href="https://github.com/musebe/video-governance-guide">github.com/musebe/video-governance-guide</a></p>
<p>Ready to start building your own AI video moderator? <a href="https://cloudinary.com/users/register_free">Sign up</a> for a free Cloudinary account today.</p>
</div><p>The post <a href="https://cloudinary.com/blog/video-governance-ai-moderation-tagging-next-js">Automating Video Governance: AI Moderation and Tagging in Next.js</a> appeared first on <a href="https://cloudinary.com/blog">Cloudinary Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39822</post-id>	</item>
	</channel>
</rss>
