arielshemesh1999@gmail.com · ישראל
← כל המאמרים

Cloudflare Workers

סביבת הריצה ה-serverless שפועלת על ה-edge של Cloudflare ביותר מ-300 ערים, בשילוב Wrangler, D1 SQLite, R2 object storage, KV, Durable Objects ו-Queues — סטאק מלא לבניית pipeline לידים מהיר ל-landing page.

מה זה

Cloudflare Workers היא פלטפורמת serverless של Cloudflare שמריצה את הקוד שלך על הרשת הגלובלית שלהם — היום ב-330+ ערים — בלי תשתית לנהל ובלי לבחור region. סביבת הריצה אינה container אלא V8 isolate: אותו מנגנון בידוד ש-Chrome משתמש בו כדי להפריד בין טאבים. instance יחיד של ה-runtime מריץ מאות או אלפי isolates ומחליף ביניהם, כך שלפי התיעוד הרשמי isolate עולה בערך פי מאה מהר יותר מ-Node process בתוך container או VM, וצורך סדר גודל פחות זיכרון בהפעלה. זו הסיבה שמודל ה-edge הזה כמעט מבטל את ה-cold start שמכאיב ב-Lambda.

סביב ה-runtime יושב סטאק primitives מלא, וכולם נקשרים ל-Worker דרך bindings: D1 (serverless SQLite), R2 (object storage תואם S3 ללא עמלות egress), KV (key-value eventually consistent לקריאות עתירות), Durable Objects (actors בעלי state, instance קנוני יחיד לכל ID עם consistency חזק), Queues (תורי הודעות) ו-Workers AI (הרצת inference על edge GPUs). שפות נתמכות: JavaScript, TypeScript, Python ו-Rust, וכל מה שמתקמפל ל-WASM.

שרשרת הכלים כולה חיה ב-monorepo אחד — cloudflare/workers-sdk — ברישיון כפול Apache-2.0 + MIT, עם למעלה מ-2,000 releases: ה-CLI הוא Wrangler (גרסה 4.101.0, נכון ליוני 2026), האמולטור המקומי הוא Miniflare (4.x), ו-scaffolder הפרויקטים הוא create-cloudflare / C3 (2.x). סביבת הריצה עצמה, workerd, היא פרויקט קוד-פתוח נפרד (Apache-2.0) שמניע גם את Miniflare מקומית וגם את ה-edge בפרודקשן — אותו binary בשני הקצוות.

איך זה עובד — ארכיטקטורה

בקשה נכנסת ל-PoP הקרוב ביותר למשתמש. ה-router של Cloudflare ממפה את ה-hostname ל-Worker שלך, ה-Worker מורם בתוך isolate באותו instance של ה-runtime שכבר רץ שם, וממנו הוא מדבר ישירות עם המשאבים שקשרת אליו: שאילתת D1 פונה לרפליקה הזמינה, קריאת R2 פותרת אובייקט מהאחסון, וקריאה ל-Durable Object מנותבת ל-instance הקנוני היחיד שאחראי על אותו ID — גם אם הבקשה הבאה לאותו ID מגיעה מיבשת אחרת. ה-Worker עצמו הוא stateless; ה-state חי ב-primitives.

מה שחשוב להבין לפני שבונים: ה-isolate חולק CPU עם isolates אחרים, ולכן המודל מטיל תקרת זמן CPU לכל invocation — לא wall-clock. אפשר לחכות שעה ל-fetch חיצוני; מה שנמדד הוא רק הזמן שבו הקוד שלך מעבד בפועל. ב-Free plan התקרה היא 10ms CPU לכל בקשה, ובתוכנית Paid ברירת המחדל היא 30 שניות וניתן להעלות עד 5 דקות CPU. הזיכרון מוגבל ל-128MB לכל isolate, וגודל הסקריפט הדחוס הוא עד 3MB ב-Free ו-10MB ב-Paid.

למה זה מנצח דווקא עבור landing pages, APIs ו-webhooks:

  • כמעט בלי cold start — isolates עולים פי ~100 מהר יותר מ-container, כך שאין warmup tax על בקשה ראשונה.
  • Free tier נדיב — 100,000 בקשות/יום ב-Workers, 10GB-month + 1M Class A + 10M Class B ב-R2 (ללא egress), 5GB אחסון D1 לכל חשבון.
  • קובץ קונפיגורציה יחידwrangler.toml מגדיר את ה-Worker, כל binding וכל שם secret; אותו קובץ נכנס ל-git.
  • פיתוח מקומי אמיתי — Miniflare מדמה D1, R2, KV ו-Durable Objects מקומית, בלי Docker ובלי חיבור לחשבון.

