<?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: Julie Do</title>
    <description>The latest articles on DEV Community by Julie Do (@juliedechili).</description>
    <link>https://dev.to/juliedechili</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3976924%2Ffd57f873-59c2-4f99-bb97-200ef69a3b8a.jpg</url>
      <title>DEV Community: Julie Do</title>
      <link>https://dev.to/juliedechili</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juliedechili"/>
    <language>en</language>
    <item>
      <title>The one-click gap nobody was solving: why I built HTML Deployer</title>
      <dc:creator>Julie Do</dc:creator>
      <pubDate>Fri, 03 Jul 2026 08:31:21 +0000</pubDate>
      <link>https://dev.to/juliedechili/the-one-click-gap-nobody-was-solving-why-i-built-html-deployer-4016</link>
      <guid>https://dev.to/juliedechili/the-one-click-gap-nobody-was-solving-why-i-built-html-deployer-4016</guid>
      <description>&lt;h2&gt;
  
  
  The one-click gap nobody was solving: why I built HTML Deployer
&lt;/h2&gt;

&lt;p&gt;A few months ago I watched a marketer friend do something that made me wince.&lt;/p&gt;

&lt;p&gt;She'd spent twenty minutes in ChatGPT crafting a landing page for a product launch. The copy was sharp, the layout was clean, the CTA button even had a nice little hover animation. She was proud of it — and she should have been.&lt;/p&gt;

&lt;p&gt;Then she asked me: "Okay, how do I actually put this online?"&lt;/p&gt;

&lt;p&gt;I told her to copy the code, create a repo, connect it to Netlify, wait for the build... and I watched her face fall somewhere around the word "repo." She closed the tab. That landing page never went live. Not because the idea was bad, not because the code was bad — because there was a wall between "I have HTML" and "I have a URL," and nobody had bothered to tear it down.&lt;/p&gt;

&lt;p&gt;That's the moment this project actually started, even though I didn't write a single line of code that day.&lt;/p&gt;

&lt;h3&gt;
  
  
  The wall everyone pretends isn't there
&lt;/h3&gt;

&lt;p&gt;Once I started paying attention, I saw the same wall everywhere. Not just with marketers — with freelancers sending demo links to clients, with solo founders trying to spin up a bio page between meetings, with small agencies juggling a dozen campaign microsites at once.&lt;/p&gt;

&lt;p&gt;They could all &lt;em&gt;generate&lt;/em&gt; a page in seconds. ChatGPT, Claude, Gemini — doesn't matter which. The AI part had gotten absurdly good. But turning that generated block of HTML into something with an actual link you could send someone? That still meant terminal commands, Git, hosting dashboards, FTP clients, or — worst of all — pinging a developer for something that should take thirty seconds.&lt;/p&gt;

&lt;p&gt;The tools to &lt;em&gt;create&lt;/em&gt; had leapt a decade ahead. The tools to &lt;em&gt;ship&lt;/em&gt; were stuck in 2012. I couldn't stop thinking about how absurd that gap was.&lt;/p&gt;

&lt;h3&gt;
  
  
  First attempt: I tried to build a platform
&lt;/h3&gt;

&lt;p&gt;My first instinct, embarrassingly, was to build Yet Another Hosting Platform. Upload your HTML, we host it, here's your link. I got a rough version working in a weekend and immediately hated it.&lt;/p&gt;

&lt;p&gt;Here's the problem: the moment you build your own hosting platform, you're asking people to trust you with their site's uptime, forever. You're also locking them in — if they ever want to move to their own domain, their own Netlify account, their own infrastructure, tough luck. I'd just be recreating the exact kind of walled garden that made the original workflow so painful in the first place, just with a shinier wrapper.&lt;/p&gt;

&lt;p&gt;I scrapped it. Worse than scrapping it — I sat on the idea for a couple of weeks not sure it was worth solving at all, since every direction I tried seemed to reintroduce the same friction I was trying to remove.&lt;/p&gt;

&lt;h3&gt;
  
  
  The unlock: don't move the destination, move the moment
&lt;/h3&gt;

