# Cloudflare สำหรับเว็บไซต์ที่สร้างด้วย Vibe Coding

> สถานะการวิจัย: 9 กันยายน 2026 · ข้อเท็จจริงทางเทคนิคในเอกสารนี้มาจากเอกสารทางการ Cloudflare ที่เปิดอ่านในวันนี้ ส่วนคำว่า **ข้อเสนอแนะในการสอน** คือการเลือกเนื้อหาสำหรับหน่วยเรียน 2 วัน / 8 ชั่วโมงและคลังศึกษาต่อ ไม่ใช่ข้อกำหนดของ Cloudflare

## คำตอบสั้นสำหรับหลักสูตรนี้

สำหรับเว็บไซต์บริษัท หน้ารวมสินค้า และตัวอย่างแอนิเมชัน GSAP ให้เริ่มด้วย **Cloudflare Worker + Workers Static Assets** เป็นจุด deploy เดียว: build frontend ไปที่ `dist/`, ให้ Worker เสิร์ฟไฟล์เหล่านั้น และให้ route `/api/*` เรียก API เดียวกันได้ เมื่อมีข้อมูลเชิงสัมพันธ์จึง bind **D1**; เมื่อมีรูปสินค้า เอกสาร หรือไฟล์ผู้ใช้จึง bind **R2**; และก่อนรับฟอร์ม/การจองให้ใช้ **Turnstile** แล้วตรวจ token ใน Worker ฝั่ง server เสมอ. นี่ลดจำนวน deployment, domain, CORS และจุดที่ผู้เรียนต้อง debug ในโปรเจกต์แรก

Cloudflare ระบุว่า Workers Static Assets เป็นวิธีที่แนะนำสำหรับ static site, SPA และ full-stack app ใหม่; Pages ยังใช้งานได้ แต่ฟีเจอร์และการปรับปรุงใหม่มุ่งไปที่ Workers มากกว่า. ดังนั้นบทเรียนให้รู้จัก Pages เพื่ออ่านโปรเจกต์เก่าและการ deploy ที่มีอยู่ แต่ไม่ใช้ Pages เป็นค่าเริ่มต้นของงานใหม่ ([Workers best practices](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/)).

### ขอบเขตของ starter repository นี้

ข้อเสนอข้างต้นเป็นสถาปัตยกรรมเป้าหมายของหลักสูตร; starter ที่ใช้สอนตอนนี้มี Worker Static Assets + `ASSETS` binding + D1 binding เท่านั้น และ `DEMO_MODE: "true"`. Repository ของผู้สอนอาจมี D1 resource ID ของ demo ที่ deploy แล้ว แต่ source package ที่แจกผู้เรียนแทนค่านั้นด้วย placeholder; ผู้เรียนต้องสร้าง D1 ของตนเองและใส่ ID ของตนตาม [บทเรียน 08](../lessons/08-cloudflare-deploy.md). ไม่มี R2 binding, named staging environment หรือ frontend Turnstile widget ที่ production-ready ใน starter. เส้นทางที่รันได้จริงคือ `npm run db:local` → `npm run dev` → smoke `/api/health` และ `/api/catalog` → `npm run deploy:check`; อย่าอ้างว่า R2/staging/production deploy สำเร็จจนกว่าจะเพิ่ม resource, config, secret และ evidence ตามบทเรียนนั้น.

## ขอบเขตและรูปแบบเว็บไซต์ตัวอย่าง

| ตัวอย่าง | ของที่ deploy | ข้อมูล | ความปลอดภัยขั้นต่ำ | สิ่งที่พิสูจน์ได้ |
| --- | --- | --- | --- | --- |
| เว็บไซต์บริษัท | Static Assets + `/api/contact` | D1 สำหรับข้อความติดต่อ (ถ้าต้องเก็บ) | Turnstile + validation ฝั่ง Worker | ฟอร์มส่งสำเร็จและไม่มี secret ใน browser |
| แค็ตตาล็อก/โชว์สินค้า GSAP | Static Assets + `/api/catalog` | starter ส่ง catalogue demo; production อาจใช้ D1 และ R2 สำหรับภาพ | validate input; cache read-only อย่างระวัง | ภาพและ animation โหลดได้ พร้อมเคารพ `prefers-reduced-motion` |
| เว็บไซต์เช่าอุปกรณ์ | Static Assets + API | D1: อุปกรณ์, ลูกค้า, ช่วงเวลา, คำขอจอง; R2: รูป/เอกสาร | Turnstile, prepared SQL, validation, rate limit ตามความเสี่ยง | ไม่เกิดการจองซ้ำในกรณีแข่งกัน และมี audit log ที่ปลอดภัย |
| back office ของทีม | Worker ที่ป้องกันด้วย Access | D1/R2 ตามงาน | Cloudflare Access/IdP และ policy แบบ deny-by-default | คนที่ไม่ตรง policy เข้าไม่ได้ |

