<?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: devhashfortheweb</title>
    <description>The latest articles on DEV Community by devhashfortheweb (@devhashfortheweb).</description>
    <link>https://dev.to/devhashfortheweb</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%2Fuser%2Fprofile_image%2F3799619%2Fc37c4bdc-61f5-41ab-88e4-1fbe694afef7.png</url>
      <title>DEV Community: devhashfortheweb</title>
      <link>https://dev.to/devhashfortheweb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devhashfortheweb"/>
    <language>en</language>
    <item>
      <title>Teach your LLM to generate consistent AI images for prototyping. One HTML tag, cached on a CDN, any provider.</title>
      <dc:creator>devhashfortheweb</dc:creator>
      <pubDate>Mon, 02 Mar 2026 20:47:11 +0000</pubDate>
      <link>https://dev.to/devhashfortheweb/teach-your-llm-to-generate-consistent-ai-images-for-prototyping-one-html-tag-cached-on-a-cdn-any-m30</link>
      <guid>https://dev.to/devhashfortheweb/teach-your-llm-to-generate-consistent-ai-images-for-prototyping-one-html-tag-cached-on-a-cdn-any-m30</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/devhashfortheweb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F3799619%2Fc37c4bdc-61f5-41ab-88e4-1fbe694afef7.png" alt="devhashfortheweb"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/devhashfortheweb/i-invested-in-stocks-turns-out-they-were-jpegs-4c3f" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I invested in stocks… turns out they were JPEGs.&lt;/h2&gt;
      &lt;h3&gt;devhashfortheweb ・ Mar 1&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#vibecoding&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#vibeimg&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#image&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>vibecoding</category>
      <category>webdev</category>
      <category>vibeimg</category>
      <category>image</category>
    </item>
    <item>
      <title>I invested in stocks… turns out they were JPEGs.</title>
      <dc:creator>devhashfortheweb</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:22:00 +0000</pubDate>
      <link>https://dev.to/devhashfortheweb/i-invested-in-stocks-turns-out-they-were-jpegs-4c3f</link>
      <guid>https://dev.to/devhashfortheweb/i-invested-in-stocks-turns-out-they-were-jpegs-4c3f</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;If you've ever vibe coded a web interface, using an AI editor, a website builder, or just a chatbot, you've probably seen this: beautiful layout, great CSS, and... stock images. Or placeholders. Or worse, nothing at all.&lt;/p&gt;

&lt;p&gt;This happens because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not every LLM is good at everything. Claude can't generate images. OpenAI isn't great at web UI. Each has its strengths.&lt;/li&gt;
&lt;li&gt;Most AI coding tools don't focus on images because they don't really know how to handle them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My usual workflow looked something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick an image model and generate images one by one&lt;/li&gt;
&lt;li&gt;Cry over the keyboard trying to keep a consistent style across them&lt;/li&gt;
&lt;li&gt;Download everything and manually upload to the platform's assets, hoping it even allows that&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I thought: LLMs are already good at writing prompts. Why not teach them how to write prompts that generate great, consistent images? After all, generating text is what they do best.&lt;/p&gt;

&lt;p&gt;I started building my first ever library. It's called &lt;a href="https://github.com/devhashfortheweb/vibe-img" rel="noopener noreferrer"&gt;vibe-img&lt;/a&gt;. Here's how it evolved through several prototypes that didn't work before landing on something that does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prototype 1: the gateway
&lt;/h2&gt;

&lt;p&gt;My first idea was classic. A backend gateway for image generation:&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;vibe-img&lt;/span&gt; &lt;span class="na"&gt;url=&lt;/span&gt;&lt;span class="s"&gt;"gateway.local?prompt=miami+beach&amp;amp;model=openai"&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;A tag that contacts a gateway server, which forwards requests to the image generation providers.&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%2F8y843yxk67b20kj9mwht.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%2F8y843yxk67b20kj9mwht.png" alt="Server architecture" width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simple, but it had problems: scalability, latency, reliability. Image generation has high I/O, and running everything through a single server wasn't going to work.&lt;/p&gt;

&lt;p&gt;So I switched to a decentralized model. Let the browsers do the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prototype 2: the browser does everything
&lt;/h2&gt;

&lt;p&gt;The idea became a JS library that any LLM can include with a single &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag. The frontend calls the image generation provider directly, no intermediaries:&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%2Fjlype02jb9t6c73nwgfm.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%2Fjlype02jb9t6c73nwgfm.png" alt="Browser architecture" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything happens in the frontend. But this opened a new set of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I didn't want to go broke&lt;/strong&gt; regenerating the same images on every page refresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The LLM still doesn't know how to generate consistent images.&lt;/strong&gt; Nobody taught it!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORS hell&lt;/strong&gt; (obviously)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every model has a different API&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how I solved each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hash algorithm (a.k.a. how I stay not-poor)
&lt;/h2&gt;

