This is the right call at two people and the wrong call at six, and the migration between them is the proxy setup above. Worth knowing which one you are before you build the sophisticated thing.
Sy
@stack_trace_sy
Reads other people's error messages for sport and thinks stack traces are underused.
45 credit Contributor
- From answers
- 0
- From questions
- 45
Fingerprint is the whole answer long term. Maintaining a runtime version by hand means the safety mechanism only works when the person doing the release remembers it exists, which is the same as it not working.
Five minutes means a refresh every five minutes from every active client, and clients have several tabs open. That is a real amount of traffic to an endpoint that touches your database on every call, so measure it rather than assuming small TTL is free.
Priority in the queue is the main thing the paid tiers buy, and it is very noticeable when the free queue is busy. Concurrency matters when a team pushes at once, which at one person is indeed irrelevant - but that is the part that does not apply, not the part that does.
Entirely depends on how often you are blocked on it, which is a different question from how often you build.
Three builds a week and none of them urgent: local is fine and free. Android especially, since you do not need particular hardware for it. Fifteen builds a week with a release waiting: the paid tier pays for itself in one afternoon of not context switching, because the real cost of a 52 minute queue is not 52 minutes, it is that you started something else and lost the thread.
What people undervalue is that a queue only costs you when you are waiting on it. Most builds are not that. Today was, because you were iterating on signing config, and that is exactly the case where you should be building locally anyway - you do not want a remote round trip in a debug loop.
Two headsets it is. I was trying to avoid clutter and instead I built a trap.
We found the header limit at around 300 memberships and it presented as a 431 from one proxy in one region, so it looked like an infrastructure problem for most of a day.
Counterpoint on scope: don't reach for a full queueing product at this size. A separate always-on worker process, a dashboard, a broker to keep alive, and now you're operating three things instead of one. Postgres-backed jobs first, migrate when the volume genuinely justifies it.
"Take the first fatal" deserves to be printed on something. The signal to noise in a React Native crash log is genuinely awful and the useful line is almost always near the top of the burst.
Small tactical thing that dropped my anxiety a lot: separate notification sounds per machine. Once I could tell from the next room which employer was pinging me, the ambiguity went away and most of the spike went with it.
And check whether whatever sits in front of your API is rewriting or dropping Set-Cookie. Some CDN caching configs strip it on responses they think are cacheable and it's invisible from the browser side.
Unrelated but adjacent: in local dev, localhost and 127.0.0.1 are different origins as far as cookies are concerned, and that will independently cost you an hour on some other day. Pick one and put it in the readme.
Rules I use now, learned the same way:
- one partner never sets the roadmap. Two minimum, and you only build what at least two of them asked for independently
- every build commitment gets a paired commitment from them. We ship X by date D, you get three named people using it within two weeks of D
- if they can't tell you who owns the budget by week two, they aren't a partner
None of this makes anyone buy. It just makes the no arrive early enough to be cheap.
curl doesn't enforce CORS, it just prints headers, so it will always look fine. Read the actual browser message rather than the summary line, it usually names the missing piece.
My first guess is Access-Control-Allow-Methods. Plenty of setups end up with GET, POST, HEAD and PATCH simply isn't in the list, which passes for your working verbs and fails for this one.
Second guess is Access-Control-Allow-Headers needing to list authorization and content-type explicitly. A bearer token makes the request non-simple, so both have to be allowed by name.
Third, and this one is sneaky: check your auth middleware runs after the CORS middleware. If something is answering OPTIONS with a 401 because there's no token on the preflight, curl won't care and the browser will refuse.
Classic. While you're there, set maxAge to something like 600 so the browser stops sending a preflight before every single write.
Test on hardware, not the simulator. Interop differences show up in the view hierarchy and in native lifecycle timing, and a simulator will happily tell you everything is fine.
There's no display at POST, before Windows exists. Drivers can't be involved that early.
The two tables are the easy part and always have been. The cost is the forty edge cases around them - email enumeration, account linking when the same person signs up with Google and then with a password, session fixation, rate limiting the login endpoint, what happens when someone changes the email on their OAuth account. You only meet those once you have users, which is the worst time to meet them.
At 2,100 users and one developer, keep paying, and it is not close.
The bill is not the cost. The cost is that you now own password reset emails and their deliverability, session invalidation, MFA the first time someone asks, SSO the first time a real customer asks, bot signups, credential stuffing, and the 2am "nobody can log in" that has no one else to escalate to. $25 and change is roughly twenty minutes of your time per month. You will spend that on auth in a bad week regardless.
Revisit when the bill is a meaningful fraction of revenue, or when you need something they will not do. Not when it crosses from zero to a number, which is a psychological event rather than a financial one.
Also drop the select * while you're in there. On a 30 million row events table you're very likely pulling a jsonb payload column across the wire for 50 rows you're only going to show three fields from, and that cost shows up in the client, not in the query plan.
The silent disappearance is the entire reason plugins exist. It is not ceremony, it is the only mechanism by which your change survives a regeneration. Once you see it that way the fifteen lines stop feeling like overhead.