
Edge Function 배포 실패? (Deno 런타임을 이해 못 해서 생긴 일)
로컬에선 잘 되는데 배포만 하면 500 에러? Node.js와 Edge Runtime의 차이부터 Import Map 설정까지, Supabase Edge Function 배포 성공을 위한 체크리스트.

로컬에선 잘 되는데 배포만 하면 500 에러? Node.js와 Edge Runtime의 차이부터 Import Map 설정까지, Supabase Edge Function 배포 성공을 위한 체크리스트.
서버를 끄지 않고 배포하는 법. 롤링, 카나리, 블루-그린의 차이점과 실제 구축 전략. DB 마이그레이션의 난제(팽창-수축 패턴)와 AWS CodeDeploy 활용법까지 심층 분석합니다.

React나 Vue 프로젝트를 빌드해서 배포했는데, 홈 화면은 잘 나오지만 새로고침만 하면 'Page Not Found'가 뜨나요? CSR의 원리와 서버 설정(Nginx, Apache, S3)을 통해 이를 해결하는 완벽 가이드.

롤링 배포, 블루/그린 배포와 카나리 배포는 무엇이 다를까요? 1%의 사용자에게만 먼저 배포하여 위험을 감지하는 카나리 배포의 원리와 Kubernetes/Istio 구현 전략을 정리합니다.

any를 쓰면 타입스크립트를 쓰는 의미가 없습니다. 제네릭(Generics)을 통해 유연하면서도 타입 안전성(Type Safety)을 모두 챙기는 방법을 정리합니다. infer, keyof 등 고급 기법 포함.

