More SaaS, Fewer Users: What Solo Builders Should Stop Building by Hand


There is a chart doing the rounds that plots three lines from “past” to “present”:

  • Users per SaaS: falling.
  • Global SaaS count: rising, steeply.
  • Solo builders and creators: rising with it.

The before-and-after panel underneath makes it concrete. The “before” column lists four products, niche finance, legacy ERP, specialised CAD, a niche code platform, each with a big block of users next to it. The “today” column lists six, CRM, task manager, design platform, video tools, e-commerce store, community hub, each with two users or one.

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

We want to talk about a specific, boring consequence of that, because it is the one that eats the most time.

Small products need the same furniture as big ones

When you were one of four 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 attached to it.

When you are one of four hundred, you are probably one person. And you still need:

  • A pricing table, because you charge money.
  • A waitlist or email capture, because you launched before you were ready.
  • A changelog or roadmap, because people keep asking what is shipping.
  • Some social proof, because nobody has heard of you.
  • A launch countdown, because you are doing a Product Hunt thing on Tuesday.

None of this is hard. That is exactly what makes it insidious. Each piece is a two hour job you do at 11pm, hardcode, and then maintain forever. The pricing table has prices in it. The countdown has a date in it. The changelog grows every week.

The fragmentation the chart describes means more people are hitting this problem, each with less time to solve it. The total amount of pricing table code being written by hand right now is probably at an all-time high, which is a strange thing for the industry to be spending time on.

The three ways this usually goes

You hardcode it. Fastest tonight. Now changing a price is a 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 hear about it from a customer.

You install a page builder. Now your landing page pulls a few hundred kilobytes of framework to render a countdown, your Lighthouse score drops, and the plugin wants a monthly fee per feature.

You build a tiny CMS. You now maintain a CMS. You did not want to maintain a CMS. You wanted to change a price.

The real problem underneath all three: the content and the code have the same lifecycle. 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.

Splitting the two lifecycles

The approach Fugte is built around is to treat each of these pieces as a small hosted app with two layers:

  • A code layer, written once, by you or by AI.
  • A settings layer, generated from a schema, that you edit later without touching the code.

Concretely, a widget is four files:

index.html    markup fragment, Liquid + HTML
style.css     styles, Liquid works here too
script.js     vanilla JavaScript, no build step
schema.json   the settings that generate the edit form

The schema is the interesting part. It is what turns hardcoded values into things you can change later:

{
  "name": "Launch countdown",
  "settings": [
    { "id": "heading", "type": "text", "label": "Heading", "default": "Launching in" },
    { "id": "date", "type": "date", "label": "Launch date", "default": "2026-09-01" },
    { "id": "accent", "type": "color", "label": "Accent", "default": "#0c6fd0" }
  ]
}

Reference those in the markup and they become form fields:

<div class="countdown">
  <h2>{{ settings.heading | default: 'Launching in' }}</h2>
  <span id="t" style="color: {{ settings.accent }}"></span>
</div>

Now the launch date is a date picker, not a deploy. That is the whole idea.

A waitlist with no backend

The piece most solo projects give up on is the form, because “collect emails” is where you normally stop and go shopping for a third-party service.

{% form "waitlist", success-message: "You're on the list." %}
  <input type="email" name="email" placeholder="[email protected]" required>
  <button type="submit">Join the waitlist</button>
  {{ form.success }}
  {{ form.error }}
{% endform %}

Plus a storage declaration in the schema:

{
  "storage": [
    {
      "id": "waitlist",
      "fields": [{ "id": "email", "type": "email", "required": true }]
    }
  ]
}

No endpoint, no database, no spam handling, no CAPTCHA wiring. Submissions land in a dashboard you can export to CSV. For a project that might get 40 signups or might get 4,000, that is about the right amount of infrastructure, which is to say almost none.

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

Where AI actually fits

AI is very good at writing these small, self-contained pieces, and it is a large part of why the solo builders line on that chart is climbing at all. A countdown, an FAQ accordion, a pricing table are all well inside what a model gets right first try.

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

So the widget format is published in full at the build reference, with a plain text mirror at /llms-build.txt for tools that prefer it. It opens with a primer you can paste into ChatGPT, Claude, or Gemini before asking for a widget. That is not a growth trick, it is the cheapest fix for the actual failure mode: the model was guessing, so give it the rules.

What we would actually take from the chart

If the direction is right, and we think it is even if the axes are decorative, the interesting consequence is not “SaaS is dying” or “the market is saturated”. It is that the fixed cost of shipping a product has to keep falling, because there are fewer users to spread it over.

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.

Where this does not fit

Worth knowing before you commit:

  • It is an iframe. Widget content is not part of your page’s DOM. For SEO-critical text, keep the words in your page and use widgets for the interactive parts.
  • Iframes need sizing care. Fixed-height content is easy; very dynamic heights take some adjustment.
  • The free tier shows Fugte branding on widgets. Free covers unlimited widgets and views with 200 AI credits a month; removing branding is on the $9.99/month plan.
  • Live form submissions are limited by plan (1 on Free, 3 on Basic), so a waitlist expecting heavy traffic needs the right tier.
  • This is not a replacement for your app. It is for the furniture around it: the marketing page, the landing page, the embed in someone else’s platform.

Start with one piece

Pick the thing you are most tired of editing. Usually it is the pricing table or the launch date.

The free tier needs no card. Describe what you want on the homepage, start from the starter widgets, or read the build reference if you would rather write the code yourself and hand the settings to someone else later.

If you are shipping your own small product and have solved this a different way, we would like to hear it: get in touch. The answers that are not “use our thing” are the useful ones.