Supabase Edge Functions run on a Deno-based runtime and experience cold starts when a function has not been invoked recently. Reduce cold start time by minimizing the function bundle size, lazy-loading heavy dependencies, avoiding top-level await for slow operations, and keeping functions warm with scheduled pings. Functions under 5MB with minimal imports typically cold start in under 200ms.
Reducing Cold Start Times in Supabase Edge Functions
Cold starts occur when a Supabase Edge Function has not been invoked recently and needs to be initialized from scratch. The runtime must load the function code, initialize dependencies, and set up the execution environment. This tutorial shows you practical techniques to minimize cold start impact, from code optimization to warm-keeping strategies.
Prerequisites
- A Supabase project with at least one Edge Function deployed
- The Supabase CLI installed and linked to your project
- Basic understanding of Deno and TypeScript
- Access to the Supabase Dashboard for monitoring function logs
Step-by-step guide
Understand what causes cold starts
Understand what causes cold starts
A cold start happens when your Edge Function has not been invoked for a period of time and the runtime instance has been shut down. The next request must spin up a new instance, which involves loading your function code, resolving and importing dependencies, and executing any top-level initialization code. The main factors affecting cold start duration are: bundle size (larger functions take longer to load), number and size of imports (each dependency adds initialization time), and top-level code execution (code that runs at import time before the request handler). Typical cold starts for small functions are 50-200ms. Large functions with many dependencies can take 500ms-2s.
Expected result: You understand the three main factors that contribute to cold start time.
Minimize bundle size by reducing imports
Minimize bundle size by reducing imports
Every import adds to the code that must be loaded on cold start. Import only what you need — avoid importing entire libraries when you only use one function. For npm packages used via the npm: specifier in Deno, the entire package is bundled. Prefer smaller, focused packages over large utility libraries. For the Supabase client specifically, import from the npm specifier rather than a CDN URL for better caching.
1// BAD: Importing entire lodash (70KB+ bundled)2import _ from 'npm:lodash'3const result = _.pick(data, ['name', 'email'])45// GOOD: Import only what you need6import pick from 'npm:lodash.pick'7const result = pick(data, ['name', 'email'])89// BEST: Use native JavaScript instead of lodash10const { name, email } = data11const result = { name, email }Expected result: Your function imports are minimized, using only the specific modules needed.
Lazy-load heavy dependencies inside the request handler
Lazy-load heavy dependencies inside the request handler
Move heavy imports inside the request handler function so they are loaded only when needed, not at initialization time. Top-level imports run during cold start initialization, adding to the startup time even if the specific code path is not used for every request. Use dynamic import() for dependencies that are only needed in certain code paths.
1import { corsHeaders } from '../_shared/cors.ts'23// Keep the Supabase client at top level (it's lightweight)4import { createClient } from 'npm:@supabase/supabase-js@2'56Deno.serve(async (req) => {7 if (req.method === 'OPTIONS') {8 return new Response('ok', { headers: corsHeaders })9 }1011 const { action } = await req.json()1213 // Lazy-load heavy dependencies only when needed14 if (action === 'generate-pdf') {15 const { default: PDFDocument } = await import('npm:pdfkit')16 // Use PDFDocument...17 }1819 if (action === 'send-email') {20 const { Resend } = await import('npm:resend')21 // Use Resend...22 }2324 return new Response(JSON.stringify({ ok: true }), {25 headers: { ...corsHeaders, 'Content-Type': 'application/json' },26 })27})Expected result: Heavy dependencies are loaded on demand instead of at initialization, reducing cold start time for requests that don't use them.
Avoid top-level await for slow operations
Avoid top-level await for slow operations
Top-level await blocks function initialization until the awaited operation completes. This means any database queries, API calls, or file reads at the top level add directly to cold start time. Move these operations inside the request handler or use lazy initialization patterns that run on the first request instead of at module load time.
1// BAD: Top-level await adds to cold start time2const supabase = createClient(3 Deno.env.get('SUPABASE_URL')!,4 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!5)6const { data: config } = await supabase.from('config').select('*').single()78// GOOD: Create client at top level but fetch data lazily9const supabase = createClient(10 Deno.env.get('SUPABASE_URL')!,11 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!12)1314let cachedConfig: any = null1516async function getConfig() {17 if (!cachedConfig) {18 const { data } = await supabase.from('config').select('*').single()19 cachedConfig = data20 }21 return cachedConfig22}2324Deno.serve(async (req) => {25 const config = await getConfig()26 // Use config...27})Expected result: Slow async operations are deferred to the first request instead of blocking cold start initialization.
Keep functions warm with scheduled pings
Keep functions warm with scheduled pings
To prevent cold starts entirely for critical functions, set up a scheduled ping that invokes the function periodically. Use the pg_cron extension in Supabase to schedule a lightweight HTTP request to your function every few minutes. The function can respond immediately to these pings, keeping the runtime instance warm. This approach costs minimal resources but ensures your function is always ready for real user requests.
1-- Enable pg_cron extension (run once in SQL Editor)2create extension if not exists pg_cron;34-- Schedule a ping every 5 minutes to keep the function warm5select cron.schedule(6 'keep-function-warm',7 '*/5 * * * *',8 $$9 select10 net.http_post(11 url := 'https://<project-ref>.supabase.co/functions/v1/my-function',12 headers := jsonb_build_object(13 'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'),14 'Content-Type', 'application/json'15 ),16 body := '{"ping": true}'::jsonb17 );18 $$19);Expected result: The function is invoked every 5 minutes, preventing the runtime from shutting down and eliminating cold starts.
Measure cold start impact
Measure cold start impact
To quantify your cold start optimization, measure the response time of your function after a period of inactivity versus after a recent invocation. Use the Edge Function logs in the Dashboard to see execution times. You can also add timing instrumentation in your function code. Compare the first request after deployment (guaranteed cold start) with subsequent requests to see the difference.
1Deno.serve(async (req) => {2 const start = performance.now()34 // Your function logic here5 const result = { message: 'Hello!' }67 const duration = performance.now() - start8 console.log(`Request processed in ${duration.toFixed(2)}ms`)910 return new Response(JSON.stringify(result), {11 headers: { 'Content-Type': 'application/json' },12 })13})Expected result: You can measure and compare cold start vs warm request times to verify that your optimizations are effective.
Complete working example
1// supabase/functions/optimized/index.ts2// An Edge Function optimized for minimal cold start time34import { corsHeaders } from '../_shared/cors.ts'5import { createClient } from 'npm:@supabase/supabase-js@2'67// Lightweight top-level initialization (synchronous, no await)8const supabase = createClient(9 Deno.env.get('SUPABASE_URL')!,10 Deno.env.get('SUPABASE_ANON_KEY')!11)1213// Lazy-initialized cache for config data14let configCache: Record<string, string> | null = null1516async function getConfig() {17 if (!configCache) {18 const { data } = await supabase19 .from('config')20 .select('key, value')21 configCache = Object.fromEntries(22 (data || []).map((row) => [row.key, row.value])23 )24 }25 return configCache26}2728Deno.serve(async (req) => {29 // Handle CORS preflight30 if (req.method === 'OPTIONS') {31 return new Response('ok', { headers: corsHeaders })32 }3334 const start = performance.now()3536 try {37 const body = await req.json()3839 // Quick return for warm-keeping pings40 if (body.ping) {41 return new Response('{"pong":true}', {42 headers: { ...corsHeaders, 'Content-Type': 'application/json' },43 })44 }4546 // Load config lazily on first real request47 const config = await getConfig()4849 // Lazy-load heavy deps only when needed50 let result: any51 if (body.action === 'process') {52 // Main logic using lightweight operations53 result = { processed: true, config }54 }5556 const duration = performance.now() - start57 console.log(`Processed in ${duration.toFixed(2)}ms`)5859 return new Response(JSON.stringify(result), {60 headers: { ...corsHeaders, 'Content-Type': 'application/json' },61 })62 } catch (err) {63 return new Response(JSON.stringify({ error: err.message }), {64 status: 400,65 headers: { ...corsHeaders, 'Content-Type': 'application/json' },66 })67 }68})Common mistakes when reducing Cold Start Time in Supabase Edge Functions
Why it's a problem: Importing large libraries at the top level that are only used in rare code paths
How to avoid: Use dynamic import() inside the specific code path that needs the library. This prevents the import from adding to cold start time for requests that don't need it.
Why it's a problem: Using top-level await for database queries or API calls that block initialization
How to avoid: Move async operations inside the request handler or use a lazy initialization pattern with caching. Only synchronous, lightweight code should run at the top level.
Why it's a problem: Deploying functions with --no-verify-jwt for warm-keeping pings without adding auth in the ping request
How to avoid: Keep JWT verification enabled and include a valid Authorization header in the ping request. Disabling JWT verification for warm-keeping creates a security vulnerability.
Best practices
- Keep function bundle size under 5MB for cold starts under 200ms
- Import only the specific modules you need instead of entire packages
- Use dynamic import() for heavy dependencies in conditional code paths
- Avoid top-level await — defer slow initialization to the first request
- Cache configuration and reusable data between requests using module-level variables
- Set up pg_cron warm-keeping pings for latency-critical functions
- Add a quick-return path for ping requests to minimize warm-keeping cost
- Monitor cold start times in Dashboard logs and optimize the slowest functions first
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My Supabase Edge Function has a 1.5 second cold start time. It imports several npm packages and makes a database query at the top level. Show me how to optimize it for faster cold starts by lazy-loading dependencies, deferring database queries, and setting up a pg_cron warm-keeping schedule.
Optimize a Supabase Edge Function for minimal cold start time. Move heavy imports to dynamic import(), use lazy initialization for database config, add a quick-return path for warm-keeping pings, and set up a pg_cron job to ping the function every 5 minutes.
Frequently asked questions
What is a typical cold start time for Supabase Edge Functions?
Small functions (under 5MB with few imports) typically cold start in 50-200ms. Larger functions with many npm dependencies can take 500ms-2 seconds. The Deno runtime caches compiled code, so subsequent cold starts after the first deployment are usually faster.
How long before an idle function gets shut down?
Supabase does not publish the exact idle timeout, but functions are typically shut down after a few minutes of inactivity. The exact timing may vary by plan and server load. Setting up a 5-minute ping schedule reliably prevents cold starts.
Does the function size limit affect cold starts?
Yes, the maximum bundled function size is 20MB. Larger bundles take longer to load on cold start. Aim to keep your bundled size under 5MB for optimal cold start performance.
Can I pre-warm functions after deployment?
Yes, after deploying with supabase functions deploy, immediately invoke the function once to trigger a cold start. This pre-warms the function so the first real user request is fast. Combine with pg_cron pings to keep it warm.
Do warm-keeping pings count toward function invocation limits?
Yes, each ping is a function invocation and counts toward your plan's limits. At one ping every 5 minutes, that is 288 invocations per day per function — well within most plan limits.
Is there a difference between cold starts on free vs paid plans?
The cold start mechanism is the same across all plans. However, paid plans may have higher concurrency limits, meaning functions stay warm longer under consistent load. The runtime and initialization process is identical.
Can RapidDev help optimize my Supabase Edge Functions for performance?
Yes, RapidDev can audit your Edge Functions for cold start bottlenecks, optimize imports and initialization patterns, and set up warm-keeping infrastructure for latency-critical functions.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation