Notes

How a one-person software workshop ships

Published 2026-09-14 · 6 min read

Crafnia is one developer in Thailand building small web apps and shipping them in the open. There is no team standup to keep scope honest, no ops group to catch a bad deploy at 2 a.m., and no marketing budget to paper over a broken first impression. The process below exists because a one-person shop cannot afford to relearn the same lesson twice. It grew out of specific mistakes on specific apps, and I will name them.

Web first, mobile only if it earns it

Every app here starts as a web app, full stop. The long-term plan for anything worth continuing is to eventually be on the web, iOS and Android — but web comes first because it is the only one of the three with no gatekeeper and no yearly toll. An Apple developer account costs $99 a year before a single build ships; Google Play is a one-time $25. Neither fee is large by itself, but paying it before knowing whether an idea is worth pursuing is backwards. A web app answers that question for free: put it on a subdomain, see whether anyone comes back, and only then decide if a store listing is worth the fee and the review queue that comes with it.

This also means picking a stack that ships cleanly to a browser rather than one that has to be rewritten for it later. BlockClock, the Bitcoin clock, runs on React and Vite specifically because an earlier Flutter-for-web build rendered to a mostly empty canvas — fine for a phone screen, bad for anything that needs to be crawled, read, or advertised against. Apps that do want a native mobile shell later keep that codebase alongside the web one instead of retrofitting one onto the other after the fact.

No backend where I can avoid it

The cheapest server to run is the one that does not exist. BlockClock has no backend and no user accounts — it is a static site that talks to public Bitcoin data sources directly from the browser (mempool.space, blockstream.info, blockchain.info, CoinGecko) and renders whatever comes back. If those public APIs are up, the clock is up. There is no database to back up, no server process to patch, and nothing that can fail independently of services that already promise to keep themselves running. FitCalc, a set of fitness calculators, goes further still: it is a static-HTML generator with zero runtime dependencies. A build script turns source files into plain HTML and CSS, Firebase Hosting serves the result, and that is the entire stack.

This is not a blanket rule against backends — some apps genuinely need one, and when they do they run on managed infrastructure rather than a server I maintain by hand. But every backend is a standing decision to take on more operational weight, and for one person that weight compounds across every app running at once. The question before adding one is never "would this be nice to have" but "what happens to this app during the week I do not touch it" — and a static site with no moving parts answers that question by default.

One app, one subdomain

Every app gets its own subdomain off a single root domain, crafnia.com, rather than its own domain name. That is partly cost — one registration instead of several — and partly reputation: a Google AdSense account is reviewed once at the root, and approval there covers every subdomain underneath it, so a new app does not have to earn ad eligibility from zero. It also means one domain builds a track record over years instead of scattering that history across several barely-used ones.

Nothing reaches production without a preview link first

Every change — a new page, a redesign, a copy fix — goes to a Firebase Hosting preview channel before it goes live. That produces a real, working URL to look at before anything is public, and it is the only way anything here gets deployed, with one narrow exception: moving content that has already been reviewed elsewhere to its permanent home without changing it. Everything else gets a preview link first. It is a small amount of ceremony for a solo operation to impose on itself, but the alternative is shipping straight to the domain that real visitors and an ad reviewer are both looking at, and finding out about a mistake from either of them instead of from a link only I opened first.

Nothing new starts until the last thing runs itself

The hardest discipline here is not technical, it is sequencing. An app that needs daily attention to stay up — a job that quietly stops running, a feed that silently stops updating, a page that needs manual review before every change — is a liability that grows every time another app gets stacked on top of it. So the rule is blunt: nothing new begins until the previous thing can run for a month without me touching it. That is slower than it could be. It is also the only way one person keeps several live products from turning into several part-time jobs of babysitting them.

Why the parts list stays short

Add it up and the pattern is really one idea in different clothes: fewer moving parts per app, and fewer distinct kinds of parts repeated across apps. Static hosting, a shared domain, a shared ad account, a preview-before-production habit, and a bias toward no backend at all — none of it is exotic, and that is deliberate. Every piece of infrastructure unique to one app is a piece that only I know how to fix, whatever hour it breaks. Keeping that list short is what makes it possible to run more than one app at all.