# How to Reduce Cold Start Time in Supabase Edge Functions

- Tool: Supabase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Supabase (all plans), Deno runtime, supabase CLI
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

```
// BAD: Importing entire lodash (70KB+ bundled)
import _ from 'npm:lodash'
const result = _.pick(data, ['name', 'email'])

// GOOD: Import only what you need
import pick from 'npm:lodash.pick'
const result = pick(data, ['name', 'email'])

// BEST: Use native JavaScript instead of lodash
const { name, email } = data
const result = { name, email }
```

**Expected result:** Your function imports are minimized, using only the specific modules needed.

### 3. 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.

```
import { corsHeaders } from '../_shared/cors.ts'

// Keep the Supabase client at top level (it's lightweight)
import { createClient } from 'npm:@supabase/supabase-js@2'

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  const { action } = await req.json()

  // Lazy-load heavy dependencies only when needed
  if (action === 'generate-pdf') {
    const { default: PDFDocument } = await import('npm:pdfkit')
    // Use PDFDocument...
  }

  if (action === 'send-email') {
    const { Resend } = await import('npm:resend')
    // Use Resend...
  }

  return new Response(JSON.stringify({ ok: true }), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
})
```

**Expected result:** Heavy dependencies are loaded on demand instead of at initialization, reducing cold start time for requests that don't use them.

### 4. 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.

```
// BAD: Top-level await adds to cold start time
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { data: config } = await supabase.from('config').select('*').single()

// GOOD: Create client at top level but fetch data lazily
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

let cachedConfig: any = null

async function getConfig() {
  if (!cachedConfig) {
    const { data } = await supabase.from('config').select('*').single()
    cachedConfig = data
  }
  return cachedConfig
}

Deno.serve(async (req) => {
  const config = await getConfig()
  // Use config...
})
```

**Expected result:** Slow async operations are deferred to the first request instead of blocking cold start initialization.

### 5. 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.

```
-- Enable pg_cron extension (run once in SQL Editor)
create extension if not exists pg_cron;

-- Schedule a ping every 5 minutes to keep the function warm
select cron.schedule(
  'keep-function-warm',
  '*/5 * * * *',
  $$
  select
    net.http_post(
      url := 'https://<project-ref>.supabase.co/functions/v1/my-function',
      headers := jsonb_build_object(
        'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'),
        'Content-Type', 'application/json'
      ),
      body := '{"ping": true}'::jsonb
    );
  $$
);
```

**Expected result:** The function is invoked every 5 minutes, preventing the runtime from shutting down and eliminating cold starts.

### 6. 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.

```
Deno.serve(async (req) => {
  const start = performance.now()

  // Your function logic here
  const result = { message: 'Hello!' }

  const duration = performance.now() - start
  console.log(`Request processed in ${duration.toFixed(2)}ms`)

  return new Response(JSON.stringify(result), {
    headers: { 'Content-Type': 'application/json' },
  })
})
```

**Expected result:** You can measure and compare cold start vs warm request times to verify that your optimizations are effective.

## Complete code example

File: `optimized-edge-function.ts`

```typescript
// supabase/functions/optimized/index.ts
// An Edge Function optimized for minimal cold start time

import { corsHeaders } from '../_shared/cors.ts'
import { createClient } from 'npm:@supabase/supabase-js@2'

// Lightweight top-level initialization (synchronous, no await)
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_ANON_KEY')!
)

// Lazy-initialized cache for config data
let configCache: Record<string, string> | null = null

async function getConfig() {
  if (!configCache) {
    const { data } = await supabase
      .from('config')
      .select('key, value')
    configCache = Object.fromEntries(
      (data || []).map((row) => [row.key, row.value])
    )
  }
  return configCache
}

Deno.serve(async (req) => {
  // Handle CORS preflight
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  const start = performance.now()

  try {
    const body = await req.json()

    // Quick return for warm-keeping pings
    if (body.ping) {
      return new Response('{"pong":true}', {
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      })
    }

    // Load config lazily on first real request
    const config = await getConfig()

    // Lazy-load heavy deps only when needed
    let result: any
    if (body.action === 'process') {
      // Main logic using lightweight operations
      result = { processed: true, config }
    }

    const duration = performance.now() - start
    console.log(`Processed in ${duration.toFixed(2)}ms`)

    return new Response(JSON.stringify(result), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), {
      status: 400,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    })
  }
})
```

## Common mistakes

- **Importing large libraries at the top level that are only used in rare code paths** — undefined Fix: 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.
- **Using top-level await for database queries or API calls that block initialization** — undefined Fix: 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.
- **Deploying functions with --no-verify-jwt for warm-keeping pings without adding auth in the ping request** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-reduce-cold-start-in-supabase-functions
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-reduce-cold-start-in-supabase-functions