**ข้อเสนอแนะในการสอน:** อย่าเริ่มด้วย e-commerce ที่รับชำระเงินจริง หรือระบบบัญชีผู้ใช้เต็มรูปแบบ. ให้เริ่ม “catalogue + enquiry/reservation request” ก่อน แล้วอธิบายว่าการเก็บเงิน, อีเมล, ภาษี, สต็อก และการยืนยันตัวตนเป็น integration แยกที่ต้องออกแบบและทดสอบเพิ่ม.

## แผนภาพสถาปัตยกรรมเริ่มต้น

![แผนภาพสถาปัตยกรรม Cloudflare แสดง browser ผ่าน HTTPS เข้าสู่ Worker ที่ Cloudflare edge แล้วแยกไป static assets และ D1 managed data](/diagrams/cloudflare-deployment.svg)

[เปิดแผนภาพ Cloudflare deployment แบบเต็มหน้า](/diagrams/cloudflare-deployment.html) เพื่อดู topology ที่ starter มีจริง ส่วน R2, Access และ Turnstile เป็น production extensions ที่ตั้งใจตัดออกจากภาพเพื่อลดความหนาแน่นและอธิบายในตารางขอบเขตด้านบน

Static Assets อยู่ในไฟล์ config ผ่าน `assets.directory`. เว็บไซต์ล้วน ๆ ไม่ต้องมี Worker script; full-stack เพิ่ม `main` และใช้ `ASSETS` binding เพื่อให้ handler ส่ง static asset ที่ไม่ใช่ `/api/*` ([แนวทางทางการ](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/)). ยึด `wrangler.jsonc` เป็น config ที่อยู่ใน Git, ใช้ `compatibility_date` ล่าสุดที่ทีมทดสอบแล้ว, และสร้าง type ของ binding หลังปรับ config. อย่า copy วันที่ในตัวอย่างโดยไม่ตรวจสอบวันที่ release/deploy จริง.

```jsonc
{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "course-catalogue",
  "main": "src/worker.ts",
  "compatibility_date": "YYYY-MM-DD",
  "assets": {
    "directory": "./dist", "binding": "ASSETS",
    "run_worker_first": ["/api/*"]
  },
  "d1_databases": [{
    "binding": "DB", "database_name": "course-catalogue",
    "database_id": "<created-by-cloudflare>", "migrations_dir": "migrations"
  }],
  "observability": { "enabled": true, "head_sampling_rate": 0.1 }
}
```

