<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cloudinary</title>
    <description>The latest articles on DEV Community by Cloudinary (@cloudinary).</description>
    <link>https://dev.to/cloudinary</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7286%2F8677e80f-fe6d-40e5-a9d6-c72af0042cd8.png</url>
      <title>DEV Community: Cloudinary</title>
      <link>https://dev.to/cloudinary</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloudinary"/>
    <language>en</language>
    <item>
      <title>Ship Your Next Great Web App in Record Time with create-cloudinary-react</title>
      <dc:creator>Eric Portis</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:53:07 +0000</pubDate>
      <link>https://dev.to/cloudinary/ship-your-next-great-web-app-in-record-time-with-create-cloudinary-react-2coa</link>
      <guid>https://dev.to/cloudinary/ship-your-next-great-web-app-in-record-time-with-create-cloudinary-react-2coa</guid>
      <description>&lt;p&gt;Over the past year or two, we've been having a lot of conversations about the future of Cloudinary's &lt;a href="https://www.cloudinary.dev" rel="noopener noreferrer"&gt;Libraries&lt;/a&gt; and &lt;a href="https://cloudinary.com/documentation/cloudinary_sdks" rel="noopener noreferrer"&gt;SDKs&lt;/a&gt;. The conversations have been wide-ranging: the future of front-end frameworks, coding, and AI. It's all been a bit dizzying, and hard to wrap our heads around.&lt;/p&gt;

&lt;p&gt;Late last year, my colleague Raya Straus brought us back down to earth with some good old-fashioned user research. By engaging users in conversations about their current day-to-day experiences, Raya got us to stop worrying about the future, and start thinking about ways we could make developers more successful with Cloudinary &lt;em&gt;right now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We released the first fruit of Raya's research last month: &lt;a href="https://github.com/cloudinary-devs/create-cloudinary-react" rel="noopener noreferrer"&gt;Cloudinary's React Starter Kit&lt;/a&gt;. If you're building a green-field, media-focused React app, and you're using LLM-powered development tools, we think &lt;code&gt;npx create-cloudinary-react&lt;/code&gt; is the best way to get started.&lt;/p&gt;

&lt;p&gt;Before we get into what it is, let's talk about how and why we built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Learned From React SDK User Research
&lt;/h2&gt;

&lt;p&gt;We limited our research scope to two of our most popular SDKs: &lt;a href="https://cloudinary.com/documentation/react_integration" rel="noopener noreferrer"&gt;React&lt;/a&gt; and &lt;a href="https://next.cloudinary.dev" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;. We looked at every piece of user feedback for those two SDKs that we could find: support tickets, GitHub issues, chat transcripts, survey responses, and even a number of individual conversations held over email and Zoom, after we reached out to top SDK users.&lt;/p&gt;

&lt;p&gt;Once we'd collected and digested it all, three things stuck out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Many developers struggle to &lt;em&gt;get started&lt;/em&gt;. The least fun part of any development project is setting up a working development environment; a handful of common config problems are tripping up many developers before they can take their first steps with Cloudinary.&lt;/li&gt;
&lt;li&gt;Once set up, features that solve common use cases were straightforward and people were quickly successful. Cloudinary and our SDKs do a great job of solving common use cases with a minimum of fuss. Great!&lt;/li&gt;
&lt;li&gt;But once use cases get more advanced, developers start to struggle again. Cloudinary is a mature product, and &lt;em&gt;can&lt;/em&gt; do &lt;em&gt;all sorts of things&lt;/em&gt;, but advanced features are more complicated to use. Niche functionality often has fiddlier syntax, less clear error messages, and trickier-to-find documentation. So even though the features are there, developers run into walls when trying to use them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyr2nrznnt15tjvhphsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyr2nrznnt15tjvhphsy.png" alt="A line chart. The x axis is labelled, " width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the React Starter Kit Works
&lt;/h2&gt;

&lt;p&gt;So, the question we ended up with, was: How can we help developers who are just getting started with Cloudinary, &lt;em&gt;and&lt;/em&gt; folks with complex use cases?&lt;/p&gt;

&lt;p&gt;We think we can address both with one new tool: &lt;code&gt;npx create-cloudinary-react&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Type that into a terminal near you and you'll be led through a wizard, which will ask you a few questions about your environment. Then, &lt;em&gt;it&lt;/em&gt; will spin up a working React project for you, using Vite as a build tool, and handle all of the configuration that people who are just starting to use Cloudinary with React so often get stuck on. The resulting project starts with a simple, one-page website that accepts and displays user-generated image uploads.&lt;/p&gt;

&lt;p&gt;In addition to doing basic configuration, the wizard &lt;em&gt;also&lt;/em&gt; installs &lt;a href="https://cloudinary.com/documentation/cloudinary_llm_mcp" rel="noopener noreferrer"&gt;Cloudinary's LLM-specific tools&lt;/a&gt;, and puts &lt;a href="https://github.com/cloudinary-devs/create-cloudinary-react/blob/main/templates/.cursorrules.template" rel="noopener noreferrer"&gt;a bunch of rules and troubleshooting guidance to help LLMs with Cloudinary's APIs&lt;/a&gt; wherever your particular LLM-powered coding environment looks for context.&lt;/p&gt;

&lt;p&gt;We derived these rules from our user research, by feeding all of the feedback we received about using Cloudinary's React SDK into an LLM and asking it to write rules to address that feedback. From there, we refined, added, and subtracted rules based on our experiences of using and helping other folks use Cloudinary.&lt;/p&gt;

&lt;p&gt;Those rules, in addition to our other LLM-specific tools, significantly improve LLMs' results when prompted with complex tasks.  To show off those capabilities, the initial built project offers a handful of example prompts which you can copy, paste into your agent, and hit the ground running.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;create-cloudinary-react&lt;/code&gt;, we believe we've built something that helps &lt;em&gt;everyone&lt;/em&gt; get started, and &lt;em&gt;also&lt;/em&gt; helps folks who have gone beyond common use cases use all of the nooks and crannies of Cloudinary's extensive feature set. &lt;/p&gt;

&lt;h2&gt;
  
  
  See the React Starter Kit in Action
&lt;/h2&gt;

&lt;p&gt;Here's &lt;code&gt;create-cloudinary-react&lt;/code&gt; in action:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/gmzYabZFUHo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How the React Starter Kit performed at Hack Canada 2026
&lt;/h2&gt;

&lt;p&gt;After a few rounds of internal development and testing, we soft-launched the React Starter Kit a few weeks ago -- just before we participated as a sponsor in this year's &lt;a href="https://hackcanada.org/" rel="noopener noreferrer"&gt;Hack Canada&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A hackathon is a perfect environment for this kind of tool, and the results blew us away. Participants leveraged the Starter Kit to produce some &lt;a href="https://hack-canada-2026.devpost.com/submissions/search?utf8=%E2%9C%93&amp;amp;prize_filter%5Bprizes%5D%5B%5D=97781" rel="noopener noreferrer"&gt;truly incredible projects&lt;/a&gt;, and cited &lt;code&gt;create-cloudinary-react&lt;/code&gt; as part of their success.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ERRFRz6LQZs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pJzrU2F_3r8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/A5McD0WwqzM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That was all the validation we needed to add a &lt;a href="https://cloudinary.com/documentation/react_starter_kit" rel="noopener noreferrer"&gt;React Starter Kit walk through to our documentation&lt;/a&gt; and announce it in this very blog post.&lt;/p&gt;

&lt;p&gt;So, go! &lt;a href="https://github.com/cloudinary-devs/create-cloudinary-react" rel="noopener noreferrer"&gt;Try it out!&lt;/a&gt; And tell us what you think in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>react</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Managing Media Files in Flask (Images, Videos, and Audio)</title>
      <dc:creator>Sharon Yelenik</dc:creator>
      <pubDate>Mon, 23 Mar 2026 19:07:00 +0000</pubDate>
      <link>https://dev.to/cloudinary/managing-media-files-in-flask-images-videos-and-audio-kdk</link>
      <guid>https://dev.to/cloudinary/managing-media-files-in-flask-images-videos-and-audio-kdk</guid>
      <description>&lt;p&gt;You’re building an app for a small business or website, maybe a portfolio site or boutique store, and you know high-quality visuals are key for attracting buyers and showcasing your work. But without a designer on the team, you’re responsible for handling all the images and videos yourself.&lt;/p&gt;

&lt;p&gt;So how do you manage a moderate amount of media files in the simplest and most efficient way?&lt;/p&gt;

&lt;p&gt;In this blog post, we’ll unveil transformative techniques for managing media files in Flask with Cloudinary to make your life easier as a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Streamlined Media Management
&lt;/h2&gt;

&lt;p&gt;Before we get into the practical steps, here some of the advantages of establishing a solid media management approach in your Flask application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less custom code and reduced storage complexity.&lt;/strong&gt; Offloading large media files from your database or filesystem keeps your application simpler and avoids managing bulky binary data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable uploads that scale.&lt;/strong&gt; A predictable upload flow prevents issues with file size limits, timeouts, or custom route handling, especially when users submit large images or videos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent, high-quality digital media.&lt;/strong&gt; Automated tasks like resizing, compression, tagging, and background removal help keep your images and videos optimized without manual editing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized, searchable delivery.&lt;/strong&gt; Well-structured metadata and scalable search make assets easier to find, while delivering the right size and format improves performance across devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By &lt;a href="https://link.cloudinary.com/umWsT" rel="noopener noreferrer"&gt;storing your digital media in Cloudinary&lt;/a&gt; instead of on your server or in your database, you can take advantage of these benefits immediately, while keeping your Flask codebase lightweight and focused on application logic.&lt;/p&gt;

&lt;p&gt;Managing media files in Flask becomes much simpler with the steps we cover next (tap the icons to navigate to the related section):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcloudinary-res.cloudinary.com%2Fimage%2Fupload%2Fv1698223367%2Fblog%2Fdjango_media_management.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcloudinary-res.cloudinary.com%2Fimage%2Fupload%2Fv1698223367%2Fblog%2Fdjango_media_management.png" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup and Configuration
&lt;/h2&gt;

&lt;p&gt;Before we dive into the specifics of media management with Python and Flask, you'll need to sign up for Cloudinary. It's quick and straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing and Configuring Cloudinary in Your Python/Flask App
&lt;/h3&gt;

&lt;p&gt;To get started with Cloudinary in your Python/Flask app, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the Cloudinary Python library using &lt;code&gt;pip install cloudinary&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Cloudinary in your Flask application, typically inside app.py or a separate &lt;code&gt;config.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.py or app.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary.uploader&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary.api&lt;/span&gt;

&lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cloud_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_cloud_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Replace &lt;code&gt;your_cloud_name&lt;/code&gt;, &lt;code&gt;your_api_key&lt;/code&gt;, and &lt;code&gt;your_api_secret&lt;/code&gt; with your actual Cloudinary credentials, which you can find on the &lt;a href="https://console.cloudinary.com/app/settings/api-keys" rel="noopener noreferrer"&gt;API keys&lt;/a&gt; page of the Console Settings.&lt;/p&gt;

&lt;ol start="3"&gt;&lt;li&gt;If you're using a &lt;code&gt;config.py&lt;/code&gt;, you'd load it in your Flask app like this:&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;  &lt;span class="c1"&gt;# or your preferred config structure
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these steps, you've successfully integrated Cloudinary into your Flask project, and you're ready to leverage its powerful media management capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhanced Media Processing With Upload Presets
&lt;/h2&gt;

&lt;p&gt;Upload presets in Cloudinary help you automate your media workflow by applying transformations, metadata rules, and upload behaviors the moment a file arrives. This means less processing in your Flask routes and far more consistent results across all your media.&lt;/p&gt;

&lt;p&gt;In this article, we’ll focus on two powerful capabilities you can unlock through presets: &lt;strong&gt;automated transformations&lt;/strong&gt; and &lt;strong&gt;auto-tagging&lt;/strong&gt;. You can explore many other preset options in Cloudinary’s &lt;a href="https://link.cloudinary.com/umWsU" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, but these two alone can dramatically simplify your media pipeline. &lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Transformation Settings
&lt;/h2&gt;

&lt;p&gt;Upload presets allow you to automatically apply transformations such as resizing, cropping, recoloring, background removal, and more, without writing additional code in Flask. For example, you can create presets that remove backgrounds from uploaded logos, ensure all portfolio images are a consistent size, or sharpen older or lower-quality photos. &lt;/p&gt;

&lt;p&gt;Each preset applies its rules automatically when the file is uploaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; By applying the &lt;a href="https://link.cloudinary.com/umWsW" rel="noopener noreferrer"&gt;g_auto&lt;/a&gt; AI-powered transformation, you can ensure that crops preserve the important parts of your images and keep the main subject of your videos in focus.&lt;/p&gt;

&lt;p&gt;By defining these settings once, every uploaded media file stays consistent and optimized with no manual editing or external image processing tools required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Fw_500%2Ch_303%2Cc_scale%2Ff_auto%2Cq_auto%2Fv1698140107%2FWeb_Assets%2Fblog%2Frecolor%2Frecolor.gif%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Fw_500%2Ch_303%2Cc_scale%2Ff_auto%2Cq_auto%2Fv1698140107%2FWeb_Assets%2Fblog%2Frecolor%2Frecolor.gif%3F_i%3DAA" width="500" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leveraging Auto-TaggingCopy link to this heading&lt;br&gt;
If you enable auto-tagging through Cloudinary’s categorization engines, images are automatically analyzed and tagged based on their content. These tags can support a variety of downstream tasks, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;organizing assets&lt;/li&gt;
&lt;li&gt;improving searchability&lt;/li&gt;
&lt;li&gt;enabling content discovery&lt;/li&gt;
&lt;li&gt;powering filters in your UI or internal tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, an image of a woman on a city street using a smartphone and carrying a bag might receive tags such as “woman,” “bag,” “mobile phone,” “purse,” or “car,” depending on the AI model you use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Important: To use auto-tagging, you’ll need to subscribe to one of Cloudinary’s &lt;a href="https://cloudinary.com/documentation/cloudinary_add_ons#auto_tagging" rel="noopener noreferrer"&gt;auto-tagging add-ons&lt;/a&gt;. The examples in this blog use the &lt;a href="https://link.cloudinary.com/umWsX" rel="noopener noreferrer"&gt;Rekognition Auto Tagging&lt;/a&gt; add-on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764259730%2FWeb_Assets%2Fblog%2Fwoman-business-suit%2Fwoman-business-suit.jpg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764259730%2FWeb_Assets%2Fblog%2Fwoman-business-suit%2Fwoman-business-suit.jpg%3F_i%3DAA" width="760" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Red-haired woman in stylish outfit chatting on phone&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Check out the &lt;a href="https://link.cloudinary.com/umWsY" rel="noopener noreferrer"&gt;Computer Vision Image Analysis for Your E-commerce Website&lt;/a&gt; demo to see Cloudinary in action, returning information about the content it identifies in your images.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating an Upload Preset
&lt;/h2&gt;