&lt;p&gt;The breakthrough wasn't technical, it was almost embarrassingly simple: the problem was never &lt;em&gt;where&lt;/em&gt; people were hosting. Netlify is fine. GitHub Pages is fine. Plenty of people already have FTP access to hosting they're already paying for. The problem was the distance between "AI just finished generating this code" and "I am now doing something about it."&lt;/p&gt;

&lt;p&gt;So instead of building a destination, I built a bridge that lives exactly where the pain happens — inside the AI chat itself.&lt;/p&gt;

&lt;p&gt;That's the core idea behind HTML Deployer: a Chrome extension that watches ChatGPT, Claude, and Gemini conversations in real time, detects when an HTML code block shows up, and puts a Deploy button right next to it. No copy-paste. No new tab. No context switch. You're still looking at the conversation where the idea was born when you click the button that makes it real.&lt;/p&gt;

&lt;p&gt;And on the other side of that button, you get to choose: Netlify, Vercel, GitHub Pages, your own FTP hosting, a self-hosted agent on your own server, or just a clean ZIP if you want to do it manually. No lock-in. If you already have hosting, use it. If you don't, get some in one click. The extension's job ends at "your HTML is now live" — it doesn't try to own what happens after.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the detection layer was the hard part
&lt;/h3&gt;

&lt;p&gt;The UI is the easy 20%. The hard 80% was making detection actually reliable across three different chat interfaces that weren't designed to be scraped, that change their DOM structure without warning, and that sometimes render multiple code blocks in a single response.&lt;/p&gt;

&lt;p&gt;A few things I had to solve along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distinguishing an actual deployable HTML document from a stray &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; snippet someone was showing for explanation, not deployment.&lt;/li&gt;
&lt;li&gt;Handling streaming responses — code blocks that are still being typed out character by character shouldn't trigger a half-broken deploy button.&lt;/li&gt;
&lt;li&gt;Making the preview render instantly, across desktop, tablet, and mobile viewports, without spinning up a server, so people catch layout problems before the page is public instead of after.&lt;/li&gt;
&lt;li&gt;Being paranoid about credentials. FTP passwords and API tokens are exactly the kind of thing that should never touch a server I control. Everything happens client-side; nothing is stored on our infrastructure until the moment you hit Deploy, and even then it goes straight to your chosen destination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point mattered more than any feature. Every person in this space — freelancer, agency, solo builder — has the same quiet fear sitting under the excitement: &lt;em&gt;what if this breaks something for a client, or leaks a credential I'm responsible for?&lt;/em&gt; If the tool didn't earn trust on that front first, none of the convenience would matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually changed once it worked
&lt;/h3&gt;

&lt;p&gt;The first time I generated a page in Claude, clicked Deploy, and got back a live HTTPS URL with a QR code before I'd even finished my coffee, it felt almost anticlimactic — which is exactly how a good tool should feel. Not magical. Just... no longer an obstacle.&lt;/p&gt;

&lt;p&gt;That agency owner scenario I mentioned earlier — the one juggling a dozen campaign microsites — turned a 30-minute manual FTP routine into something closer to a minute, without changing the hosting they already trusted.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual lesson
&lt;/h3&gt;

&lt;p&gt;If I had to compress this into one sentence for anyone building dev tools: the biggest wins aren't always in making something new possible — sometimes they're in deleting the five boring steps between "I already did the hard, creative part" and "this exists in the world now." AI didn't need another platform. It needed the wall between generation and publishing to just... not be there.&lt;/p&gt;

&lt;p&gt;I still think about my friend closing that tab. That landing page never seeing daylight is a small thing, but it's the small thing multiplied across everyone who's ever built something good in an AI chat and then quietly given up at the last step. That's the gap HTML Deployer is trying to close — one Deploy button at a time.&lt;/p&gt;

&lt;p&gt;If you're curious, it's a free Chrome extension and works with ChatGPT, Claude, and Gemini. &lt;/p&gt;

&lt;p&gt;If you want to try it: &lt;a href="https://chromewebstore.google.com/detail/gihmknkabkkghpiocgnoiejagngdegea" rel="noopener noreferrer"&gt;HTML Deployer on the Chrome Web Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Would genuinely love to hear how other builders here are handling the "AI output → live thing" problem in their own stacks — I doubt HTML is the only place this wall exists.&lt;/p&gt;

