CloudflareでStatic+Worker

Page content

Cloudflare で
Static(画面)+Worker(処理)構成を作る手順を、実務レベルでそのまま使える形で説明します。


■ 全体構成(これを作る)

ユーザー
   ↓
Cloudflare Pages(Static)
   ↓ fetch()
Cloudflare Workers(API)
   ↓
DB / KV(必要なら)

■ 手順①:Staticサイトを用意

例:Astro

npm create astro@latest
cd my-site
npm run build

👉 dist/ フォルダができる(これがStatic)


■ 手順②:Cloudflare Pagesにデプロイ

方法(簡単)

  1. GitHubにpush

  2. Cloudflareダッシュボード

  3. 「Pages」→「プロジェクト作成」

  4. リポジトリ選択

設定

  • Build command:npm run build

  • Output directory:dist

👉 これで
Staticサイト公開完了


■ 手順③:Worker(API)を作る

CLI準備

npm install -g wrangler
wrangler login

Worker作成

wrangler init my-worker
cd my-worker

コード編集

src/index.js

export default {
  async fetch(request) {
    return new Response(
      JSON.stringify({ message: "Hello from Worker!" }),
      { headers: { "Content-Type": "application/json" } }
    );
  }
};

デプロイ

wrangler deploy

👉 URLが発行される
例:

https://my-worker.xxx.workers.dev

■ 手順④:StaticからWorkerを呼ぶ

Static側(例:AstroやHTML)

<button onclick="callApi()">API実行</button>

<script>
async function callApi() {
  const res = await fetch("https://my-worker.xxx.workers.dev");
  const data = await res.json();
  alert(data.message);
}
</script>

👉 これで
Static → Worker通信完成


■ 手順⑤(重要):同一ドメイン化(本番向け)

そのままだと

  • Pages → example.pages.dev

  • Worker → workers.dev

👉 ドメインが分かれる


解決方法:Workerをルーティング

Cloudflare設定:

  • Workers → 「Routes」

  • 例:

