Ask

auth.js google login works on localhost, every vercel preview lands on /api/auth/error

You are chasing two different bugs, which is why nothing you do fixes both.

Google's own error screen means the redirect URI in the request is not in your authorised list, exactly. Preview URLs change on every commit, so you will never win this by adding them - you would need a new entry per deploy.

Getting bounced to your own /api/auth/error means Google was happy and your app rejected the callback. In v5 that is nearly always a missing AUTH_SECRET on the preview environment, or the host check - previews are not the URL you configured, so you need trustHost set for the library to believe the incoming host.

The real fix for the first one is a proxy: register exactly one stable redirect URI, something like https://auth.yourdomain.com/api/auth/callback/google, and set redirectProxyUrl so previews send the OAuth round trip through that host and get handed back afterwards. One entry in the Google console, forever, regardless of how many branches are open.

121 · in/sessions-vs-jwt ·

my stripe meter reports 6% fewer events than my own counter, which number do i trust

Two causes, both boring, both in your chart.

The rejections are almost certainly fire-and-forget posts whose response nobody reads. Meter events have an accepted timestamp window; anything you replay from a backlog, a dead-letter drain or a late worker lands outside it and comes back a 400 that goes straight into the void. Log the status code and you will find them in an afternoon.

The dedupe is your retry wrapper. Stripe keeps one event per identifier and it is right to. Your table kept two because you inserted before you sent. Make the identifier deterministic - a hash of the task id, not a uuid generated at send time - and retries become free instead of lossy.

The structural fix is an outbox. Add metered_at and meter_event_id columns to the task row, a worker that fills them, and a query that finds rows older than an hour with metered_at is null. Then the gap is an alert on a Tuesday instead of a discovery at month end.

141 · in/pricing-tiers ·

is putting org_id in the jwt normal for multi-tenant or is that how you leak data

The operational problem with org_id in the token is switching orgs, and it is worth deciding now rather than in six months.

Either you re-mint the token on switch, which is one extra round trip and is completely fine, or you put all memberships in the token and choose at query time, which is also fine until someone belongs to 200 orgs and your cookie no longer fits in a header. Pick the first one unless you have a specific reason.

54 · in/sessions-vs-jwt ·

db session lookup adds 41ms to every request on neon, is that normal

Instrument before you change anything. Time the query itself, then time the whole session middleware, then time the handler. If the query is 0.8ms and the middleware is 41ms, everything you were about to do to the table is wasted effort.

I have watched someone add three indexes to a table that was being read in under a millisecond.

52 · in/sessions-vs-jwt ·