<?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: Mary Hill</title>
    <description>The latest articles on DEV Community by Mary Hill (@fugte).</description>
    <link>https://dev.to/fugte</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%2F4034535%2F87608614-de40-45de-885a-7767147ca4ea.png</url>
      <title>DEV Community: Mary Hill</title>
      <link>https://dev.to/fugte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fugte"/>
    <language>en</language>
    <item>
      <title>I put a public graffiti wall on my SaaS homepage</title>
      <dc:creator>Mary Hill</dc:creator>
      <pubDate>Mon, 03 Aug 2026 15:05:06 +0000</pubDate>
      <link>https://dev.to/fugte/i-put-a-public-graffiti-wall-on-my-saas-homepage-4n4l</link>
      <guid>https://dev.to/fugte/i-put-a-public-graffiti-wall-on-my-saas-homepage-4n4l</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw1dlfykcpjbam4xfwfnm.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw1dlfykcpjbam4xfwfnm.png" alt=" " width="800" height="557"&gt;&lt;/a&gt;Somewhere below the hero, most SaaS homepages have the same section: a screenshot of the product, slightly angled, maybe in a browser mockup with fake traffic numbers. Everyone scrolls past it, because everyone knows screenshots are where marketing happens.&lt;/p&gt;

&lt;p&gt;Our homepage section is literally titled "Proof, not promises," so a screenshot felt like self-parody. Instead we embedded the product itself: a public guestbook called &lt;strong&gt;Owners Were Here&lt;/strong&gt;, built with &lt;a href="https://fugte.com" rel="noopener noreferrer"&gt;Fugte&lt;/a&gt; (my widget builder), embedded on the homepage with the exact iframe snippet any customer gets. Anyone can sign it. The notes pile up into a wall. It is a graffiti wall with a submit button.&lt;/p&gt;

&lt;p&gt;This post is the why, and then the three sharp edges you hit the moment you let strangers write to your landing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a guestbook, of all things
&lt;/h2&gt;

&lt;p&gt;Two reasons, one honest and one sneaky.&lt;/p&gt;

&lt;p&gt;The honest one: &lt;strong&gt;forms are the hardest widget to fake.&lt;/strong&gt; A countdown timer in a screenshot proves nothing; anything can render a countdown. A form that visibly works proves the entire invisible half of the product: the submit endpoint, the spam screening, the storage, the fact that a random visitor on a third-party page can post data and it lands somewhere real. That invisible half is the actual product. So the demo had to be a form, and a contact form demo is boring because you can't show strangers each other's messages. A guestbook is a form whose submissions are &lt;em&gt;meant&lt;/em&gt; to be public. The demo demos itself.&lt;/p&gt;

&lt;p&gt;The sneaky one: a wall of real notes from real visitors is &lt;strong&gt;social proof that compounds while I sleep.&lt;/strong&gt; Testimonial sections take months of collection and permission-asking. A graffiti wall fills itself, and its scrappiness is the charm. Even a wall with some junk on it says "people were here," which is more than a polished quote carousel says.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge 1: the endpoint is public, and there is no secret to hide
&lt;/h2&gt;

&lt;p&gt;The widget is an iframe pointed at its hosted page. The instance id is right there in the URL. Anyone can read the JS. So the design constraint is brutal and clarifying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The submit endpoint must be safe to expose to the entire internet, because it is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is no API key you can bury in client-side widget code; a "secret" shipped to the browser is a public value with extra steps. So the endpoint protects itself instead: &lt;strong&gt;Cloudflare Turnstile&lt;/strong&gt; runs on every submission (the widget asks its host shell for a token over &lt;code&gt;postMessage&lt;/code&gt;, and each token is single-use and origin-bound), rate limiting sits behind that, and the server validates submissions against the fields the widget's schema declares, with a hard size cap. Free-form JSON from an anonymous visitor never gets stored as-is.&lt;/p&gt;