example.com/api/*

👉 Workerに紐づけ


呼び出しURL

fetch("/api/hello")

👉 同一ドメインで安全&高速


■ 手順⑥(応用):フォーム送信

// Worker側
export default {
  async fetch(request) {
    if (request.method === "POST") {
      const data = await request.json();
      return new Response(
        JSON.stringify({ received: data }),
        { headers: { "Content-Type": "application/json" } }
      );
    }
  }
};
// フロント
fetch("/api/form", {
  method: "POST",
  body: JSON.stringify({ name: "test" })
});

■ よくある構成パターン

① シンプル

  • Pages(静的)

  • Worker(API)

② 本格

  • Pages

  • Worker

  • KV(キャッシュ)

  • D1(DB)


■ メリット

  • 超高速(CDN + エッジ)

  • サーバー不要

  • スケール自動


■ よくあるミス

❌ CORSエラー
→ 同一ドメインにする

❌ Worker URL直叩き
/api/ ルーティングにする

❌ build設定ミス
dist確認


■ まとめ

  • Static=画面

  • Worker=処理

  • Cloudflareで両方簡単に統合できる


「Worker」と「Staticサイト」は、仕組みとできることが根本的に違うので、用途によって使い分けます。


■ 結論(ざっくり)

  • Staticサイト=「ただの完成済みファイル(HTMLなど)を配るだけ」

  • Worker=「サーバーの代わりにプログラムを動かせる」


■ Staticサイトとは

✔ 特徴

  • HTML / CSS / JSファイルをそのまま配信

  • サーバー処理なし

  • 超高速・安定・安い

✔ できること

  • ホームページ表示

  • ブログ(事前生成)

  • LP(ランディングページ)

✔ できないこと

  • ログイン処理

  • DB操作

  • API処理

👉 例

  • Astroで生成したサイト

  • Webstudioの書き出しHTML

  • GitHub Pages / Cloudflare Pages


■ Workerとは

(例:Cloudflare Workers)

✔ 特徴

  • JavaScriptでサーバー処理を書ける

  • エッジ(世界中)で実行される

  • APIや動的処理が可能

✔ できること

  • API作成

  • 認証処理

  • HTMLを書き換え

  • DB(KV / D1など)操作

✔ 例

export default {
  async fetch(request) {
    return new Response("Hello from Worker!");
  }
}

■ 一番大事な違い(本質)

項目 Staticサイト Worker
中身 ファイル プログラム
処理 なし あり
動的処理
表示速度 超速い 速い(+処理)
用途 表示専用 ロジック

■ よくある構成(実務)

実際は「組み合わせ」が最強👇

  • Staticサイト(UI)

  • Worker(API・処理)

👉 例

  • フロント:Static(Astro)

  • バック:Worker(API)


■ イメージ

  • Static = 「印刷された本」

  • Worker = 「受付スタッフ」


■ どっちを使うべき?

  • サイト表示だけ → Static

  • 何か処理したい(ログイン・送信) → Worker

  • 迷ったら → Static + Worker


Cloudflare で
Staticサイト → Worker → メール送信の実装を、コピペで動くレベルで説明します。


■ 全体構成

HTMLフォーム(Static)
   ↓ POST
Worker(API)
   ↓
メール送信サービス(Resendなど)

■ ① メール送信サービス準備

ここではシンプルな
Resend を使います

手順

  1. Resendに登録

  2. APIキー取得

例:

re_xxxxxxxxx

■ ② Workerに環境変数を設定

wrangler secret put RESEND_API_KEY

👉 APIキーを安全に保存


■ ③ Workerコード(メール送信)

src/index.js

export default {
  async fetch(request, env) {
    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    const data = await request.json();

    const res = await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${env.RESEND_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        from: "onboarding@resend.dev",
        to: "あなたのメール@example.com",
        subject: "お問い合わせが届きました",
        html: `
          <h1>お問い合わせ内容</h1>
          <p>名前: ${data.name}</p>
          <p>メール: ${data.email}</p>
          <p>内容: ${data.message}</p>
        `
      })
    });

    return new Response(JSON.stringify({ ok: true }), {
      headers: { "Content-Type": "application/json" }
    });
  }
};

■ ④ デプロイ

wrangler deploy

■ ⑤ Static側フォーム

<form id="contact-form">
  <input type="text" name="name" placeholder="名前" required>
  <input type="email" name="email" placeholder="メール" required>
  <textarea name="message" placeholder="内容"></textarea>
  <button type="submit">送信</button>
</form>

<script>
document.getElementById("contact-form").addEventListener("submit", async (e) => {
  e.preventDefault();

  const formData = new FormData(e.target);

  const data = {
    name: formData.get("name"),
    email: formData.get("email"),
    message: formData.get("message")
  };

  const res = await fetch("/api/contact", {
    method: "POST",
    body: JSON.stringify(data)
  });

  alert("送信完了");
});
</script>

■ ⑥ ルーティング(重要)

Cloudflareで設定👇

example.com/api/*
→ Workerに接続

👉 これで

/api/contact

がWorkerになる


■ 完成イメージ

  • / → Staticページ

  • /api/contact → Worker(メール送信)


■ よくあるエラー

❌ メールが届かない

  • Resendの送信元ドメイン未認証

👉 対策

  • 自分のドメインをResendに追加

❌ CORSエラー

👉 同一ドメインにする(/api)


❌ スパム対策なし

👉 最低限これ入れる👇

if (!data.email.includes("@")) {
  return new Response("Invalid", { status: 400 });
}

■ 応用(おすすめ)

✔ 自動返信(ユーザーにも送る)

to: data.email

✔ スパム対策

  • Cloudflare Turnstile(reCAPTCHA代替)

✔ 保存もしたい

  • D1(DB)

  • KV(軽量保存)


■ まとめ

  • Staticだけではメール送信できない

  • Workerを挟めば可能

  • Resendで超簡単に実装できる


Webstudio に
フォーム+Workerメール送信(Resend)を組み込む方法を、実際に動く形でまとめます。


■ 全体像

Webstudio(フォームUI)
   ↓ fetch
Cloudflare Worker(API)
   ↓
Resend(メール送信)

■ ① 前提(先にやること)

すでに作っておく👇

  • Cloudflare Worker(/api/contact)

  • Resend APIキー設定済み

👉 ここは前の手順どおり


■ ② Webstudioでフォーム作成

手順

  1. フォーム要素追加

  2. 中に入力フィールド配置

構成例

  • Input(name=“name”)

  • Input(name=“email”)

  • Textarea(name=“message”)

  • Button(送信)

👉 name属性が重要


■ ③ デフォルト送信を無効化

Webstudioのフォームは通常「外部送信」するので止める

方法

  • Formの「Action」 → 空にする

  • Method → POST(でも実際はJSで制御)


■ ④ カスタムJSを追加(これが核心)

Webstudioの
Custom Code(Before ) に追加👇

<script>
document.addEventListener("DOMContentLoaded", () => {
  const form = document.querySelector("form");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();

    const formData = new FormData(form);

    const data = {
      name: formData.get("name"),
      email: formData.get("email"),
      message: formData.get("message")
    };

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

      if (res.ok) {
        alert("送信しました");
        form.reset();
      } else {
        alert("送信エラー");
      }
    } catch (err) {
      alert("通信エラー");
    }
  });
});
</script>

■ ⑤ Cloudflare側ルーティング

example.com/api/*
→ Worker

👉 これ必須


■ ⑥ デプロイ

Webstudio → Publish
(Cloudflare Pages or export)


■ 動作確認

  1. フォーム入力

  2. 送信

  3. メール届く


■ よくある詰まりポイント

❌ 送信されない

  • form selector間違い
    👉 querySelector("form")を確認

❌ データが空

👉 name属性ミス

<input name="email">

❌ CORSエラー

👉 必ず /api/contact にする
(Worker直URLは使わない)


■ UX改善(おすすめ)

✔ 送信中表示

button.textContent = "送信中...";

✔ 成功メッセージ表示

alert  非表示divに変更

■ 応用(強くおすすめ)

① スパム対策

👉 Cloudflare Turnstile


② バリデーション強化

if (!data.email.includes("@")) return;

③ 自動返信メール

Workerで追加


■ まとめ

  • Webstudioは「見た目担当」

  • Workerは「処理担当」

  • JSでつなぐだけで完成


Cloudflare の
Turnstile(スパム対策)+フォーム送信+メール送信(Resend)完全版
👉 そのままコピペで動く形でまとめます。


■ 全体構成

Webstudio(フォーム)
   ↓ + Turnstileトークン
Worker(検証 + メール送信)
   ↓
Resend(メール)

■ ① Turnstileキー取得

Cloudflareダッシュボード
→ Turnstile → サイト追加

取得するもの👇

SITE_KEY(公開用)
SECRET_KEY(Worker用)

■ ② Workerにシークレット登録

wrangler secret put TURNSTILE_SECRET
wrangler secret put RESEND_API_KEY

■ ③ Workerコード(完全版)

src/index.js

export default {
  async fetch(request, env) {
    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    const data = await request.json();

    // ------------------------
    // ① Turnstile検証
    // ------------------------
    const token = data.token;

    const verifyRes = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: `secret=${env.TURNSTILE_SECRET}&response=${token}`
    });

    const verifyData = await verifyRes.json();

    if (!verifyData.success) {
      return new Response(JSON.stringify({ error: "bot detected" }), {
        status: 400
      });
    }

    // ------------------------
    // ② バリデーション
    // ------------------------
    if (!data.email || !data.email.includes("@")) {
      return new Response("Invalid email", { status: 400 });
    }

    // ------------------------
    // ③ メール送信(Resend)
    // ------------------------
    await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${env.RESEND_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        from: "onboarding@resend.dev",
        to: "あなたのメール@example.com",
        subject: "お問い合わせ",
        html: `
          <h2>新しい問い合わせ</h2>
          <p>名前: ${data.name}</p>
          <p>メール: ${data.email}</p>
          <p>内容: ${data.message}</p>
        `
      })
    });

    return new Response(JSON.stringify({ ok: true }), {
      headers: { "Content-Type": "application/json" }
    });
  }
};

■ ④ Webstudio側(HTML)

フォームにTurnstile追加👇

<form id="contact-form">
  <input name="name" placeholder="名前" required>
  <input name="email" type="email" placeholder="メール" required>
  <textarea name="message" placeholder="内容"></textarea>

  <!-- Turnstile -->
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>

  <button type="submit">送信</button>
</form>

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

■ ⑤ JS(超重要)

<script>
document.addEventListener("DOMContentLoaded", () => {
  const form = document.getElementById("contact-form");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();

    const formData = new FormData(form);

    const token = document.querySelector("[name='cf-turnstile-response']").value;

    const data = {
      name: formData.get("name"),
      email: formData.get("email"),
      message: formData.get("message"),
      token: token
    };

    const btn = form.querySelector("button");
    btn.disabled = true;
    btn.textContent = "送信中...";

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

      if (res.ok) {
        alert("送信成功");
        form.reset();
      } else {
        alert("送信失敗");
      }
    } catch (err) {
      alert("通信エラー");
    }

    btn.disabled = false;
    btn.textContent = "送信";
  });
});
</script>

■ ⑥ ルーティング設定(必須)

example.com/api/*
→ Worker

■ 完成

これで👇

  • ボット → ブロック(Turnstile)

  • 正常ユーザー → メール送信


■ よくあるミス

❌ tokenが取れない

👉 cf-turnstile-response のname確認


❌ always失敗

👉 SECRETキー間違い


❌ メール届かない

👉 Resend ドメイン未認証


■ 強化(おすすめ)

✔ 自動返信

to: data.email

✔ IP制限

Workerで可能


✔ 保存(D1)

問い合わせ履歴DB化


■ まとめ

  • Turnstileでスパムほぼ防止

  • Workerで安全に処理

  • WebstudioはUIだけでOK


必要なら👇
👉「Webstudioの画面操作レベルでの配置手順」
👉「D1保存付き完全版」
👉「reCAPTCHAとの違い」

も出せます。