Scope the workspace, exclude generated code, drop the optional analyses, and keep the language server on a recent version. In that order, and stop after whichever one fixes it.
Bo Halvorsen
@edge_runtime_bo
Performance-obsessed developer at a small booking startup. I chase p95 latency for a living and I keep a spreadsheet of cold start times across every runtime we have tried.
60 credit Contributor
- From answers
- 0
- From questions
- 60
The protections that have actually saved me are all at the application layer, because they stop the loop rather than the invoice:
- App Check, so unknown clients cannot hammer your backend at all
- limits in your security rules, so an unbounded query is not expressible
- mandatory pagination, never an open ended collection fetch from a client
- a guard on anything recursive or retrying, especially a function that writes to a collection it also triggers on
That last one is the classic runaway bill: a trigger that fires itself. No budget setting will save you from it and it will do a month of usage in an hour.
Payment links plus the hosted portal covers upgrade, cancel and card updates, which is most of the billing support you'd otherwise be doing manually. An afternoon, and you never think about it again.
Noted, and mildly relevant - I currently don't handle SIGTERM at all, which explains why every deploy takes exactly ten seconds.
Plus the structural bit: Auto does not draw from the allowance. So the cheapest sane setup is Auto for the grunt work, explicit model picks for the steps where you actually care, and never leaving an expensive pick selected out of habit between sessions. Most of my allowance used to go on unremarkable edits that Auto would have handled identically.
Two days. That's how long I spent reading my own code looking for a leak. Set it to 384, ran the same load test, peak RSS 470, no kills.
If the hotel-network use is the frequent one, care about the app quality on the platform you use in a hurry, and about whether the kill switch fails closed on a flaky captive portal. That has caused me more grief than any difference in the underlying service.
Idle is mostly a big in-memory geo lookup table, about 180 MB. Legitimate, but I should probably move it out of the API process entirely.
The window seat is the real answer and on a lot of routes it costs less than most of these pillows.
Do a two-hour table spike before you commit. Load 5,000 rows with sorting, column resize and a sticky header in both and watch what happens. That single test has decided this for me twice, and not in the same direction both times.
Listeners, almost certainly.
A snapshot listener bills for the documents it delivers, and it delivers the whole matching set when it attaches. So a screen that attaches a listener to a collection, and re-attaches on every navigation, tab focus, reconnect or hot reload, pays for that set again each time. One screen showing 200 documents, mounted a few hundred times across your users in a day, is your entire allowance on its own and there is no single dramatic event in your traffic graph to point at.
What to look for: listeners that are never unsubscribed, listeners attached in a component that remounts, and anything that re-subscribes on network flap. That last one explains days that are 4x other days with identical traffic, because it depends on how good your users' connections were.
What you're doing has a name, it's just called deployment. Tutorials sell complexity because complexity is what there is to teach: nobody writes a course called "ssh in and restart it".
The one thing I'd add at your size is knowing how to get back. Tag your images with something other than latest, or at minimum know the git SHA that's running. When you break it, "go back to the previous thing" should be a command you already know rather than one you look up while it's down.
The reason to buy an arm isn't motion though, it's reclaiming the desk surface and being able to push the screen further back than any stand allows. My eye strain dropped when I got the monitor out to arm's length, and no riser would have let me.
The pattern that fixed this for us was a dispatcher job that pages rather than a cron that loops. One job wakes up, selects a page of five hundred due users, enqueues those, and enqueues a copy of itself with the next cursor. The database sees a series of small bounded queries instead of one enormous transaction, the work is naturally resumable if a worker dies mid-run, and you can watch it progress in the dashboard.
We went from a twenty minute lock-heavy sweep to a stream of small jobs that finished in about four minutes, and the app stopped being slow during it. That was one afternoon of work and it did not require changing how anything downstream behaved.
base resolves from the project root, not from src/content. Your files are in src/content/blog and you pointed it at ./content/blog, so it globbed a directory that doesn't exist and matched zero files. No error because an empty glob is perfectly legal.
loader: glob({ pattern: '**/*.md', base: './src/content/blog' })
Two follow-ups worth doing while you're there:
- delete
.astro/and re-run. The content store is cached and a stale one will keep serving you an empty collection after you've fixed the path, which is how people conclude the fix didn't work - put
if (posts.length === 0) throw new Error('no posts')in your index page. This will happen again, and a build that silently ships an empty blog is far worse than a red build
z.coerce.date() handles a real Date object fine. What breaks people is an unquoted 2026-03-04 09:00, which YAML parses into a Date in the machine's local time, so you get an off-by-a-timezone rather than an error. Quote your dates in frontmatter and it goes away.
We do have a cache keyed by user id with no eviction. I feel slightly stupid now.
Do not self-host on a cheap virtual server to save money unless you understand that you become the only user of that address. It is great for bypassing a nosy network and terrible for blending in, which is the opposite of what most people want when they say privacy.
Whatever you choose, separate it from the backup fix. Do WAL archiving this week regardless. If you migrate to managed in three months you've lost nothing and you'll know how your own restore works, which you'll want anyway the day the managed provider has a bad afternoon and you need a copy somewhere else.
The look you want usually comes from a single-tone fabric with the hardware hidden, or canvas with leather trim, and that style does exist at your budget. The trade is that those bags almost always have a worse harness and no sternum strap, which on a bike with a 16" machine you will feel by about month two. At $200 you get the look or the carry, not both. Decide whether the three bike days or the two office days are the ones you're optimising for.
No, and the confusion is in the naming. OAuth is a delegated authorization protocol: it exists so a third party can act on a user's behalf against a resource they don't own. Your frontend is not a third party. It's the same application.
What people mean when they say "use oauth" is usually either "add social login so users don't make another password" or "outsource credential storage so you never handle a hash." Both are legitimate product reasons. Neither is a security requirement for a first-party client with a session cookie, which is the boring and correct design you already have.
Worth knowing the other common source of this exact error, because it catches people who didn't write anything weird in a derived: mutating state from a template expression. A function called inside {#each} that pushes to an array will do it. Same rule, much less obvious. If the stack trace points at your markup rather than at a $derived, that's what happened.
this is why my personal projects live on providers that offer an actual spend limit that stops serving. for a hobby project i'd genuinely rather the site goes down at $20 than stays up at $2,000, and the big clouds are philosophically incapable of offering me that.
Small honest numbers from mine, because everyone quotes percentages off a base of nine users. First 40 signups: 6 converted to paid, 3 of those cancelled inside two months, 2 of the cancellers came back over a year later after a feature email. So my dramatic churn disaster was three people, and two of them were recoverable with one email. The fear is real but it's operating at a scale where it costs you almost nothing, the six months of building in silence would have cost far more.
Cost note while you are in there: multipart bills a Class A operation per part, plus initiate and complete. 5 MB parts on a 2 GB file is 400-odd Class A ops for one upload. Unless you specifically need fine-grained resume, use much bigger parts: 64 or 128 MB: and your operation count drops by an order of magnitude.