0

Staging web tĩnh tức thì, không cần tài khoản: mổ xẻ Cloudflare Drop và tự dựng stage-drop

Lấy cảm hứng từ Cloudflare Drop (llms.txt). Bài này là phân tích sản phẩm độc lập kèm một bản clone mã nguồn mở, không phải reverse-engineering phần nội bộ độc quyền.

Bản gốc tiếng Anh trên DEV.to: Instant static staging without an account: tearing down Cloudflare Drop and building stage-drop

Kéo thả một thư mục. Nhận ngay một live URL. Claim trong 60 phút, không thì nó biến mất.

Đó là ý tưởng của Cloudflare Drop: staging site tĩnh tức thì trên edge, bắt đầu mà không cần tài khoản. Với dev cần đẩy preview nhanh, và cả AI agent cần một nơi để deploy mà gần như không phải làm thủ tục gì, sản phẩm này đánh trúng nhu cầu.

Đây là Phần 1 trong series bốn phần. Hôm nay mình mổ xẻ sản phẩm và dựng một bản clone mã nguồn mở, chạy local được trong khoảng năm phút. Các phần sau đưa cùng codebase lên Cloudflare Workers, rồi AWS, rồi một lớp agent-first (llms.txt + MCP).

Clone: github.com/vovanduc/stage-drop


Trải nghiệm trong mười giây

Hành vi công khai (quan sát từ trang sản phẩm và llms.txt, không dựa vào source nội bộ):

  1. Bạn kéo thả một thư mục hoặc file .zip của site tĩnh (HTML/CSS/JS, có index.html).
  2. Cloudflare phân phối nó lên mạng lưới toàn cầu.
  3. Bạn nhận một live URL dạng workers.dev.
  4. Trong 60 phút, bạn phải mở claim URL để giữ lại deployment. Quá hạn thì bản preview tạm sẽ bị xoá.

Ngoài ra còn có đường CLI chính thức: wrangler deploy … --temporary (Wrangler ≥ 4.102.0) cho workflow project ở local. Wrangler đã đăng nhập thì deploy bình thường — chế độ temporary dành cho luồng không cần tài khoản.

Lần nào cũng trả về hai URL: live URL (chia sẻ preview) và claim URL (cấp quyền sở hữu, rất nhạy cảm). Hãy coi claim link như một mật khẩu có TTL.

Vòng lặp đó chính là toàn bộ sản phẩm. Mọi thứ thú vị còn lại — chống lạm dụng, TTL cho metadata, tài liệu cho agent — đều xoay quanh nó.


Góc nhìn sản phẩm: vì sao tặng một công cụ miễn phí, không cần đăng nhập?

Nhìn từ phía product, Drop giống một nước đi về funnel + định vị hơn là món đồ chơi phụ:

  • Vào Workers không ma sát. Lần deploy đầu không đòi OAuth. Hành động thứ hai (claim) mới tạo quyền sở hữu. Đúng kiểu "dùng thử trước khi cam kết", áp dụng cho static asset trên Workers.
  • Agent Experience (AX) là người dùng hạng nhất. Việc đưa ra một file llms.txt chỉ cho agent cách upload, nhận gì về, và nên dùng Wrangler thay vì tự động hoá trình duyệt cho thấy: AI agent không phải chuyện phụ, mà là nhóm đối tượng chính.
  • Trung thực với toolchain. Temporary deploy nằm ngay trong Wrangler, không chỉ ở giao diện marketing. Vùng kéo thả trên web và CLI dùng chung một mô hình tư duy.

Đánh đổi khó của sản phẩm thì rõ: upload không cần xác thực đối đầu với lạm dụng, phishing và trộm claim token. Drop bản production phải dựa vào các lớp kiểm soát ở edge, rate limit và hệ thống tài khoản của Cloudflare. Một bản clone để học nên nói thẳng khoảng trống này, thay vì giả vờ một demo cuối tuần là CDN.


Kiến trúc suy luận (chỉ dựa trên hành vi)

Đây là suy luận từ hành vi công khai và tài liệu, không phải khẳng định về cách Cloudflare triển khai bên trong.