&lt;p&gt;If you are wiring up your own public form endpoint this is the checklist that matters: bot check, rate limit, schema validation, size cap. Any of the four missing, and the internet will find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge 2: you are now rendering strangers' text on your own homepage
&lt;/h2&gt;

&lt;p&gt;A guestbook renders untrusted input, in an iframe, potentially forever. The classic mistake is building the wall with &lt;code&gt;innerHTML&lt;/code&gt; because it is Tuesday and it works. Then someone signs your wall with a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag and their note executes instead of displaying.&lt;/p&gt;

&lt;p&gt;The rule is one sentence: &lt;strong&gt;untrusted text goes through &lt;code&gt;textContent&lt;/code&gt;, never &lt;code&gt;innerHTML&lt;/code&gt;.&lt;/strong&gt; On this wall, a note containing &lt;code&gt;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&lt;/code&gt; renders as exactly those characters, which honestly looks right at home on a graffiti wall. Street art, not code execution.&lt;/p&gt;

&lt;p&gt;(Defense in depth still applies: the widget lives in a sandboxed iframe on a separate origin from the app, so even a mistake there does not run next to anyone's session. But the sandbox is the seatbelt, not the driving.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge 3: the wall grows forever, your homepage should not
&lt;/h2&gt;

&lt;p&gt;Fugte embeds auto-size: the hosted widget posts its rendered height to the parent page, keyed by widget id, and the embedding page applies it. Lovely for a pricing table. A disaster for a wall that gets taller with every note, because in six months the homepage would be 94% guestbook.&lt;/p&gt;

&lt;p&gt;The fix is a viewport: cap the applied height and let the wall scroll inside its frame.&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;var&lt;/span&gt; &lt;span class="nx"&gt;MAX_HEIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ev&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="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;d&lt;/span&gt; &lt;span class="o"&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;source&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webdyi-widget&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;id&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;var&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&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;height&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;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;frame&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;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MAX_HEIGHT&lt;/span&gt;&lt;span class="p"&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;px&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;Below the cap, the frame hugs the content, so a young wall has no dead space. Past the cap, the frame stops growing and the wall scrolls internally, with an "open the full wall" link for anyone who wants the whole thing. Bonus: a capped iframe is also a layout-shift fix, since an embed that resizes itself mid-scroll is a CLS penalty you inflicted on yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I expect to go wrong
&lt;/h2&gt;

&lt;p&gt;Public wall, public internet: some of it will be junk. Turnstile and rate limiting keep the bots out, but nothing stops a human from contributing "hi" seventeen times, and moderation means the worst entries need deleting by hand. I have made peace with this. A too-clean guestbook reads as fake anyway; the failure mode I actually care about (the wall becoming an attack surface) is handled by the three edges above.&lt;/p&gt;

&lt;p&gt;There is also the possibility the wall just stays sparse and slightly embarrassing for a while. That is fine too. It is still a live form on a homepage, doing the one thing screenshots cannot: working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Come vandalize it
&lt;/h2&gt;

&lt;p&gt;The wall is on the &lt;a href="https://fugte.com" rel="noopener noreferrer"&gt;Fugte homepage&lt;/a&gt;, or you can open &lt;a href="https://fugte.net/w/097e7878-caaf-400a-972e-12f2e4554eea" rel="noopener noreferrer"&gt;the full wall&lt;/a&gt; directly. Sign it, try to break it politely, and if you find an edge I missed, the contact form (also a Fugte widget, naturally) works.&lt;/p&gt;

&lt;p&gt;Want a wall of your own? The guestbook is &lt;a href="https://fugte.com/widgets/owners-were-here/" rel="noopener noreferrer"&gt;a starter widget now&lt;/a&gt;, so you can put one on your own site and let your users sign it.&lt;/p&gt;

&lt;p&gt;And if what you actually need is the boring version of this, a lead form with the submit path, spam screening, and inbox already attached, that starter is &lt;a href="https://fugte.com/widgets/contact-us-form/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>The average SaaS now has fewer users, and that changes what you should build</title>
      <dc:creator>Mary Hill</dc:creator>
      <pubDate>Mon, 27 Jul 2026 14:14:00 +0000</pubDate>
      <link>https://dev.to/fugte/the-average-saas-now-has-fewer-users-and-that-changes-what-you-should-build-3j7f</link>
      <guid>https://dev.to/fugte/the-average-saas-now-has-fewer-users-and-that-changes-what-you-should-build-3j7f</guid>
      <description>&lt;p&gt;There is a chart going around that shows three lines from "past" to "present":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Users per SaaS&lt;/strong&gt;: falling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global SaaS count&lt;/strong&gt;: rising, steeply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo builders and creators&lt;/strong&gt;: rising with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The before/after panel underneath makes it concrete. The "before" column lists four products, Niche Finance, Legacy ERP, Specialized CAD, a niche code platform, each with a big block of users. The "today" column lists six, CRM, task manager, design platform, video tools, e-com store, community hub, each with two users or 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvq409jdxwrjnhszez44z.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvq409jdxwrjnhszez44z.jpg" alt="Graph of SAAS Market Trends" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can argue with the axes. There are no numbers on them, and "scale" is&lt;br&gt;
doing a lot of work. But the direction matches what anyone shipping software in the last few years has felt: &lt;strong&gt;the market is fragmenting into many more, much smaller products.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to talk about a specific, boring consequence of that, because it is the one that has eaten the most of my time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Small products need the same furniture as big ones
&lt;/h2&gt;

&lt;p&gt;When you were one of four SaaS products in a category, you had a team. Someone owned the marketing site. Someone owned the design system. The pricing page was a project with a ticket.&lt;/p&gt;

&lt;p&gt;When you are one of four hundred, you are probably one person. And you still need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pricing table, because you charge money.&lt;/li&gt;
&lt;li&gt;A waitlist or email capture, because you launched before you were ready..&lt;/li&gt;
&lt;li&gt;A changelog or roadmap, because people ask what is shipping.&lt;/li&gt;
&lt;li&gt;Some social proof, because nobody has heard of you.&lt;/li&gt;
&lt;li&gt;A launch countdown, because you are doing a Product Hunt thing on Tuesday.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hard. That is what makes it insidious. Each piece is a two hour job that you will do at 11pm, hardcode, and then maintain forever. The pricing table has prices in it. The countdown has a date in it. &lt;/p&gt;

&lt;p&gt;The fragmentation the chart describes means &lt;strong&gt;more people are hitting this&lt;br&gt;
problem, each with less time to solve it.&lt;/strong&gt; The absolute amount of pricing&lt;br&gt;
table code being written by hand right now is probably at an all time high, which is a genuinely stupid thing for our industry to be spending time on.&lt;/p&gt;
&lt;h2&gt;
  
  
  The three ways this usually goes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You hardcode it.&lt;/strong&gt; Fastest tonight. Now changing a price is a git commit, a build, and a deploy. You will postpone it. Your pricing page will be wrong for two weeks at some point, and you will find out from a customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You install a page builder.&lt;/strong&gt; Now your landing page pulls 400KB of framework to render a countdown, your Lighthouse score drops, and the thing wants $19/month per feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You build a tiny CMS.&lt;/strong&gt; You now maintain a CMS. You did not want to maintain a CMS. You wanted to change a price.&lt;/p&gt;

&lt;p&gt;The real problem in all three: &lt;strong&gt;the content and the code have the same&lt;br&gt;
lifecycle.&lt;/strong&gt; A price changes monthly. The code that renders it changes almost never. Coupling them means every content edit pays the full cost of a code change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Splitting the two lifecycles
&lt;/h2&gt;

&lt;p&gt;The approach I ended up building around is to treat each of these pieces as a tiny hosted app with two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A code layer&lt;/strong&gt; written once, by you or by an AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A settings layer&lt;/strong&gt; generated from a schema, that you edit later without touching the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concretely, a widget is four files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;index.html    markup fragment, Liquid + HTML
style.css     styles, Liquid works here too
script.js     vanilla JS, no build step
schema.json   the settings that generate the edit form
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema is the interesting part. It is what turns hardcoded values into&lt;br&gt;
things you can change later:&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Launch countdown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settings"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"heading"&lt;/span&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;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Heading"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Launching in"&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&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;"date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Launch date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-01"&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accent"&lt;/span&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;"color"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Accent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0c6fd0"&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;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;Reference them in the markup, and they become form fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&amp;lt;div class="countdown"&amp;gt;
  &amp;lt;h2&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;heading&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="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Launching in'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/h2&amp;gt;
  &amp;lt;span id="t" style="color: &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;accent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;"&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the launch date is a date picker, not a deploy. That is the entire idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  A waitlist with no backend
&lt;/h3&gt;

&lt;p&gt;The piece I reach for most is the form, because "collect emails" is where&lt;br&gt;
solo projects usually give up and reach for a third party.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waitlist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;success-message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You're on the list."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
  &amp;lt;input type="email" name="email" placeholder="you@example.com" required&amp;gt;
  &amp;lt;button type="submit"&amp;gt;Join the waitlist&amp;lt;/button&amp;gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;success&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a storage declaration in the schema:&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;"storage"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"waitlist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fields"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&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;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;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;No endpoint, no database, no spam handling, no CAPTCHA wiring. Submissions&lt;br&gt;
land in a dashboard you can export to CSV. For a project that might get 40&lt;br&gt;
signups or might get 4,000, that is the right amount of infrastructure, which is to say almost none.&lt;/p&gt;

&lt;p&gt;One design decision worth calling out, because it is the kind of thing that bites people: &lt;strong&gt;submissions are never public by default.&lt;/strong&gt; If you want to show entries back on the widget, a testimonial wall or a public goal tracker, you tick specific fields to expose. Email and phone stay private unless you deliberately make them public. The failure mode of "we accidentally published everyone's email address" is common enough that the default should not be opt-out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI actually fits
&lt;/h2&gt;

&lt;p&gt;The honest version: AI is very good at writing these small self-contained pieces, and it is the reason the "solo builders" line on that chart is going up at all. A countdown, an FAQ accordion, a pricing table are all well inside what a model writes correctly on the first try.&lt;/p&gt;

&lt;p&gt;What models are &lt;em&gt;not&lt;/em&gt; good at is guessing a platform's conventions. Ask any assistant for a widget in a format it has never seen and you get plausible code that fails validation: a raw &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; with its own fetch handler, a &lt;code&gt;{% if settings.image %}&lt;/code&gt; that is true when the image is blank because empty strings are truthy in Liquid, a &lt;code&gt;new Date()&lt;/code&gt; fed a formatted date string.&lt;/p&gt;

&lt;p&gt;So we published the spec at &lt;a href="https://fugte.com/docs/build/" rel="noopener noreferrer"&gt;fugte.com/docs/build&lt;/a&gt;, with a plain text mirror at &lt;code&gt;/llms-build.txt&lt;/code&gt;. It opens with a primer you can paste into ChatGPT or Claude before asking for a widget. That is not a growth hack, it is the cheapest fix for the actual failure mode: the model was guessing, so give it the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would actually take from the chart
&lt;/h2&gt;

&lt;p&gt;If the trend is real, and I think the direction is even if the axes are decorative, then the interesting consequence is not "SaaS is dying" or "the market is saturated". It is that &lt;strong&gt;the fixed cost of shipping a product has to keep falling, because there are fewer users to amortise it over.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A product with 200 users cannot justify the same infrastructure as one with 200,000. The pricing table still has to exist. It just cannot cost a week anymore.&lt;/p&gt;

&lt;p&gt;That is the bet behind what I build: the small pieces every product needs&lt;br&gt;
should be something you assemble in an afternoon and edit in a form field&lt;br&gt;
afterwards, not something you own forever.&lt;/p&gt;




&lt;p&gt;Disclosure: I build &lt;a href="https://fugte.com" rel="noopener noreferrer"&gt;Fugte&lt;/a&gt;, so read the middle section with that bias in mind. The problem it addresses is real whether or not my answer is the one you pick, and if you would rather hardcode a countdown and never think about it again, that is a completely defensible call. Free tier if you want to try it, and the widget format is documented in full at &lt;a href="https://fugte.com/docs/build/" rel="noopener noreferrer"&gt;fugte.com/docs/build&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What is your split? Do you hardcode the small stuff and eat the edit cost, or do you build the settings layer up front?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>saas</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>Stop being the human CMS for a date field</title>
      <dc:creator>Mary Hill</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:10:00 +0000</pubDate>
      <link>https://dev.to/fugte/small-businesses-keep-paying-developers-to-change-a-date-so-i-built-a-widget-layer-1ohh</link>
      <guid>https://dev.to/fugte/small-businesses-keep-paying-developers-to-change-a-date-so-i-built-a-widget-layer-1ohh</guid>
      <description>&lt;p&gt;A bakery owner pays a freelancer $120 to change the end date on a sale countdown. Not to build it. To change the date.&lt;/p&gt;

&lt;p&gt;A freelancer builds their fifth FAQ accordion of the year, for the fifth client, from scratch, again.&lt;/p&gt;

&lt;p&gt;Both of these people are losing. The owner is paying developer rates for a content edit. The developer is doing work that stopped being interesting four accordions ago, and now owns an informal support contract for every tiny widget they ever shipped.&lt;/p&gt;

&lt;p&gt;I kept running into both sides of this, so I built &lt;a href="https://fugte.com" rel="noopener noreferrer"&gt;Fugte&lt;/a&gt; to fix it. Disclosure up front: this is my product, so read with that bias in mind. But the problem is real whether or not my solution is the one you pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  The widget treadmill
&lt;/h2&gt;

&lt;p&gt;Almost every small business site needs the same handful of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A countdown for the next sale or launch&lt;/li&gt;
&lt;li&gt;An FAQ accordion for the repeat questions (shipping, returns, opening hours)&lt;/li&gt;
&lt;li&gt;A pricing table&lt;/li&gt;
&lt;li&gt;A testimonial carousel&lt;/li&gt;
&lt;li&gt;A donation or goal tracker&lt;/li&gt;
&lt;li&gt;A newsletter signup or contact form&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are hard to build. That is exactly the problem. They are too small to justify a real project, too custom for a generic plugin, and too dynamic to hardcode. The countdown has a date in it. The pricing table has prices in it. The FAQ grows. Whoever builds it becomes the person who maintains it, forever, one "quick change" email at a time.&lt;/p&gt;

&lt;p&gt;The usual outcomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The owner hires someone for every edit.&lt;/strong&gt; The site stays accurate but every text change costs real money, so edits get postponed and the site quietly goes stale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The owner installs a page builder plugin.&lt;/strong&gt; Now the site loads 400 KB of widget framework to render a countdown, and the plugin wants $19/month per feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The developer hardcodes it.&lt;/strong&gt; Fast to ship, and now a code deploy is required to change a price.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The approach: widgets as tiny hosted apps
&lt;/h2&gt;

&lt;p&gt;The fix I landed on: treat each widget as a small hosted app with two layers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A code layer&lt;/strong&gt; (HTML, CSS, JavaScript) that a developer or an AI writes once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A content layer&lt;/strong&gt; (text, dates, prices, images, colors) that the owner edits through a form, with no access to the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The widget is published at a hosted URL, and the site embeds it with one iframe or link. When the owner changes the sale date, every embed updates. No deploy, no plugin, no email to the developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're a developer or freelancer
&lt;/h2&gt;

&lt;p&gt;You already write widget code like this:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"countdown"&lt;/span&gt; &lt;span class="na"&gt;data-ends=&lt;/span&gt;&lt;span class="s"&gt;"2026-08-01T00:00:00Z"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"days"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt; days left in our summer sale
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.countdown&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;ends&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&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;ends&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;tick&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ends&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is the easy part. The expensive part is everything around it: hosting it somewhere, and fielding the email three weeks later asking you to change &lt;code&gt;2026-08-01&lt;/code&gt; to &lt;code&gt;2026-09-01&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Fugte you paste (or write, or generate) that code once, then choose which parts are editable: the end date, the message, the colors. Those become safe settings your client can change through a simple form. They get knobs, not source code. You get one hosted, sandboxed embed URL you can reuse across client projects, and you stop being the human CMS for a date field.&lt;/p&gt;

&lt;p&gt;For agency and freelance work this changes the handoff conversation. "Here is your widget, and here is where you edit it yourself" is a better deliverable than "email me when the sale changes."&lt;/p&gt;

&lt;h2&gt;
  
  
  If you run a small business
&lt;/h2&gt;

&lt;p&gt;You do not need the code layer at all. You describe what you want in a sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a donation goal tracker for my bakery's community fundraiser, warm colors&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and the AI builds it. Or you start from a starter widget (countdown, FAQ, pricing table, testimonial carousel, spinning wheel, before/after slider, and so on) and change the content to yours. Either way, the parts you care about stay editable by you: the goal amount, the prices, the questions, the photos.&lt;/p&gt;

&lt;p&gt;Then you copy one link or iframe into wherever your site lives. That includes the platforms that normally make custom code painful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shopify&lt;/strong&gt; (no theme editing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress&lt;/strong&gt;, &lt;strong&gt;Webflow&lt;/strong&gt;, any custom site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion&lt;/strong&gt; pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canva&lt;/strong&gt; websites, which accept embeds but give you almost no way to run custom interactive code otherwise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one surprises people. If you built your site in Canva, an embed link is essentially your only path to a live countdown or FAQ, and it works well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;Because this is dev.to and not a landing page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's an iframe.&lt;/strong&gt; Embedded widget content is not part of your page's DOM. For SEO-critical content, keep important text in your page itself and use widgets for the interactive parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iframes need sizing care.&lt;/strong&gt; Fixed-height content is easy; very dynamic heights can take a little adjustment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The free tier shows Fugte branding&lt;/strong&gt; on widgets. Free gives you unlimited widgets and views with 200 AI credits a month; removing the branding is on the $4.99/month plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live form submissions are limited by plan&lt;/strong&gt; (1 on Free, 3 on Basic), so a contact form that gets heavy traffic needs the right tier.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The free tier needs no card: describe a widget at &lt;a href="https://fugte.com" rel="noopener noreferrer"&gt;fugte.com&lt;/a&gt; and it is ready to embed in a couple of minutes. Developers can start from the &lt;a href="https://fugte.com/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; or the &lt;a href="https://fugte.com/developers" rel="noopener noreferrer"&gt;developer page&lt;/a&gt; instead. And if you get stuck at any point, the &lt;a href="https://fugte.app/learn/" rel="noopener noreferrer"&gt;Learn tab&lt;/a&gt; inside the app has short how-tos for the whole loop: creating a widget, choosing editable fields, handling form submissions, and publishing.&lt;/p&gt;

&lt;p&gt;If you have fought the widget treadmill from either side, the freelancer side or the owner side, I would genuinely like to hear how you handled it. And if you try Fugte and something is rough, tell me that too. It is early, and that feedback is the useful kind.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>ai</category>
      <category>freelancing</category>
    </item>
  </channel>
</rss>