&lt;p&gt;You can create an upload preset using the Cloudinary Admin API right from your Flask application or a setup script. Here’s an example using the Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Define the upload preset details
&lt;/span&gt;&lt;span class="n"&gt;upload_preset_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_preset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;upload_preset_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsigned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;folder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_folder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transformation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;width&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fill&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;effect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vignette&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;categorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_rek_tagging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto_tagging&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create the upload preset using the SDK
&lt;/span&gt;&lt;span class="n"&gt;upload_preset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_upload_preset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_preset_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_preset_options&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Check if the upload preset was created successfully
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;upload_preset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;upload_preset_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Upload preset created successfully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to create upload preset.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once created, simply reference the preset name during upload, and all rules will apply automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading Media With Python and Flask
&lt;/h2&gt;

&lt;p&gt;Uploading files is central to your Flask workflow, and Cloudinary provides two flexible methods: the Upload Widget for client-side uploads and server-side uploads from your Flask routes.&lt;/p&gt;

&lt;p&gt;Let’s explore both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Widget
&lt;/h3&gt;

&lt;p&gt;If your app accepts user-generated content (UGC), such as portfolio images from creators or product photos submitted by small business owners, the Cloudinary Upload Widget provides a smooth, reliable upload experience directly from the browser. You can attach any upload presets you’ve created so transformations, optimization, tagging, and other processing happen automatically on upload, without additional Flask code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You can also create a preset that automatically &lt;a href="https://cloudinary.com/documentation/user_generated_content#moderate" rel="noopener noreferrer"&gt;moderates&lt;/a&gt; user-uploaded images and videos to help ensure that the content appearing on your site is appropriate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764260271%2FWeb_Assets%2Fblog%2Fupload_widget_accessible-1_3948189bcb%2Fupload_widget_accessible-1_3948189bcb.png%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1764260271%2FWeb_Assets%2Fblog%2Fupload_widget_accessible-1_3948189bcb%2Fupload_widget_accessible-1_3948189bcb.png%3F_i%3DAA" width="1580" height="1220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Use Case
&lt;/h2&gt;

&lt;p&gt;A portfolio gallery or a simple shop catalog where users can upload photos or videos is an ideal scenario for the Upload Widget. It handles the entire client-side upload flow, while your presets manage all media processing in the background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_scale%2Cw_500%2Fl_moon_layer%2Fc_scale%2Cw_150%2Ffl_layer_apply%2Cg_north_east%2Fl_text%3Aroboto_20_bold%3AMoonlight%2Ffl_layer_apply%2Cg_north_east%2Cx_30%2Cy_65%2Fcity_night_time.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_scale%2Cw_500%2Fl_moon_layer%2Fc_scale%2Cw_150%2Ffl_layer_apply%2Cg_north_east%2Fl_text%3Aroboto_20_bold%3AMoonlight%2Ffl_layer_apply%2Cg_north_east%2Cx_30%2Cy_65%2Fcity_night_time.jpg" width="500" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdemo-res.cloudinary.com%2Fw_500%2Fl_docs%3Awedding.jpg%2Cc_pad%2Cw_250%2Ch_250%2Fl_radialize%2Ffl_layer_apply%2Ce_displace%2Cy_-8%2Ffl_layer_apply%2Cx_10%2Cb_transparent%2Fleft_mug" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdemo-res.cloudinary.com%2Fw_500%2Fl_docs%3Awedding.jpg%2Cc_pad%2Cw_250%2Ch_250%2Fl_radialize%2Ffl_layer_apply%2Ce_displace%2Cy_-8%2Ffl_layer_apply%2Cx_10%2Cb_transparent%2Fleft_mug" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fv1489074100%2Fgirl_camera.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fv1489074100%2Fgirl_camera.jpg" width="800" height="1195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Implement the Upload Widget in Flask
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a route that renders the template:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload-media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_media&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload_media.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol start="2"&gt;&lt;li&gt; Create the template (templates/upload_media.html):
&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Media&lt;/span&gt; &lt;span class="n"&gt;Upload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload_widget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cloudinary-button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Upload&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://upload-widget.cloudinary.com/global/all.js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/javascript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/javascript&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;myWidget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUploadWidget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="n"&gt;cloudName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_cloud_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;uploadPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_preset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secure_url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Done! Here is the file info: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Send&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;
                &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/save-media&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload_widget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;click&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;myWidget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol start="3"&gt;&lt;li&gt; Add the URL rule (if not using decorators):
&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_url_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload-media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol start="4"&gt;&lt;li&gt; Test it by visiting:
&lt;/li&gt;&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a complete client-side upload flow, with Cloudinary handling media processing automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-Side Upload
&lt;/h2&gt;

&lt;p&gt;Server-side uploads are perfect when you need automation or want to process files uploaded through a traditional &lt;/p&gt;.
&lt;h3&gt;
  
  
  Example Use Case
&lt;/h3&gt;

&lt;p&gt;News aggregators, podcast platforms, or content curation apps often automate large-scale ingestion:&lt;/p&gt;
&lt;h1&gt;
  
  
  analyze images
&lt;/h1&gt;
&lt;h1&gt;
  
  
  apply tags
&lt;/h1&gt;
&lt;h1&gt;
  
  
  create captions
&lt;/h1&gt;
&lt;h1&gt;
  
  
  run moderation
&lt;/h1&gt;
&lt;h1&gt;
  
  
  convert formats
&lt;/h1&gt;

&lt;p&gt;Cloudinary can handle these tasks server-side with very little code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Uploading From Flask
&lt;/h3&gt;


&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary.uploader&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;file_to_upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;file_to_upload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;upload_preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_upload_preset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secure_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;And that’s it. Your media is uploaded, transformed, stored, and ready to deliver.&lt;/p&gt;
&lt;h2&gt;
  
  
  Searching Made Simple
&lt;/h2&gt;

&lt;p&gt;Once your assets are stored in Cloudinary, especially with auto-tagging enabled, searching becomes incredibly fast.&lt;/p&gt;

&lt;p&gt;Instead of digging through folders or writing custom queries, you can retrieve matching media with a single expression:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_media_by_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Example queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tags:portrait&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tags:handmade&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resource_type:video&lt;/code&gt; AND &lt;code&gt;tags:nature&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use search to build quick admin dashboards, galleries, filters, or automated content workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivering Your Media
&lt;/h2&gt;

&lt;p&gt;With Cloudinary managing your media, delivery becomes as powerful as upload. You can apply on-the-fly transformations directly in the image or video URL with no processing required in Flask.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Dynamic Image Sizing in a Flask Template
&lt;/h3&gt;



&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; 
  &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{ cloudinary_url(public_id, width=width, height=height, crop=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fill&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;) }}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your Image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Or, using the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cloudinary.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cloudinary_url&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cloudinary_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;public_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fill&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Why It’s Powerful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You don’t store multiple versions of each image&lt;/li&gt;
&lt;li&gt;Users automatically receive optimized formats (WebP, AVIF, MP4, etc.)&lt;/li&gt;
&lt;li&gt;Dynamic resizing keeps your site lightweight and fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For responsive layouts, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloudinary.com/documentation/responsive_html" rel="noopener noreferrer"&gt;Responsive images using HTML and dynamic image transformations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudinary.com/documentation/video_overview" rel="noopener noreferrer"&gt;Managing and delivering videos at scale.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149581%2FWeb_Assets%2Fblog%2Fguitar-man-1%2Fguitar-man-1.jpeg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149581%2FWeb_Assets%2Fblog%2Fguitar-man-1%2Fguitar-man-1.jpeg%3F_i%3DAA" width="1600" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149585%2FWeb_Assets%2Fblog%2Fguitar-man2-1%2Fguitar-man2-1.jpeg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149585%2FWeb_Assets%2Fblog%2Fguitar-man2-1%2Fguitar-man2-1.jpeg%3F_i%3DAA" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149587%2FWeb_Assets%2Fblog%2Fguitar-man3-1%2Fguitar-man3-1.jpeg%3F_i%3DAA" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fcloudinary-marketing%2Fimages%2Ff_auto%2Cq_auto%2Fv1698149587%2FWeb_Assets%2Fblog%2Fguitar-man3-1%2Fguitar-man3-1.jpeg%3F_i%3DAA" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret to Media File Management
&lt;/h2&gt;

&lt;p&gt;In short, the features we’ve covered include upload presets, smart uploads, AI-powered search, and dynamic delivery. Apply these features to take managing media files in Flask from “painful but necessary” to “effortless and scalable.”&lt;/p&gt;

&lt;p&gt;Instead of writing boilerplate logic for uploads, storage, and optimization, you can focus on building features your users care about.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/umWs0" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>backend</category>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Create a Digital Portfolio That Visually Pops</title>
      <dc:creator>Sharon Yelenik</dc:creator>
      <pubDate>Mon, 23 Mar 2026 18:13:17 +0000</pubDate>
      <link>https://dev.to/cloudinary/how-to-create-a-digital-portfolio-that-visually-pops-8li</link>
      <guid>https://dev.to/cloudinary/how-to-create-a-digital-portfolio-that-visually-pops-8li</guid>
      <description>&lt;p&gt;Job searching can be tough, and so is standing out among the competition.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That’s why I put together this digital portfolio demo project.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo (Optional, but Highly Recommended)
&lt;/h2&gt;

&lt;p&gt;Before diving into the code, you can check out the live portfolio demo here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/github/cloudinary-devs/digital_portfolio?file=README.md" rel="noopener noreferrer"&gt;View the live demo on StackBlitz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to explore it, then come back and see how everything works under the hood.&lt;/p&gt;


  
  Your browser does not support the video tag.


&lt;h2&gt;
  
  
  Keys to Making Your Digital Portfolio Stand Out
&lt;/h2&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;And when you build it efficiently, you’re also signaling to future employers that you know how to work efficiently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deliver images that load fast but look crisp.&lt;/li&gt;
&lt;li&gt;Make videos that play smoothly without eating bandwidth.&lt;/li&gt;
&lt;li&gt;Create responsive layouts that look perfect on every device.&lt;/li&gt;
&lt;li&gt;Apply visual effects that make content pop.&lt;/li&gt;
&lt;li&gt;Optimize everything without sacrificing quality.&lt;/li&gt;
&lt;li&gt;So really, building a great portfolio is just practice for the real * thing. And that’s pretty cool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Tech Stack I ChoseCopy link to this heading
&lt;/h2&gt;

&lt;p&gt;For my portfolio, I went with tools that are popular in the industry and honestly just fun to work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 19 with TypeScript for type safety and component architecture.&lt;/li&gt;
&lt;li&gt;Vite for lightning-fast development and optimized builds.&lt;/li&gt;
&lt;li&gt;CSS for beautiful, responsive styling.&lt;/li&gt;
&lt;li&gt;Cloudinary for all image and video transformations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to &lt;a href="https://github.com/cloudinary-devs/digital_portfolio" rel="noopener noreferrer"&gt;clone my code&lt;/a&gt; and adapt it to whatever you’re used to working with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the starter portfolio repo&lt;/span&gt;
git clone https://github.com/your-username/digital-portfolio.git

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;digital-portfolio
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Start the development server&lt;/span&gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m excited to see how you make it your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Cloudinary Helps Meet Stand-Out Goals
&lt;/h2&gt;

&lt;p&gt;A portfolio that stands out needs to be fast, visually sharp, and responsive across devices.&lt;/p&gt;

&lt;p&gt;Without automation, that usually means resizing images manually, generating multiple breakpoints, compressing files carefully, and managing large video assets.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Transformations run on the fly, and the right size and format are delivered automatically for each device and browser.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Swap the Demo Media for Your Own
&lt;/h2&gt;

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

&lt;h3&gt;
  
  
  Step 1: Create a Cloudinary Account
&lt;/h3&gt;

&lt;p&gt;Sign up for a &lt;a href="https://link.cloudinary.com/umWb1" rel="noopener noreferrer"&gt;free Cloudinary account&lt;/a&gt; (the free tier is more than enough for a portfolio).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Find Your Cloud Name
&lt;/h3&gt;

&lt;p&gt;After logging in, copy your cloud name from the dashboard. You’ll use it in URLs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://res.cloudinary.com/&amp;lt;your_cloud_name&amp;gt;/image/upload/&amp;lt;public_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Update One Line in the Code
&lt;/h3&gt;

&lt;p&gt;In your project, change this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set this to your cloud name when you're ready to use your own media&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLOUDINARY_CLOUD_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…to your cloud name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLOUDINARY_CLOUD_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your_cloud_name&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. Since CLOUDINARY_BASE is built from CLOUDINARY_CLOUD_NAME, all image/video URLs that use CLOUDINARY_BASE will automatically point to your account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Upload Your Own Media and Swap the Public IDs
&lt;/h3&gt;

&lt;p&gt;In your code, you reference assets using public IDs — for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;docs/profile-pic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means Cloudinary is looking for an asset with the public ID &lt;code&gt;docs/catwalk&lt;/code&gt; in your cloud.&lt;/p&gt;

&lt;p&gt;After you upload your own images/videos to Cloudinary, replace those image values with your own public IDs, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portfolio/catwalk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You don’t need to change the transformations. Everything in the URL after &lt;code&gt;/upload/&lt;/code&gt; (like &lt;code&gt;c_fill,g_auto,h_400,w_600/f_auto/q_auto&lt;/code&gt;) can stay the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cool Cloudinary Features I Used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Smart Cropping With Face Detection
&lt;/h3&gt;

&lt;p&gt;For the testimonials section, I needed consistent circular profile images that focused tightly on each person’s face.&lt;/p&gt;

&lt;p&gt;Here’s the original image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fdocs%2Fprofile-pic.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fdocs%2Fprofile-pic.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/image/upload/" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/image/upload/&lt;/a&gt;&lt;br&gt;
docs/profile-pic.jpg&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And here’s the transformed version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_thumb%2Cg_face%2Ch_300%2Cw_300%2Fr_max%2Fe_sharpen%3A80%2Ff_auto%2Fq_auto%2Fdocs%2Fprofile-pic.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_thumb%2Cg_face%2Ch_300%2Cw_300%2Fr_max%2Fe_sharpen%3A80%2Ff_auto%2Fq_auto%2Fdocs%2Fprofile-pic.jpg" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;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" rel="noopener noreferrer"&gt;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&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What the Transformation Does
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;c_thumb,g_face&lt;/code&gt; automatically detects the face and crops around it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;h_300,w_300&lt;/code&gt; enforces a fixed square size.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r_max&lt;/code&gt; makes the image circular.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e_sharpen:80&lt;/code&gt; restores clarity after resizing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto,q_auto&lt;/code&gt; handle format and compression.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That means no manual cropping, guessing focal points, or layout inconsistencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Hero Background Blur
&lt;/h3&gt;

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

&lt;p&gt;Original image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fvieste_italy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fvieste_italy.jpg" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/image/upload/vieste_italy.jpg" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/image/upload/vieste_italy.jpg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Transformed version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_auto%2Ch_1080%2Cw_1920%2Fe_blur%3A800%2Ff_auto%2Fq_auto%2Fvieste_italy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_auto%2Ch_1080%2Cw_1920%2Fe_blur%3A800%2Ff_auto%2Fq_auto%2Fvieste_italy.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="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" rel="noopener noreferrer"&gt;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&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What the Transformation Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c_fill,g_auto&lt;/code&gt; crops intelligently to 1920×1080.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e_blur:800&lt;/code&gt; applies a strong blur effect.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto,q_auto&lt;/code&gt; optimize delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The original image is detailed and high contrast — great for photography, not ideal for text overlays.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI-Enhanced Portrait
&lt;/h3&gt;

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

