Engineering

Running a real app on the Cloudflare Workers free plan

What the free plan's 10 ms of CPU per request means in practice, and the design choices that keep a live travel app inside it.

Our app Perron shows live departures and runs chat rooms at every Dutch train station. Its backend runs on Cloudflare Workers, on the free plan. That is a deliberate choice for a free, ad-free app: a cost of zero means it can stay free. But the free plan has limits, and they change how you design things.

The limits that matter

  • 10 ms of CPU time per request. Not wall-clock time: waiting on the network does not count, but parsing JSON, sorting and building responses does.
  • Daily request budgets for Durable Objects, the stateful building block Workers use for things like rooms and caches.
  • Duration billing on Durable Objects that stay awake.

None of this is a problem for a typical API call. It becomes one the moment you do something heavy on every request.

Keep each request's work small

The obvious way to serve departures is to fetch a big timetable file and filter it on every request. Parsing a few megabytes of JSON alone can take longer than 10 ms. So the data is split into shards that are each small enough to parse in a couple of milliseconds, and a request only touches the shard it needs.

A rule of thumb that served us well: measure the CPU cost of your largest JSON parse, and design the data layout so the worst case stays at a fraction of the limit.

Do not keep things awake

The textbook fix for a cold cache is a keep-warm timer: an alarm every minute that refreshes data so it is always ready. On a paid plan that is fine. On the free plan, a Durable Object that wakes up every minute spends its duration allowance doing nothing useful most of the time.

Instead, idle caches are allowed to go to sleep. When a board is requested and its cache is cold, the request is answered from the last good copy kept at the edge, and the cache refreshes in the background. Travellers get an answer immediately; the system only works when someone is actually looking.

Ration the shared budgets

Daily request budgets are shared across the whole app, so the code treats them as a resource: rate limits per visitor, batching where possible, and no chatty polling from clients. Comments next to the rate limiter record why each number is what it is, so future changes do not quietly spend the budget.

Know which plan you are on

The most useful habit was the simplest: check the plan before proposing an optimisation. An idea that is right on a paid plan (keep it warm, do more per request) can be exactly wrong on the free one. Writing the plan's limits down next to the code saves you from rediscovering them.

When to upgrade

The paid plan is inexpensive, and for a business it is often the right call: more CPU per request, more headroom, fewer design constraints. Staying on the free plan made sense here because the app is free to its users. If you are deciding for your own product, the question is simple: is the time spent designing around the limits worth more or less than the subscription?

Building something that needs to run lean? Tell us about it.

Start a project

Have something you want built?

Tell us about it. We reply to every message with an honest take on whether we are the right fit.

Start a project