&lt;h1&gt;
  
  
  webdev #ai #chrome #productivity
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>html</category>
      <category>devops</category>
    </item>
    <item>
      <title>I built a Chrome extension to deploy AI-generated HTML in one click</title>
      <dc:creator>Julie Do</dc:creator>
      <pubDate>Tue, 16 Jun 2026 08:02:09 +0000</pubDate>
      <link>https://dev.to/juliedechili/i-built-a-chrome-extension-to-deploy-ai-generated-html-in-one-click-3l1a</link>
      <guid>https://dev.to/juliedechili/i-built-a-chrome-extension-to-deploy-ai-generated-html-in-one-click-3l1a</guid>
      <description>&lt;h2&gt;
  
  
  The problem I kept running into
&lt;/h2&gt;

&lt;p&gt;Every time I used Claude or ChatGPT to build a quick landing page or a&lt;br&gt;
microsite prototype, I hit the same wall at the end.&lt;/p&gt;

&lt;p&gt;The HTML looked great. But getting it &lt;em&gt;live&lt;/em&gt; meant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the code out of the chat window&lt;/li&gt;
&lt;li&gt;Create a new project on Netlify (or push to a GitHub repo)&lt;/li&gt;
&lt;li&gt;Wait for the deploy pipeline&lt;/li&gt;
&lt;li&gt;Share the link&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a developer, that's maybe 5 minutes of boring overhead. But for a marketer or no-code builder? It might as well be a locked door.&lt;/p&gt;

&lt;p&gt;And even for me — when I'm doing it 10+ times a week across small&lt;br&gt;
client projects — those 5-minute tasks add up fast.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTML Deployer&lt;/strong&gt; is a Chrome extension that sits inside your AI chat session (ChatGPT, Claude, Gemini) and adds a &lt;strong&gt;Deploy button directly next to every HTML code block&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click it → preview the page on desktop, tablet and mobile → choose&lt;br&gt;
where to publish → done.&lt;/p&gt;

&lt;p&gt;No copy-paste. No tab switching. No terminal.&lt;/p&gt;


&lt;h2&gt;
  
  
  The four deploy targets
&lt;/h2&gt;

&lt;p&gt;I wanted to avoid locking users into a single platform. So the&lt;br&gt;
extension currently supports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Netlify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free CDN, HTTPS, custom domain — easiest for most people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast preview URLs, zero config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Pages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Version-controlled static hosting, free forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FTP / cPanel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your own shared hosting — most agencies already have this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-hosted agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A tiny PHP file on your server, no FTP credentials stored in the extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ZIP export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Download and upload manually — maximum flexibility&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fjdhclc5eg2m6mdor5au9.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%2Fjdhclc5eg2m6mdor5au9.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
The self-hosted option was a deliberate design choice. A lot of&lt;br&gt;
freelancers and small agencies have existing hosting accounts they're paying for. There was no reason to force them onto Netlify just because it's the path of least resistance.&lt;/p&gt;


&lt;h2&gt;
  
  
  How it actually works (technical overview)
&lt;/h2&gt;

&lt;p&gt;When the extension loads on a supported AI page, it uses a&lt;br&gt;
&lt;code&gt;MutationObserver&lt;/code&gt; to watch for new &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; blocks being rendered in the DOM. When it detects a block that looks like an HTML document (it checks for &lt;code&gt;&amp;lt;!DOCTYPE&lt;/code&gt;, &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tags), it injects asmall Deploy button into the parent element.&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;// Simplified detection logic&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&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;MutationObserver&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre code&lt;/span&gt;&lt;span class="dl"&gt;'&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;block&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="nf"&gt;isHTMLDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hdInjected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;injectDeployButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hdInjected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&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;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;childList&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;subtree&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the preview, the extension renders the HTML in a sandboxed &lt;code&gt;iframe&lt;/code&gt;inside a side panel. It uses CSS transforms to simulate three viewport sizes without making any network requests — everything stays local.&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%2Foba3mrlv2qffxkcx4j1w.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%2Foba3mrlv2qffxkcx4j1w.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
For Netlify and GitHub deploys, the extension uses their respective&lt;br&gt;
REST APIs. Credentials are stored in Chrome's &lt;code&gt;storage.local&lt;/code&gt; (not&lt;br&gt;
synced across devices, not sent to our servers).&lt;/p&gt;

