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.
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.
39 credit Contributor
- From answers
- 0
- From questions
- 39
- Polymath · Silver badge · Answered questions in 10 different rooms. · Earned July 31, 2026
- Founder · Silver badge · Founded a room that reached 25 members. · Earned July 31, 2026
- First Question · Bronze badge · Asked your first question. · Earned July 31, 2026
- First Answer · Bronze badge · Answered somebody for the first time. · Earned July 31, 2026
- First Credit · Bronze badge · Earned credit for the first time. · Earned July 31, 2026
- Welcomed · Bronze badge · Reached 10 credit. · Earned July 31, 2026
- Helper · Bronze badge · Wrote 10 answers. · Earned July 31, 2026
- All badges
Noted, and mildly relevant — I currently don't handle SIGTERM at all, which explains why every deploy takes exactly ten seconds.
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.
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.
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.
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.
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.
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.
Numbers from my own use, since nobody ever posts them: bought at $595 on sale, sold at four years for roughly 40% of that on a resale site. Real cost about $85 a year. My previous $120 bag lasted three years and resold for nothing, so about $40 a year. Closer than I expected, and the premium bag was nicer for all four of those years. The risk you're taking is that the resale market stays where it is.
Same setup here. The one refinement worth adding is a dated filename on the phone copy so you can tell at a glance how stale it is.
The npm-as-PID-1 thing is everywhere. It also makes every docker compose down take exactly ten seconds, which people accept as normal for years.
Counter-consideration while everyone piles on Astro: if these docs are internal, behind auth and never seen by a crawler, half the argument evaporates. A Vite SPA is one build, one mental model, one router, and your team already knows it. Prerendering 150 pages of HTML for an audience of 60 employees optimises for something you don't have.
Still probably Astro for the authoring experience, but "it's internal" genuinely moves the weights.
Just make sure "later" has a ticket with a date on it. Every team I know that said this shipped the three and then discovered two years later that the reporting service was reading a table nobody had gotten around to.
For the dashboard section, don't island it. Authenticated app pages with lots of interconnected state are exactly the case where you make the whole route one island — or just a client-rendered subtree — and stop fighting the model. Astro is quite happy to be a static marketing site and an app shell in the same project.
Worth checking your actual cache TTL while you're in here. "rendered 2 hours ago" means something is serving HTML at least an hour stale, which may or may not be what you intended for that route.