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 AIFounder5 min readUpdated Jul 6, 2026

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.

Free estimate · 2 minutes

Read the playbook. Now see it for your business.

The posts are the mechanism. The estimate below sketches the version we'd actually ship for your business, at your scale. About a minute, no opt-in.

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.

Answers

Frequently asked questions

What is an Edge Function actually for?

Server-side code that runs close to the user, deployed separately from your application. It suits work that must happen outside the app's request cycle: webhook receivers, scheduled jobs, and small independent services. It is not a general home for application logic.

When does an Edge Function earn its keep?

Three cases: receiving webhooks from a third party that needs a stable endpoint independent of your app deploys, running scheduled work triggered from the database, and small compute that other services call directly. All three share a shape, which is work your web app should not own.

When is a server action or API route better?

When the work belongs to a user's request in your own application. Then the app already has the session, the types, and the deploy pipeline, and moving it out adds a network hop, a second deployment, and a second place to look when something breaks.

How should webhook receivers be built?

Verify the signature first, respond quickly, and do the slow part afterwards. A receiver that processes synchronously will eventually time out and the sender will retry, producing duplicate work. Idempotency on the receiving side is not optional, because retries are normal rather than exceptional.

How does scheduling work?

The database scheduler triggers the function on a cron expression, which keeps the schedule next to the data it operates on. The failure mode to watch is a job that overruns its interval and overlaps itself, so a run lock is worth having from the start.

How do I debug an Edge Function?

Through its logs, which means logging deliberately at entry, at each decision, and at exit, because there is no debugger attached in production. Functions that log only errors leave you unable to distinguish never ran from ran and did nothing.

Can I develop these locally?

Yes, and you should, because the deploy loop is slow enough that debugging by deploying wastes an afternoon quickly. Local runs also surface the environment differences, which are the class of bug that only appears once something is deployed.

What do Edge Functions cost?

Nothing at the volumes a typical marketing or small-business site produces, and meaningfully more once something high-frequency is running there. The cost trap is a function invoked per request from the application, which is exactly the case where it should not have been a function.

What runtime constraints should I expect?

A restricted runtime rather than full Node: some libraries and built-ins are unavailable, execution time is bounded, and cold starts exist. Code that runs in your app can fail there, which is why porting logic rather than writing for the constraint tends to disappoint.

How do secrets work in an Edge Function?

As environment configuration on the function itself, set separately from your application. That separation is a feature and a trap: a rotated secret has to be updated in both places, and the one nobody updates is always the function.

When should I not use an Edge Function?

For anything that needs your application's session context, for long-running work, for anything needing a Node API the runtime lacks, and for logic that changes every time your app changes. That last one is the practical killer, because two deploy cycles for one feature gets abandoned.

How do Edge Functions interact with RLS?

Exactly like any other server-side caller: with a service-role key they bypass policies entirely, so tenant scoping becomes the function's responsibility. A function that trusts the policies while using a service role is not protected by anything.

Question we did not answer? Ask us directly and we will answer it here.

John Cravey, Founder
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.