← Back to articles
AI-generated image

A stack for solo developers and early startups: build small, keep operations light

How to choose technology to build and run with a small team, using the setup of a real web app (React Router, Cloudflare Workers, D1, Clerk) as the example.

Written by AI · 13 min · Enough if you have built one web app
Solo devCloudflareArchitecture
What this article covers

A small team should choose its stack for light operations, not raw speed. Own no servers, delegate authentication, automate deployment, and keep a human check only for production. These four keep a small team going for a long time.

How a small team should choose technology

Big companies start from “will it survive thousands of users?” Solo developers and newly founded startups have different problems. There are few people, little time, and when something breaks at night, you fix it yourself.

This article uses the setup of a small web app that is actually running, PAIBOT.app, to show how to choose a stack you can build and keep operating with few people. Five criteria guide the choices.

  1. Own no servers: do not carry OS updates, reboots or disk worries yourself
  2. Low fixed cost: you can start in a form that costs almost nothing when nobody uses it
  3. Delegate what you can: hand things that are dangerous to get wrong, such as authentication, payments and email, to specialist services
  4. Automated tests and deployment: the fewer people you are, the more time manual checks waste
  5. Stay movable: do not tie yourself to one company more than you must

This setup is one example, not the answer. Service features, prices and free tiers change, so check each company's official documentation before you use it.

The overall setup

PAIBOT.app is a web app of small browser tools with login and favorites added. Its setup is:

A small web app setup (React Router + Cloudflare)BrowserClerkexternal authsign inverify identityCLOUDFLAREWorkerReact Router SSR and APID1SQLite-based DBWorkers AIcalls modelsRate limitper-IP countsStatic assetsserves images, JS, CSS
Figure 1 No servers of your own. A Worker receives browser requests and uses the database, AI and rate limiting through bindings. Authentication is delegated to an external service.
  • Framework: React Router (framework mode). Pages are built on the server (SSR), and run as interactive parts in the browser
  • Runtime: Cloudflare Workers. There is no server of your own; the code runs at sites around the world for each request
  • Database: Cloudflare D1 (SQLite-compatible), with Drizzle as the ORM
  • Authentication: Clerk. You build neither the login screen nor password storage
  • AI: call Workers AI through a binding, so you need not hold an API key for an outside AI service
  • Rate limiting: limit the number of requests per IP with Workers' rate limiting feature

Choices by layer, and why

LayerChoiceWhy
PagesReact Router (SSR)Pages that search engines can read are built on the server. Routing and data loading come together in one mechanism
RuntimeCloudflare WorkersNo server operations. You can start in a form that costs almost nothing while unused
DataD1 + DrizzleNo separate database server to run. You write typed queries. Schema changes are managed with migrations
AuthenticationClerkAuthentication is easy to get wrong and costly when wrong. Delegate it to a specialist service
i18nuse-intlJapanese, English and Chinese are managed in message files
TestsNode's built-in test runner + PlaywrightLight unit tests for calculations and the like, and one browser flow per screen
Config managementWrangler + TerraformNever manage the same thing in two places. Wrangler for what is close to the app, Terraform for account-wide settings

The reason is not “Workers is fast” or “D1 is cheap” but that they need little operational effort.

A tip for growth: one tool, one folder

So that things do not get messy as tools multiply, each new tool keeps all its own parts in one folder. The screen, logic, unit tests and browser test live together, and registering it in one shared list is enough for it to appear in the top-page search and the sitemap.

  • Run browser tests only for the tools a change can affect (all of them when shared code changes)
  • Shared checks (opens in three languages, no errors, fits a phone width) apply to every tool automatically, with no test code
  • Environment-specific code (Cloudflare-only features) is confined to server-only files

The last point serves “stay movable”. Keeping the environment-dependent part a thin layer keeps the impact small if you change the runtime.

The deployment flow

On a small team, the more you deploy by hand, the more mistakes you make. PAIBOT.app works like this:

How a change reaches productionSubmit a change (PR)Automatic checkstypes, unit tests, build, browser testsAuto-deploy to developmentAccess lets only allowed people inProduction run by handconfirm it passed in dev; roll back by tagDeploy to productionDB migrations in the same procedureAUTOMATICHUMAN CHECK
Figure 2 Checks and the deployment to development are automatic; only production is run by a person. That manual run is the human check.
  • For every change (PR), type checks, unit tests, the build and browser tests run automatically
  • When a change lands on main, it is deployed to the development environment automatically. The development environment is protected with Cloudflare Access so only allowed people can open it
  • Production is run by hand. The production procedure confirms that the change succeeded in development. If something is wrong, you can roll back to an earlier version by specifying a tag
  • Database changes (migrations) are part of the production deployment procedure

Production is manual because “everything automatically reaches production” is frightening. The manual run itself is the human check. (At the time, the plan we used lacked the team approval feature, which is why it is shaped this way.)

Traps we actually hit

The setup was not like this from the start. The app was first built on Next.js, carried onto Cloudflare Workers with a beta adapter. It worked, but it was a combination with few precedents, and we got stuck several times at the joins: how the authentication library was loaded, the internationalization library loaded twice, and so on. In the end we moved to React Router, which runs naturally on Workers.

What this shows:

  • Pick well-trodden combinations for production: with a beta part in between, you cannot tell whether a bug is in your code or in the part
  • When stuck, suspect a duplicated module: if the same library is loaded by two paths, one copy's state is invisible to the other, and the error shows up inside the library
  • Keep it movable: because the boundaries were thin, it was a migration, not a rewrite

What changes between solo dev and an early startup

AspectSolo developerEarly-stage startup
What mattersBeing able to keep buildingSpeed of validation, and fixing things later
AuthenticationDelegate (do not build it)Delegate. Check early whether enterprise login (SSO) is needed
DatabaseStart small and keep it in one placeGather database access in one place, expecting a future move
CostStart on free tiersEstimate where the free tier runs out, and the price after it
MonitoringJust enough to see errorsDecide who is notified of failures and who responds
When people joinHardly a concernAutomated tests and deployment are what let you add people

Cautions when choosing

  • Dependence on a service: the more convenient the feature, the more you lean on that service. Keep the entrance to the dependency in a thin layer so you can move
  • Free-tier traps: even a large free tier has conditions and limits that change. Look early at the price when your usage grows
  • Where data lives: where user data is stored (region, regulations) is hard to change later. Decide early
  • What you cannot delegate: even if a service handles it, designing permissions (who can see what) remains your job

Checklist

  • Is your setup free of OS and server management by you?
  • Have you avoided building authentication or payments yourself?
  • Do tests run automatically for every change?
  • Is there a human check before production, and a way to roll back?
  • Are database changes recorded and managed?
  • Is environment-dependent code gathered in one place?
  • Have you looked up the free-tier limits and the price beyond them?
  • Have you decided who notices and who fixes a failure?

Official references

React Router — Documentation ↗Cloudflare Workers — Documentation ↗Cloudflare D1 — Documentation ↗Clerk — Documentation ↗Drizzle ORM — Documentation ↗
PAIBOT / DEVELOPMENT JOURNALBack to articles →

Find what you need

Searches titles, tags and body text esc to close