DEV Community

Mary Hill
Mary Hill

Posted on • Originally published at fugte.com

The average SaaS now has fewer users, and that changes what you should build

There is a chart going around that shows three lines from "past" to "present":

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

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.

Graph of SAAS Market Trends

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.

I want to talk about a specific, boring consequence of that, because it is the one that has eaten the most of my time.

Small products need the same furniture as big ones

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.

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 ask 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 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.

The fragmentation the chart describes means more people are hitting this
problem, each with less time to solve it.
The absolute amount of pricing
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.

The three ways this usually goes

You hardcode it. 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.

You install a page builder. Now your landing page pulls 400KB of framework to render a countdown, your Lighthouse score drops, and the thing wants $19/month 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 in 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 I ended up building around is to treat each of these pieces as a tiny hosted app with two layers:

  • A code layer written once, by you or by an 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 JS, no build step
schema.json   the settings that generate the edit form
Enter fullscreen mode Exit fullscreen mode

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" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Reference them 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>
Enter fullscreen mode Exit fullscreen mode

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

A waitlist with no backend

The piece I reach for most is the form, because "collect emails" is where
solo projects usually give up and reach for a third party.

{% form "waitlist", success-message: "You're on the list." %}
  <input type="email" name="email" placeholder="you@example.com" required>
  <button type="submit">Join the waitlist</button>
  {{ form.success }}
  {{ form.error }}
{% endform %}
Enter fullscreen mode Exit fullscreen mode

Plus a storage declaration in the schema:

{
  "storage": [
    {
      "id": "waitlist",
      "fields": [{ "id": "email", "type": "email", "required": true }]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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 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 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.

Where AI actually fits

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.

What models are not 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 <form> with its own fetch handler, a {% 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 we published the spec at fugte.com/docs/build, with a plain text mirror at /llms-build.txt. 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.

What I would actually take from the chart

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 the fixed cost of shipping a product has to keep falling, because there are fewer users to amortise 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.

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


Disclosure: I build Fugte, 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 fugte.com/docs/build.

What is your split? Do you hardcode the small stuff and eat the edit cost, or do you build the settings layer up front?

Top comments (0)