&lt;p&gt;For FTP deploys, the extension talks to a small backend proxy (because browsers can't open raw TCP connections). The FTP credentials are encrypted in transit and not logged or stored on our side.&lt;/p&gt;


&lt;h2&gt;
  
  
  The self-host option explained
&lt;/h2&gt;

&lt;p&gt;Some users — particularly agencies with client hosting accounts — don't want to enter FTP credentials into a browser extension at all. That's a completely reasonable security concern.&lt;/p&gt;

&lt;p&gt;The self-hosted mode works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You download a single &lt;code&gt;agent.php&lt;/code&gt; file (~30 lines)&lt;/li&gt;
&lt;li&gt;You upload it to your own server&lt;/li&gt;
&lt;li&gt;You point the extension at your server URL + a secret token you set&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The extension sends the HTML payload to your own server, which writes the file to disk. Your FTP credentials never leave your server. The extension only knows the public URL of the agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// Simplified agent.php&lt;/span&gt;
&lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your-secret-token-here'&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="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_METHOD'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;exit&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="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'token'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'filename'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'index.html'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'html'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not fancy, but effective.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the workflow actually looks like
&lt;/h2&gt;

&lt;p&gt;Here's a typical session for a freelancer building a landing page for a client:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Claude&lt;/li&gt;
&lt;li&gt;Ask it to build a landing page with a specific offer and CTA&lt;/li&gt;
&lt;li&gt;HTML Deployer auto-detects the code block&lt;/li&gt;
&lt;li&gt;Click the Deploy button in the sidebar&lt;/li&gt;
&lt;li&gt;Preview looks good on mobile — approve&lt;/li&gt;
&lt;li&gt;Select Netlify, click Deploy&lt;/li&gt;
&lt;li&gt;Get a live URL + QR code in about 8 seconds&lt;/li&gt;
&lt;li&gt;Send the URL to the client for feedback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing from "AI generates the code" to "client has a link" is under 2 minutes. Previously, that same flow was 10–15 minutes of&lt;br&gt;
switching between tabs, copying code, creating Netlify projects, etc.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned building this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MutationObserver is your friend — but also your enemy.&lt;/strong&gt;&lt;br&gt;
AI chat interfaces re-render constantly. ChatGPT in particular&lt;br&gt;
re-renders code blocks multiple times as streaming completes. I had to add debouncing and a &lt;code&gt;dataset&lt;/code&gt; flag to avoid injecting the button multiple times into the same block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser storage is trickier than it looks.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;chrome.storage.local&lt;/code&gt; has a 5MB limit per item. For an extension that stores deploy history, that fills up faster than you'd expect if someone is deploying large HTML files with embedded base64 images. I had to add a cleanup routine that prunes old history entries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Users are more security-conscious than I expected.&lt;/strong&gt;&lt;br&gt;
The most common first question was: &lt;em&gt;"Does this store my FTP&lt;br&gt;
credentials?"&lt;/em&gt; — not &lt;em&gt;"How does the preview work?"&lt;/em&gt;. That pushed me to build the self-host option earlier than planned, and to make the&lt;br&gt;
security model explicit in the onboarding flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;HTML Deployer is free to install on Chrome Web Store:&lt;br&gt;
→ &lt;a href="https://chromewebstore.google.com/detail/gihmknkabkkghpiocgnoiejagngdegea" rel="noopener noreferrer"&gt;Install HTML Deployer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have a use case that doesn't fit the current deploy targets, or you find a bug, feel free to drop a comment below or reach out.&lt;/p&gt;

&lt;p&gt;Would genuinely love to hear how other builders are handling the&lt;br&gt;
"AI output → live page" gap in their workflows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>html</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