התקנה

פקודה אחת מקימה פרויקט מקצה לקצה:

npm create cloudflare@latest
# or
pnpm create cloudflare@latest
# or
yarn create cloudflare@latest

C3 שואל שם, בוחר תבנית (Hello World, API, Hono, Astro, Next.js, Remix, SvelteKit, Workers AI…), מתקין תלויות ומציע לפרוס. Wrangler הוא ה-CLI שתעבוד איתו לאחר מכן:

# Add Wrangler to an existing project
npm install --save-dev wrangler

# Auth once
npx wrangler login

# Dev locally on Miniflare
npx wrangler dev

# Deploy globally
npx wrangler deploy

פקודות provisioning שימושיות:

npx wrangler d1 create my-db
npx wrangler r2 bucket create my-bucket
npx wrangler kv namespace create MY_KV
npx wrangler secret put RESEND_API_KEY

קונפיגורציה

הכל נמצא ב-wrangler.toml. יש להתייחס אליו כמקור האמת — הוא נשמר ב-git, זהה בין dev לפרודקשן, והוא המקום היחיד שבו bindings מוגדרים.

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2026-05-27"
compatibility_flags = ["nodejs_compat"]

# D1 serverless SQLite
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "12345678-1234-1234-1234-123456789012"

# R2 object storage
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"

# KV namespace
[[kv_namespaces]]
binding = "KV_STORE"
id = "abcdef1234567890"

# Durable Object — single-instance stateful actor
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_sqlite_classes = ["Counter"]

# Queues
[[queues.producers]]
queue = "email-jobs"
binding = "EMAIL_QUEUE"

# Vars and secrets
[vars]
ENVIRONMENT = "production"

# Secrets are added via: wrangler secret put RESEND_API_KEY

דוגמאות קוד

fetch handler מינימלי — המבנה הקנוני של Workers:

export default {
  async fetch(request, env, ctx) {
    return new Response("Hello, World!");
  }
};

API לידים מבוסס D1 — מבנה אופייני של pipeline לטופס לידים: POST של payload JSON, ולידציה, כתיבה ל-D1, שליחת אימייל דרך Resend.

-- schema.sql
CREATE TABLE leads (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL,
  phone TEXT,
  message TEXT,
  source TEXT,
  created_at INTEGER DEFAULT (strftime('%s','now'))
);
CREATE INDEX idx_leads_created ON leads(created_at DESC);

-- Apply
npx wrangler d1 execute my-database --file=./schema.sql --remote
// src/index.ts
export interface Env {
  DB: D1Database;
  RESEND_API_KEY: string;
}

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext) {
    if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });

    const body = await req.json<{
      name: string; email: string; phone?: string; message?: string;
    }>();

    if (!body.email || !body.name) {
      return new Response('Missing fields', { status: 400 });
    }

    const id = crypto.randomUUID();
    await env.DB
      .prepare('INSERT INTO leads (id, name, email, phone, message, source) VALUES (?, ?, ?, ?, ?, ?)')
      .bind(id, body.name, body.email, body.phone ?? null, body.message ?? null, 'landing')
      .run();

    // Fire-and-forget notification
    ctx.waitUntil(
      fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.RESEND_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          from: 'noreply@example.com',
          to: 'me@example.com',
          subject: `New lead: ${body.name}`,
          text: `${body.name} <${body.email}>\n${body.message ?? ''}`
        })
      })
    );

    return Response.json({ ok: true, id });
  }
};

העלאת קבצים ל-R2 + קריאה חתומה:

export default {
  async fetch(req: Request, env: { BUCKET: R2Bucket }) {
    const url = new URL(req.url);

    if (req.method === 'PUT') {
      const key = url.pathname.slice(1);
      await env.BUCKET.put(key, req.body, {
        httpMetadata: { contentType: req.headers.get('content-type') || 'application/octet-stream' }
      });
      return Response.json({ key });
    }

    if (req.method === 'GET') {
      const obj = await env.BUCKET.get(url.pathname.slice(1));
      if (!obj) return new Response('Not found', { status: 404 });
      return new Response(obj.body, {
        headers: { 'content-type': obj.httpMetadata?.contentType ?? 'application/octet-stream' }
      });
    }

    return new Response('Method Not Allowed', { status: 405 });
  }
};

Durable Object — counter אחד strongly-consistent לכל room id, מושלם עבור rate-limiters, websocket rooms ו-state שיתופי:

export class Counter {
  state: DurableObjectState;
  constructor(state: DurableObjectState) { this.state = state; }

  async fetch(req: Request) {
    let count = (await this.state.storage.get<number>('n')) ?? 0;
    count += 1;
    await this.state.storage.put('n', count);
    return Response.json({ count });
  }
}

export default {
  async fetch(req: Request, env: { COUNTER: DurableObjectNamespace }) {
    const id = env.COUNTER.idFromName('global');
    const stub = env.COUNTER.get(id);
    return stub.fetch(req);
  }
};

פיתוח מקומי עם Miniflare

ה-quality-of-life win הגדול ביותר באקוסיסטם Workers הוא ש-wrangler dev מעלה סימולטור מקומי מלא — Miniflare — שמדמה D1, R2, KV, Durable Objects, Queues ו-Workers AI על הדיסק. ללא Docker, ללא צורך ברישום. אותו wrangler.toml מניע גם את הסביבה המקומית וגם את הפרודקשן; שמות ה-bindings שכותבים בקוד מתורגמים לאמולטור המקומי ב-dev ולשירותי ה-edge האמיתיים בפריסה. ה-state נשמר בין הרצות ב-.wrangler/state/, כלומר מסד הנתונים D1 לפיתוח שורד אתחולים מחדש.

# Run locally with live-reload + persistent local state
npx wrangler dev

# Run against real remote bindings (still local code, but talks to prod D1/R2)
npx wrangler dev --remote

# Tail production logs
npx wrangler tail

גרסה ויכולות מרכזיות

נקודות שכדאי להכיר לפני שמתחילים:

  • Wrangler 4.101.0 (נכון ליוני 2026) — ה-CLI הנוכחי; פלט רזה יותר, קונפיגורציה TOML-first נקייה, ופיתוח מקומי שמורם דרך Miniflare 4.x מתוך אותו monorepo.
  • D1 (serverless SQLite) — עד 10 מסדים לחשבון ב-Free, 500MB לכל מסד ועד 5GB סך הכל; שחזור Time Travel (point-in-time) עד 7 ימים ב-Free, ועד 50 שאילתות לכל invocation. הדגם המומלץ הוא scale-out למסדים קטנים, לא מסד ענק יחיד.
  • R2 ללא egress — משלמים על אחסון ועל פעולות (Class A/B), לעולם לא על bandwidth יוצא; ה-Free tier הוא 10GB-month, מיליון פעולות Class A ו-10 מיליון Class B. זה ה-killer feature מול S3.
  • Durable Objects — instance יחיד strongly-consistent לכל ID, הבסיס ל-rate-limiters, WebSocket rooms ו-state שיתופי בזמן אמת.
  • Workers AI — הרצת inference (Llama, Whisper, Stable Diffusion ועוד) על edge GPUs דרך binding אחד, בלי לנהל GPU.
  • workerd בקוד פתוח — אותה סביבת ריצה שמניעה גם את Miniflare מקומית וגם את ה-edge, Apache-2.0; אפשר אפילו לאחסן אותה בעצמך.

מתי להשתמש — ומגבלות

בשימוש יומיומי זה הצירוף שהכי קל לחזור אליו עבור landing page קטן: Vercel לאתר הסטטי + Cloudflare Worker + D1 לאחסון + Resend לאימייל, חתום HMAC מקצה לקצה. ה-free tier מכסה חודשים של תנועה אמיתית, פריסה היא פקודה אחת, ואין Docker, אין VPC, אין Lambda warmup ואין cold-start tax. מניסיון, המנוף הגדול הוא שאותו wrangler.toml רץ זהה בין dev לפרודקשן, כך שמה שעובד מקומית מתנהג אותו דבר על ה-edge.

איפה Workers מפסיק להיות התשובה הנכונה: כל עומס שדורש זמן CPU ממושך — זכור שה-Free plan חוסם ב-10ms CPU לכל בקשה ו-Paid ב-30 שניות (עד 5 דקות) — וכן משקלי מודל ML כבדים שלא נכנסים ל-128MB, או סמנטיקת קבצי POSIX מדויקת. בנקודות האלה VM אמיתי, container, או Supabase Edge מתאימים יותר. כדאי גם לזכור ש-KV הוא eventually consistent (לא לסמוך עליו ל-state טרנזקציוני), ו-D1 מתוכנן ל-scale-out על מסדים קטנים ולא למסד ענק יחיד. לכל השאר — טפסים, APIs, webhooks, cron ו-scheduled jobs, rate-limits לפי ID דרך Durable Objects — Workers הוא בחירה שקשה להתחרות בה.

מקורות