&lt;p&gt;The core idea is simple. A &lt;code&gt;&amp;lt;vibe-img&amp;gt;&lt;/code&gt; tag has several attributes:&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;vibe-img&lt;/span&gt; &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"recraft"&lt;/span&gt; &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"vector"&lt;/span&gt; &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"a cozy fall cabin in the forest with smoke coming from the chimney"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hash all these attributes together with SHA-256 and use that hash as the cache key on a CDN.&lt;/p&gt;

&lt;p&gt;On the next page load:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if the image exists in cache (local IndexedDB, then CDN)&lt;/li&gt;
&lt;li&gt;If yes: load it instantly, zero API calls&lt;/li&gt;
&lt;li&gt;If no: generate it, cache it, done&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%2Frlmfeq9n4xfrff5tawtd.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%2Frlmfeq9n4xfrff5tawtd.png" alt="the hash algorithm" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The beauty of this: change your CSS all you want, the images don't regenerate. Only attribute changes produce a new hash and a new image. And you can share the page with anyone, the images load from the CDN, no API key needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consistent images with &lt;code&gt;&amp;lt;vibe-theme&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;LLMs are already good at writing prompts that maintain style consistency. You just need to teach them to do it. I did this in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;llms.txt and skills&lt;/strong&gt;: instruction files that teach the LLM how to use the library and write good prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;vibe-theme&amp;gt;&lt;/code&gt;&lt;/strong&gt;: a wrapper tag that appends a shared style prompt to every child image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The theme prompt is simply prepended to each image's prompt at generation time. You can even nest themes for different sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  First experiment: Parisian bookshop
&lt;/h3&gt;

&lt;p&gt;I asked Claude to build me a landing page for a vintage Parisian bookshop using vibe-img with Recraft. Here's what came out:&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%2Fylo29yxn7fh6w6q9g942.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%2Fylo29yxn7fh6w6q9g942.PNG" alt="Parisian bookshop landing page" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the code Claude wrote:&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;vibe-theme&lt;/span&gt; &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"classic vintage hand-drawn book illustration, fine detailed ink lines with soft watercolor washes, warm muted tones, reminiscent of old French editorial illustrations from the 1920s, elegant and refined, cream colored background rgb(245 240 232), no text no watermark no signature"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"recraft"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;quality=&lt;/span&gt;&lt;span class="s"&gt;"hd"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"portrait"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"interior of an old Parisian bookshop, floor-to-ceiling wooden shelves filled with books, a rolling library ladder, a green banker lamp on a desk, Persian rug on parquet floor, cozy intimate atmosphere, viewed from inside"&lt;/span&gt;
    &lt;span class="na"&gt;params=&lt;/span&gt;&lt;span class="s"&gt;'{"controls":{"colors":[{"rgb":[44,62,45]},{"rgb":[184,148,90]},{"rgb":[245,240,232]},{"rgb":[90,70,50]}],"background_color":{"rgb":[245,240,232]}}}'&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Intérieur de la librairie avec ses étagères en bois sombre"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"recraft"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;quality=&lt;/span&gt;&lt;span class="s"&gt;"hd"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"landscape"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"a curated stack of vintage hardcover books on a wooden reading table, a porcelain cup of coffee beside them, a small vase with a single flower, soft light from a nearby window, bookshelves in the background"&lt;/span&gt;
    &lt;span class="na"&gt;params=&lt;/span&gt;&lt;span class="s"&gt;'{"controls":{"colors":[{"rgb":[44,62,45]},{"rgb":[184,148,90]},{"rgb":[245,240,232]},{"rgb":[90,70,50]}],"background_color":{"rgb":[245,240,232]}}}'&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Livres anciens e café sur une table de lecture"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"recraft"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;quality=&lt;/span&gt;&lt;span class="s"&gt;"hd"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"landscape"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"charming Parisian bookshop storefront facade, dark painted wood with elegant signage, large display window showing stacked books, cobblestone street, flower boxes with ivy, classic Haussmann building above, evening glow from inside"&lt;/span&gt;
    &lt;span class="na"&gt;params=&lt;/span&gt;&lt;span class="s"&gt;'{"controls":{"colors":[{"rgb":[44,62,45]},{"rgb":[184,148,90]},{"rgb":[245,240,232]},{"rgb":[90,70,50]}],"background_color":{"rgb":[245,240,232]}}}'&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"La façade de la librairie sur sa rue pavée"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/vibe-theme&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the AI also used Recraft's custom color controls through &lt;code&gt;params&lt;/code&gt;. Advanced provider-specific features, accessible through the same universal tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second experiment: Dolomites cabin
&lt;/h3&gt;