&lt;p&gt;Original:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fdocs%2Fprofile-pic1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fdocs%2Fprofile-pic1.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Transformed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_face%2Ch_300%2Cw_300%2Fr_max%2Cbo_2px_solid_green%2Fe_enhance%2Ff_auto%2Fq_auto%3Abest%2Fdocs%2Fprofile-pic1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_face%2Ch_300%2Cw_300%2Fr_max%2Cbo_2px_solid_green%2Fe_enhance%2Ff_auto%2Fq_auto%3Abest%2Fdocs%2Fprofile-pic1.jpg" width="304" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/image/upload/c_fill,g_face,h_300,w_300/r_max,bo_2px_solid_green/e_enhance/f_auto/q_auto:best/docs/profile-pic1.jpg" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/image/upload/c_fill,g_face,h_300,w_300/r_max,bo_2px_solid_green/e_enhance/f_auto/q_auto:best/docs/profile-pic1.jpg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What the Transformation Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;g_face&lt;/code&gt; centers the subject.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r_max&lt;/code&gt; applies a circular crop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bo_2px_solid_green&lt;/code&gt; adds a clean, circular border.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e_enhance&lt;/code&gt; enhances lighting and contrast using AI.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;q_auto:best&lt;/code&gt; balances compression with quality.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It looks like a designed component, not just an uploaded image.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Responsive Project Images
&lt;/h3&gt;

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

&lt;p&gt;Here’s one of the original images:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fwoman_mountain_ledge.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fwoman_mountain_ledge.jpg" width="800" height="1059"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/image/upload/woman_mountain_ledge.jpg" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/image/upload/woman_mountain_ledge.jpg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And here’s the version used in the grid:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_auto%2Ch_400%2Cw_600%2Fr_20%2Fe_saturation%3A20%2Ff_auto%2Fq_auto%2Fwoman_mountain_ledge.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_auto%2Ch_400%2Cw_600%2Fr_20%2Fe_saturation%3A20%2Ff_auto%2Fq_auto%2Fwoman_mountain_ledge.jpg" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="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" rel="noopener noreferrer"&gt;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&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What the Transformation Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c_fill,h_400,w_600&lt;/code&gt; forces a consistent 600×400 frame.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g_auto&lt;/code&gt; intelligently selects the focal area.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r_20&lt;/code&gt; adds rounded corners.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e_saturation:20&lt;/code&gt; slightly boosts color.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto,q_auto&lt;/code&gt; optimize delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The original image has its own natural proportions.&lt;/p&gt;

&lt;p&gt;The transformed version guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every card in the grid is exactly the same size.&lt;/li&gt;
&lt;li&gt;No distortion.&lt;/li&gt;
&lt;li&gt;No manual cropping.&lt;/li&gt;
&lt;li&gt;No awkward whitespace.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  5. Video Transformations That Actually Work
&lt;/h3&gt;

&lt;p&gt;Video is usually where portfolios fall apart. Files are large, aspect ratios are inconsistent, and playback isn’t optimized.&lt;/p&gt;

&lt;p&gt;Here’s the original full video:&lt;/p&gt;


  
  Your browser does not support the video tag.


&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/video/upload/v1731855790/guy_woman_mobile.mp4" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/video/upload/v1731855790/guy_woman_mobile.mp4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And here’s the version used in the portfolio:&lt;/p&gt;


  
  Your browser does not support the video tag.


&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/video/upload/so_0.5,eo_2.5/c_pad,h_400,w_600/b_rgb:d4a520/v1731855790/guy_woman_mobile.mp4" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/video/upload/so_0.5,eo_2.5/c_pad,h_400,w_600/b_rgb:d4a520/v1731855790/guy_woman_mobile.mp4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What the Transformation Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;so_0.5,eo_3&lt;/code&gt; trims the clip to a specific segment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c_pad,h_400,w_600&lt;/code&gt; fits it into a 600×400 frame without cutting off content.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;b_rgb:d4a520&lt;/code&gt; fills extra space with a consistent background color.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto,q_auto&lt;/code&gt; optimize format and compression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of uploading a separately edited clip, I trim and resize at delivery time.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The video is shorter and lighter.&lt;/li&gt;
&lt;li&gt;The layout dimensions are guaranteed.&lt;/li&gt;
&lt;li&gt;There are no black bars.&lt;/li&gt;
&lt;li&gt;The browser gets the best possible format automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It behaves like a designed component — not a raw media file dropped onto a page.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Small Details That Make a Difference
&lt;/h3&gt;

&lt;p&gt;Once layout and performance were handled, I added subtle refinements.&lt;/p&gt;

&lt;p&gt;Here’s the original image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fdocs%2Fprofile-pic1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fdocs%2Fprofile-pic1.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg" rel="noopener noreferrer"&gt;https://res.cloudinary.com/demo/image/upload/docs/profile-pic1.jpg&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And here’s the polished version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_auto%2Ch_800%2Cw_700%2Fe_vignette%3A30%2Fe_sharpen%3A100%2Fr_20%2Fbo_1px_solid_rgb%3Ae0e0e0%2Ff_auto%2Fq_auto%2Fdocs%2Fprofile-pic1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdemo%2Fimage%2Fupload%2Fc_fill%2Cg_auto%2Ch_800%2Cw_700%2Fe_vignette%3A30%2Fe_sharpen%3A100%2Fr_20%2Fbo_1px_solid_rgb%3Ae0e0e0%2Ff_auto%2Fq_auto%2Fdocs%2Fprofile-pic1.jpg" width="702" height="802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="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" rel="noopener noreferrer"&gt;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&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What the Transformation Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c_fill,g_auto&lt;/code&gt; enforces consistent framing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e_vignette:30&lt;/code&gt; darkens the edges slightly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e_sharpen:100&lt;/code&gt; restores clarity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r_20&lt;/code&gt; rounds the corners.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bo_1px_solid_rgb:e0e0e0&lt;/code&gt; adds a subtle border.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f_auto,q_auto&lt;/code&gt; optimize delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these effects are dramatic, but together they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve edge definition.&lt;/li&gt;
&lt;li&gt;Add separation from the background.&lt;/li&gt;
&lt;li&gt;Standardize presentation across sections.&lt;/li&gt;
&lt;li&gt;These are small adjustments, but they’re the difference between “image placed on a page” and “designed component.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What This Means for Performance
&lt;/h4&gt;

&lt;p&gt;You might be thinking, “All these effects must slow things down, right?” Actually, the opposite! &lt;/p&gt;

&lt;p&gt;With Cloudinary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;70-80% smaller file sizes&lt;/strong&gt; compared to unoptimized images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3-5x faster loading&lt;/strong&gt; thanks to automatic optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero manual editing time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic device optimization&lt;/strong&gt; — phone users get mobile-sized images, desktop users get high-res.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcloudinary-res.cloudinary.com%2Fimage%2Fupload%2Fv1771198908%2Fblog%2Fdigital_portfolio_mi_img.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcloudinary-res.cloudinary.com%2Fimage%2Fupload%2Fv1771198908%2Fblog%2Fdigital_portfolio_mi_img.png" width="800" height="1285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing It Full Circle
&lt;/h2&gt;

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

&lt;p&gt;When you build this portfolio, you’re learning how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build modern React applications with TypeScript.&lt;/li&gt;
&lt;li&gt;Create responsive, mobile-first designs.&lt;/li&gt;
&lt;li&gt;Optimize images and videos for real-world performance.&lt;/li&gt;
&lt;li&gt;Use cloud services to solve practical problems.&lt;/li&gt;
&lt;li&gt;Write clean, maintainable code.&lt;/li&gt;
&lt;li&gt;Think about user experience.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Making a portfolio that stands out doesn’t have to be complicated or stressful. It’s really about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polish that shows you care about details.&lt;/li&gt;
&lt;li&gt;Performance that respects people’s time.&lt;/li&gt;
&lt;li&gt;Smart visual choices that guide the eye.&lt;/li&gt;
&lt;li&gt;Responsive design that works everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the right tools (like Cloudinary) to make your life easier.&lt;br&gt;
If you’re job searching right now, I hope this helps. You’ve got this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo&lt;/strong&gt;: &lt;a href="https://stackblitz.com/github/cloudinary-devs/digital_portfolio?file=README.md" rel="noopener noreferrer"&gt;https://stackblitz.com/github/cloudinary-devs/digital_portfolio?file=README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code&lt;/strong&gt;: &lt;a href="https://github.com/cloudinary-devs/digital_portfolio" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudinary docs&lt;/strong&gt;: &lt;a href="https://link.cloudinary.com/umWb2" rel="noopener noreferrer"&gt;https://cloudinary.com/documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/umWb1" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>career</category>
      <category>design</category>
      <category>frontend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your Images Are Probably Slowing Down Your Website (Here’s How to Fix It)</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Wed, 11 Mar 2026 18:43:45 +0000</pubDate>
      <link>https://dev.to/cloudinary/your-images-are-probably-slowing-down-your-website-heres-how-to-fix-it-23je</link>
      <guid>https://dev.to/cloudinary/your-images-are-probably-slowing-down-your-website-heres-how-to-fix-it-23je</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Images often account for the majority of a webpage’s weight. Learn how developers can improve performance using modern image formats, responsive images, and smarter delivery strategies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The hidden performance problem on most websites
&lt;/h2&gt;

&lt;p&gt;Open Chrome DevTools on almost any modern site and check the &lt;strong&gt;Network tab&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What you’ll usually see is something surprising:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images dominate the payload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The average homepage ships &lt;strong&gt;around 1 MB of images&lt;/strong&gt;, often &lt;strong&gt;more than JavaScript, CSS, and HTML combined&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.thoughtindustries.com%2Fcourse-uploads%2F4338ce4e-f809-4f5a-80f4-1d317c4a390d%2Fj0bb7waszgtt-Screenshot2025-11-25at12.31.09PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.thoughtindustries.com%2Fcourse-uploads%2F4338ce4e-f809-4f5a-80f4-1d317c4a390d%2Fj0bb7waszgtt-Screenshot2025-11-25at12.31.09PM.png" alt="watches in a web site" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A super heavy page with a lot of big product images. Lazy loading and optimization would help here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That product page with 20 images?&lt;br&gt;
Each one is an opportunity to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deliver a fast, polished experience&lt;/li&gt;
&lt;li&gt;Or frustrate users with slow loading and layout shifts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you care about &lt;strong&gt;Core Web Vitals, SEO, and user experience&lt;/strong&gt;, image optimization is one of the &lt;strong&gt;highest-impact improvements you can make&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was generated from our &lt;a href="https://link.cloudinary.com/umq4g" rel="noopener noreferrer"&gt;Cloud to Crowd: Media IQ with Next.js&lt;/a&gt; curriculum, built by Cloudinary Developer Relations. Go through this curriculum and pass the certification exam to qualify to become an official &lt;a href="https://link.cloudinary.com/umq4h" rel="noopener noreferrer"&gt;Cloudinary Creator&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  Why images matter (and when they don’t)
&lt;/h1&gt;

&lt;p&gt;Humans process images much faster than text.&lt;/p&gt;

&lt;p&gt;A well-chosen image can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explain a product instantly&lt;/li&gt;
&lt;li&gt;Replace paragraphs of documentation&lt;/li&gt;
&lt;li&gt;Capture attention in fast-scrolling feeds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But images can also &lt;strong&gt;hurt your product experience&lt;/strong&gt; when used poorly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too many images slow down page loads&lt;/li&gt;
&lt;li&gt;Poor cropping hides important information&lt;/li&gt;
&lt;li&gt;Large files damage &lt;strong&gt;Core Web Vitals&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Undisclosed AI or edited images reduce user trust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you need to follow this simple rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Right format + right size + smart delivery&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41mtd49vkcucf25n7lid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41mtd49vkcucf25n7lid.png" alt="Images in a correct format" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out the way these images are delivered to the browser &lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  Images and Core Web Vitals
&lt;/h1&gt;

&lt;p&gt;Images heavily influence Google's &lt;strong&gt;Core Web Vitals&lt;/strong&gt;, particularly:&lt;/p&gt;
&lt;h3&gt;
  
  
  Largest Contentful Paint (LCP)
&lt;/h3&gt;

&lt;p&gt;Measures how long it takes for the &lt;strong&gt;largest visible element&lt;/strong&gt; on the page to load.&lt;/p&gt;

&lt;p&gt;This is often your &lt;strong&gt;hero image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A good LCP target is under 2.5 seconds. &lt;/p&gt;
&lt;h3&gt;
  
  
  Cumulative Layout Shift (CLS)
&lt;/h3&gt;

&lt;p&gt;Images without defined dimensions can cause layout shifts while loading.&lt;/p&gt;
&lt;h3&gt;
  
  
  Interaction to Next Paint (INP)
&lt;/h3&gt;

&lt;p&gt;Heavy pages delay responsiveness.&lt;/p&gt;

&lt;p&gt;Optimizing images can dramatically improve &lt;strong&gt;all three metrics&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  The current state of images on the web
&lt;/h1&gt;

&lt;p&gt;Nearly &lt;strong&gt;every webpage (99.9%) loads at least one image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Average number of images per page:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Avg Images&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Common formats used across the web:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;32.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;28.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIF&lt;/td&gt;
&lt;td&gt;16.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVG&lt;/td&gt;
&lt;td&gt;6.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVIF&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But these numbers reflect &lt;strong&gt;habit, not best practices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Performance-focused teams are already shifting toward modern formats.&lt;/p&gt;

&lt;p&gt;Example data from optimized platforms shows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;49.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;19.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVIF&lt;/td&gt;
&lt;td&gt;13.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEIC / PNG&lt;/td&gt;
&lt;td&gt;8.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The takeaway:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern image formats are replacing JPEG-first strategies.&lt;/strong&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Choosing the right image format
&lt;/h1&gt;

&lt;p&gt;Different formats solve different problems.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lossy compression
&lt;/h2&gt;

&lt;p&gt;Reduces file size by permanently discarding some data.&lt;/p&gt;

&lt;p&gt;Formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JPEG&lt;/li&gt;
&lt;li&gt;WebP&lt;/li&gt;
&lt;li&gt;AVIF&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Much smaller file sizes&lt;/li&gt;
&lt;li&gt;Ideal for photos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slight quality loss&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Lossless compression
&lt;/h2&gt;

&lt;p&gt;Reduces size &lt;strong&gt;without losing data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PNG&lt;/li&gt;
&lt;li&gt;SVG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perfect image quality&lt;/li&gt;
&lt;li&gt;Ideal for UI graphics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Larger file sizes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tu5adjaeuss92qfydpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tu5adjaeuss92qfydpv.png" alt="quality varying in images" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out the difference between a lower res image vs JPEG XL&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Quick developer cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Best format&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Photos&lt;/td&gt;
&lt;td&gt;WebP / AVIF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graphics&lt;/td&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Icons&lt;/td&gt;
&lt;td&gt;SVG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy fallback&lt;/td&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Modern formats like &lt;strong&gt;WebP and AVIF can reduce file size by 40–60% compared to JPEG&lt;/strong&gt; while keeping similar visual quality.&lt;/p&gt;


&lt;h1&gt;
  
  
  Delivering images efficiently
&lt;/h1&gt;

