Set an explicit time limit for each route
Find the platform ceiling and set a lower limit on purpose, so a runaway call ends where you chose. On Vercel the defaults and maximums depend on plan and on Fluid compute, so read the duration table for your plan before you set it.
// app/api/report/route.ts (Next.js App Router on Vercel)
export const maxDuration = 30; // seconds; must not exceed your plan's maximum
Index what you filter and sort on, and measure at production size
Run EXPLAIN ANALYZE against a copy with production-sized data and look for Seq Scan on large tables. EXPLAIN ANALYZE executes the statement, so wrap anything that writes in a transaction and roll back.
CREATE INDEX orders_user_created_idx ON orders (user_id, created_at DESC);
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC LIMIT 25;
Stop counting the whole table on every request
Page results with a cursor or a limit. Show '10,000+' instead of an exact total, or keep the count in a small counter row that you update when records change. A count that runs before every query is a common cause of the search that times out.
Put a timeout on every outbound call
Base it on the downstream service's measured latency, not a guess. AbortSignal.timeout() aborts a fetch and throws a TimeoutError you can handle.
try {
const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
} catch (err) {
if (err.name === 'TimeoutError') { /* fail fast, record it, move on */ }
}
Cap retries, back off with jitter and retry in one layer
Choose a maximum attempt count and increase the wait after each attempt. Add jitter so clients that failed together do not retry together, and only retry idempotent operations. For queues, a dead-letter queue stops a poisoned message from looping. In Amazon SQS, the redrive policy's maxReceiveCount sets how many receives are allowed before the message moves to the DLQ.
{
"deadLetterTargetArn": "arn:aws:sqs:REGION:ACCOUNT:jobs-dlq",
"maxReceiveCount": 5
}
Make long jobs resumable
Split the work into steps and store a checkpoint (the last id processed) after each one, so the next invocation can pick up from it. Give every step an idempotency key so a replay does not charge or email twice.
Alert on every quota before it reaches 100%
List each limit on each plan you use (function duration, requests, CPU time, build minutes, email sends, project activity, billing status) and set an alert or a calendar check for each. Keep a valid payment method on file. Use an account email you can still receive codes at, because a billing pause can also block the sign-in code you need to fix it.