OpenAI API 키를 프론트엔드에 노출할 수 없어서, Supabase Edge Function을 썼습니다.
supabase functions serve로 로컬에서 테스트할 때는 완벽했습니다.
Postman으로 찔러봐도 200 OK가 잘 떨어졌죠.
"좋아, 배포하자!"
supabase functions deploy my-function
배포 성공 메시지가 떴고, 자신만만하게 프론트엔드랑 연동했습니다. 그런데...
500 Internal Server Error
{ "error": "Worker failed to start" }
로그를 보니 Uncaught Error: Import module "..." not found 같은 에러가 떴습니다.
"아니, 로컬에선 된다니까? 왜 배포하면 모듈을 못 찾아?"
저는 Supabase Edge Function이 그냥 "서버리스 Node.js"인 줄 알았습니다.
그래서 npm install axios 하고 import axios from 'axios'를 썼습니다.
하지만 Supabase Edge Function은 Deno 기반입니다. 그리고 Edge Runtime이라는 아주 제한적인 환경에서 돌아갑니다.
fs, path, process 같은 Node 내장 모듈이 없습니다.npm: 프리픽스로 지원하기 시작했지만, 여전히 무거운 라이브러리는 엣지 환경에서 안 돌 수 있습니다.무엇보다 "로컬에 있는 node_modules를 통째로 올려주는 게 아니다"라는 걸 몰랐습니다.
이걸 "국내 여행 vs 해외 배낭여행"으로 비유해봤습니다.
배포(Deploy)는 배낭 하나만 달랑 메고 비행기 타는 겁니다. 그런데 배낭 속에 "여권(Import Map)"이나 "필수 의약품(Dependencies)"을 안 챙기고, "집에 책상 위에 뒀는데?"라고 해봤자 소용없는 거죠.
Deno는 package.json이 없습니다. 대신 deno.json이나 import_map.json에서 의존성을 관리합니다.
로컬에서 잘 된 이유는 로컬 캐시에 의존성이 있었기 때문이고, 배포 환경은 텅 빈 새 장비라서 모듈을 못 찾은 겁니다.
/supabase/functions/deno.json을 만들고 명시해줘야 합니다.
{
"imports": {
"openai": "https://deno.land/x/openai@v4.20.1/mod.ts",
"cors": "https://deno.land/x/cors/mod.ts"
}
}
그리고 코드에서는 이렇게 씁니다:
// 절대 경로 URL 쓰지 말고, 별칭(Alias) 사용!
import OpenAI from 'openai';
로컬 .env 파일은 배포되지 않습니다.
배포된 함수에도 환경 변수를 따로 심어줘야 합니다.
# 로컬 .env 파일을 비밀 값으로 설정
supabase secrets set --env-file ./supabase/.env
이걸 안 하면 Deno.env.get('OPENAI_API_KEY')가 undefined가 되어 500 에러가 납니다.
프론트엔드(브라우저)에서 Edge Function을 호출하면 99% 확률로 CORS 에러가 납니다.
엣지 함수는 기본적으로 모든 요청을 거부하거든요.
응답 헤더에 Access-Control-Allow-Origin을 넣어줘야 합니다.
가장 쉬운 방법은 OPTIONS 요청(Preflight)을 처리해주는 겁니다.
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
// Preflight 요청 대응
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// 본 로직...
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})
"왜 익숙한 Node.js 안 쓰고 Deno를 쓰냐?" 불만이 생길 수 있습니다. 핵심은 Cold Start 시간과 보안입니다.
fetch, Request, Response)를 그대로 씁니다. 즉, 프론트엔드 개발자가 서버 코드를 짤 때 위화감이 적습니다. axios 대신 fetch 쓰면 됩니다.Edge Function에서 Supabase DB에 postgres.js 등으로 직접 붙으려고 하면 에러가 날 수 있습니다.
Edge Function은 수천 개가 동시에 뜰 수 있는데, DB 연결(Connection) 개수는 한정되어 있기 때문입니다(Connection Exhaustion).
해결책: Supaver(Connection Pooler) 사용
DIRECT_URL (포트 5432) 대신 DATABASE_URL (포트 6543, Transaction Mode)을 사용해야 합니다.
Supabase 프로젝트 설정에서 Connection Pooling이 켜져 있는지 확인하세요.
Edge Function은 CPU 제한과 메모리 제한이 빡빡합니다.
만약 비디오 인코딩 같은 무거운 작업을 해야 한다면? Edge Function은 틀렸습니다. 이건 AWS Lambda나 Google Cloud Run 같은 Serverless Containers로 가야 합니다. Edge Function은 "API 라우팅, 인증, 간단한 DB 조작" 같은 가벼운 작업용입니다.
배포할 때마다 supabase secrets set을 치는 건 고역입니다.
특히 팀원들과 작업할 때는 실수하기 딱 좋습니다.
비법: .env 파일을 로컬과 운영으로 분리하세요.
.env.local: 로컬 테스트용 (Git Ignore).env.production: 배포용 (CI/CD 파이프라인에서 주입)그리고 supabase/config.toml이나 배포 스크립트를 만들어서 관리해야 합니다.
Supabase CLI는 아직 .env의 자동 배포를 완벽히 지원하지 않으므로, 아래와 같은 스크립트를 package.json에 넣어두면 편합니다.
"scripts": {
"deploy:secrets": "supabase secrets set --env-file ./supabase/.env.production",
"deploy": "npm run deploy:secrets && supabase functions deploy"
}
사소하지만 배포 사고를 막는 중요한 습관입니다.
"배포했는데 500 에러만 뜨고 로그를 모르겠어요!"
supabase functions serve --debug
이렇게 띄우면 Chrome DevTools(chrome://inspect)를 붙여서 브레이크포인트를 걸고 디버깅할 수 있습니다.Edge Function의 가장 흔한 용도는 OpenAI API Proxy입니다.
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
serve(async (req) => {
const { query } = await req.json();
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: query }],
}),
});
const data = await response.json();
return new Response(JSON.stringify(data), { ... });
});
이렇게 하면 클라이언트는 내 API 키를 몰라도 AI 기능을 쓸 수 있습니다.
I needed to hide my OpenAI API key, so I used a Supabase Edge Function to proxy the request.
Running supabase functions serve locally worked perfectly.
Postman returned 200 OK.
"Great, let's deploy!"
supabase functions deploy my-function
It said "Deployed successfully." I connected my frontend confidently. But then...
500 Internal Server Error
{ "error": "Worker failed to start" }
Logs showed Uncaught Error: Import module "..." not found.
"It works locally! Why can't it find the module in the cloud?"
I assumed Supabase Edge Function was just a "Serverless Node.js."
So I did npm install axios and import axios from 'axios'.
But Supabase Edge Function runs on Deno. And specifically, on the Edge Runtime, which is very restrictive.
fs, path, process modules.npm: prefix support is recent, and heavy libraries might fail.I didn't realize "It doesn't upload my node_modules folder to the cloud."
I compared Node.js vs. Edge to "Living at Home vs. Backpacking Abroad."
Deploying is jumping on a plane with just one backpack. If you forgot your passport (Import Map) or meds (Dependencies) and say "But I left them on my desk at home?", it's useless.
Deno doesn't use package.json. It uses deno.json or import_map.json.
Local works because of local cache. Cloud fails because it's a fresh machine without that cache.
Create /supabase/functions/deno.json:
{
"imports": {
"openai": "https://deno.land/x/openai@v4.20.1/mod.ts",
"cors": "https://deno.land/x/cors/mod.ts"
}
}
In code:
// Don't use raw URLs. Use the alias!
import OpenAI from 'openai';
Your local .env is NOT deployed automatically.
You must manually set secrets for the deployed function.
# Push local .env vars to production secrets
supabase secrets set --env-file ./supabase/.env
Without this, Deno.env.get('OPENAI_API_KEY') returns undefined, causing 500 errors.
Calling Edge Functions from a browser triggers CORS errors 99% of the time.
Edge functions block all requests by default.
You must insert Access-Control-Allow-Origin headers manually.
Handle the OPTIONS (Preflight) request explicitly:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
// Handle Preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// Logic...
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})
"Why Deno instead of the familiar Node.js?" The answer is Cold Start Time and Security.
fetch, Request, Response). Frontend devs feel right at home. No need for axios; just use fetch.Trying to connect to Postgres directly (postgres.js)?
You will hit the Connection Limit quickly because Edge Functions scale to thousands.
Solution: Use the Supabase Pooler (Supaver).
Use the DATABASE_URL (Port 6543) instead of DIRECT_URL (Port 5432).
Ensure Transaction Mode is enabled in Supabase Project Settings.
Edge Functions are strict.
If you need video encoding or ML inference, use Serverless Containers (AWS Lambda / Google Cloud Run). Edge is for "Routing, Auth, Simple Logic."
Updating secrets with supabase secrets set every time is painful and error-prone.
Secret: Split .env files.
.env.local: Local testing (Git Ignored).env.production: Deployment (Injected via CI/CD)Use a script in package.json:
"scripts": {
"deploy:secrets": "supabase secrets set --env-file ./supabase/.env.production",
"deploy": "npm run deploy:secrets && supabase functions deploy"
}
This prevents deployment accidents.
"Deployed, but getting 500 error!"
supabase functions serve --debug
Use Chrome DevTools (chrome://inspect) to attach to the Deno runtime and set breakpoints.The most common use case is an OpenAI API Proxy:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
serve(async (req) => {
const { query } = await req.json();
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: query }],
}),
});
const data = await response.json();
return new Response(JSON.stringify(data), { ... });
});
This lets clients use AI features without exposing your private keys.