&lt;p&gt;Same thing, different provider. I asked Claude to build a Dolomites cabin landing page using OpenAI:&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%2Ferepkxyyj19re8wif1m0.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%2Ferepkxyyj19re8wif1m0.PNG" alt="Dolomites landing page" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the code Claude wrote:&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;vibe-theme&lt;/span&gt; &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"refined editorial illustration style, muted warm earth tones, soft gradients, elegant and sophisticated alpine atmosphere, clean lines, gentle texture"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"openai"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"wide"&lt;/span&gt;
    &lt;span class="na"&gt;quality=&lt;/span&gt;&lt;span class="s"&gt;"hd"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"exterior view of a traditional wooden A-frame cabin nestled in the jagged snowy peaks of the Italian Dolomites at golden hour sunset, warm glowing windows, pine trees, soft warm light, cinematic composition"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"openai"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"square"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"cozy cabin interior with a crackling stone fireplace, comfortable leather armchair, thick wool rug, warm ambient lighting, window showing snowy mountains"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"openai"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"square"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"winding hiking trail through lush green alpine meadows with wildflowers, majestic jagged Dolomite peaks towering in background under clear blue sky."&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;vibe-img&lt;/span&gt;
    &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"openai"&lt;/span&gt;
    &lt;span class="na"&gt;img-style=&lt;/span&gt;&lt;span class="s"&gt;"illustration"&lt;/span&gt;
    &lt;span class="na"&gt;aspect=&lt;/span&gt;&lt;span class="s"&gt;"square"&lt;/span&gt;
    &lt;span class="na"&gt;prompt=&lt;/span&gt;&lt;span class="s"&gt;"wooden balcony of an alpine cabin overlooking a breathtaking deep valley in Italian Alps, two steaming mugs of coffee on a small table, morning golden light"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/vibe-img&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/vibe-theme&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not bad for one-shot generation. No cherry-picking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CORS situation
&lt;/h2&gt;

&lt;p&gt;Of course, calling AI APIs from the browser means dealing with CORS. Some providers (like Recraft) actually set CORS headers, so requests go straight from the browser to their API. No problem there.&lt;/p&gt;

&lt;p&gt;Others (like OpenAI) don't. For those, I built a minimal CORS proxy. It doesn't log anything, the API key only lives in memory for the duration of the request, and you can swap it with your own or turn it off entirely if you only use CORS-friendly providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  "API keys in the frontend?!"
&lt;/h2&gt;

&lt;p&gt;I know, I know.&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%2Fzu3ppcg4qvq4sr2acijm.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu3ppcg4qvq4sr2acijm.jpg" alt="Parisian bookshop" width="500" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But hear me out. The security model has two phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;During prototyping&lt;/strong&gt;: you're on localhost or in your dev environment. It goes directly to the AI provider and nowhere else. There's no attack surface because there are no third-party users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When sharing&lt;/strong&gt;: you send the URL to your client or deploy the page. The images are already cached on the CDN. No API key is present, needed, or even requested. The key's job is done.&lt;/p&gt;

&lt;p&gt;The CDN URLs are unguessable (SHA-256 hash):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdn.vibe-img.com/vibeimg-4479b58ffa14e53da9ec06e3feedcdb802dbca14b5c4a42fba6c093ba31ef5bc.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And keys never leak into LLM system prompts. Someone once said "LLM stands for Leak Language Model." Not here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching providers
&lt;/h2&gt;

&lt;p&gt;One thing that bugged me about image APIs is that they're all different. Different endpoints, different parameter names, different response formats. I wanted to switch providers by changing one word, not rewriting everything.&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;model&lt;/code&gt; attribute is the only thing you change. &lt;code&gt;model="recraft"&lt;/code&gt; or &lt;code&gt;model="openai"&lt;/code&gt;, same tag, same attributes. Internally, each provider has an adapter that translates universal attributes to whatever the API expects.&lt;/p&gt;

&lt;p&gt;This also means adding a new provider is just writing one adapter file. Which brings me to...&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;There's a lot to do. Two main areas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding providers.&lt;/strong&gt; Only OpenAI and Recraft are supported right now. If you use another model, adding an adapter is straightforward:&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="nb"&gt;cp &lt;/span&gt;src/adapters/_template.ts src/adapters/my-provider.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fill in the mapping tables, implement &lt;code&gt;buildRequest()&lt;/code&gt;, write at least 5 test fixtures, register it. Run &lt;code&gt;npm test&lt;/code&gt;. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improving prompts.&lt;/strong&gt; The llms.txt and skill files can definitely be better. Contributions from people experimenting with different prompt strategies are very welcome.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/devhashfortheweb/vibe-img" rel="noopener noreferrer"&gt;github.com/devhashfortheweb/vibe-img&lt;/a&gt;&lt;br&gt;
Site: &lt;a href="https://www.vibe-img.com" rel="noopener noreferrer"&gt;vibe-img.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>webdev</category>
      <category>vibeimg</category>
      <category>image</category>
    </item>
  </channel>
</rss>
