loading image

土豆哥 一人公司手册 · 005:30分钟,把你的想法部署到全球

用 Cloudflare Workers 和 Pages 在短时间内上线一个产品等待列表。

Posted by Enovace on May 27, 2026

很多人的产品,死在了"还没找到服务器"里。

想验证一个想法,要先学 AWS、配 Nginx、搞 Docker。还没开始写代码,环境已经让人放弃了。

我现在验证一个想法,从写代码到全球上线,不超过 30 分钟,成本 $0。

用的是 Cloudflare Workers 和 Pages。本期手把手做一个等待列表——产品没上线,先把用户邮箱收起来。


Workers 和 Pages 是什么

Workers 是 Cloudflare 的无服务器计算平台。

你写一段 JavaScript/TypeScript,wrangler deploy 一行命令,代码就同时跑在全球 335 个城市的服务器上。没有服务器要管,没有 Nginx 要配,冷启动几乎为零。

免费额度:每天 10 万次请求。月活 1 万以下的产品,完全免费。

Pages 是 Cloudflare 的前端部署平台,对标 Vercel。

连接 GitHub 仓库,每次 push 自动构建部署。支持 React、Vue、Next.js、Astro 等所有主流框架。每个 PR 自动生成预览链接。

两者的关系:

  • Pages 管前端(HTML、CSS、JS)
  • Pages Functions = Workers,管后端 API
  • 两者共用一个项目,一次部署,前后端同时上线

动手:30 分钟部署一个产品等待列表

这是每个一人公司都会用到的东西:产品还没做完,先把等待列表上线,收集感兴趣的用户邮箱。

我们要做的:

  • 一个页面,用户输入邮箱点提交
  • 后端 API,把邮箱存进数据库
  • 完全运行在 Cloudflare,$0

Waitlist deployment setup screenshot

第一步:安装工具,创建项目(5 分钟)

安装 Wrangler(Cloudflare 命令行工具)并登录:

npm install -g wrangler
wrangler login

浏览器弹出授权页面,点允许。

创建项目:

npm create cloudflare@latest waitlist -- --type=hello-world --ts --no-git --no-deploy
cd waitlist

项目结构:

waitlist/
  src/
    index.ts       ← 这里写后端 API
  public/          ← 这里放前端页面
  wrangler.jsonc

第二步:创建数据库(3 分钟)

D1 是 Cloudflare 的 SQLite 数据库,免费 5GB。

wrangler d1 create waitlist-db

命令返回一个 database_id,把它填进 wrangler.jsonc:

"d1_databases": [
  {
    "binding": "waitlist_db",
    "database_name": "waitlist-db",
    "database_id": "这里填你的 id"
  }
],
"assets": { "directory": "./public/", "run_worker_first": ["/api/*"] }

建表:

wrangler d1 execute waitlist-db --command \
  "CREATE TABLE emails (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP);"

第三步:写后端 API(10 分钟)

在 src/index.ts 里写 Worker 入口:

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const headers = {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
    };

    if (request.method === "OPTIONS") {
      return new Response(null, { headers });
    }

    if (url.pathname === "/api/subscribe" && request.method === "POST") {
      try {
        const { email } = await request.json<{ email: string }>();

        if (!email || !email.includes("@")) {
          return Response.json({ error: "邮箱格式不对" }, { status: 400, headers });
        }

        await env.waitlist_db.prepare(
          "INSERT INTO emails (email) VALUES (?)"
        ).bind(email).run();

        return Response.json({ message: "成功加入等待列表!" }, { headers });

      } catch (e: any) {
        if (e.message?.includes("UNIQUE constraint")) {
          return Response.json({ error: "这个邮箱已经登记过了" }, { status: 409, headers });
        }
        return Response.json({ error: "出错了,请重试" }, { status: 500, headers });
      }
    }

    return new Response("Not Found", { status: 404 });
  },
} satisfies ExportedHandler<Env>;

run_worker_first 配置让 /api/* 路由走 Worker,其余路由自动走 public/ 静态文件。不需要配路由框架,不需要写 Express。

第四步:写前端页面(5 分钟)

在 public/ 目录下新建 index.html:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>即将上线</title>
  <style>
    body {
      font-family: -apple-system, sans-serif;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      margin: 0;
      background: #0f0f0f;
      color: #fff;
    }
    .container { text-align: center; max-width: 480px; padding: 2rem; }
    h1 { font-size: 2rem; margin-bottom: 0.5rem; }
    p { color: #888; margin-bottom: 2rem; }
    .form { display: flex; gap: 0.5rem; }
    input {
      flex: 1;
      padding: 0.75rem 1rem;
      border: 1px solid #333;
      border-radius: 8px;
      background: #1a1a1a;
      color: #fff;
      font-size: 1rem;
    }
    button {
      padding: 0.75rem 1.5rem;
      background: #fff;
      color: #000;
      border: none;
      border-radius: 8px;
      font-size: 1rem;
      cursor: pointer;
      font-weight: 600;
    }
    #msg { margin-top: 1rem; min-height: 1.5rem; color: #888; }
  </style>
</head>
<body>
  <div class="container">
    <h1>即将上线</h1>
    <p>留下邮箱,第一时间通知你。</p>
    <div class="form">
      <input type="email" id="email" placeholder="[email protected]">
      <button onclick="subscribe()">通知我</button>
    </div>
    <div id="msg"></div>
  </div>

  <script>
    async function subscribe() {
      const email = document.getElementById("email").value;
      const msg = document.getElementById("msg");

      if (!email) { msg.textContent = "请输入邮箱"; return; }

      msg.textContent = "提交中…";

      const res = await fetch("/api/subscribe", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email }),
      });

      const data = await res.json();
      msg.textContent = data.message || data.error;
      if (res.ok) document.getElementById("email").value = "";
    }
  </script>
</body>
</html>

第五步:本地测试(2 分钟)

wrangler dev

打开 http://localhost:8787,填一个邮箱,点"通知我"。

Cloudflare project setup screenshot

Cloudflare Workers configuration screenshot

验查数据库有没有写进去:

wrangler d1 execute waitlist-db --command "SELECT * FROM emails;"

第六步:部署上线(3 分钟)

wrangler deploy

两分钟后,你拿到一个地址:

https://waitlist.<your-subdomain>.workers.dev

全球可访问,自动 HTTPS,$0。

Cloudflare Pages deployment screenshot

以后每次 git push,自动重新部署。


查看收集到的邮箱

随时可以查:

wrangler d1 execute waitlist-db --command "SELECT * FROM emails ORDER BY created_at DESC;"

或者在 Cloudflare 控制台 → D1 → 你的数据库 → 直接跑 SQL。

Waitlist API or database setup screenshot


这套方案能撑多少用户

Completed Cloudflare waitlist deployment screenshot

免费额度:每天 10 万次 Workers 请求 + 每天 10 万行 D1 写入。

产品没上线之前,用等待列表收邮箱,完全免费。


这期之后,你的一人公司有了

下一期,我们在这套基础设施上,做一个真正能收钱的产品。


**mousepotato(土豆哥) | 美国计算机全奖博士 | 硅谷 11 年技术管理 | AI · OPC · 产品 | X **@iluciddreaming

关注我,获取 AI 前沿、技术、管理、产品、英语和硅谷生活见闻。