&lt;p&gt;Choosing the right format is only part of the story.&lt;br&gt;
Delivery strategy matters just as much.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Guess what? Cloudinary takes the guesswork out of all of this by helping you optimize, transform and deliver images the right way for your users. Check out how on the &lt;a href="https://link.cloudinary.com/umq4i" rel="noopener noreferrer"&gt;Developers Hub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  1. Use responsive images
&lt;/h2&gt;

&lt;p&gt;Don't force mobile users to download desktop-sized images.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"hero.avif"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/avif"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"hero.webp"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"hero.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Product hero image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser automatically chooses the &lt;strong&gt;best supported format and size&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Use a CDN
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Content Delivery Network (CDN)&lt;/strong&gt; reduces the physical distance between your users and your images.&lt;/p&gt;

&lt;p&gt;Modern image CDNs can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resize images automatically&lt;/li&gt;
&lt;li&gt;Convert formats (WebP / AVIF)&lt;/li&gt;
&lt;li&gt;Adjust compression&lt;/li&gt;
&lt;li&gt;Crop dynamically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows you to store &lt;strong&gt;one high-quality original&lt;/strong&gt; while serving optimized versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Lazy-load images (strategically)
&lt;/h2&gt;

&lt;p&gt;Lazy loading delays images until they approach the viewport.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"gallery.jpg"&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Gallery image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is one important rule:&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Never lazy-load above-the-fold images.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Doing so hurts &lt;strong&gt;Largest Contentful Paint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lazy loading should only be used for &lt;strong&gt;below-the-fold content&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Responsible image usage
&lt;/h1&gt;

&lt;p&gt;Performance isn't the only consideration.&lt;/p&gt;

&lt;p&gt;Developers should also use images &lt;strong&gt;ethically and accessibly&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Avoid misleading visuals
&lt;/h3&gt;

&lt;p&gt;Don't use edited or cropped images that misrepresent a product.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Disclose AI-generated images
&lt;/h3&gt;

&lt;p&gt;Transparency builds trust with users.&lt;br&gt;
The banner at the top of this article was generated using Nano Banana.&lt;/p&gt;


&lt;h3&gt;
  
  
  3. Make images accessible
&lt;/h3&gt;

&lt;p&gt;Always include descriptive alt text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dashboard.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Analytics dashboard showing user growth trends"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Respect privacy and licensing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check image licenses&lt;/li&gt;
&lt;li&gt;Get consent when photographing people&lt;/li&gt;
&lt;li&gt;Remove sensitive EXIF metadata&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Balance aesthetics and performance
&lt;/h3&gt;

&lt;p&gt;A beautiful image that takes &lt;strong&gt;8 seconds to load&lt;/strong&gt; isn't beautiful.&lt;/p&gt;

&lt;p&gt;It's broken.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common image optimization myths
&lt;/h1&gt;

&lt;h3&gt;
  
  
  “A CDN replaces responsive images”
&lt;/h3&gt;

&lt;p&gt;False.&lt;/p&gt;

&lt;p&gt;Responsive images help the &lt;strong&gt;browser choose the correct size&lt;/strong&gt;, while CDNs deliver optimized assets.&lt;/p&gt;

&lt;p&gt;You need &lt;strong&gt;both&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  “More images mean more engagement”
&lt;/h3&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;Images should &lt;strong&gt;help users make decisions&lt;/strong&gt;, not clutter the interface.&lt;/p&gt;




&lt;h1&gt;
  
  
  Three practical tips for developers
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1. Start with your largest image
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;DevTools → Network&lt;/strong&gt; and find the biggest image.&lt;/p&gt;

&lt;p&gt;Check if delivered size == display size.&lt;/p&gt;

&lt;p&gt;A common mistake: 3000px image displayed in a 600px container.&lt;/p&gt;

&lt;p&gt;That’s wasted bandwidth.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Measure your format mix
&lt;/h3&gt;

&lt;p&gt;Check what formats your pipeline actually delivers.&lt;/p&gt;

&lt;p&gt;If you're still mostly serving JPEG, there's likely a &lt;strong&gt;huge optimization opportunity&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Don’t assume mobile is optimized
&lt;/h3&gt;

&lt;p&gt;Mobile networks amplify performance problems.&lt;/p&gt;

&lt;p&gt;Check your &lt;strong&gt;75th and 90th percentile metrics&lt;/strong&gt;, not just averages.&lt;/p&gt;




&lt;h1&gt;
  
  
  Try this: Analyze a website’s image performance
&lt;/h1&gt;

&lt;p&gt;You can test any website using &lt;strong&gt;Cloudinary’s Web Speed Test&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://webspeedtest.cloudinary.com" rel="noopener noreferrer"&gt;https://webspeedtest.cloudinary.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then analyze:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance score&lt;/li&gt;
&lt;li&gt;Total image weight&lt;/li&gt;
&lt;li&gt;Largest Contentful Paint&lt;/li&gt;
&lt;li&gt;Potential size reductions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll often discover &lt;strong&gt;massive performance gains from simple fixes&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;Images are often the &lt;strong&gt;largest performance lever on a webpage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you remember only three things:&lt;/p&gt;

&lt;p&gt;1️⃣ Optimize your &lt;strong&gt;largest image first&lt;/strong&gt;&lt;br&gt;
2️⃣ Use &lt;strong&gt;modern formats like WebP and AVIF&lt;/strong&gt;&lt;br&gt;
3️⃣ Deliver &lt;strong&gt;responsive images with proper sizing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Small improvements in image delivery can produce &lt;strong&gt;huge gains in performance and user experience&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What’s the biggest image optimization issue you’ve found on a website?&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://link.cloudinary.com/umq4j" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>frontend</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Meet the Cloudinary Creators Community: A New Space for Developers Building with Media</title>
      <dc:creator>eugene musebe</dc:creator>
      <pubDate>Mon, 23 Feb 2026 14:13:47 +0000</pubDate>
      <link>https://dev.to/cloudinary/from-cloud-to-crowd-introducing-the-cloudinary-creators-community-3g8e</link>
      <guid>https://dev.to/cloudinary/from-cloud-to-crowd-introducing-the-cloudinary-creators-community-3g8e</guid>
      <description>&lt;p&gt;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 &lt;a href="https://cloudinary.com/developers/community?utm_source=dev-dot-to&amp;amp;utm_content=community-post" rel="noopener noreferrer"&gt;Cloudinary Creators Community&lt;/a&gt; (CCC).&lt;/p&gt;

&lt;p&gt;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'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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision: ‘From Cloud to Crowd’
&lt;/h2&gt;

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

&lt;p&gt;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're just starting to understand how images and videos power the internet or you'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 &lt;strong&gt;recognized Cloudinary Creator.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the heart of everything we do are our three core values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learn.&lt;/strong&gt; Master the fundamentals of modern media management and high-performance web development through structured, accessible education.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build.&lt;/strong&gt; Turn that knowledge into action! We provide the platform for you to ship real-world projects that make your portfolio shine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect.&lt;/strong&gt; Join a vibrant, global ecosystem of peers and industry experts who are all rooting for your success.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Education
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Joining the Community: Two Pathways
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Partner Pathway
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  The Individual Pathway
&lt;/h3&gt;

&lt;p&gt;Are you an independent builder ready to level up? You can join, too! Simply dive into our "&lt;a href="https://training.cloudinary.com/courses/devrel-c2c-next?utm_source=dev-dot-to&amp;amp;utm_content=community-post" rel="noopener noreferrer"&gt;&lt;strong&gt;Cloud to Crowd&lt;/strong&gt;&lt;/a&gt;" 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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Phase 2: Activation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Discord: Our Digital Headquarters
&lt;/h3&gt;

&lt;p&gt;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: &lt;strong&gt;New members must introduce themselves within their first month to keep their seat.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selectivity and Waitlist Management
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Membership Benefits and Rewards
&lt;/h2&gt;

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

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

&lt;p&gt;Beyond the tech, you’ll join an ecosystem built on mentorship. This includes dedicated office hours for technical Q&amp;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 &lt;strong&gt;Swag Pack&lt;/strong&gt; directly to your door.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Engagement Path (12 Points/Year)
&lt;/h2&gt;

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

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

&lt;h2&gt;
  
  
  Community Life and Activities
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Professionalism and Governance
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;We’re ready to build the future of the visual web with you. Visit our &lt;a&gt;Community page&lt;/a&gt; to find your path and start your journey from learner to creator today. Let's go!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://cloudinary.com/users/register_free?utm_campaign=5266-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=cloud-to-crowd" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>community</category>
      <category>webdev</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>DRY January: Demotivational Posters with the TanStack or Next.js</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Mon, 26 Jan 2026 18:18:58 +0000</pubDate>
      <link>https://dev.to/cloudinary/dry-january-demotivational-posters-with-the-tanstack-or-nextjs-5164</link>
      <guid>https://dev.to/cloudinary/dry-january-demotivational-posters-with-the-tanstack-or-nextjs-5164</guid>
      <description>&lt;p&gt;January can be a time when we make all the resolutions to do and be better in the shiny new year. We resolve to do more... more exercise, a more rigorous diet, even more mindfulness, doggone it. But what if we (and hear me out) instead thought about doing LESS. Less struggling against those 4PM sugar cravings, less fighting to not take an after-lunch nap, less battling frozen driveways to back the car out so you can make it to pilates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9qsh0vde33s9gh072fx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9qsh0vde33s9gh072fx.png" alt="I don't know if this is creepy or inspiring, but let's build it anyway" width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't know if this is creepy or inspiring, but let's build it anyway&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What if we embraced our inner zen and simply...did the best we could? After all, isn’t all the productivity that AI is bestowing upon us freeing up our time to do less, rather than more? I’m only half-kidding here.&lt;/p&gt;

&lt;p&gt;To encourage us to embrace a reductionist mentality, I did a little experiment to see if I could rethink some popular messaging that I’m sure you encountered if you ever worked in an American corporate office in the early 2000s: motivational posters. &lt;/p&gt;

&lt;p&gt;These little gems were plastered on the walls to exhort us to Bring Our Best Selves To Work, Achieve More, and Be Bold. Here’s an example from the &lt;a href="https://www.successories.com/" rel="noopener noreferrer"&gt;“Successories” company&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65gl0x461ov0amz2fvpm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65gl0x461ov0amz2fvpm.webp" alt="Leadership Wolfs FTW" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, they have a particular format of a black background, an inspiring photo, an uplifting word with a lot of kerning, and a blurb at the bottom to drive the message home. A &lt;strong&gt;Whole Vibe&lt;/strong&gt; was created as you walked down a poster-adorned corridor in order to toss your bag lunch into the office fridge.&lt;/p&gt;

&lt;p&gt;I particularly like this wolf. This is me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gsnex7pigdpp8c2tgds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gsnex7pigdpp8c2tgds.png" alt="skeptical wolf" width="578" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even back in those days, people made fun of these posters, and I remember being employed in one company packed with sarcastic Eastern Europeans that was instead decorated with &lt;strong&gt;&lt;em&gt;demotivational&lt;/em&gt;&lt;/strong&gt; posters such as those crafted by the brilliant &lt;a href="https://despair.com/" rel="noopener noreferrer"&gt;Despair.inc&lt;/a&gt;. Here’s a good one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3htoxtakrayhn6zsyn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3htoxtakrayhn6zsyn0.png" alt="lewwwwwsers" width="790" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem with all of these posters is that, well, you have to buy them, and that’s no fun. So let’s see if we can build something like this ourselves, and actually make it a more surprising experience by connecting two APIs to generate shady messaging like this at random. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74uean0wyf2ne26v842q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74uean0wyf2ne26v842q.png" alt="You Got This fr fr" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;not sure what 'this' is, but you got it!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Temporarily shelve that resolution against unproductive and negative thinking and let’s see what we can build using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Affirmations API &lt;/li&gt;
&lt;li&gt;Unsplash API&lt;/li&gt;
&lt;li&gt;TanStack for a web frontend&lt;/li&gt;
&lt;li&gt;Cloudinary image storage to display the image so you can print off a PDF of your poster&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You'll need two API keys, one for the ever-useful affirmations.dev API and one for Unsplash images. You'll also need a Cloudinary account, so &lt;a href="https://cloudinary.com?utm_campaign=5266-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=dry-january" rel="noopener noreferrer"&gt;create one for free here&lt;/a&gt; if you aren't already setup. You then need to gather your Cloudinary API key, secret, cloud name and build an &lt;strong&gt;upload preset&lt;/strong&gt; so that you can upload images neatly into Cloudinary. Learn how to get credentials &lt;a href="https://cloudinary.com/documentation/developer_onboarding_faq_find_credentials?utm_campaign=5266-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=dry-january#banner" rel="noopener noreferrer"&gt;here&lt;/a&gt; and how to create an upload preset &lt;a href="https://cloudinary.com/documentation/upload_presets?utm_campaign=5266-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=dry-january#creating_and_managing_upload_presets" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’re going to grab one of the pithy affirmations from the API, extract the longest word as the central message to display with plenty of kerning, and then send that word to Unsplash to find a match. Store the image returned in Cloudinary and display the lot a as a printable poster, as a stacked image, word, and affirmation on a black background, like this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88v2nxoyha0hgf9uywip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88v2nxoyha0hgf9uywip.png" alt=" " width="800" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The miracle of survival&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Continuing my tradition of building useless things to try new stacks, let’s give &lt;a href="https://tanstack.com/" rel="noopener noreferrer"&gt;TANStack&lt;/a&gt; a whirl. TanStack is a React framework that's pitched as a less 'magical' alternative to Next.js. I also built this same app using Next.js, to compare the two codebases, for learning purposes. Check out the GitHub repo with the two apps &lt;a href="https://github.com/jlooper/affirmation-posters" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure; I used Cursor to help build the two comparable apps in the demo repo. According to Cursor, the difference between the two boils down to your personal dev preference: "Next.js is a full-stack framework with conventions and a large ecosystem. TanStack Router/Start is a type-safe, flexible routing solution that can be extended to full-stack. The choice often comes down to preferring conventions (Next.js) vs. flexibility and type safety (TanStack)."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some differences between these two frameworks in the context of this app include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-side data fetching architecture
&lt;/h3&gt;

&lt;p&gt;Next.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Server Actions ('use server') and async Server Components&lt;/li&gt;
&lt;li&gt;Server actions in actions.ts are callable from both server and client
TanStack:&lt;/li&gt;
&lt;li&gt;Uses route loaders and createServerFn for server functions&lt;/li&gt;
&lt;li&gt;Data fetching happens in the route's loader function&lt;/li&gt;
&lt;li&gt;Loaders run on the server before the component renders&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Environment variable access
&lt;/h3&gt;

&lt;p&gt;Next.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses process.env.* for environment variables&lt;/li&gt;
&lt;li&gt;Variables are available on the server by default
TanStack:&lt;/li&gt;
&lt;li&gt;Uses import.meta.env.VITE_* (Vite convention)&lt;/li&gt;
&lt;li&gt;Only variables prefixed with VITE_ are exposed to the client
buildCloudinaryUrl.ts, because this app was built with Vite as its build tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Difference in image handling
&lt;/h3&gt;

