Skip to main content
Core DeliveryIntermediate8 min read

Building Small Apps

A day-one checklist for solo builders, and the stories behind it

I build a lot of small apps. Fantasy leagues for reality TV, a card collection tracker, a fan site for a show that ended in 2010, tools on this site. In October 2026 I audited five of the live ones end to end, then mined about 30 more of my own projects for the same patterns, and checked every finding against outside sources.

The result was humbling in a specific way. Almost nothing was broken in a way that threw an error. Things were broken in ways that looked fine: a dashboard that counted the wrong thing, a scheduled job that simply didn't run, an analytics library mounted on a project where analytics was switched off. Small apps don't fail loudly. They fail quietly, and a solo builder has nobody else watching.

This is the checklist I now run before launch. It's written for one person with a database, a host and an AI coding assistant, which is how most small apps get built now.

The day-one checklist

Read it as a set of boxes. When one doesn't tick, that's where to spend the hour.

Security

  • Every table a user can update limits which columns they can change, not just which rows. A rule like "users can edit their own record" still lets them edit every field on it.
  • No view, function or shortcut writes around your access rules. Check what each one can do as an anonymous user, not what it was meant to do.
  • Every trust check fails closed. If the check depends on a claim or header that might be missing, test it with the claim missing.
  • No AI agent that reads untrusted content (the web, a forum, user uploads) can reach a database or deploy secret. See Security Thinking.
  • Every API route that writes data or spends a paid API key checks who's calling and is rate-limited.
  • No secret sits behind a public environment prefix (NEXT_PUBLIC_, VITE_). Anything with that prefix ships to the browser.
  • Payment webhooks verify the signature and handle each event once.
  • Deploying is a checked step, not an assumption. Merged is not deployed, and a merged migration is not an applied one.

Measurement

  • Your North Star is a query you can run today against the tables you already have, and you've written down its first reading with a date.
  • People are counted from product tables. Client-side events count attempts, never people.
  • Money is counted from the payment provider's webhook, and your table matches the provider's count exactly.
  • Analytics is confirmed on at the provider. One test event from production has actually arrived.
  • Bot, preview and your own traffic are filtered out, and the filter has been tested against a spike.
  • Any A/B test proposal states its baseline, minimum lift, weekly users and how many weeks it needs. Most small apps can't finish one. See Experiment-Driven Development.
  • Every zero is written with its denominator: "0 of 8", not "nobody".

Operations

  • Every scheduled job has an alarm for "didn't run" and "started but never finished", not just "failed".
  • Every alarm reaches a channel a person actually reads.
  • Error reporting is tested with a deliberate throw in a page, a layout, a click handler and a server route.
  • Dates, seasons and deadlines come from config, never literals in the code.
  • After any port, migration or domain move, you run the core action yourself in production, signed out.
  • You've restored from a backup once.

Experience

  • The main call to action lands on something an anonymous visitor can actually do.
  • Every list screen has an empty state with one action that creates the first item.
  • Every page has its share image in the raw HTML, so links unfurl.
  • Keyboard focus is visible everywhere, and contrast passes in every theme you ship.

AI features

  • Every AI route has a scheduled production probe that checks a real model answered.
  • Every fallback is logged as an event, so "the AI is down" can't hide behind "the feature still works".
  • Every eval number names its denominator and adds up.

Strategy

  • You've named three places the first 100 users will come from, and run one cheap test of one of them before the first build week.
  • Your demand test has a metric, a threshold, a date and a default outcome if nobody decides.
  • Targets say "measure the baseline" until one ordinary cycle of real traffic exists.
  • You've written one "sunset if" line, so the app can end cleanly instead of decaying.

The stories behind it

Every box above came from something real. These five are the ones that changed how I build.

The spike that every event believed

Over three days, one of my apps logged about 34,000 one-page sessions, roughly 90% of a month's page views. The host's analytics scored them as human. Every event tile went up. The product tables, the rows where real people had saved real things, didn't move at all.

That's the whole case for counting people from tables. Client events are wrong in both directions: bots and retries inflate them, and ad blockers deflate them (independent measurements put blocking at 6% to 26% of ordinary audiences, and much higher for technical ones). Over half of bad bots now pose as real browsers (Imperva, 2025), so a user-agent filter lets the damaging spike straight through. The table didn't care. It only counts what people did. More on this in Metrics.

The payment event that caught 12 of 29

On a fantasy league with a paid season pass, my analytics event for "payment completed" recorded 12 purchases. The payment provider recorded 29. The event fired from the success page, and buyers don't always reach the success page. Stripe says so plainly in its fulfillment docs: webhooks are required because a customer may never load it.

If your revenue tile reads from a client event, it can only undercount. Count money from the webhook into a table, and check that table against the provider once a month.

The job that never ran

A scheduled job on another league was meant to score each episode. For three episodes it didn't. One run started and hung. The admin health card showed yellow, because nothing had failed. Nothing had run either.

A "failed" alarm can't catch a job that never starts. Each job now writes a start row and a finish row, and the health check goes red when the expected heartbeat is missing or a start has no finish in time. Missing never renders yellow.

The analytics that were switched off

On a fan site, the analytics component was mounted on every page and every custom event was wired. The host project had analytics disabled. Every event went nowhere, and the North Star the strategy called "measurable now" had never been measured.

Wiring isn't the same as working. After you set up analytics, send one test event from production and look for it at the provider.

The model that retired

Every AI tool on this site started failing one day because a dated model id had been retired. Nothing paged me. Some tools had fallbacks that kept them looking alive. I found it by using one.

AI features fail more quietly than anything else you'll ship, because a fallback is designed to hide the failure. Now every AI route gets a scheduled probe that asserts a real model answered, fallbacks are logged and alarmed, and a test fails on any dated model id.

What the checklist is really about

Most of these boxes are the same idea in different clothes: "built" is not "proven." A migration merged is not applied. A function merged is not deployed. A metric defined is not computable. A job scheduled is not running. An event wired is not arriving.

The habit that closes the gap is cheap. After anything ships, go and look at the thing it was supposed to change, in production, as the person it was for.

Try this today

Pick the app you're proudest of. Run its North Star as a query against today's tables and write the number down with today's date. If you can't, you've found your first box.

Skills for this topic

AI skills you can run with Claude or Codex to put this practice to work.

See this in practice

Real engagements where this practice did the work.

Want help with building small apps?

I coach teams on this practice. Let's talk about your situation.

Get in touch