Cloudflare Workers로 서버리스 애플리케이션 만들기
서버리스 애플리케이션을 만들고 싶은데 AWS Lambda는 너무 복잡하고, 배포도 번거롭다고 느껴본 적 있으신가요? 🤔 Cloudflare Workers를 사용하면 간단한 자바스크립트 코드 몇 줄로 전 세계 300개 이상의 엣지 로케이션에서 실행되는 서버리스 함수를 만들 수 있습니다.
이번 포스팅에서는 Cloudflare Workers의 기본 개념부터 실제 배포까지 단계별로 알아보겠습니다.
Cloudflare Workers란?
Cloudflare Workers는 Cloudflare의 엣지 네트워크에서 실행되는 서버리스 플랫폼입니다. 기존 서버리스 플랫폼과 다른 점은 V8 자바스크립트 엔진을 사용해서 콜드 스타트가 거의 없고, 전 세계 엣지 로케이션에서 실행되기 때문에 사용자와 가까운 곳에서 빠르게 응답할 수 있다는 점입니다.
Cloudflare Workers의 가장 큰 장점은 속도입니다. 기존 서버리스 플랫폼의 고질적인 문제인 콜드 스타트가 1ms 미만으로 사실상 없다고 봐도 무방합니다. 게다가 전 세계 300개 이상의 데이터 센터에서 자동으로 실행되기 때문에 어느 나라에서 접속하든 사용자와 가까운 곳에서 빠르게 응답할 수 있습니다. 비용 측면에서도 매력적인데, 하루 10만 건의 요청까지 무료로 사용할 수 있어서 개인 프로젝트나 사이드 프로젝트에 부담 없이 적용할 수 있습니다. 배포 과정도 간단해서 CLI 도구 하나로 몇 초 만에 전 세계에 배포할 수 있습니다.
Cloudflare 가입
Cloudflare Workers를 사용하려면 먼저 Cloudflare 계정이 필요합니다. Cloudflare 웹사이트에서 무료로 가입할 수 있습니다.
Worker 프로젝트 생성
이제 Wrangler CLI를 사용하여 Worker를 만들어보겠습니다. Wrangler는 Cloudflare Workers를 개발하고 배포하는 데 사용하는 공식 CLI 도구입니다. Wrangler의 설치부터 다양한 명령어까지 자세히 알고 싶으시다면 Wrangler CLI 사용법을 참고하세요.
wrangler init 명령어로 새 프로젝트를 생성할 수 있습니다.
$ npx wrangler init our-worker
차세대 런타임 Bun을 사용하신다면 다음 명령어를 사용합니다.
$ bunx wrangler init our-worker
터미널에서 몇 가지 질문에 답하면 바로 프로젝트가 생성됩니다.
╭ Create an application with Cloudflare Step 1 of 3
│
├ In which directory do you want to create your application?
│ dir ./our-worker
│
├ What would you like to start with?
│ category Hello World example
│
├ Which template would you like to use?
│ type Worker only
│
├ Which language do you want to use?
│ lang TypeScript
│
├ Copying template files
│ files copied to project directory
│
├ Updating name in `package.json`
│ updated `package.json`
│
├ Installing dependencies
│ installed via `npm install`
│
╰ Application created
╭ Configuring your application for Cloudflare Step 2 of 3
│
├ Installing wrangler A command line tool for building Cloudflare Workers
│ installed via `npm install wrangler --save-dev`
│
├ Retrieving current workerd compatibility date
│ compatibility date 2025-12-30
│
├ Do you want to use git for version control?
│ no git
│
╰ Application configured
╭ Deploy with Cloudflare Step 3 of 3
│
├ Do you want to deploy your application?
│ no deploy via `npm run deploy`
│
╰ Done
생성된 프로젝트 디렉토리로 이동하면 src/index.ts 파일이 있습니다.
이 파일에 Worker 코드를 작성하면 됩니다.
export default {
async fetch(request, env, ctx): Promise<Response> {
return new Response("Hello World!");
},
} satisfies ExportedHandler<Env>;
Worker는 fetch 핸들러를 export 해야 합니다.
이 핸들러는 HTTP 요청이 들어올 때마다 실행되며, Request 객체를 받아서 Response 객체를 반환합니다.
개발 서버 구동
코드를 작성했으면 로컬에서 테스트해볼 수 있습니다.
wrangler dev 명령어를 실행하면 로컬 개발 서버가 시작됩니다.
$ npx wrangler dev
⛅️ wrangler 4.54.0
───────────────────
╭──────────────────────────────────────────────────────────────────────╮
│ [b] open a browser [d] open devtools [c] clear console [x] to exit │
╰──────────────────────────────────────────────────────────────────────╯
⎔ Starting local server...
[wrangler:info] Ready on http://localhost:8787
기본적으로 http://localhost:8787에서 Worker가 실행됩니다.
브라우저나 curl로 접속하면 “Hello World!” 메시지를 확인할 수 있습니다.
$ curl http://localhost:8787
Hello World!
배포하기
로컬 테스트가 완료되면 실제 Cloudflare 네트워크에 배포할 수 있습니다.
배포는 wrangler deploy 명령어 하나로 끝납니다.
$ npx wrangler deploy
Total Upload: 0.19 KiB / gzip: 0.16 KiB
Uploaded our-worker (1.88 sec)
Deployed our-worker triggers (0.87 sec)
https://our-worker.daleseo.workers.dev
Current Version ID: 7684fad8-4c2e-4452-9f05-907be04f7d8b
브라우저가 열리면서 Cloudflare 계정 인증을 진행하게 됩니다. 인증이 완료되면 터미널에서 Workers를 생성하고 배포할 수 있습니다.
배포가 완료되면 Worker가 실행되는 URL이 출력됩니다.
일반적으로 https://our-worker.<your-subdomain>.workers.dev 형식의 URL이 생성됩니다.
이 URL로 접속하면 전 세계 어디서든 가장 가까운 Cloudflare 엣지 로케이션에서 Worker가 실행되어 응답을 받을 수 있습니다. 🚀
요청 처리하기
Worker에서 HTTP 요청을 처리하는 방법을 좀 더 자세히 알아보겠습니다.
Request 객체를 통해 URL, 메서드, 헤더, 바디 등의 정보에 접근할 수 있습니다.
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
if (path === "/") {
return new Response("홈페이지입니다");
}
if (path === "/about") {
return new Response("소개 페이지입니다");
}
return new Response("페이지를 찾을 수 없습니다", { status: 404 });
},
} satisfies ExportedHandler<Env>;
이렇게 URL 경로에 따라 다른 응답을 반환할 수 있습니다.
$ curl http://localhost:8787/
홈페이지입니다
$ curl http://localhost:8787/about
소개 페이지입니다
$ curl http://localhost:8787/unknown
페이지를 찾을 수 없습니다
JSON 응답 반환하기
API를 만들 때는 JSON 형식으로 응답을 반환하는 경우가 많습니다.
Response 객체를 만들 때 헤더를 설정하면 됩니다.
export default {
async fetch(request, env, ctx): Promise<Response> {
const data = {
message: "Hello from Cloudflare Workers!",
timestamp: new Date().toISOString(),
};
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
},
});
},
} satisfies ExportedHandler<Env>;
더 간단하게는 Response.json() 메서드를 사용할 수도 있습니다.
export default {
async fetch(request, env, ctx): Promise<Response> {
const data = {
message: "Hello from Cloudflare Workers!",
timestamp: new Date().toISOString(),
};
return Response.json(data);
},
} satisfies ExportedHandler<Env>;
테스트해보면 다음과 같은 결과가 출력됩니다.
$ curl http://localhost:8787/
{"message":"Hello from Cloudflare Workers!","timestamp":"2025-02-16T24:59:04.123Z"}
환경 변수 사용하기
API 키나 데이터베이스 연결 정보 같은 민감한 정보는 환경 변수로 관리하는 것이 좋습니다.
Cloudflare Workers에서는 wrangler.jsonc 파일에 환경 변수를 정의할 수 있습니다.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "our-project",
"main": "src/index.ts",
"compatibility_date": "2025-02-16",
"vars": {
"ENVIRONMENT": "production",
},
}
민감한 정보는 Cloudflare 대시보드에서 Secret으로 등록하거나, wrangler secret put 명령어를 사용할 수 있습니다.
$ npx wrangler secret put API_KEY
⛅️ wrangler 4.54.0
───────────────────
✔ Enter a secret value: … ****
🌀 Creating the secret for the Worker "our-project"
✨ Success! Uploaded secret API_KEY
코드에서는 env 객체를 통해 환경 변수에 접근할 수 있습니다.
interface Env {
ENVIRONMENT: string;
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
return Response.json({
environment: env.ENVIRONMENT,
API_KEY: env.API_KEY,
});
},
};
테스트해보면 다음과 같은 결과가 출력됩니다.
$ curl http://localhost:8787/
{"environment":"production","API_KEY":"****"}
외부 API 호출하기
Worker에서 외부 API를 호출할 수도 있습니다.
fetch API를 사용하면 됩니다.
interface GitHubUser {
name: string;
bio: string;
followers: number;
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const response = await fetch("https://api.github.com/users/github", {
headers: {
"User-Agent": "Cloudflare-Worker",
},
});
const data: GitHubUser = await response.json();
return Response.json({
name: data.name,
bio: data.bio,
followers: data.followers,
});
},
} satisfies ExportedHandler<Env>;
테스트해보면 다음과 같은 결과가 출력됩니다.
$ curl http://localhost:8787/
{"name":"GitHub","bio":"How people build software.","followers":65647}
이렇게 Worker를 프록시나 API 게이트웨이로 사용할 수 있습니다. 예를 들어 여러 API를 조합하거나, 응답을 가공하거나, 캐싱을 추가하는 등의 작업을 할 수 있습니다.
CORS 처리하기
API를 만들 때는 CORS(Cross-Origin Resource Sharing) 설정이 필요한 경우가 많습니다. Worker에서는 응답 헤더에 CORS 관련 헤더를 추가하면 됩니다.
export default {
async fetch(request, env, ctx): Promise<Response> {
const data = { message: "Hello, CORS!" };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Content-Type",
},
});
},
} satisfies ExportedHandler<Env>;
프리플라이트 요청(OPTIONS)도 처리해야 합니다.
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
// OPTIONS 요청 처리
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
// 실제 요청 처리
const data = { message: "Hello, CORS!" };
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
},
};
테스트해보면 다음과 같은 결과가 출력됩니다.
$ curl -i http://localhost:8787/
HTTP/1.1 200 OK
Content-Length: 26
Date: Tue, 16 Feb 2025 21:13:27 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Server: cloudflare
Vary: Accept-Encoding
CF-Ray: 9b648a8c8c1852cf-YYZ
{"message":"Hello, CORS!"}%
wrangler.jsonc 더 알아보기
Worker 프로젝트의 모든 설정은 wrangler.jsonc 파일에 담겨 있습니다.
처음 프로젝트를 만들면 name, main, compatibility_date 정도만 있는데요, 실무에서 자주 쓰이는 몇 가지 옵션을 알아두면 편합니다.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "our-worker",
"main": "src/index.ts",
"account_id": "94480614a6df7731f1e4491bdac5c440",
"compatibility_date": "2025-02-16",
"workers_dev": true,
"preview_urls": true,
"observability": {
"enabled": true,
},
}
account_id는 여러 Cloudflare 계정을 쓰거나 협업하는 환경에서 어느 계정으로 배포할지 명시해두는 필드입니다.
시크릿이 아니라 단순 식별자이기 때문에 깃 저장소에 그대로 커밋해도 안전한데요, Cloudflare 공식 문서에서도 다중 계정 환경에서는 명시적으로 적어둘 것을 권장합니다.
계정 ID는 Cloudflare 대시보드 우측 사이드바에서 확인할 수 있습니다.
compatibility_date는 Worker가 실행될 런타임 API 버전을 고정하는 날짜입니다.
이 날짜 이후에 런타임에 호환되지 않는 변경이 생겨도 Worker는 영향을 받지 않습니다.
새 프로젝트를 만든 시점의 날짜로 두고, 새 기능이 필요할 때만 의식적으로 올리는 게 안전합니다.
workers_dev는 *.workers.dev 서브도메인 활성화 여부를 제어합니다.
기본값이 true라 별도로 적지 않아도 workers.dev URL이 생성되는데요, 커스텀 도메인만 쓰고 싶을 때는 false로 두면 됩니다.
같은 콘텐츠가 두 URL에서 서빙되어 검색 엔진 색인이 분산되는 걸 막을 수 있습니다.
preview_urls는 버전별 프리뷰 URL의 활성화 여부를 제어합니다.
Worker는 배포할 때마다 새 버전이 만들어지는데요, 이 옵션을 켜두면 각 버전마다 <버전 prefix>-<Worker 이름>.<서브도메인>.workers.dev 형식의 고유 URL이 자동으로 생성됩니다.
이 URL로 정식 배포 전에 새 버전을 미리 테스트해볼 수 있고, 문제가 생기면 이전 버전 URL로 곧바로 비교해볼 수 있어 디버깅에 편합니다.
값을 따로 적지 않으면 workers_dev 값을 그대로 따라갑니다.
즉 workers.dev 서브도메인을 끄면 프리뷰 URL도 자동으로 꺼져서, 의도치 않게 미리보기 주소가 외부에 노출되는 일을 막아줍니다.
observability.enabled를 켜두면 Worker의 실행 로그와 오류, 호출 지표가 Cloudflare 대시보드에 표시됩니다.
디버깅과 모니터링에 큰 도움이 됩니다.
정적 파일 호스팅하기
Worker는 API 서버뿐 아니라 정적 웹사이트도 호스팅할 수 있습니다.
React, Vue, Svelte 같은 프레임워크로 만든 싱글 페이지 애플리케이션(SPA)도 같은 방식으로 배포할 수 있는데요, wrangler.jsonc의 assets 필드에 정적 파일이 있는 디렉토리를 지정하면 됩니다.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "our-spa",
"compatibility_date": "2025-02-16",
"assets": {
"directory": "./dist",
"not_found_handling": "single-page-application",
},
}
assets.directory는 정적 파일이 빌드되는 디렉토리입니다.
Vite는 보통 dist, Next.js는 out, Astro는 dist에 정적 산출물을 만드는데요, 각 프레임워크의 빌드 결과 경로를 적어주면 됩니다.
여기서 눈여겨볼 건 not_found_handling인데요, 값을 "single-page-application"으로 두면 정의되지 않은 모든 경로 요청에 대해 index.html을 200 응답으로 반환합니다.
이 동작이 있어야 SPA의 클라이언트 측 라우팅이 정상 동작합니다.
사용자가 브라우저에서 /about 같은 URL을 직접 입력해도 404가 아니라 앱이 떠서 React Router 같은 라우터가 URL을 받아 올바른 페이지를 마운트하게 됩니다.
Vite 프로젝트라면 @cloudflare/vite-plugin을 함께 쓰면 더 편한데요, 개발 서버에서 Cloudflare 런타임을 그대로 흉내 내주기 때문에 “로컬에서 됐는데 배포에서 안 됨” 같은 문제를 줄여줍니다.
$ npm install -D @cloudflare/vite-plugin wrangler
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [cloudflare()],
});
이렇게 설정한 뒤 wrangler deploy 한 번이면 SPA가 곧바로 Cloudflare 엣지에서 서빙됩니다.
커스텀 도메인 연결하기
기본적으로 제공되는 workers.dev 도메인 대신 자신의 도메인을 사용할 수도 있습니다.
Cloudflare에서 도메인을 관리하고 있다면 두 가지 방식으로 연결할 수 있는데요, 대시보드에서 클릭으로 설정하거나 wrangler.jsonc에 코드로 적어둘 수 있습니다.
대시보드 방식은 간단합니다. Workers 대시보드에서 해당 Worker를 선택하고, “Settings” 탭의 “Domains & Routes”에서 “Add”를 클릭한 뒤 원하는 도메인을 입력하면 됩니다. 자동으로 DNS 레코드가 생성되고 SSL 인증서도 발급됩니다.
설정을 코드로 관리하고 싶다면 wrangler.jsonc의 routes 배열에 적어줍니다.
{
"name": "our-worker",
"main": "src/index.ts",
"compatibility_date": "2025-02-16",
"routes": [
{ "pattern": "example.com", "custom_domain": true },
{ "pattern": "www.example.com", "custom_domain": true },
],
}
여기서 custom_domain: true 플래그가 핵심입니다.
이 플래그가 있으면 해당 호스트 전체가 Worker로 라우팅되고, Cloudflare가 DNS 레코드와 SSL 인증서까지 자동으로 발급하고 갱신해줍니다.
플래그가 없으면 zone의 일부 경로만 Worker로 보내는 “Custom Route” 방식으로 해석되는데요, 두 방식이 같은 routes 배열에 들어가기 때문에 헷갈리기 쉽습니다.
apex 도메인(example.com)과 www 서브도메인(www.example.com)을 함께 연결하면 어느 쪽으로 들어와도 같은 사이트가 뜹니다.
둘 중 하나를 대표 URL로 정해 다른 쪽에서 리다이렉트하는 게 SEO 측면에서 권장됩니다.
설정을 마친 뒤 wrangler deploy를 실행하면 Cloudflare가 알아서 DNS와 SSL 발급까지 처리합니다.
도메인이 Cloudflare zone으로 등록되어 있어야 하며, 그렇지 않으면 Wrangler가 zone을 찾지 못해 실패합니다.
이미 같은 호스트에 다른 DNS 레코드가 있다면 충돌 메시지가 뜨는데요, 대시보드의 DNS 메뉴에서 기존 레코드를 삭제한 뒤 다시 배포하면 됩니다. 🎉
자동 배포 설정하기
지금까지는 wrangler deploy 명령어를 직접 실행해 배포했는데요, 실제 프로젝트에서는 깃 저장소에 푸시할 때 자동으로 배포되는 게 편합니다.
Cloudflare는 “Workers Builds”라는 자동 배포 기능을 제공하는데요, GitHub 저장소를 한 번 연결해두면 푸시 이벤트마다 Cloudflare가 직접 빌드하고 배포까지 처리해줍니다.
먼저 Cloudflare 대시보드의 Workers 페이지에서 배포할 Worker를 선택합니다. “Settings” 탭의 “Build” 섹션에서 “Connect” 버튼을 누르면 GitHub 계정 연동 화면이 뜨는데요, 권한을 승인한 뒤 다음과 같이 설정합니다.
- Repository: 배포할 저장소
- Production branch: 보통
main - Build command:
npm run build - Deploy command:
npx wrangler deploy
저장하면 그 시점부터 해당 브랜치에 푸시되는 모든 커밋이 자동으로 빌드되고 배포됩니다. 빌드 로그는 대시보드의 “Builds” 탭에서 확인할 수 있고, 실패하면 알림도 받을 수 있습니다.
여기서 한 가지 주의할 점은 대시보드의 Worker 이름과 wrangler.jsonc의 name 필드가 정확히 일치해야 한다는 것입니다.
이름이 다르면 첫 빌드가 미스매치 오류로 실패합니다.
GitHub Actions로도 같은 일을 할 수 있지만 Workers Builds는 워크플로우 파일을 따로 작성할 필요가 없고, Cloudflare API 토큰을 시크릿으로 관리할 필요도 없습니다. 반면 빌드 환경이 Cloudflare 인프라에 묶여 있어 세밀한 커스터마이징은 GitHub Actions가 더 유연합니다. 단순한 정적 사이트나 Worker 배포에는 Workers Builds가 충분히 편하고, 빌드 전후로 테스트나 린트, 알림 같은 부수 작업이 많이 필요한 프로젝트라면 GitHub Actions를 쓰는 게 낫습니다.
마치며
지금까지 Cloudflare Workers의 기본 사용법을 알아봤습니다. 간단한 API부터 프록시, 엣지 컴퓨팅까지 다양한 용도로 활용할 수 있는 강력한 플랫폼입니다.
특히 무료 플랜으로도 하루 10만 건의 요청을 처리할 수 있어서 개인 프로젝트나 사이드 프로젝트에 부담 없이 사용할 수 있습니다. 콜드 스타트가 거의 없고 전 세계 엣지에서 실행되기 때문에 성능도 뛰어나죠. 💪
Cloudflare Workers로 여러분만의 서버리스 애플리케이션을 만들어보시는 건 어떨까요? Wrangler CLI의 KV, R2, D1 관리 기능이나 환경 분리 같은 고급 기능이 궁금하시다면 Wrangler CLI 사용법도 함께 읽어보세요. Workers에서 키-값 데이터를 저장하고 싶다면 Workers KV로 전역 키-값 저장소 사용하기, 파일을 저장하려면 R2로 오브젝트 스토리지 사용하기, SQL 데이터베이스가 필요하다면 D1으로 서버리스 데이터베이스 시작하기를 참고해보세요. Workers에서 AI 모델을 실행해보고 싶다면 Workers AI로 서버리스 AI 추론하기도 확인해보세요. Next.js 앱을 Cloudflare Workers에 배포하고 싶다면 Cloudflare Vinext도 확인해보시길 추천드립니다. Workers에서 처리하기 어려운 무거운 연산이 필요하다면 Cloudflare Containers로 엣지에서 컨테이너 실행하기도 살펴보세요. 더 자세한 내용은 Cloudflare Workers 공식 문서를 참고해보세요!
This work is licensed under
CC BY 4.0