Hugo のような静的サイトは、HTML を配信するだけならとても軽いのですが、POST /api/... のような 小さい受付口 を足したくなることがあります。
例えば次のような用途です。
- 記事末尾の軽いリアクション
- お問い合わせの簡易受付
- Slack への通知トリガー
- ちょっとした webhook の受け口
このとき、サイト全体を動的構成へ寄せるほどではないが、POST /api/... だけは欲しい、という場面があります。
そういう用途にちょうどよいのが Cloudflare Worker です。
今回は、Hugo サイトに軽いリアクション導線を追加した実例をもとに、
Cloudflare Worker + D1 + Slack でサーバーレスな受付口を作る方法 を、実際のファイルと設定をそのまま出しながらまとめます。
Cloudflare Worker と D1 は何をするものか
まず役割を分けておきます。
Cloudflare Worker
Cloudflare Worker は、Cloudflare のエッジ上で動くサーバーレス実行環境です。
ざっくり言うと、Cloudflare 側に小さな API を置ける仕組み です。
今回の用途では、POST /api/like を Worker で受けます。
Cloudflare D1
Cloudflare D1 は、Cloudflare が提供している SQL データベースです。
SQLite に近い感覚で使え、Worker から直接読み書きできます。
今回の用途では、次のようなイベント記録を保存します。
- どの記事に対するリアクションか
- 同じ visitor がすでに送っていないか
- いつ送られたか
Slack Incoming Webhook
通知先です。
新しいイベントがあったときだけ Slack に飛ばします。
つまり今回の分担はこうです。
- Worker: リクエスト受付、入力検証、通知判定
- D1: イベント保存、重複抑止
- Slack: 新規イベントの通知先
もともとの配信構成
このサイトは Cloudflare Pages ではなく、かなり素朴な構成です。
- Hugo で
public/を生成する rsyncで Ubuntu サーバーへ反映する- 本番は nginx で配信する
- Cloudflare は前段の CDN / Proxy として使う
つまり、ページ本体は静的です。
そのうえで、/api/like だけを Cloudflare Worker へルーティングします。
今回の完成形
今回の構成はこうです。
- フロント: Hugo 側でボタンを描画
- ブラウザ:
localStorageと Cookie に押下済み状態を保存 - API: Cloudflare Worker
- 保存先: Cloudflare D1
- 通知先: Slack Incoming Webhook
データの流れは次の通りです。
- 記事ページでハートを押す
- ブラウザが
POST /api/likeを送る - Worker が payload を検証する
visitorIdをハッシュ化して D1 に保存する- 新規イベントなら Slack に通知する

実際に使ったファイル
このリポジトリでは、次のファイルを使っています。
layouts/partials/article_like.htmllayouts/partials/extend_footer.htmlassets/css/extended/article-like.csscloudflare/like-handler.jscloudflare/likes-schema.sqlworkers/like-api/src/index.jsworkers/like-api/wrangler.jsonc
ここから実際の導入手順
ここからは、そのまま真似できる形で順番に書きます。
1. D1 を作る
まず workers/like-api/ へ移動して wrangler を使える状態にします。
cd workers/like-api
npm install
npx wrangler d1 create araisun-like-db
実行すると、database_id が返ってきます。
この値を workers/like-api/wrangler.jsonc に入れます。
今回の設定はこうです。
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "araisun-like-api",
"main": "src/index.js",
"compatibility_date": "2026-04-21",
"workers_dev": true,
"d1_databases": [
{
"binding": "LIKES_DB",
"database_name": "araisun-like-db",
"database_id": "YOUR_DATABASE_ID"
}
],
"observability": {
"enabled": true
}
}
ポイントは binding 名です。
Worker 側コードでは env.LIKES_DB を参照しているので、ここは必ず LIKES_DB に合わせます。

2. D1 にテーブルを作る
次に schema を流します。
cd workers/like-api
npx wrangler d1 execute araisun-like-db --remote --file=../../cloudflare/likes-schema.sql
使っている schema はこれです。
CREATE TABLE IF NOT EXISTS article_likes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
article_slug TEXT NOT NULL,
article_title TEXT NOT NULL,
article_url TEXT NOT NULL,
visitor_hash TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(article_slug, visitor_hash)
);
UNIQUE(article_slug, visitor_hash) を置いているので、
同じ visitor が同じ記事へ何度も送っても重複しにくくなります。
3. Slack Incoming Webhook を secret に登録する
通知先の Webhook URL を Cloudflare の secret に入れます。
cd workers/like-api
npx wrangler secret put SLACK_LIKE_WEBHOOK_URL
入力欄には、コマンドではなく webhook URL 本体だけ を貼ります。
https://hooks.slack.com/services/XXXX/XXXX/XXXX
4. Worker を deploy する
cd workers/like-api
npx wrangler deploy
このとき workers.dev サブドメイン登録を聞かれても、
本番で araisun.com/api/like の Route だけ使うなら n で大丈夫です。
5. Cloudflare ダッシュボードで Route を設定する
ここが記事で省略されがちな部分ですが、実際にはここをやらないと動きません。
Cloudflare ダッシュボードで次の順に進みます。
Workers & Pagesを開くaraisun-like-apiを開くSettingsまたはTriggersを開くRoutesのAdd routeを押す- Route に
araisun.com/api/likeを入れる - 対象 zone として
araisun.comを選ぶ - 保存する
これで、
- HTML 本体は nginx
/api/likeだけ Worker
という構成になります。
注意点として、対象ドメインは Cloudflare Proxy 配下である必要があります。

