Skip to content

Supabase Edge Functions: When They’re Worth It and When They’re Not

Edge Functions are great for jobs that have to live outside your Next app. Not everything does. Here’s the decision framework.

John Cravey with EleviFounder5 min read

Supabase Edge Functions run TypeScript on the edge — Deno-based, deployed via the Supabase CLI, billed per invocation. They’re the right tool for jobs that need to live outside your Next.js app: webhook receivers, scheduled tasks, cross-tenant utilities that shouldn’t be per-site-deploy. They’re the wrong tool for the things most teams reach for them on (replacing API routes, server actions, or per-tenant business logic). Here’s the decision framework we use.

What an Edge Function actually is

A single TypeScript file deployed to the Supabase project. It runs on Cloudflare-style edge infrastructure, has access to the Supabase service-role key and your environment variables, can be invoked via HTTP, by a cron schedule, or by a database trigger. Cold starts are ~50ms; warm invocations are sub-10ms.

// supabase/functions/score-lead/index.ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";

serve(async (req) => {
  const { lead } = await req.json();
  const score = computeScore(lead);  // your scoring logic
  return new Response(JSON.stringify({ score }), {
    headers: { "content-type": "application/json" },
  });
});

function computeScore(lead: unknown): number {
  // …
  return 0;
}

Three cases where Edge Functions earn their keep

  1. Webhook receivers from third-party services (Stripe, Twilio, Resend). The function lives at a stable URL across all clients, doesn’t need a per-site deploy to update, and can write to any tenant table.
  2. Scheduled jobs. Daily lead-score refresh, weekly digest emails, hourly sitemap regeneration. Supabase has a built-in scheduler — set a cron expression, the function runs.
  3. Cross-tenant utilities. A function that processes data across all clients’ submissions tables. Easier to maintain in one place than in N per-site Next apps.

Three cases where a Next.js API route or server action is better

  1. Per-tenant form submissions. Use a server action in the tenant’s Next app — closer to the user, ships with the deploy, no extra layer.
  2. Anything that needs Node-only APIs. Edge Functions are Deno-based; not every npm package works there. A Next.js API route gives you the full Node ecosystem.
  3. Anything that needs to read request cookies for an authenticated session. Edge Functions don’t share cookies with your Next app — you’d have to pass tokens manually.

Pattern: the lead-score function

FH’s lead-scoring lives in an Edge Function because the same scoring rubric applies to every tenant’s submissions. The function reads a submission by ID, computes the score using the 100-point rubric (intent 30 / contact 25 / phone 20 / message 15 / method 10), and writes the score back to the row. The function is invoked by a database trigger on every INSERT into `submissions`.

// supabase/functions/score-lead/index.ts
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";

const admin = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);

serve(async (req) => {
  const { id } = await req.json();
  const { data: lead } = await admin
    .from("submissions")
    .select("*")
    .eq("id", id)
    .single();
  if (!lead) return new Response("not found", { status: 404 });

  const score = scoreLead(lead);
  await admin
    .from("submissions")
    .update({ score })
    .eq("id", id);

  return new Response(JSON.stringify({ score }));
});

function scoreLead(lead: any): number {
  let s = 0;
  if (lead.message?.length > 80) s += 15;
  if (lead.phone) s += 20;
  // …
  return s;
}

Webhook receivers: the Resend example

We use Resend to send transactional email across the client book. Resend posts delivery/open/click events to a webhook. The webhook lives in an Edge Function, not in any of the client sites — one stable URL, written once, updates without a redeploy.

Scheduling: pg_cron + Edge Functions

Supabase ships pg_cron. Schedule a SQL job that calls an Edge Function via `net.http_post()`. Cron expression in Postgres, function logic in TypeScript, no separate scheduler infrastructure.

select cron.schedule(
  'daily-sitemap-refresh',
  '15 3 * * *',  -- 3:15 AM UTC every day
  $$
  select net.http_post(
    url := 'https://your-project.supabase.co/functions/v1/refresh-sitemaps',
    headers := jsonb_build_object('Authorization', 'Bearer ' || current_setting('app.functions_key'))
  );
  $$
);

Logs and debugging

Edge Function logs surface in the Supabase dashboard. They’re searchable, structured, and retain for 7 days. For long-term observability, pipe them to a separate destination (Better Stack, Axiom). We use Better Stack because the alerting is good and the cost is flat at our volume.

Local development

`supabase functions serve` runs the function locally with hot-reload. Pair it with the local Supabase stack (`supabase start`) and you can develop the full flow without touching production. We test every function locally before deploying — Deno is permissive but the deno runtime has more subtle differences from Node than people expect.

Cost: free until you’re really using it

Supabase Pro includes 500k function invocations per month. Across the FH client book we run about 80k per month — score-lead on every submission, four cron jobs, two webhook receivers. The free included tier covers us 6× over. Overage is $2 per million invocations.

When NOT to use Edge Functions

  • When the logic is tightly coupled to a single tenant. Ship it with that tenant’s Next.js app instead.
  • When you need Node-only npm packages.
  • When you need to read request cookies from your Next app (the auth context doesn’t carry over).
  • When latency requirements are sub-50ms and you can’t tolerate the cold start.
  • When you need stable IP addresses for IP-allowlisting on a third-party service. Edge IPs rotate; run an Express app behind a static IP instead.

How this lands across FH client work

Across the client book we run six Edge Functions: lead scoring, Resend webhook receiver, Twilio Lookup proxy (used before Google Ads lead-form integration), a daily sitemap refresh, a weekly digest email job, and a slop-detector trigger that runs on new uploads to image buckets. Six functions for the entire client book. Everything else lives in the per-site Next apps where it belongs.

If you’re reaching for Edge Functions to replace every API route, you’re using the wrong tool. If you’re ignoring them entirely, you’re duplicating per-tenant logic across every deploy. Book a consultation if you want a second opinion on which side of the line your work lives on.

Written by
John Cravey
Founder

Founder of Frontend Horizon. Writes most of the long-form work on the FH blog.

Newer post
RAG for SMB Sites: When Retrieval-Augmented Generation Actually Solves a Real Problem
Older post
Accessibility Law in 2026: The Lawsuit Landscape and the Compliant Build Posture
Keep reading

More from the blog

Supabase·6 min

Supabase Row Level Security: The Multi-Tenant Pattern We Use Across FH Clients

One Postgres database, many tenants, zero data leakage. Here’s the RLS setup that holds up under real production traffic.

Supabase·3 min

Reading Supabase Logs: The Five Queries That Catch 80% of Production Issues

The Supabase log explorer is underused. These five queries are the first place we look when something’s wrong.

Supabase·4 min

Migrating from Firebase to Supabase: The Real Cost and the Step-by-Step Plan

Firebase pricing scales worse than Supabase past a certain point. Here’s the migration plan that worked for one of our clients.