AI Web Studio977-121 · PSU PHUKET
รายงานวิจัย
ดาวน์โหลด Markdown

AI Web Studio · 977-121 · PSU Phuket

Labs เสริม: Cloudflare Pages สำหรับ brochure และ R2 สำหรับ media ที่มี policy

ตรวจสอบกับเอกสาร Cloudflare ทางการ: 9 กันยายน 2026. Labs นี้เสริม deployment หลักใน บทเรียน 08 ซึ่งต้องเป็น Worker + D1 ที่ deploy จริง; ไม่ใช่ทางเลี่ยง requirement นั้น

เลือก service ให้ตรงงาน

Cloudflare แนะนำ Workers สำหรับโปรเจกต์ใหม่ที่เป็น static, SPA หรือ full-stack เพราะมีความสามารถกว้างกว่า; Pages ยังเหมาะกับ static brochure ที่แยกเป็น artifact อย่างชัดเจน (Workers best practices, Pages getting started).

งาน เลือก เหตุผล
เว็บไซต์หลักของหลักสูตร: static UI + /api/* + D1 Worker Static Assets src/worker.ts และ D1 binding deploy พร้อม static assets
Landing/company brochure ที่ไม่มี form/API Pages Direct Upload deploy โฟลเดอร์ static เดียวได้ง่าย
รูปสินค้า/เอกสารที่ต้องรักษา policy R2 ผ่าน Worker R2 เก็บ bytes; Worker ตรวจสิทธิ์/key ก่อนตอบ

ดู แผนภาพ Cloudflare deployment แบบเต็มหน้า เพื่อเห็น topology หลักของ Worker, Static Assets และ D1 ก่อนเลือก lab; Pages กับ R2 เป็นทางเลือกเสริมที่อ่านจากตารางนี้ ตารางยังคงไว้เพราะคำถามส่วนนี้คือการเปรียบเทียบตัวเลือก ไม่ใช่การติดตามลำดับ request

Lab A — Pages: single-file company brochure ที่ static ล้วน

Pages Direct Upload upload build asset ไปยัง Pages; เริ่มด้วย wrangler pages project create แล้วใช้ wrangler pages deploy <directory>. โปรเจกต์แบบ Direct Upload เปลี่ยนเป็น Git integration ภายหลังไม่ได้ จึงเลือกวิธีนี้เมื่อเป็น artifact static ที่ตั้งใจ deploy จากเครื่อง/CI (Pages Direct Upload).

สร้างโฟลเดอร์ที่แยกจาก project Worker เพื่อไม่ให้เข้าใจผิดว่า Pages กำลัง deploy API ของหลักสูตร:

mkdir -p pages-brochure

สร้าง pages-brochure/index.html ด้วยเนื้อหาต่อไปนี้. ตัวอย่างนี้มี ไม่มี form, login, fetch หรือ secret เพื่อให้ browser ไม่เรียก /api/*:

<!doctype html>
<html lang="th">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="บริษัทสาธิตสำหรับ AI Web Studio">
  <title>เส้นตั้ง สตูดิโอ</title>
  <style>
    :root { color-scheme: dark; font-family: system-ui, sans-serif; }
    body { max-width: 70rem; margin: auto; padding: 3rem 1.25rem; background: #10120f; color: #eef0e9; }
    .eyebrow { color: #bdc89b; letter-spacing: .12em; text-transform: uppercase; }
    h1 { font-size: clamp(2.5rem, 10vw, 6rem); margin: .2em 0; line-height: .9; }
    main { display: grid; gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
    section { border-top: 1px solid #72815f; padding-top: 1rem; }
    a { color: #dce9b8; }
  </style>
</head>
<body>
  <p class="eyebrow">Bangkok · Architecture & Interior</p>
  <h1>พื้นที่ที่ดี<br>เริ่มจากการฟังชีวิต</h1>
  <p>เราสร้างบ้าน ที่ทำงาน และพื้นที่ค้าปลีกจากเรื่องจริงของผู้ใช้งาน</p>
  <main>
    <section><h2>งานของเรา</h2><p>บ้านพักอาศัย · พื้นที่ทำงาน · ร้านค้าขนาดเล็ก</p></section>
    <section><h2>ติดต่อ</h2><p><a href="mailto:hello@example.test">hello@example.test</a></p></section>
  </main>
</body>
</html>

จากนั้น login/create project/deploy ด้วย account ของผู้เรียนเอง:

npx wrangler whoami
npx wrangler pages project create
npx wrangler pages deploy ./pages-brochure --project-name=<your-pages-project>
curl -fsSI "https://<your-pages-project>.pages.dev/"

Wrangler จะถามชื่อ project และ production branch เมื่อสร้าง project; deployment production อยู่ที่ https://<project>.pages.dev, และ preview ใช้ --branch=<branch> ได้ (Pages Direct Upload). บันทึก URL กับเวลา deploy เป็นหลักฐาน lab.

Caveat สำคัญ

ห้าม deploy src/worker.ts ของหลักสูตรไป Pages Direct Upload แล้วหวังว่า /api/catalog, /api/contact หรือ D1 จะทำงาน: Direct Upload ใน lab นี้ส่งเฉพาะ static files, จึงไม่มี Worker handler ของ repository และ /api/* จะ fail/404. Pages Functions เป็น integration อีกแบบที่ต้องสร้าง function layout และ test ใหม่; อย่าเรียกว่า “นำ Worker เดิมไปใช้ได้ทันที”. เว็บไซต์หลักของคอร์สจึงยังใช้ Worker Static Assets + D1.

Lab B — R2: media ของสินค้าโดยไม่เปิด bucket write ต่อสาธารณะ

R2 bucket ไม่ public โดย default. การ bind R2 เข้ากับ Worker ทำให้ Worker อ่าน/เขียน object ได้ แต่ Cloudflare เตือนว่า route ที่เปิด bucket operations ต่อ incoming request ทุกคนจะ expose data; application ต้อง implement authorization เอง (create a bucket, R2 from Workers).

1. สร้าง bucket และวาง object สาธิตใน account ของตน

npx wrangler whoami
npx wrangler r2 bucket create vibe-academy-<your-initials>-media
npx wrangler r2 bucket list
mkdir -p fixtures
printf '%s\n' 'Demo media only; no customer data.' > fixtures/camera.txt
npx wrangler r2 object put vibe-academy-<your-initials>-media/products/camera.txt --file ./fixtures/camera.txt

ชื่อ bucket ใช้ lowercase, number และ hyphen เท่านั้น และยาว 3–63 ตัวอักษร (R2 bucket creation). สองคำสั่งก่อน object put สร้าง fixture สาธิตในเครื่องผู้เรียน; คำสั่ง upload เป็นการเขียน account จริง จึงใช้ไฟล์ demo ที่ไม่มี PII และไม่ใส่ PUT endpoint public ใน Worker.

2. เพิ่ม binding และ D1 metadata ใน branch ที่ review ได้

เพิ่ม binding ต่อไปนี้ใน wrangler.jsonc ของ branch media (เปลี่ยนชื่อ bucket ให้ตรงของตน) แล้วรัน npm run types/npm run typecheck:

{
  "r2_buckets": [{
    "binding": "MEDIA",
    "bucket_name": "vibe-academy-<your-initials>-media"
  }]
}

เพิ่ม migration แทนการ hard-code object key กระจัดกระจาย:

CREATE TABLE media_assets (
  id TEXT PRIMARY KEY,
  product_id TEXT NOT NULL REFERENCES products(id),
  object_key TEXT NOT NULL UNIQUE,
  content_type TEXT NOT NULL,
  visibility TEXT NOT NULL CHECK (visibility IN ('public'))
) STRICT;

INSERT INTO media_assets (id, product_id, object_key, content_type, visibility)
VALUES ('camera-demo', 'headphones', 'products/camera.txt', 'text/plain', 'public');

แยก metadata ใน D1 ออกจาก bytes ใน R2: D1 เป็น source of truth ว่า key ใดถูกเผยแพร่สำหรับสินค้าใด; R2 เก็บ object. Binding ถูกประกาศใน Wrangler config และ type ของ binding ต้อง generate ใหม่ (R2 Workers API).

เมื่อต้องอธิบาย boundary หลัก ให้อ่าน Worker → D1 ใน แผนภาพ deployment แล้วต่อแขนง R2 จาก code และ policy ใน lab นี้; ภาพตั้งใจไม่วาด extension ที่ยังไม่มีใน starter และ code block ด้านล่างยังเป็นหลักฐาน contract ที่แผนภาพแทนไม่ได้

3. อนุญาตเฉพาะ read route ที่ validate key จาก D1

เพิ่ม route GET /media/:id ใน Worker—not generic GET /media/:key, and never public PUT/DELETE. ตัวอย่างนี้รับ id ที่จำกัดรูปแบบ, lookup metadata แบบ bound parameter, และตอบ object เฉพาะ row ที่ visibility='public':

async function getPublicMedia(request: Request, env: Env): Promise<Response> {
  if (request.method !== "GET") return new Response("Method Not Allowed", { status: 405 });
  const id = new URL(request.url).pathname.slice("/media/".length);
  if (!/^[a-z0-9-]{1,64}$/.test(id)) return new Response("Not Found", { status: 404 });

  const media = await env.DB.prepare(
    "SELECT object_key, content_type FROM media_assets WHERE id = ? AND visibility = 'public'",
  ).bind(id).first<{ object_key: string; content_type: string }>();
  if (!media) return new Response("Not Found", { status: 404 });

  const object = await env.MEDIA.get(media.object_key);
  if (!object) return new Response("Not Found", { status: 404 });
  return new Response(object.body, {
    headers: { "Content-Type": media.content_type, "X-Content-Type-Options": "nosniff" },
  });
}

Merge this route only with test cases for unknown ID, malformed ID, missing object and allowed object. Upload/delete belongs to an authenticated admin/control-plane endpoint with explicit file-size/MIME/key policy, or to a short-lived presigned URL flow—never to PUT /media/:key on the public website. Presigned URLs are bearer tokens and need expiry, content-type constraints and CORS rules (R2 presigned URLs).

4. Verify and deploy the media extension

Run local migration/tests, deploy the same Worker extension, then test only the allowlisted media ID:

npm run db:local
npm run types
npm run typecheck
npm test
npx wrangler d1 migrations apply vibe-academy-<your-initials>-prod --remote
npm run deploy
curl -fsSI "$WORKER_URL/media/camera-demo"
curl -i "$WORKER_URL/media/not-a-real-id"

Record URL, Worker version and 200/404 outcomes. Do not treat this optional R2 lab as complete if bucket, binding, migration, route and authorization tests were not all performed.

ความคืบหน้าบันทึกเฉพาะเบราว์เซอร์นี้ ไม่มีบัญชีผู้เรียนหรือการส่งข้อมูลการเรียนขึ้นเซิร์ฟเวอร์