6. Worker 側コード
今回の Worker 本体はかなり薄いです。
workers/like-api/src/index.js
import { handleLikeRequest, handlePreflight } from "../../../cloudflare/like-handler.js";
export default {
async fetch(request, env) {
if (request.method === "OPTIONS") {
return handlePreflight();
}
if (request.method !== "POST") {
return new Response("Method Not Allowed", {
status: 405,
headers: { Allow: "POST, OPTIONS" },
});
}
return handleLikeRequest(request, env);
},
};
本体ロジックは cloudflare/like-handler.js に寄せています。
7. Worker が実際にやっていること
cloudflare/like-handler.js では、次の処理をしています。
LIKES_DBとSLACK_LIKE_WEBHOOK_URLの存在確認articleSlugarticleTitlearticleUrlvisitorIdの検証visitorIdの SHA-256 ハッシュ化- D1 への
INSERT OR IGNORE - 新規 insert のときだけ Slack 通知
実際の保存部分はこの形です。
const insertResult = await env.LIKES_DB.prepare(
`INSERT OR IGNORE INTO article_likes (article_slug, article_title, article_url, visitor_hash)
VALUES (?1, ?2, ?3, ?4)`
)
.bind(articleSlug, articleTitle, articleUrl, visitorHash)
.run();
8. Hugo 側のフロント実装
Hugo 側では、記事末尾にボタンを置いて POST /api/like を呼びます。
やっていることは次の程度です。
- 記事末尾にハートボタンを差し込む
- 押下済みならピンク表示にする
localStorageと Cookie に visitor ID を保存する- 押したら
/api/likeへ POST する
つまり、フロントは見た目と押下状態の保持、バックエンドは受付と保存です。
9. 動作確認
確認は次の順でやると分かりやすいです。
- 記事ページを開く
- ハートを押す
- ブラウザの
NetworkでPOST /api/likeを確認する 200が返ることを確認する- Slack に通知が届くことを確認する
Response が次なら、新規保存まで成功しています。
{"ok":true,"liked":true,"inserted":true}
10. 導入中にハマりやすいところ
今回、実際に詰まった点もそのまま書いておきます。
compatibility_date を未来日にしない
未来日にすると deploy 時に
Can't set compatibility date in the future
で落ちます。wrangler.jsonc には実行日以前の日付を入れます。
schema を流す前に叩かない
D1 にテーブルが無い状態だと
no such table: article_likes
になります。
Slack secret には URL 本体だけを入れる
コマンド文字列を貼ると
TypeError: Invalid URL
になります。
workers.dev は今回必須ではない
araisun.com/api/like の Route で使うだけなら、workers.dev の登録は不要です。
11. シークレットモードでは別人扱いになる
今回の visitor 判定はブラウザ側の保存に依存しているため、
- シークレットモード
- 別ブラウザ
- 別端末
では別 visitor 扱いになります。
匿名のまま完全な「一人一回」にはなりませんが、
軽いリアクション導線としてはまず十分です。
必要なら今後、
- IP ハッシュを併用する
- User-Agent を組み合わせる
- Turnstile を挟む
といった方向へ強化できます。
12. 無料枠でもかなり試しやすい
今回のような軽いイベント受付なら、無料枠でもかなり試しやすい のも大きいです。
2026年4月時点の Cloudflare 公式情報では、Workers Free は 1日 100,000 リクエスト、D1 Free は 1日 500万 rows read / 10万 rows written / 5GB storage が含まれています。
根拠:
- Cloudflare Workers Limits: https://developers.cloudflare.com/workers/platform/limits/
- Cloudflare D1 Pricing: https://developers.cloudflare.com/d1/platform/pricing/
個人ブログのリアクション受付や簡易 webhook 用途なら、まずは無料の範囲で十分回しやすいと思います。
まとめ
Cloudflare Worker を使うと、静的サイトでも POST /api/... のようなサーバーレス受付口を後付けできます。
今回のように
- 受付は Worker
- 保存は D1
- 通知は Slack
と分けると、既存の Hugo + nginx 構成を崩さずに導入できます。
静的サイトに小さな動的機能を足したいなら、かなり扱いやすい選択肢です。
関連記事
参考
- Cloudflare Workers Pricing: https://developers.cloudflare.com/workers/platform/pricing/
- Cloudflare Workers Limits: https://developers.cloudflare.com/workers/platform/limits/
- Cloudflare D1 Pricing: https://developers.cloudflare.com/d1/platform/pricing/