&lt;p&gt;Next.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images are displayed using the built-in optimized  component 
TanStack:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TanStack doesn't have such a component built-in, so you need to make sure to optimize your images! This is consistent with its 'magic-free' approach.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Guess what! You can easily optimize your images in Cloudinary. While there are &lt;a href="https://cloudinary.com/blog/how-to-build-a-tanstack-start-project-for-image-optimization-and-uploading?utm_campaign=5266-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=dry-january" rel="noopener noreferrer"&gt;other ways to do this&lt;/a&gt;, I decided to just add some URL transformations to the Cloudinary URL returned once the Unsplash image is uploaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function buildCloudinaryImageUrl(publicId: string): string {
  const cloudName = import.meta.env.VITE_CLOUDINARY_CLOUD_NAME

  if (!cloudName) {
    throw new Error('Cloudinary cloud name not configured')
  }

  const plainPublicId = publicId

  const transformations = [
    `c_fill,w_1200,h_800`, // Fill to 1200x800 for poster feel
    `f_auto`, // Auto format
    `q_auto`, // Auto quality
  ].join('/')

  // Return the complete Cloudinary URL with transformations `https://res.cloudinary.com/${cloudName}/image/upload/${transformations}/${plainPublicId}`
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you're able to grab an affirmation from the API and use it to query Unsplash, storing the result into Cloudinary and using it to build your poster. In TANStack it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const fetchNewAffirmationData = createServerFn({
  method: 'GET',
}).handler(async (): Promise&amp;lt;AffirmationData&amp;gt; =&amp;gt; {
  // First fetch the affirmation
  const affirmationData = await getAffirmation()

  // Extract the longest word from the affirmation to use as image search query
  const words = affirmationData.affirmation.split(' ').filter(word =&amp;gt; word.length &amp;gt; 0)
  const longestWord = words.reduce((longest, current) =&amp;gt; 
    current.length &amp;gt; longest.length ? current : longest, ''
  ).toLowerCase()

  // Fetch an image based on the longest word
  const accessKey = import.meta.env.VITE_UNSPLASH_ACCESS_KEY
  if (!accessKey) {
    throw new Error('Unsplash access key not configured')
  }

  const unsplashResponse = await fetch(
    `https://api.unsplash.com/photos/random?client_id=${accessKey}&amp;amp;orientation=landscape&amp;amp;query=${encodeURIComponent(longestWord)}`
  )
  if (!unsplashResponse.ok) {
    throw new Error('Failed to fetch Unsplash photo')
  }
  const unsplashData = await unsplashResponse.json() as { id: string; urls: { full: string } }

  // Upload the Unsplash image to Cloudinary
  const publicId = await uploadToCloudinary(unsplashData.urls.full)

  // Build the Cloudinary URL with transformations
  const cloudinaryUrl = buildCloudinaryImageUrl(publicId)

  return {
    affirmation: affirmationData.affirmation,
    longestWord,
    cloudinaryUrl,
    photoId: unsplashData.id,
    publicId,
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And once you have all your pieces in place, you'll be able to view that matching affirmation, image, and printable styling all in one place so you can build a PDF poster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handlePrint = () =&amp;gt; {
    if (posterRef.current) {
      const printWindow = window.open('', '_blank')
      if (printWindow) {
        printWindow.document.write(`
          &amp;lt;!DOCTYPE html&amp;gt;
          &amp;lt;html&amp;gt;
            &amp;lt;head&amp;gt;
              &amp;lt;title&amp;gt;Affirmation Poster&amp;lt;/title&amp;gt;
              &amp;lt;style&amp;gt;
                ...some fancy styling
              &amp;lt;/style&amp;gt;
            &amp;lt;/head&amp;gt;
            &amp;lt;body&amp;gt;
              &amp;lt;div class="poster-container"&amp;gt;
                &amp;lt;img src="${imageUrl}" alt="${affirmation}" class="poster-image" /&amp;gt;
                &amp;lt;h1&amp;gt;${longestWordWithDots}&amp;lt;/h1&amp;gt;
                &amp;lt;div class="poster-line"&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;h2&amp;gt;${affirmation}&amp;lt;/h2&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/body&amp;gt;
          &amp;lt;/html&amp;gt;
        `)
        printWindow.document.close()

        // Wait for image to load before printing
        setTimeout(() =&amp;gt; {
          printWindow.focus()
          printWindow.print()
          printWindow.onafterprint = () =&amp;gt; printWindow.close()
        }, 250)
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So there you have it...you've built a delightfully useless little app that combines a few API calls with optimized image presentation to make you feel a little less seasonally-affected, we hope. &lt;/p&gt;

&lt;p&gt;Happy New Year! If you liked this little app, there are plenty more where it comes from from your friends at Cloudinary Developer Relations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxcjllxeu58jo1thwi6z5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxcjllxeu58jo1thwi6z5.png" alt="In boca leone" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://cloudinary.com/users/register_free?utm_campaign=5266-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=dry-january" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>fullstack</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Hacktoberfest as a First-Time Maintainer</title>
      <dc:creator>Eric Portis</dc:creator>
      <pubDate>Fri, 19 Dec 2025 20:02:29 +0000</pubDate>
      <link>https://dev.to/cloudinary/hacktoberfest-as-a-first-time-maintainer-2pkn</link>
      <guid>https://dev.to/cloudinary/hacktoberfest-as-a-first-time-maintainer-2pkn</guid>
      <description>&lt;p&gt;Earlier this year, I took over maintenance duties on &lt;a href="https://github.com/cloudinary-community" rel="noopener noreferrer"&gt;Cloudinary's Community Libraries&lt;/a&gt;. Fast forward a few months, and my team was gearing up to participate in Digital Ocean's annual &lt;a href="https://hacktoberfest.com" rel="noopener noreferrer"&gt;Hacktoberfest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reader, I was worried.&lt;/p&gt;

&lt;p&gt;I was still very much familiarizing myself with how the libraries worked, and trying to wrap my head around their issue backlogs. And while I'd never participated in a Hacktoberfest in any capacity before, I had read some &lt;a href="https://joel.net/how-one-guy-ruined-hacktoberfest2020-drama" rel="noopener noreferrer"&gt;scary&lt;/a&gt; &lt;a href="https://domenic.me/hacktoberfest/" rel="noopener noreferrer"&gt;things&lt;/a&gt; from other maintainers, who complained of an annual avalanche of spam.&lt;/p&gt;

&lt;p&gt;What’s worse than an avalanche? A &lt;em&gt;turbo&lt;/em&gt;-avalanche powered by the recent explosion of AI-powered coding tools. As September drew to a close, I imagined hundreds of AI-written PRs coming in, each hundreds of lines long, all based on prompts that poorly understood the underlying issues, containing code that the submitters themselves didn't understand, but which we would be expected to review.&lt;/p&gt;

&lt;p&gt;The good news: It wasn't that bad. While the underlying incentive structures of Hacktoberfest &lt;em&gt;did&lt;/em&gt; generate some spam, and while almost all of that spam smelled like AI, our team was able to get ahead of many problems with strong policies, and would have been able to prevent many more with a bit of additional preparation and planning. &lt;/p&gt;

&lt;p&gt;The worst PRs were about as bad as I expected, but there weren't as many of them as I'd feared, and the best PRs were actually, &lt;em&gt;dare I say&lt;/em&gt;, really good? Hackathon participants tackled some of the issues I'd been putting off addressing for months, helping me push the libraries forward.&lt;/p&gt;

&lt;p&gt;All in all, while participation as a maintainer did take a &lt;em&gt;lot&lt;/em&gt; of time (and we're in active discussions about whether or not we want to participate next year), it also provided some tangible value, without overwhelming us. And hopefully it helped some folks get more comfortable with the mechanics of open source along the way.&lt;/p&gt;

&lt;p&gt;If you're thinking about participating as a maintainer in years to come, read on to learn about what worked for us — and what didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrowing Scope
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;best&lt;/em&gt; decision we made was to limit participation to fixes for a specific set of existing issues, which we identified with a &lt;code&gt;Hacktoberfest&lt;/code&gt; tag. This addressed a few issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We had a clear mandate to quickly close any drive-by, low-value PRs (e.g. "improve docs").&lt;/li&gt;
&lt;li&gt;We could spend our time during Hacktoberfest reviewing PRs rather than trying to reproduce new issues or define new features.&lt;/li&gt;
&lt;li&gt;We could further limit contributions to issues whose fixes would be reasonably straightforward, allowing for quick reviews.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It was a good theory! In practice, we applied the &lt;code&gt;Hacktoberfest&lt;/code&gt; tag fairly liberally, and &lt;em&gt;did&lt;/em&gt; spend some time reproducing issues, specifying new features, and reviewing complex PRs. This led to a lot of work and some very long review delays.&lt;/p&gt;

&lt;p&gt;We weren't stingy enough with the tag because we set a goal for how many contributions we wanted to accept by the end of the month, &lt;em&gt;up front&lt;/em&gt;, and tagged that many issues, rather than seeing how many issues were going to be a good fit for Hacktoberfest first, and using &lt;em&gt;that&lt;/em&gt; number to set the goal.&lt;/p&gt;

&lt;p&gt;We had good reasons to set a number ahead of time. For one, it's always good to define a success metric up front so that you can quantify (and communicate) success or failure; we set the quota based on the number of successful submissions that we received in &lt;a href="https://cloudinary.com/blog/hacktoberfest-open-source-celebration-cloudinary" rel="noopener noreferrer"&gt;2024&lt;/a&gt;?utm_source=dev-dot-to&amp;amp;utm_content=hacktoberfest2025). For two, we were offering our own swag (a plushie Cloudinary unicorn) to contributors of accepted PRs, and it's best to know how many of those you're going to need well ahead of time.&lt;/p&gt;

&lt;p&gt;The end result, though, was a &lt;code&gt;Hacktoberfest&lt;/code&gt; tag on a number of issues that I only poorly or vaguely understood, which came back to bite us later.&lt;/p&gt;

&lt;p&gt;Knowing that we had a quota to hit did motivate me to do one good thing; I did a full docs review and identified and filed a handful of minor issues, which would be easy to fix and review. It would have been much (much!) faster to fix these myself, as soon as I identified them, but:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I wouldn't have done the docs review without Hacktoberfest.&lt;/li&gt;
&lt;li&gt;One of the best things Hacktoberfest does is invite people who are new to open source to learn the mechanics of it. In some ways, it doesn't matter what the contributions are, as long as new contributors are "getting their feet wet" forking, committing, PRing, and responding to feedback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, the stage was set. As October came around, the PRs started to come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hacktoberfest Begins
&lt;/h2&gt;

&lt;p&gt;Most of the PRs we received for the month came in during the first week. People were also very good at identifying which issues would be easiest to fix. All of my "easy" issues were taken off of the board immediately. We received a number of the predicted nonsensical drive-bys that were easy to close, but after that, it was time to chew through dozens of substantive PRs.&lt;/p&gt;

&lt;p&gt;It wasn't long before I started to regret that we'd applied the tag to some poorly defined and understood issues. The most embarrassing of these was: Someone submitted a "fix" for an issue, but their changes seemed unrelated to the problem at hand. Turns out, the issue had &lt;em&gt;already&lt;/em&gt; been fixed a year ago, as part of a much broader update, but the issue was never closed. My guess at what happened is that the submitter fed the issue into Cursor, which, being asked to solve a solved problem, did its best.&lt;/p&gt;

&lt;p&gt;It was clear that many submissions were created with the help of AI. It was also clear — both in the initial PRs that came in and in the reviews and discussions that followed — that the humans behind these submissions had varying levels of understanding of what they were trying to accomplish, and how their PRs were accomplishing it. The more understanding they had, the better things went. A number of follow-up conversations — where we tried to clarify the issue or suggest possible alternate paths forward — went nowhere, leading to closed PRs and wasted time on both sides.&lt;/p&gt;

&lt;p&gt;However, the quality of the highest-quality submissions was well beyond my expectations. A handful of contributors clearly understood the underlying frameworks better than I do, and took the time to suggest thoughtful solutions to nuanced problems that we hadn't yet been able to tackle. I'm extremely grateful for their time and effort, which was worth far more than a unicorn plushie and a Digital Ocean T-shirt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overwhelmed, but Just a Little
&lt;/h2&gt;

&lt;p&gt;Because a number of the issues turned out to be rather thorny (and because I and the other technical reviewers had more to do in October than review Hacktoberfest PRs), review times stretched to weeks, which I know was frustrating for contributors. In a couple of cases, we were not able to either accept or reject PRs before the November 15 deadline. We sent those contributors a unicorn anyway, but if the real purpose of Hacktoberfest is to familiarize folks with the mechanics of open source, learning that things are surprisingly complicated and your code might never land isn't a great lesson. Again, I wish we'd been a little more careful about which issues we invited people to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth It?
&lt;/h2&gt;

&lt;p&gt;From Cloudinary's side, it's hard to measure the value of a thing like Hacktoberfest. Our community library docs are in better shape now, and dozens of small fixes that would have been easy to put off have now been done. In addition, we made real progress on a handful of meaty issues that I'd been putting off tackling for months. And, hopefully, the participants are more familiar with Cloudinary and our SDKs, and are more likely to use us as they continue to build and grow their careers.&lt;/p&gt;

&lt;p&gt;At the same time, the slow (and in a couple of cases, unresolved) reviews may have had the opposite effect, driving participants away. And the cost in time to Cloudinary was real, as we prioritized Hacktoberfest work over other projects in October and November.&lt;/p&gt;

&lt;p&gt;Personally, I'm definitely glad I did it, once. Whether we as a company decide to do it again next year remains to be seen.&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://cloudinary.com/users/register_free?utm_campaign=4870-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=hacktoberfest-maintainer" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>community</category>
    </item>
    <item>
      <title>Build Holiday Card Collages with One Line of Code</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Thu, 04 Dec 2025 14:48:59 +0000</pubDate>
      <link>https://dev.to/cloudinary/build-holiday-card-collages-with-one-line-of-code-3cp5</link>
      <guid>https://dev.to/cloudinary/build-holiday-card-collages-with-one-line-of-code-3cp5</guid>
      <description>&lt;p&gt;In this article, I'm not going to show you much code. Instead, I'm going to show you how you can build a holiday card collage, complete with a ribbon footer with text, a background, and 5 styled photos with rounded, colored borders &lt;strong&gt;in &lt;em&gt;one line&lt;/em&gt;&lt;/strong&gt; using the magic of Cloudinary's transformations. &lt;/p&gt;

&lt;p&gt;This is way cool, so buckle up and let me show you how I built this URL-generating app for your holiday card-sending needs. You can try it right now &lt;a href="https://photocardmaker.netlify.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;; it generates cards that look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqna10jwiurvmzdb9d5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqna10jwiurvmzdb9d5f.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Maybe this tool will even save you some money as printing photo-quality holiday cards can get expensive! &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Build an app, any app
&lt;/h2&gt;

&lt;p&gt;I have been enjoying building Astro apps, and, let's face it, using Cursor makes architecting this sort of thing a real breeze. Use your favorite AI tool to craft an app that will allow you to upload five photos to Cloudinary, rearrange them, and generate a URL to build the holiday card. &lt;/p&gt;

&lt;p&gt;💡 Here's a tip. If you are looking to quickly scaffold an app via prompting in Cursor or your IDE of choice, you can get better results by using two tools: ChatGPT to build the prompt, and Cursor to execute it.&lt;/p&gt;

&lt;p&gt;My initial lazy half-baked idea sent over to ChatGPT:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;what kind of demo could I build to create a holiday photo card maker for cloudinary&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ChatGPT, of course, thought I wanted it to build an app for me, and went straight to scaffolding a Next.js app with code everywhere. A few clarifications later, after requesting Astro for use in Cursor...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;create this as a prompt I can give to cursor&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...ChatGPT generated a really nice, detailed prompt ready for building which you can read in full &lt;a href="https://gist.github.com/jlooper/cf5e18996ce86d00b5393de25fb0b017" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By having this kind of very detailed prompt, Cursor is able to build a perfectly reasonable app on the first try. Then it's up to the engineer to tweak it, redesign it, and add elements while keeping the core functionality of uploading and URL-building intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add some extras and get ready to upload
&lt;/h2&gt;

&lt;p&gt;I went ahead and changed the app colors for a wintry, cozy look, adding some falling snow, a popup elf with changing holiday messages (can you find him?), glowing lights, and a candy cane border, mostly borrowed from &lt;a href="https://codepen.io/alvaromontoro/pen/xxXrzqj" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;. Just because we use AI doesn't mean we can't continue to make interesting interfaces! And AI can help you do that, too. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyemqooblxuwb5a5bajg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyemqooblxuwb5a5bajg1.png" alt="The app's top" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After those fun things are complete, you can start making sure that you're set up to use Cloudinary's image upload and image transformation capabilities. To make this work, you need to ensure that you have some environment variables set up. Create an account on &lt;a href="https://cloudinary.com?utm_source=dev-dot-to&amp;amp;utm_content=holiday-card" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; (there's a generous free tier) and grab your API keys from the console by clicking the "⚙️" gear icon in the left nav and selecting 'API Keys':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fou05wjbtcfrvkec1ctae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fou05wjbtcfrvkec1ctae.png" alt="Finding your API keys in Cloudinary" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You want to add those keys to your .env file locally, and then make sure they're available on your hosting service when you push your app to the cloud. I use Netlify so I add these keys into the Netlify console before publishing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUBLIC_CLOUDINARY_CLOUD_NAME=&amp;lt;your cloud name&amp;gt;
PUBLIC_CLOUDINARY_UPLOAD_PRESET=christmas-collage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This app also needs what's called an 'Upload Preset', which is essentially a placeholder that helps your app figure out where your uploaded images should go. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up that preset by clicking the 'Uploads' navigation link that's right under the 'API Keys' navigation on the left. This will let you set up an unsigned preset. &lt;/li&gt;
&lt;li&gt;Call it 'christmas-collage' (or something else, and make sure to update your app). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c1jom2vo9t8s849muat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c1jom2vo9t8s849muat.png" alt="Upload Preset area" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure you have a folder set up by clicking &lt;code&gt;Assets &amp;gt; folders&lt;/code&gt; on the left nav in the Cloudinary console. Call this folder 'collages'. &lt;/li&gt;
&lt;li&gt;Also create a folder called 'holiday-assets' and add a collage background image and a background image for your ribbon element on the card. I used a white pixel image and a fancy holiday background like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fbeanpot-studio%2Fimage%2Fupload%2Fv1763602104%2Fholiday-assets%2Fcollage-bg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fbeanpot-studio%2Fimage%2Fupload%2Fv1763602104%2Fholiday-assets%2Fcollage-bg.jpg" alt="Background" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@jeshoots?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;JESHOOTS.COM&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/assorted-christmas-ornaments-7VOyZ0-iO0o?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Watch the URL builder work
&lt;/h2&gt;

&lt;p&gt;Now here's the interesting bit. Once you're done messing with your interface to make it look cute, take a look at the very clever way that your card is generated. First, you upload a series of 5 photos of your family to the app, and the interface lets you rearrange them into the order in the card that you want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qzl7l1ed0m6jt4m67c6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qzl7l1ed0m6jt4m67c6.png" alt="uploading images for the card" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;True story, my daughter asked me what the difference is between this and PicCollage, the answer being "Mom made it!"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The URL that is generated, packed with Cloudinary image transformations, looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://res.cloudinary.com/jen-demos/image/upload/w_1600,h_900,c_fill,q_auto,f_auto
/l_holiday-assets:jhi9cpio4ahxomhagwvs/c_fill,w_473,h_680/r_25,bo_8px_solid_rgb:FFD700/fl_layer_apply,g_north_west,x_50,y_50
/l_holiday-assets:d9njoekpa1zzzepy2y5d/c_fill,w_473,h_426/r_25,bo_8px_solid_rgb:FFD700/fl_layer_apply,g_north_west,x_563,y_50
/l_holiday-assets:nm9i3sjh6c3mkfj0ari1/c_fill,w_473,h_214/r_25,bo_8px_solid_rgb:FFD700/fl_layer_apply,g_north_west,x_563,y_516
/l_holiday-assets:rg0c1ul7ux1gcgl8c2lr/c_fill,w_473,h_214/r_25,bo_8px_solid_rgb:FFD700/fl_layer_apply,g_north_west,x_1076,y_50
/l_holiday-assets:d0brmcfuqfyzwtelsiyt/c_fill,w_473,h_426/r_25,bo_8px_solid_rgb:FFD700/fl_layer_apply,g_north_west,x_1076,y_304
/l_holiday-assets:white-pixel/c_fill,w_1600,h_100/o_70/fl_layer_apply,g_south,y_20
/l_text:Pacifico_70:Our%20Family%20-%20Holiday%202025,co_rgb:000000/fl_layer_apply,g_center,y_380
/holiday-assets/collage-bg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, that one line created this card:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fjen-demos%2Fimage%2Fupload%2Fw_1600%2Ch_900%2Cc_fill%2Cq_auto%2Cf_auto%2Fl_holiday-assets%3Ajhi9cpio4ahxomhagwvs%2Fc_fill%2Cw_473%2Ch_680%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_50%2Cy_50%2Fl_holiday-assets%3Ad9njoekpa1zzzepy2y5d%2Fc_fill%2Cw_473%2Ch_426%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_563%2Cy_50%2Fl_holiday-assets%3Anm9i3sjh6c3mkfj0ari1%2Fc_fill%2Cw_473%2Ch_214%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_563%2Cy_516%2Fl_holiday-assets%3Arg0c1ul7ux1gcgl8c2lr%2Fc_fill%2Cw_473%2Ch_214%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_1076%2Cy_50%2Fl_holiday-assets%3Ad0brmcfuqfyzwtelsiyt%2Fc_fill%2Cw_473%2Ch_426%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_1076%2Cy_304%2Fl_holiday-assets%3Awhite-pixel%2Fc_fill%2Cw_1600%2Ch_100%2Fo_70%2Ffl_layer_apply%2Cg_south%2Cy_20%2Fl_text%3APacifico_70%3AOur%2520Family%2520-%2520Holiday%25202025%2Cco_rgb%3A000000%2Ffl_layer_apply%2Cg_center%2Cy_380%2Fholiday-assets%2Fcollage-bg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fjen-demos%2Fimage%2Fupload%2Fw_1600%2Ch_900%2Cc_fill%2Cq_auto%2Cf_auto%2Fl_holiday-assets%3Ajhi9cpio4ahxomhagwvs%2Fc_fill%2Cw_473%2Ch_680%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_50%2Cy_50%2Fl_holiday-assets%3Ad9njoekpa1zzzepy2y5d%2Fc_fill%2Cw_473%2Ch_426%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_563%2Cy_50%2Fl_holiday-assets%3Anm9i3sjh6c3mkfj0ari1%2Fc_fill%2Cw_473%2Ch_214%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_563%2Cy_516%2Fl_holiday-assets%3Arg0c1ul7ux1gcgl8c2lr%2Fc_fill%2Cw_473%2Ch_214%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_1076%2Cy_50%2Fl_holiday-assets%3Ad0brmcfuqfyzwtelsiyt%2Fc_fill%2Cw_473%2Ch_426%2Fr_25%2Cbo_8px_solid_rgb%3AFFD700%2Ffl_layer_apply%2Cg_north_west%2Cx_1076%2Cy_304%2Fl_holiday-assets%3Awhite-pixel%2Fc_fill%2Cw_1600%2Ch_100%2Fo_70%2Ffl_layer_apply%2Cg_south%2Cy_20%2Fl_text%3APacifico_70%3AOur%2520Family%2520-%2520Holiday%25202025%2Cco_rgb%3A000000%2Ffl_layer_apply%2Cg_center%2Cy_380%2Fholiday-assets%2Fcollage-bg" alt="Photo Card" width="1600" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's break down that one-liner. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Set up the base “card” background:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.../image/upload/w_1600,h_900,c_fill,q_auto,f_auto/ ... /holiday-assets/collage-bg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;w_1600,h_900&lt;/code&gt; – make the final image 1600×900.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c_fill&lt;/code&gt; – crop/resize to fill that aspect ratio.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;q_auto,f_auto&lt;/code&gt; – optimize quality and format automatically (WebP/AVIF/etc).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;holiday-assets/collage-bg&lt;/code&gt; – this is the base background image for the card.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Add the first photo (top left)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/l_holiday-assets:uzfzkx8d5xd0jr1jxzyo/c_fill,w_473,h_680
/r_25,bo_8px_solid_rgb:FFD700/fl_layer_apply,g_north_west,x_50,y_50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;l_holiday-assets:...&lt;/code&gt; – load this image as a layer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c_fill,w_473,h_680&lt;/code&gt; – size it to 473×680, cropping to fill.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r_25&lt;/code&gt; – rounded corners (radius 25).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bo_8px_solid_rgb:FFD700&lt;/code&gt; – 8px solid gold (#FFD700) border.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fl_layer_apply,g_north_west,x_50,y_50&lt;/code&gt; – apply the layer at the top-left, offset 50px from the top and left.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add the remaining images, some taller than others and placed in different areas of the card.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Add a semi-transparent footer bar
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/l_holiday-assets:white-pixel/c_fill,w_1600,h_100/o_70
/fl_layer_apply,g_south,y_20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This bar uses a white-pixel asset as a tiny base and stretches it to &lt;code&gt;w_1600,h_100&lt;/code&gt; to form a strip which is &lt;code&gt;o_70&lt;/code&gt; – 70% opacity (semi-transparent). It's then applied at the bottom (&lt;code&gt;g_south&lt;/code&gt;) with a small vertical offset (&lt;code&gt;y_20&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Finally, add the holiday greeting text
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/l_text:Pacifico_70:Our%20Family%20-%20Holiday%202025,co_rgb:000000
/fl_layer_apply,g_center,y_380
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;l_text:Pacifico_70:...&lt;/code&gt; – create a text layer with Font: Pacifico in size: 70. Give it the text: &lt;code&gt;Our Family - Holiday 2025&lt;/code&gt; with &lt;code&gt;co_rgb:000000&lt;/code&gt; – black text color.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fl_layer_apply,g_center,y_380&lt;/code&gt; – center the text horizontally and shift it vertically to overlay the footer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, this monster URL &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starts with a festive background&lt;/li&gt;
&lt;li&gt;lays out five photos in a collage with rounded corners and gold frames&lt;/li&gt;
&lt;li&gt;adds a semi-transparent white bar at the bottom&lt;/li&gt;
&lt;li&gt;and writes “Our Family - Holiday 2025” in a script font on top of that bar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...rendering the whole card on demand in a single Cloudinary transformation URL. Really nice!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: you can make edits right in the URL to change elements on the card - try adding your own family name and changing the border color.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Enjoy the results
&lt;/h2&gt;

&lt;p&gt;You could save this image and print it for snail mail, or send it right away via email to the folks who are eager to hear from you this holiday season. And you did it in one line. That's awesome!&lt;/p&gt;

&lt;p&gt;Try this in &lt;a href="https://cloudinary.com?utm_source=dev-dot-to&amp;amp;utm_content=holiday-card" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; today, and get one more item checked off your holiday to-do list. Learn more about image transformations &lt;a href="https://cloudinary.com/documentation/image_transformations?utm_source=dev-dot-to&amp;amp;utm_content=holiday-card" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Happy holidays from Cloudinary Developer Relations to you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://cloudinary.com/users/register_free?utm_campaign=4870-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=holiday-card" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>react</category>
      <category>codepen</category>
    </item>
    <item>
      <title>Building a Fashion App Using Cloudinary’s GenAI in React and Node.js</title>
      <dc:creator>Pato</dc:creator>
      <pubDate>Thu, 06 Nov 2025 01:30:09 +0000</pubDate>
      <link>https://dev.to/cloudinary/building-a-fashion-app-using-cloudinarys-genai-in-react-and-nodejs-25k2</link>
      <guid>https://dev.to/cloudinary/building-a-fashion-app-using-cloudinarys-genai-in-react-and-nodejs-25k2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Upload a picture → get &lt;strong&gt;four&lt;/strong&gt; styled looks (elegant, streetwear, sporty, business casual). In this walkthrough we’ll build &lt;strong&gt;FashionistaAI&lt;/strong&gt; with &lt;strong&gt;Cloudinary GenAI&lt;/strong&gt;, &lt;strong&gt;React (Vite)&lt;/strong&gt; on the frontend, and a tiny &lt;strong&gt;Node.js/Express&lt;/strong&gt; backend for secure uploads.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href="https://github.com/cloudinary-devs/Cloudinary-FashionistaAI" rel="noopener noreferrer"&gt;Cloudinary-FashionistaAI&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What you’ll build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A React app that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uploads an image to your Node backend&lt;/li&gt;
&lt;li&gt;asks Cloudinary GenAI to swap tops/bottoms&lt;/li&gt;
&lt;li&gt;replaces the background&lt;/li&gt;
&lt;li&gt;lets you &lt;strong&gt;recolor&lt;/strong&gt; top or bottom on click&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;A Node.js server that securely uploads files to Cloudinary using the official SDK.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Demo (what it looks like)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The background adapts to the look; each tile is a different style:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elegant&lt;/li&gt;
&lt;li&gt;Streetwear&lt;/li&gt;
&lt;li&gt;Sporty&lt;/li&gt;
&lt;li&gt;Business casual&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizvn599ke0w4kmugmf2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizvn599ke0w4kmugmf2a.png" alt="Fashonista App" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node 18+ and npm&lt;/li&gt;
&lt;li&gt;A free &lt;a href="https://cloudinary.com" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; account&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;GenAI features may need to be enabled depending on your plan.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic React/TypeScript familiarity (optional but helpful)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1) Set up Cloudinary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create/Login&lt;/strong&gt; → &lt;strong&gt;Settings → Product Environments&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Confirm your &lt;strong&gt;Cloud name&lt;/strong&gt; (keep it consistent across tools).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings → Product Environments → API Keys&lt;/strong&gt; → &lt;strong&gt;Generate New API Key&lt;/strong&gt;.
Save: &lt;strong&gt;Cloud name&lt;/strong&gt;, &lt;strong&gt;API key&lt;/strong&gt;, &lt;strong&gt;API secret&lt;/strong&gt; (secret stays on the server).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2) Bootstrap the React app (Vite)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a Vite + React + TS app&lt;/span&gt;
npm create vite@latest fashionistaai &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; react-ts
&lt;span class="nb"&gt;cd &lt;/span&gt;fashionistaai

&lt;span class="c"&gt;# Frontend deps&lt;/span&gt;
npm i axios @cloudinary/react @cloudinary/url-gen

&lt;span class="c"&gt;# Dev tooling&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitejs/plugin-react

&lt;span class="c"&gt;# Backend deps (we'll use one package.json for both)&lt;/span&gt;
npm i express cors cloudinary multer streamifier dotenv

&lt;span class="c"&gt;# Nice-to-have dev deps&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; nodemon concurrently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3) Configure Vite dev proxy (frontend → backend)
&lt;/h2&gt;

&lt;p&gt;Create/replace &lt;code&gt;vite.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;changeOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forwards any &lt;code&gt;/api/*&lt;/code&gt; calls to the Express server on port &lt;code&gt;8000&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4) Environment variables
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Server (Node) reads these:&lt;/span&gt;
&lt;span class="nv"&gt;CLOUDINARY_CLOUD_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_CLOUD_NAME
&lt;span class="nv"&gt;CLOUDINARY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_KEY
&lt;span class="nv"&gt;CLOUDINARY_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_API_SECRET

&lt;span class="c"&gt;# Frontend (Vite) reads those prefixed with VITE_&lt;/span&gt;
&lt;span class="nv"&gt;VITE_CLOUDINARY_CLOUD_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_CLOUD_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt; expose &lt;code&gt;CLOUDINARY_API_SECRET&lt;/code&gt; on the frontend. That’s why we’re using a server.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5) Node/Express backend (&lt;code&gt;server.js&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;server.js&lt;/code&gt; in the project root. You can find the complete server file &lt;a href="https://github.com/cloudinary-devs/Cloudinary-FashionistaAI/blob/main/server.js" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's explain the main parts of the &lt;code&gt;server.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cloud_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDINARY_CLOUD_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDINARY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;api_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDINARY_API_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part responsible for connecting your server to your Cloudinary account by pulling your credentials from &lt;code&gt;.env&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// 10MB&lt;/span&gt;
  &lt;span class="na"&gt;fileFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/image&lt;/span&gt;&lt;span class="se"&gt;\/(&lt;/span&gt;&lt;span class="sr"&gt;png|jpe&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;g|webp&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Only PNG/JPG/WEBP images are allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember the npm dependency &lt;code&gt;multer&lt;/code&gt; we installed? Time to put it to work! Multer stores uploaded files in memory, not on disk which means is faster &amp;amp; simpler. Limits uploads to 10MB and only accepts PNG, JPG, WEBP images.&lt;/p&gt;

&lt;p&gt;Why in memory?&lt;/p&gt;

&lt;p&gt;Because Cloudinary works great with streams, no need to save files to disk!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cloudinary error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;streamifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's talk about uploading images to the Cloudinary using the NodeJS SDK. &lt;/p&gt;

&lt;p&gt;The Cloudinary NodeJS SDK expects either a file path or a stream. Since &lt;code&gt;multer&lt;/code&gt; stored the image in memory, we convert the buffer to a readable stream then we pipe the stream directly into Cloudinary’s upload_stream(). When Cloudinary finishes, it calls the callback, and we return the Cloudinary result to the frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;package.json scripts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;package.json&lt;/code&gt; and add these scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start:both"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"concurrently -k &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm:server&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;npm:dev&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run &lt;strong&gt;both&lt;/strong&gt; servers with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start:both
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Or use two terminals: &lt;code&gt;npm run server&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt;.)&lt;/p&gt;




&lt;h2&gt;
  
  
  6) React UI (&lt;code&gt;src/App.tsx&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Below is a &lt;strong&gt;drop‑in&lt;/strong&gt;, TypeScript friendly version that keeps your original logic but tightens types, separates file vs. Cloudinary images, and reads the cloud name from env. You can find the complete code for the App.tsx here.&lt;/p&gt;

&lt;p&gt;Now, let's dive into the UI code!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the clothing styles
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StyleKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StyleConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STYLES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StyleConfig&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suit jacket for upper body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suit pants for lower body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;office&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;business casual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sport tshirt for upper body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sport shorts for lower body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gym&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sporty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;streetwear shirt for upper body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;streetwear pants for lower body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;street&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;streetwear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;elegant tuxedo for upper body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;elegant tuxedo pants for lower body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gala&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;elegant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The StyleKey type indicates whether the user is recoloring the top or bottom of an outfit, while StyleConfig represents a complete look, including the top, bottom, background, and a human-readable label. The STYLES array acts as a preset wardrobe: each entry specifies what clothing should replace the user’s upper and lower garments, the general background aesthetic for the image, and the style’s name. Each of these presets becomes one of the “cards” displayed in the grid, such as Business Casual, Sporty, Streetwear, or Elegant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submitting to the Backend and Getting the Base Image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setLooks&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
    &lt;span class="nf"&gt;setLoadingStatus&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/generate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multipart/form-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;508&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;508&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="nf"&gt;setBaseImg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;createLooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Upload failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user submits an image, the function begins by clearing any previous errors and removing any previously generated looks. It then builds a &lt;code&gt;FormData&lt;/code&gt; object containing the uploaded image and sends it via a &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;/api/generate&lt;/code&gt; endpoint you created earlier. The backend uploads this file to Cloudinary and returns Cloudinary’s full response, including the crucial &lt;code&gt;public_id&lt;/code&gt;. Once the upload succeeds, the frontend creates a new CloudinaryImage based on that ID, resizes it to &lt;code&gt;508×508&lt;/code&gt; for consistent display, and stores it in baseImg. With the base image ready, the function then calls createLooks(publicId) to generate all of the AI-styled outfit variations&lt;/p&gt;

&lt;h3&gt;
  
  
  Preloading Derived Images (Poll Until Ready)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CloudinaryImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toURL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;setLoadingStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 423 means "still deriving" on Cloudinary&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;423&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error loading image. Please try again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setLoadingStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;preload&lt;/code&gt; function works by converting a &lt;code&gt;CloudinaryImage&lt;/code&gt; into a URL and creating a temporary &lt;code&gt;Image()&lt;/code&gt; object to load it in the background. If the image loads successfully, it marks that particular look as finished by updating &lt;code&gt;loadingStatus[index]&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;. If the load fails, the function sends a HEAD request to check whether Cloudinary is still generating the derived asset, indicated by a &lt;code&gt;423&lt;/code&gt; status code. When this happens—and as long as the maximum number of attempts hasn’t been reached—it retries after an increasing delay. If the error persists for reasons other than derivation, the function sets an error message and stops retrying. &lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Different Looks (Generative Effects)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createLooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;STYLES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generativeReplace&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shirt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generativeReplace&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generativeBackgroundReplace&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// optional: prompt with your background&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generativeRestore&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;setLooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setLoadingStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;imgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To generate each outfit variation, the app creates a new &lt;code&gt;CloudinaryImage&lt;/code&gt; from the same &lt;code&gt;publicId&lt;/code&gt; and applies a series of generative effects: it replaces the shirt with the style’s top, swaps the pants for the style’s bottom, updates the background, and restores the image to remove artifacts. After resizing the result, the image is added to the &lt;code&gt;looks&lt;/code&gt; array and marked as loading. The app then calls &lt;code&gt;preload()&lt;/code&gt; on each look to determine when Cloudinary has finished processing it. This is the step where the “magic wardrobe” is created, turning a single uploaded image into multiple styled variations.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;It replaces the shirt with the style's designated top using &lt;code&gt;generativeReplace().from('shirt').to(style.top)&lt;/code&gt; and swaps the pants for the appropriate bottom using &lt;code&gt;generativeReplace().from('pants').to(style.bottom)&lt;/code&gt;, allowing prompts like “suit jacket for upper body” or “tuxedo pants” to transform the clothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recolor Modal Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openRecolorModal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setSelectedLookIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setOpenModal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyRecolor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;looks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedLookIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="nf"&gt;setLoadingStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedLookIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;setOpenModal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Recolor only the chosen item for the chosen look&lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generativeRecolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STYLES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedLookIndex&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;selectedItem&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;setLooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedLookIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recoloring works by letting the user click any generated look, which opens a modal and stores the index of the selected outfit. Inside the modal, the user chooses whether to recolor the top or bottom and picks a new color. When they confirm, &lt;code&gt;applyRecolor()&lt;/code&gt; marks that look as loading again, applies a new &lt;code&gt;generativeRecolor()&lt;/code&gt; transformation using the appropriate prompt and selected hex value, updates the &lt;code&gt;looks&lt;/code&gt; array, and triggers &lt;code&gt;preload()&lt;/code&gt; to wait for Cloudinary to finish generating the updated image. In essence, the app layers an additional AI transformation on top of an already styled outfit.&lt;/p&gt;

&lt;p&gt;Add some love to your app by adding our &lt;a href="https://github.com/cloudinary-devs/Cloudinary-FashionistaAI" rel="noopener noreferrer"&gt;css&lt;/a&gt; or your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  7) How it works (quick tour)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upload&lt;/strong&gt;: The file is sent to &lt;code&gt;POST /api/generate&lt;/code&gt;. The server uses &lt;code&gt;cloudinary.uploader.upload_stream&lt;/code&gt; to store it and returns the &lt;strong&gt;&lt;code&gt;public_id&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transform&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;generativeReplace().from('shirt').to(style.top)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;generativeReplace().from('pants').to(style.bottom)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generativeBackgroundReplace()&lt;/code&gt; (optionally prompt it to steer the scene)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generativeRestore()&lt;/code&gt; for quality&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recolor&lt;/strong&gt;: On a generated tile, open a modal and apply &lt;code&gt;generativeRecolor(&amp;lt;item&amp;gt;, &amp;lt;hex&amp;gt;)&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;423 handling&lt;/strong&gt;: When the first request for a derived image hits Cloudinary while it’s still being generated, you might see HTTP &lt;strong&gt;423&lt;/strong&gt;. The preload helper retries with backoff; for heavy use, consider preparing &lt;em&gt;eager transformations&lt;/em&gt; on upload.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  8) Testing locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install (already done if you followed along)&lt;/span&gt;
npm i

&lt;span class="c"&gt;# Run both servers&lt;/span&gt;
npm run start:both
&lt;span class="c"&gt;# Frontend: http://localhost:3000&lt;/span&gt;
&lt;span class="c"&gt;# Backend:  http://localhost:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production notes (optional but recommended)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secrets&lt;/strong&gt;: Keep &lt;code&gt;CLOUDINARY_API_SECRET&lt;/code&gt; server‑side only; use environment vars on your host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload presets&lt;/strong&gt;: Lock down transformations and content rules with a Cloudinary upload preset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits&lt;/strong&gt;: Add rate limiting to your API if you open it to the public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt;: Keep the Multer &lt;code&gt;fileFilter&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt; in place; consider scanning/validating uploads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching/CDN&lt;/strong&gt;: Cloudinary URLs are CDN‑backed; reusing the same &lt;code&gt;public_id&lt;/code&gt; improves cache hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: Provide helpful &lt;code&gt;alt&lt;/code&gt; text for generated images (the example includes captions).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrap‑up
&lt;/h2&gt;

&lt;p&gt;FashionistaAI shows how a small React app plus Cloudinary’s GenAI can turn one image into four on‑brand looks with background changes and easy recoloring. Fork it, tweak the prompts, and ship your own AI‑powered try‑on experience.&lt;/p&gt;

&lt;p&gt;If you build something with this, drop a link—DEV readers will want to see it!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>react</category>
    </item>
    <item>
      <title>Hacktoberfest 2025: Open Source Celebration With Cloudinary</title>
      <dc:creator>Pato</dc:creator>
      <pubDate>Fri, 10 Oct 2025 17:54:57 +0000</pubDate>
      <link>https://dev.to/cloudinary/hacktoberfest-2025-open-source-celebration-with-cloudinary-1il</link>
      <guid>https://dev.to/cloudinary/hacktoberfest-2025-open-source-celebration-with-cloudinary-1il</guid>
      <description>&lt;p&gt;Hacktoberfest is back for 2025 — and Cloudinary is joining the celebration! 🎉&lt;br&gt;
We’re excited to recognize the power of open source and the developers who keep the community thriving.&lt;/p&gt;

&lt;p&gt;This year, we’ve opened up a wide range of SDKs and issues ready for your contributions. Whether you’re fixing bugs, improving docs, or building new features, your pull requests help make Cloudinary better for everyone.&lt;/p&gt;

&lt;p&gt;And yes — there’s &lt;strong&gt;swag&lt;/strong&gt;. 👕&lt;/p&gt;

&lt;h2&gt;
  
  
  🌟 What’s Hacktoberfest?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hacktoberfest&lt;/strong&gt; is a month-long celebration of open source that happens every October. It’s a chance for developers around the world to contribute to projects, grow their skills, and earn recognition (and sometimes prizes!) for their pull requests.&lt;/p&gt;

&lt;p&gt;While the official Hacktoberfest program now offers digital badges, &lt;strong&gt;Cloudinary is keeping the swag tradition alive.&lt;/strong&gt;&lt;br&gt;
Make a valid contribution to any of our participating repositories and you could snag one of &lt;strong&gt;30 exclusive Cloudinary swag packs&lt;/strong&gt;, featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🦄 A limited-edition &lt;strong&gt;2025 Hacktoberfest T-shirt&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✨ Custom &lt;strong&gt;Cloudinary stickers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🦄 The fan-favorite &lt;strong&gt;Cloudinary unicorn plushie&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧩 How You Can Contribute
&lt;/h2&gt;

&lt;p&gt;We’re opening up several SDKs and community projects for Hacktoberfest participants.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Official SDKs&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudinary/cloudinary_flutter" rel="noopener noreferrer"&gt;Cloudinary Flutter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudinary/cloudinary-react-native" rel="noopener noreferrer"&gt;Cloudinary React Native&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Community SDKs&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudinary-community/next-cloudinary" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudinary-community/cloudinary-laravel" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudinary-community/astro-cloudinary" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudinary-community/svelte-cloudinary" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 To confirm a repo is part of the event, look for the &lt;strong&gt;“Hacktoberfest”&lt;/strong&gt; label in the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  📜 Contribution Rules
&lt;/h2&gt;

&lt;p&gt;We love all contributions, but only those following the rules below qualify for swag:&lt;/p&gt;

&lt;p&gt;❌ No spammy or low-effort PRs.&lt;br&gt;
❌ No typo-only fixes.&lt;br&gt;
❌ Don’t submit PRs without approved issues.&lt;br&gt;
❌ Don’t take over issues already assigned to others.&lt;br&gt;
✅ Do have fun and collaborate!&lt;/p&gt;

&lt;p&gt;Also, please ensure your code passes tests and follows project guidelines. Each repo includes detailed &lt;strong&gt;Contributing&lt;/strong&gt; instructions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Cloudinary reserves the right to withhold swag for contributions that don’t meet these standards or the spirit of Hacktoberfest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🎁 What’s in the Swag Pack?
&lt;/h2&gt;

&lt;p&gt;This year’s &lt;strong&gt;Cloudinary Hacktoberfest kit&lt;/strong&gt; includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 × Cloudinary Hacktoberfest T-shirt (Limited Edition)&lt;/li&gt;
&lt;li&gt;2 × Cloudinary Hacktoberfest Stickers (Limited Edition)&lt;/li&gt;
&lt;li&gt;3 × Classic Cloudinary Stickers&lt;/li&gt;
&lt;li&gt;1 × Cloudinary Unicorn Plushie&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🕒 Only &lt;strong&gt;30 swag packs&lt;/strong&gt; are available — first-come, first-served!&lt;/p&gt;

&lt;h2&gt;
  
  
  ❓ Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🌍 Where do you ship?&lt;/strong&gt;&lt;br&gt;
We’ll ship worldwide where possible (excluding sanctioned countries). Some locations, like Brazil, may require a Tax ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 How do I share my address?&lt;/strong&gt;&lt;br&gt;
Once your PR is merged, we’ll send you a form to collect shipping details. Please double-check your address — we’ll ship it exactly as you submit it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧑‍🤝‍🧑 Can I get more than one swag pack?&lt;/strong&gt;&lt;br&gt;
No — one per person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⏰ When do I need to submit my PR?&lt;/strong&gt;&lt;br&gt;
All valid pull requests must be &lt;strong&gt;submitted during October 2025&lt;/strong&gt;. Even if we merge it later, it still counts as long as it’s submitted this month.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Join Us!
&lt;/h2&gt;

&lt;p&gt;Ready to make your mark in open source?&lt;br&gt;
Head over to one of our &lt;a href="https://github.com/cloudinary" rel="noopener noreferrer"&gt;Cloudinary open-source repositories&lt;/a&gt; and start contributing today.&lt;/p&gt;

&lt;p&gt;Together, let’s make &lt;strong&gt;Hacktoberfest 2025&lt;/strong&gt; one to remember — with great code, great community, and great swag. 💙&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>cloudinary</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cloudinary Transformations, But Make It a Mystery Adventure - meet Clara Denari!</title>
      <dc:creator>Jen Looper</dc:creator>
      <pubDate>Tue, 30 Sep 2025 20:51:16 +0000</pubDate>
      <link>https://dev.to/cloudinary/clara-denari-and-the-mysterious-transformations-a-new-way-to-explore-cloudinary-57od</link>
      <guid>https://dev.to/cloudinary/clara-denari-and-the-mysterious-transformations-a-new-way-to-explore-cloudinary-57od</guid>
      <description>&lt;p&gt;Tired of plowing through blogs and scanning through video to learn? Done with 🔥&lt;strong&gt;Tutorial Hell&lt;/strong&gt;🔥? Want to get hands on quickly to explore a toolset? Looking to spice up your world with a little mystery? &lt;/p&gt;

&lt;p&gt;&lt;a href="https://claradenari.com" rel="noopener noreferrer"&gt;Have we got an experience for you.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Introducing a new heroine for the ages: Clara Denari, Master Detective. Join her as she prowls through Boston, recovering stolen property by solving the mysteries left by the nefarious Dr. Chiaro Obscuro. &lt;/p&gt;

&lt;p&gt;This villain, who is both a meticulous kleptomaniac AND someone with considerable long-term memory issues, has done it again. His habit is to hide his ill-gotten gains in obscure places, leaving a trail of clues that only he will understand. This time he’s stolen something valuable and has hidden it...but where?&lt;/p&gt;

&lt;p&gt;It’s up to Clara to save the day. By deciphering each clue, she may be able to find the stolen property...but it’s up to you to help her!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8hrxlhxsjb5m2s1gesl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8hrxlhxsjb5m2s1gesl.png" alt="Homepage of Clara Denari app" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start your adventure here!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow the trail in the story, picking up objects and storing them in inventory. Use Cloudinary transformations to clean a piece of string, brighten an image to reveal hidden text, use image refiners to tidy up lost keys, and discover the secret hiding place of the lost treasure. You’ll even be able to view clues by digging into Content Credentials, which allow you to view an image’s history as it undergoes changes through its lifecycle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlg3jwy022i330oiij0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlg3jwy022i330oiij0i.png" alt="Content Credentials" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Content Credentialing using C2PA standards&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’ll be able to solve the mystery only by getting hands-on with Cloudinary’s powerful image transformations - try them out yourself by creating a free account at &lt;a href="https://cloudinary.com?utm_campaign=4870-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=claradenari" rel="noopener noreferrer"&gt;cloudinary.com&lt;/a&gt;. Solve all 5 challenges and arrive at your destination, key in hand. &lt;/p&gt;

&lt;p&gt;As you progress, you’ll complete labs to work with the clues scattered through Boston. Add your cloud name and the public ID of the image in the Lab to experiment with different transformations that will help you solve the clue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4e9wo5blnh3wzrjlue8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4e9wo5blnh3wzrjlue8.png" alt="Lab screenshot" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clara's Lab&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Préférez-vous le français ? ¿Qué tal el español? Português brasileiro? We have you covered, as the app is fully localized to four languages. Speaking of local, this first edition Clara Denari Adventure features the gritty underside of Boston, USA, cannoli and all. If you’d like Clara to come to your city to solve a mystery, just say the word!&lt;/p&gt;

&lt;p&gt;Because this app is a true learning experience, it features both short embedded quizzes to test your knowledge. Pass the quiz and earn a custom &lt;a href="https://www.holopin.io/@cloudinary" rel="noopener noreferrer"&gt;Holopin&lt;/a&gt;, showing your own detective mastery on your favorite social media channels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm3dp6no2uffm0fcisbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm3dp6no2uffm0fcisbu.png" alt="holopin" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Earn your limited edition Holopin!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ready? Have your trench coat ready and your magnifying glasses polished? Join us on the inaugural Clara Denari Adventure, new from your friends in Developer Relations at Cloudinary. &lt;/p&gt;

&lt;p&gt;Click &lt;a href="https://claradenari.com" rel="noopener noreferrer"&gt;here&lt;/a&gt; to start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It is a happy talent to know how to play.“ - Ralph Waldo Emerson, American writer, 1803–1882&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://cloudinary.com/users/register_free?utm_campaign=4870-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=claradenari" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Build a Docs‑Aware Chatbot with React, Vite, Node, and OpenAI (plus fun DALL·E avatars)</title>
      <dc:creator>Pato</dc:creator>
      <pubDate>Wed, 17 Sep 2025 17:57:11 +0000</pubDate>
      <link>https://dev.to/cloudinary/build-a-docs-aware-chatbot-with-react-vite-node-and-openai-plus-fun-dalle-avatars-1chi</link>
      <guid>https://dev.to/cloudinary/build-a-docs-aware-chatbot-with-react-vite-node-and-openai-plus-fun-dalle-avatars-1chi</guid>
      <description>&lt;h2&gt;
  
  
  What you’ll build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A chat UI that streams answers from your own examples/context&lt;/li&gt;
&lt;li&gt;A Node/Express API that calls OpenAI for text and image generation&lt;/li&gt;
&lt;li&gt;Two cute, auto‑generated avatars (user &amp;amp; assistant)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Demo question shown here: “How do I use the Cloudinary React SDK?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repo (reference): &lt;strong&gt;Cloudinary-Chatbot-OpenAI-Demo&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prereqs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node 18+&lt;/li&gt;
&lt;li&gt;An OpenAI API key stored server‑side (never in the browser). How to create/manage keys: see the official docs. (&lt;a href="https://platform.openai.com/docs/quickstart/step-2-setup-your-api-key?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1) Create the React app (Vite)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# New project&lt;/span&gt;
npm create vite@latest cloudinary-chatbot &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; react
&lt;span class="nb"&gt;cd &lt;/span&gt;cloudinary-chatbot
npm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vite proxy (avoid CORS while developing)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;vite.config.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:6000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;changeOrigin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chatbot UI
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;src/App.jsx&lt;/code&gt; file. You can file the full code of this file in the &lt;a href="https://github.com/cloudinary-devs/Cloudinary-Chatbot-OpenAI-Demo/blob/main/src/App.jsx" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Chat functionality
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inputMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
    &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newMessages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setInputMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newMessages&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;newMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;newMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Server error. Try again.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function sends the user’s message to the backend and updates the chat UI. It first adds the user’s message to the conversation, clears the input, and sets a loading state. Then it POSTs the full message history to &lt;code&gt;/api/chat&lt;/code&gt; and appends the assistant’s reply when the server responds. If the request fails, it adds an error message instead. Finally, it resets the status to idle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating avatars
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;makeAvatars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/avatar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// [{url}, {url}]&lt;/span&gt;
        &lt;span class="nf"&gt;setUserImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setAssistantImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// silently ignore; UI still works without avatars&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;makeAvatars&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make a more realistic chatBot, we are going to generate 2 avatars. In our backend, we have the endpoint &lt;code&gt;/api/avatar&lt;/code&gt;. We call that endpoint, and we assign the avatars one for the user and one for the bot/assistant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add your own CSS or use our existing &lt;a href="https://github.com/cloudinary-devs/Cloudinary-Chatbot-OpenAI-Demo/blob/main/src/App.css" rel="noopener noreferrer"&gt;App.css&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2) Add the backend (Express + OpenAI)
&lt;/h2&gt;

&lt;p&gt;Inside the project root, create a folder named &lt;code&gt;backend&lt;/code&gt; and a file &lt;code&gt;server.js&lt;/code&gt;. We’ll reuse the root &lt;code&gt;package.json&lt;/code&gt; for simplicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install deps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i express dotenv openai
&lt;span class="c"&gt;# optional: nodemon for dev&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment variables
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; in the &lt;strong&gt;project root&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=sk-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server code (Responses API + Images API)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why Responses API?&lt;/strong&gt; It’s the recommended, modern way to generate text and stream outputs going forward; if you’re coming from Chat Completions, see the official migration guide. (&lt;a href="https://platform.openai.com/docs/guides/migrate-to-responses?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why SDK over raw fetch?&lt;/strong&gt; Cleaner code, types, and built‑ins for images. (&lt;a href="https://platform.openai.com/docs/libraries/javascript?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;). You can find the full code to the backend in the &lt;a href="https://github.com/cloudinary-devs/Cloudinary-Chatbot-OpenAI-Demo/tree/main/backend" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;backend/server.js&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Training the chatBot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;demoModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Where is the Cloudinary React SDK documentation?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;See: https://cloudinary.com/documentation/react_integration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Where can I read about Cloudinary image transformations in React?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;See: https://cloudinary.com/documentation/react_image_transformations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How do I display an image using the Cloudinary React SDK?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="s2"&gt;`Use @cloudinary/react and @cloudinary/url-gen. Example:

import { AdvancedImage } from '@cloudinary/react';
import { Cloudinary } from '@cloudinary/url-gen';
import { sepia } from '@cloudinary/url-gen/actions/effect';

const cld = new Cloudinary({ cloud: { cloudName: 'demo' } });
const img = cld.image('front_face').effect(sepia());

&amp;lt;AdvancedImage cldImg={img} /&amp;gt;`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code initializes the OpenAI client on the server using an API key from environment variables, ensuring all AI calls are authenticated. It also defines a &lt;code&gt;demoModel&lt;/code&gt; array containing a series of example question-and-answer pairs. These serve as “conversation seeds” that help guide the AI toward Cloudinary-specific knowledge by showing it how the assistant should respond. Each entry mimics a real chat message, with a &lt;code&gt;role&lt;/code&gt; (&lt;code&gt;user&lt;/code&gt; or &lt;code&gt;assistant&lt;/code&gt;) and &lt;code&gt;content&lt;/code&gt;. Together, these samples act as contextual training data, steering the model to provide accurate Cloudinary documentation links and example React code when similar questions are asked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the chatBot with OpenAI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// System prompt keeps the bot scoped to your docs&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a helpful developer docs assistant. Prefer official Cloudinary docs. Include links when helpful.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;demoModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Convenience: return plain text&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_text&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint takes the user’s chat messages, prepends a system prompt, and mixes in example Q&amp;amp;A pairs to guide the model toward Cloudinary-focused answers. It sends this combined conversation to OpenAI using the gpt-4o-mini model, then returns the assistant’s plain-text reply to the client. If an error occurs, it logs the issue and responds with a 500 error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Avatars with DALL-E
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/avatar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-image-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minimal, cute round animal avatar on flat background, high contrast, centered, no text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;256x256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Return {url} objects for the UI&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint generates simple AI-created avatars. When called, it sends a prompt to OpenAI’s image generation model (&lt;code&gt;gpt-image-1&lt;/code&gt;) requesting two minimal, cute, round animal avatars at 256×256 resolution. The OpenAI API returns image objects containing URLs, which the server maps into a clean &lt;code&gt;{ url }&lt;/code&gt; format for the frontend. If generation fails, the server logs the error and responds with a &lt;code&gt;500&lt;/code&gt; status code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responses API reference (Node): see &lt;strong&gt;Responses&lt;/strong&gt; docs. (&lt;a href="https://platform.openai.com/docs/api-reference/responses?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Images API reference: see &lt;strong&gt;Images&lt;/strong&gt; docs. (&lt;a href="https://platform.openai.com/docs/api-reference/images?lang=node.js&amp;amp;utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3) Dev scripts
&lt;/h2&gt;

&lt;p&gt;Add these to your root &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node backend/server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server:dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon backend/server.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4) Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# terminal 1&lt;/span&gt;
npm run server:dev

&lt;span class="c"&gt;# terminal 2&lt;/span&gt;
npm run dev
&lt;span class="c"&gt;# open http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type:&lt;br&gt;
“&lt;strong&gt;How do I use the Cloudinary React SDK?&lt;/strong&gt;”&lt;br&gt;
You should get a helpful, linked answer pulled in the spirit of your seed examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production notes (important)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never expose your API key&lt;/strong&gt; in client code or public repos. Use env vars and a server. (&lt;a href="https://platform.openai.com/docs/guides/production-best-practices/api-keys?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Prefer &lt;strong&gt;Responses API&lt;/strong&gt; for new builds and streaming; see migration notes if you used Chat Completions before. (&lt;a href="https://platform.openai.com/docs/guides/migrate-to-responses?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;If you want retrieval over your actual docs (beyond hardcoded examples), look at the &lt;strong&gt;Assistants API&lt;/strong&gt; with tools or Retrieval. (&lt;a href="https://platform.openai.com/docs/assistants/overview?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrap‑up
&lt;/h2&gt;

&lt;p&gt;You now have a lightweight, dev‑friendly chatbot that answers from your docs and greets users with generated avatars. From here you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swap the seed examples for real retrieval.&lt;/li&gt;
&lt;li&gt;Add streaming UI for token‑by‑token responses. (&lt;a href="https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses&amp;amp;utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Validate outputs with structured JSON. (&lt;a href="https://platform.openai.com/docs/guides/structured-outputs?lang=node.js&amp;amp;utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI Quickstart (Node) (&lt;a href="https://platform.openai.com/docs/quickstart?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Responses API (text generation) (&lt;a href="https://platform.openai.com/docs/api-reference/responses?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Images API (generation &amp;amp; sizes) (&lt;a href="https://platform.openai.com/docs/api-reference/images?lang=node.js&amp;amp;utm_source=chatgpt.com" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/cloudinary-devs/Cloudinary-Chatbot-OpenAI-Demo/tree/main" rel="noopener noreferrer"&gt;Cloudinary-Chatbot-OpenAI-Demo&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Cloudinary ❤️ developers&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;👉 &lt;strong&gt;&lt;a href="https://cloudinary.com/users/register_free?utm_campaign=4870-&amp;amp;utm_medium=employee_referral&amp;amp;utm_source=dev-dot-to&amp;amp;utm_content=docs-aware-chatbot" rel="noopener noreferrer"&gt;Create your free account&lt;/a&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>react</category>
      <category>cloudinary</category>
      <category>firebase</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