Mối quan tâm Hành vi công khai gợi ý điều gì
Compute Worker trên edge (live URL dạng workers.dev)
Blob Object storage chứa các file tĩnh đã giải nén
Metadata Record có TTL: site id, thời điểm hết hạn, trạng thái claim
Claim Secret dùng một lần; cấp quyền sở hữu / xoá hạn tạm thời
Dọn dẹp Một cơ chế nào đó xoá các deploy chưa claim đã hết hạn

CLI temporary deploy và giao diện web đều trả live + claim URL, nên control plane nhiều khả năng dùng chung, dù đường upload có thể khác.

Những thứ mình chủ động không làm: decompile Workers, scrape API nội bộ, hay chép giao diện/branding. Bản clone chỉ lấy cảm hứng, với interface và lựa chọn bảo mật riêng.


Thiết kế stage-drop: core dùng chung + adapter

Luận điểm kỹ thuật của cả series khá đơn giản:

Một core TypeScript. Đổi storage adapter. Cùng ngữ nghĩa upload / claim / serve / expire trên laptop, Cloudflare và AWS.

[Web UI / curl]
       |
       v
+-----------------+     +------------------------+
|  Core (Hono)    |---->| BlobStore (interface)  |  local FS | R2 (#2) | S3 (#3)
|  upload/claim   |     +------------------------+
|  serve/expire   |---->| MetaStore (interface)  |  memory   | KV (#2) | DynamoDB TTL (#3)
+-----------------+     +------------------------+

Vì sao chọn Hono? Nó chạy trên Node cho demo local, và sau này chạy trên Workers / runtime kiểu Lambda mà không phải viết lại phần HTTP.

Vì sao dùng interface? Phần 2 và Phần 3 chỉ nên là việc viết adapter, không phải viết lại. Nhờ vậy mới có phép so sánh Cloudflare với AWS công bằng, trên cùng ngữ nghĩa sản phẩm.

Ba quyết định khó (bản demo local)

  1. Claim token — Sinh token ngẫu nhiên >=128-bit; chỉ lưu hash SHA-256; dùng một lần rồi huỷ. Token gốc chỉ nằm trong claim URL, không bao giờ nằm trong storage bền vững.
  2. Vòng đời TTL — Site chưa claim sẽ hết hạn (~60 phút). Sau đó serve trả 410 Gone. Một tiến trình quét định kỳ xoá blob + meta của các site hết hạn chưa claim. Claim sẽ xoá thời hạn.
  3. Mức chặn lạm dụng tối thiểu — Zip <=10 MB, <=500 file; chặn zip-slip; allowlist content-type theo đuôi file; rate limit theo IP trong bộ nhớ khi upload. Chủ ý không có: quét malware, heuristic phát hiện phishing, CAPTCHA. Những thứ đó thuộc về một sản phẩm edge thực thụ.

Kiến trúc (lab + clone)

Worker ở lab, lab.vovanduc.tech/stage-drop/, dùng cùng core với R2 cho file đã giải nén và KV cho metadata. Cách đặt key:

Store Key pattern Vai trò
R2 stage-drop-blobs sites/{id}/... File tĩnh sau khi unzip
KV META meta:{id} Record của site (đã claim chưa, expiresAt, ...)
KV claim:{sha256} Tra cứu claim dùng một lần (chỉ lưu hash)
KV exp:{expiresAt}:{id} Index để quét TTL cho site chưa claim

TTL: site chưa claim hết hạn sau 60 phút. Cron: trigger của Worker */5 * * * * chạy sweepExpired (xoá object R2 + key KV của các site chưa claim đã hết hạn). Health check: /stage-drop/health.

Luồng request

stage-drop-part-1-request-flow.png

State machine của site

stage-drop-part-1-state-machine.png

Claim hoạt động thế nào, nói cho dễ hiểu

Upload trả về hai URL. Live URL chia sẻ thoải mái — ai cũng xem được preview khi nó còn sống. Claim URL mới là quyền sở hữu: nó mang một token >=128-bit dùng một lần. Khi bạn mở nó, Worker hash token, tra claim:{sha256}, đánh dấu site là Claimed, xoá expiresAt, xoá entry index exp:, rồi xoay (rotate) hash để link không thể dùng lại. Nếu bỏ qua claim, sau 60 phút serve sẽ trả 410 Gone; vài phút sau cron sweep sẽ dọn blob và meta.

Vậy là hết vòng đời: LiveUnclaimed → Claimed (giữ mãi trên bản demo này) hoặc LiveUnclaimed → Expired → bị quét.


Chạy demo local (<=5 phút)

git clone https://github.com/vovanduc/stage-drop.git
cd stage-drop
npm install
npm test          # should be green
npm run dev       # http://localhost:8787
  1. Mở UI và kéo thả một file zip có chứa index.html.
  2. Mở live URL — file được serve ở /s/:siteId/.
  3. Mở claim URL khi còn trong cửa sổ TTL. Token dùng một lần; chỉ có hash được lưu.

Hoặc upload bằng curl:

zip -r site.zip index.html style.css
curl -sS -X POST http://localhost:8787/api/upload \
  -H 'content-type: application/zip' \
  --data-binary @site.zip | jq

Kết quả mong đợi là JSON có liveUrl và claimUrl. Mở live → ra HTML. Claim → site còn sống sau đợt quét hết hạn. Bỏ qua claim → cuối cùng nhận 410.

Vitest kiểm tra: claim dùng một lần, TTL → 410, từ chối zip-slip, giới hạn kích thước, và trả đúng content-type. Nếu npm test xanh, bạn có đúng những lớp bảo vệ mà README cam kết.

Hoặc bỏ qua bước cài local và mở thẳng live lab: lab.vovanduc.tech/stage-drop/.

Hướng dẫn setup Cloudflare thân thiện với agent cho ai làm việc với repo (Claude, OpenCode, Orca, Cursor, ... — không gắn với host nào):

https://github.com/vovanduc/stage-drop/blob/master/docs/agent-setup.md


Bản clone này dạy được gì mà ảnh chụp màn hình thì không

Tự dựng vòng lặp này buộc bạn đối mặt với các câu hỏi của production:

  • Logic hết hạn đặt ở đâu — tiến trình quét, cron, hay TTL của datastore?
  • Làm sao serve site có tiền tố đường dẫn (/s/:id/...) mà không làm hỏng asset dùng đường dẫn tương đối?
  • Mức chặn lạm dụng tối thiểu là bao nhiêu để không bị bẽ mặt khi public?
  • Làm sao viết tài liệu cho con người và agent mà không phải duy trì hai bản "sự thật"?

Các câu hỏi đó là xương sống của series. Local FS + meta trong bộ nhớ trả lời được chúng cho một cái laptop. Adapter cloud phải trả lời lại, với SLO và hoá đơn thật.


Tiếp theo — Phần 2

Cùng core. Adapter Cloudflare: Workers + R2 + KV, Wrangler nằm ngay trong repo, và một live lab tại:

https://lab.vovanduc.tech/stage-drop/

GitHub Pages tại vovanduc.github.io/stage-drop/ chỉ là redirect về repo — không phải upload API. Worker mới là nơi lo upload/claim/serve.

Phần 3 dựng lại stack trên AWS (S3 + CloudFront + Lambda + DynamoDB TTL) và so sánh chi phí / độ trễ / DX. Phần 4 biến bản clone thành agent-first: llms.txt, HTTP API, MCP.


Thử stage-drop

  1. Source / docs: https://vovanduc.github.io/stage-drop/ → redirect về GitHub repo
  2. Live demo: https://lab.vovanduc.tech/stage-drop/

(Kiểm tra vận hành, tuỳ chọn: https://lab.vovanduc.tech/stage-drop/health)


Links

Cloudflare Drop https://www.cloudflare.com/drop/
Drop llms.txt https://www.cloudflare.com/drop/llms.txt
Repo stage-drop https://github.com/vovanduc/stage-drop
Pages → repo https://vovanduc.github.io/stage-drop/
Live lab https://lab.vovanduc.tech/stage-drop/
Agent setup (dùng chung) https://github.com/vovanduc/stage-drop/blob/master/docs/agent-setup.md
Prompt agent chính thức của CF https://developers.cloudflare.com/agent-setup/prompt.md

All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.