while building a low‑latency analytics dashboard on supabase edge functions, the first request after a few minutes of inactivity consistently hit ~800 ms latency. the spike broke the UI's 200 ms frame budget and forced me to rethink the function deployment.
why edge cold‑starts matter
edge functions run in lightweight containers that spin down after a period of inactivity. when a new request arrives the platform pulls the latest bundle, resolves dependencies and starts the runtime. this process is fast compared to traditional VMs, but it’s still enough to cause noticeable jitter in real‑time UIs.
three pragmatic knobs to pull
-
bundle size – every kilobyte adds network and unzip time. using esbuild with
--bundle --minifystrips dead code and inlines small assets. - runtime pre‑warm – a tiny cron job that pings the function every 30 s keeps the container alive without costing much quota.
- cold‑start guard – detect a cold start inside the function and return a short‑lived placeholder while the heavy work finishes in the background.
example: a lean typescript edge handler
import { serve } from "@supabase/functions-js"
// small helper that can be tree‑shaken
export const getStats = async (userId: string) => {
const res = await fetch(`https://api.example.com/stats/${userId}`)
return res.json()
}
serve(async (req) => {
// simple cold‑start flag – runs once per container lifecycle
const isCold = !globalThis.__warm
globalThis.__warm = true
if (isCold) {
// fire‑and‑forget heavy init (e.g. DB pool)
setTimeout(() => getStats("placeholder"), 0)
return new Response(JSON.stringify({ status: "warmup" }), { status: 202 })
}
const { userId } = await req.json()
const data = await getStats(userId)
return new Response(JSON.stringify(data), { headers: { "content-type": "application/json" } })
})
trade‑offs to watch
- pre‑warm traffic: pinging every 30 s adds a few hundred requests per day; check your quota limits and consider a serverless‑friendly cron (supabase scheduled functions or GitHub actions).
-
stateful globals: using
globalThisis cheap but only works for data that can survive a container restart. never store user secrets here. - error handling: the placeholder response should be idempotent; the client must be ready to retry or show a loading spinner.
-
bundle vs native deps: pulling in heavy native modules (e.g.
pg-native) forces the platform to compile a larger binary, dramatically increasing cold‑start time. prefer pure‑js alternatives when latency is critical.
wrapping up
by shrinking the bundle, keeping the container warm, and explicitly handling the first request, I cut the 800 ms cold‑start tail to under 120 ms on repeat calls. the pattern works for most supabase edge functions that serve UI‑critical data.
what strategies have you used to mitigate edge cold‑starts in your stack, and how do you balance quota cost against latency guarantees?
Top comments (0)