ตัวอย่างนี้เป็นโครง ไม่ใช่ค่าพร้อม deploy: `database_id`, route และ secret ต้องมาจากบัญชีของผู้เรียน. R2 เพิ่มได้เฉพาะเมื่อมี media route/authorization/test ที่ใช้จริง. หากเพิ่ม named environment, bindings และ `vars` ไม่ inherit; ต้องประกาศค่าของ staging/production ให้ครบ ([Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/)). ตรวจ schema/คำสั่งของ Wrangler เวอร์ชันที่ติดตั้งก่อน execute ([Wrangler](https://developers.cloudflare.com/workers/wrangler/)).

## D1: SQL ที่ปลอดภัย, migration และการแข่งกัน

D1 เหมาะกับข้อมูลสัมพันธ์ขนาดของตัวอย่างนี้: catalogue, availability, booking request และ contact message. ห้ามต่อค่าจาก input เข้า SQL string. ใช้ `prepare(...).bind(...)`: Cloudflare แนะนำ prepared statement เพราะการ bind ช่วย reuse และป้องกัน SQL injection ([Prepared statements](https://developers.cloudflare.com/d1/worker-api/prepared-statements/)). ตรวจชนิด/ขอบเขต input ก่อน query และคืน error message ที่ไม่เผย SQL หรือ secret.

```ts
const row = await env.DB.prepare(
  "SELECT id, name, daily_rate FROM equipment WHERE id = ?"
).bind(equipmentId).first();
```

ไฟล์ migration คือส่วนหนึ่งของ source code: สร้างไฟล์ลำดับ `migrations/0001_initial.sql`, commit พร้อม code และใช้ `wrangler d1 migrations apply <database> --local` ก่อน จากนั้นตรวจ staging แล้วจึง `--remote`. D1 บันทึก migration ที่ apply แล้วในตาราง `d1_migrations`; ใช้ **database name** ในคำสั่ง migration เมื่อเป็นไปได้ เพราะ binding name เปลี่ยนได้ ([D1 migrations](https://developers.cloudflare.com/d1/reference/migrations/)). คำสั่ง DDL ที่เสี่ยงลบ/แก้ข้อมูลต้องมี backup/restore plan และต้อง rehearsal บน staging ก่อน production.

ใช้ `DB.batch([...])` เมื่อต้องการหลาย prepared statements เป็นชุดเดียว: Cloudflare ระบุว่า batch ทำงานตามลำดับ, ไม่พร้อมกัน, และเป็น SQL transaction; หาก statement ใด fail จะ rollback ทั้งชุด ([D1Database batch](https://developers.cloudflare.com/d1/worker-api/d1-database/)). สำหรับการจอง ให้ schema มี constraint ที่สะท้อนกติกาธุรกิจ และให้การตรวจ availability กับ insert/update อยู่ใน transaction เดียว—อย่าคิดว่า “SELECT ก่อนแล้วค่อย INSERT” สอง request จะแข่งกันไม่ได้.

D1 database หนึ่งตัว single-threaded และ query เข้าเป็นลำดับ; เมื่อ queue เต็มอาจเกิด `overloaded`. ลด query ที่ยาว, ใช้ index ตาม query ที่วัดจริง, ออกแบบ workload ให้สั้น และ retry เฉพาะ error ที่ transient ด้วย exponential backoff + jitter ตามคำแนะนำ ([D1 limits](https://developers.cloudflare.com/d1/platform/limits/), [retry queries](https://developers.cloudflare.com/d1/best-practices/retry-queries/)). การ retry ต้องไม่ทำให้ “สร้าง booking ซ้ำ”: ใช้ idempotency key หรือ unique request identifier ที่บันทึกและตรวจใน D1.

## R2: ไฟล์ไม่ใช่แถวฐานข้อมูล

เก็บ metadata ของสินค้า/ไฟล์ใน D1 และเก็บ bytes ของรูป PDF หรือเอกสารใน R2. Worker binding ให้ Worker เรียก `get`/`put` โดยไม่ส่ง API credential ไป browser. เอกสาร R2 เตือนชัดว่า Worker ที่เปิด GET/PUT ทั้งหมดโดยไม่มี authorization จะเปิด bucket ต่อผู้ไม่หวังดี; authorization เป็นหน้าที่ของ application ([R2 from Workers](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/)).

สำหรับรูปสินค้าสาธารณะ ใช้ key ที่ควบคุมได้และ route read-only หรือ public custom domain ที่ตั้งใจเปิด. สำหรับ upload ของผู้ใช้: ตรวจ authentication/authorization, ขนาด, MIME type และชื่อ key ที่สร้างจาก server; อย่าเชื่อ file name จาก browser. ถ้าต้องการ browser upload ตรง R2 ให้ Worker ออก presigned URL อายุสั้น, จำกัด operation และ `Content-Type`, และตั้ง CORS เฉพาะ origin ที่อนุญาต. Presigned URL คือ bearer credential—ผู้ได้ URL ใช้งานได้จนหมดอายุ; Cloudflare รองรับ GET/HEAD/PUT/DELETE แต่ไม่รองรับ POST multipart form ([R2 presigned URLs](https://developers.cloudflare.com/r2/api/s3/presigned-urls/)).

## Turnstile, Access และ secret

Turnstile เป็นชั้นป้องกัน form ไม่ใช่ login system. Browser ส่ง token พร้อม POST, Worker สร้าง request `POST https://challenges.cloudflare.com/turnstile/v0/siteverify` พร้อม `secret` และ `response`, ตรวจ `success` ก่อนเขียน D1/R2. การตรวจฝั่ง server เป็นข้อบังคับ: token อยู่ 300 วินาทีและใช้ได้ครั้งเดียว; client widget เพียงอย่างเดียวป้องกัน form ไม่ได้ ([Server-side validation](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/)). เก็บ Turnstile secret ด้วย Workers secret และตรวจ hostname/action ที่คาดหวังเมื่อใช้ค่าเหล่านั้น. อย่า log token, secret หรือข้อมูลฟอร์มที่ละเอียดอ่อน.

Cloudflare Access เหมาะกับ `/admin`, preview หรือเครื่องมือทีม—not a replacement for customer authorization ภายใน application. Access เป็น identity-aware proxy ที่ตรวจ request ด้วย policy; self-hosted/Worker application ใช้ policy engine และ Access applications เป็น deny-by-default จนผู้ใช้ match Allow policy ([Access web apps](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/), [application type](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/choose-application-type/)). หากมี origin ของตนเองที่อาจ bypass Cloudflare ต้อง validate Access token ที่ origin ด้วย; สำหรับ Worker ที่ Cloudflare host ให้ผูก Worker กับ Access app เป็นจุดป้องกันตรงไปตรงมา.

`vars` ใช้เฉพาะค่าที่ไม่ลับ เช่น environment label; secret ของแอป เช่น Turnstile secret หรือ API token ของบริการที่ Worker เรียก ต้องใช้ Workers Secrets. Local ใช้ `.dev.vars` หรือ `.env` (เลือกหนึ่งแบบ) และ ignore ใน Git. ส่วน `CLOUDFLARE_API_TOKEN` สำหรับ deploy เก็บใน environment ของ CLI/CI ไม่ส่งเข้า Worker runtime. Cloudflare แนะนำไม่เก็บ sensitive data ใน `vars`; สามารถประกาศ required secret names เพื่อให้ deploy fail หากขาด secret ([Workers secrets](https://developers.cloudflare.com/workers/configuration/secrets/)).

## Delivery, operations และต้นทุน

สอนวงจร base ของ starter ก่อน: `npm run db:local` → `npm run dev` → test API/การเข้าถึง → `npm run deploy:check`. จากนั้นจึงเป็น production extension: เพิ่ม config/resource ของ staging → smoke test URL จริง → deploy production. แยก `staging` และ `production`, แยก secret และ D1/R2 resource ตามระดับความเสี่ยง. การ deploy static frontend ไม่ยืนยันว่า migration หรือ API ใช้ได้ จึงต้องมี smoke checklist สำหรับ POST, query, upload และ authorization.

เปิด `observability.enabled`; Workers Logs เก็บ invocation, custom log, error และ uncaught exception และ Cloudflare แนะนำ structured JSON เพื่อ query/filter ได้ดี. เลือก `head_sampling_rate` เพื่อควบคุมปริมาณข้อมูล โดยเฉพาะ production traffic; metrics มี request/error rate, CPU/wall time และ duration ([Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/), [Observability](https://developers.cloudflare.com/workers/observability/)). Log event ควรมี `requestId`, route, status, duration และ error class โดยตัด email, token, cookie, body และ secret ออก.

ก่อน release ที่เสี่ยง: ดู version ที่ active, backup/export D1 ตามแผน, ทำ migration ที่ forward-compatible, และเตรียม rollback code. `wrangler rollback` เปลี่ยน Worker version ได้ แต่ไม่ย้อน D1/R2 schema/data โดยอัตโนมัติ และ Cloudflare ระบุว่า rollback ใช้ไม่ได้หาก resource ถูกลบหรือแก้ไม่เข้ากันระหว่าง version ([Rollbacks](https://developers.cloudflare.com/workers/versions-and-deployments/rollbacks/)). จึงออกแบบ migration แบบ expand → deploy code ที่อ่านได้ทั้งสอง schema → backfill → contract ภายหลัง ไม่ใช่ deploy destructive change พร้อมกัน.

ตัวเลข quota/pricing เปลี่ยนได้ ให้ผู้เรียนเปิดหน้า pricing/limits ของแผนตัวเองก่อนเปิด production. สิ่งที่ต้อง track เป็นตัวชี้วัด ได้แก่ Worker request/CPU/subrequest, D1 rows read/write/storage, R2 storage/Class A/Class B operations และ log volume. ตั้ง CPU limit เพื่อกัน runaway cost/denial-of-wallet ตามคำแนะนำของ Workers pricing และทำ budget/alert ใน account. แหล่งจริงคือ [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/), [Workers limits](https://developers.cloudflare.com/workers/platform/limits/), [D1 pricing](https://developers.cloudflare.com/d1/platform/pricing/) และ [R2 pricing](https://developers.cloudflare.com/r2/pricing/); ไม่ให้ hard-code ตัวเลขในสไลด์เพราะแผนและราคาอาจเปลี่ยน.

## ลำดับแนะนำในหน่วยเรียน 8 ชั่วโมง

ขั้นตอนรับรองตัวตนก่อน deploy อยู่ใน [Lab Wrangler login และ API token](../labs/cloudflare-wrangler-auth.md) แยก token ที่ CLI/CI ใช้จัดการ Cloudflare ออกจาก secret ที่ application ใช้ขณะรัน: `CLOUDFLARE_API_TOKEN` เป็น credential ของเครื่องมือ deploy จึงไม่ใส่ใน frontend หรือส่งเป็น Worker runtime secret

กำหนดการ canonical อยู่ที่ [คู่มือผู้สอน 2 วัน / 8 ชั่วโมง](../instructor/course-guide.md): วัน 1 ครอบคลุม brief/skills/design/company และวัน 2 เลือก rental/D1, motion/quote overview, deploy boundaries และ capstone vertical slice. Operations เชิงลึกกับ R2 เป็นเนื้อหา **optional/self-study** หลังเส้นทาง Worker + D1 ไม่ใช่เงื่อนไขของ capstone ทุกคน.

เอกสารนี้เป็นฐานให้บทเรียน [08 Cloudflare deployment](../lessons/08-cloudflare-deploy.md) และ [09 production operations](../lessons/09-production-operations.md). นอกเหนือจาก Cloudflare ให้กลับไปใช้ checklist ของหลักสูตรสำหรับ UX, accessibility, legal/privacy, payment provider และ review โค้ดที่ AI สร้าง.
