---
title: "EmDash のブログを Cloudflare の無料プランで公開するまでにやった設定まとめ"
url: https://okinako.dev/posts/emdash-cloudflare-setup
published: 2026-10-25T16:00:00.000Z
updated: 2026-10-25T16:00:29.043Z
description: "EmDash（Astro ベースの CMS）のブログを Cloudflare Workers に置き、独自ドメイン・費用の見張り・AI に見つけてもらうための設定まで、無料プランでやったことを画面つきでまとめました。"
---

# EmDash のブログを Cloudflare の無料プランで公開するまでにやった設定まとめ

このブログ（おきなこ備忘録）は、Cloudflare が作った CMS「EmDash」で動いています。Cloudflare の無料プランのまま、独自ドメインで公開して、費用の見張りと「AI に見つけてもらう」ための設定までやりました。あとで自分が見返せるように、やった設定を画面つきでまとめておきます。料金やプランの条件、画面の見た目は 2026年10月時点のものです。

## 全体の構成

- **CMS:** EmDash 1.3（Astro ベース）の `cloudflare:blog` テンプレート
- **置き場所:** Cloudflare Workers（記事は D1、画像は R2、セッションは KV）
- **ドメイン:** Cloudflare Registrar で取った `okinako.dev`
- **デプロイ:** 手元から `npm run deploy`（GitHub との自動デプロイ連携はしていない）

## 1. 独自ドメインだけで配信する

![Worker のドメインタブ。workers.dev の URL は本番もプレビューもオフで、okinako.dev がカスタムドメインとして割り当てられている](https://okinako.dev/_emdash/api/media/file/01M5SNBVP3B00P5BGMGVJKQC2S.png)

`wrangler.jsonc` で、workers.dev の URL を無効にして、独自ドメインだけで配信しています。

```jsonc
{
  "name": "okinako-blog",
  "workers_dev": false,
  "preview_urls": false,
  "routes": [{ "pattern": "example.com", "custom_domain": true }],
  "vars": { "EMDASH_SITE_URL": "https://example.com" }
}
```

- workers.dev の URL には、アカウントのサブドメインが入ります。ブログの名義とアカウントを結び付けたくなかったので、使わないことにしました。
- 同じサイトが2つの URL で見えると、検索エンジンに別のサイトとして扱われることがあります。ページには正式な URL（`canonical`）が入っていますが、そもそも1つにしておくほうがすっきりします。
- 管理画面のログイン（パスキー）は独自ドメインに結び付くので、workers.dev からは管理画面に入れません。
- ドメインを取るときの登録者情報は、Cloudflare Registrar の既定で WHOIS には出ません。組織名の欄は空にしておきます。

## 2. 変数・シークレット・データの置き場所

![Worker の設定タブ。EMDASH_ENCRYPTION_KEY はシークレット、EMDASH_SITE_URL は変数として登録され（値は非表示）、D1・R2・KV がバインディングとして並んでいる](https://okinako.dev/_emdash/api/media/file/01M5SPA24Z6R9D5VYCV6FTP5PQ.png)

- `EMDASH_SITE_URL` を**固定する**: 指定しないと、管理画面の初期設定をした URL がサイトの URL として記録され、管理者のパスキーもその URL に結び付きます。
- `EMDASH_ENCRYPTION_KEY`: 本番用にローカルとは別のキーを `npx emdash secrets generate` で作り、`wrangler secret put` で登録しました。なくすとプラグインに保存した秘密情報が読めなくなるので、控えはパスワードマネージャーに置いています。
- **D1・R2・KV:** 初回の `wrangler deploy` で自動で作られます。ただし R2 だけは、アカウントで一度ダッシュボードから有効にしておく必要がありました。

## 3. 費用を見張る

![請求の予算アラート。usage-spend-1usd という名前で、従量課金が 1 ドルを超えたらメールで知らせる設定](https://okinako.dev/_emdash/api/media/file/01M5SNBYDBCEW454KZ5MPD5422.png)

- Workers・D1・KV の無料プランは、上限に達するとリクエストが止まるだけで、請求は発生しません。
- **R2 だけは、無料枠を超えた分が従量で請求されます。**
- 製品ごとの利用量の通知は Pro プラン以上でしか使えないので、代わりに**予算アラート**を使っています。アカウント全体の従量課金が 1 ドルを超えたらメールが来ます。R2 を有効にしたときに自動で作られた 10 ドルのアラートを、1 ドルに下げました。

## 4. アクセス解析

![Web Analytics の一覧で、okinako.dev が自動セットアップで追加されている](https://okinako.dev/_emdash/api/media/file/01M5SNC0D9HWAV9ZV5GVEZREDY.png)

Cloudflare Web Analytics を使っています。Cookie を使わない仕組みです。Cloudflare で配信しているサイトなら、自動セットアップで計測用のスクリプトが入ります。

## 5. AI に見つけてもらうための設定

### AI クローラーをブロックしない

![AI Crawl Control のセキュリティ画面。クローラーをブロックのスイッチはすべてオフで、ClaudeBot のリクエストが許可済みになっている](https://okinako.dev/_emdash/api/media/file/01M5SNC32X3GM47BQ41Z0VB3V9.png)

AI Crawl Control のセキュリティ画面で、どのクローラーもブロックしていません。ClaudeBot がすでに来て、正常に読めています。

### robots.txt で利用を許可する（Content Signals）

![robots.txt の中身。Content-Signal で search、ai-input、ai-train をすべて yes にしていて、コメントで Ko-fi への一言を添えている](https://okinako.dev/_emdash/api/media/file/01M5SNC4F03FEVYY8BF8X2R62Z.png)

- `Content-Signal` で、検索（`search`）・AI の回答での引用（`ai-input`）・AI の学習（`ai-train`）を、すべて許可しています。
- コメントで、役に立ったら Ko-fi で応援してほしい、と一言添えています。
- robots.txt は、EmDash の管理画面の SEO 設定で全文を書けます。既定の内容が丸ごと置き換わるので、管理画面を除外する行（`Disallow: /_emdash/`）とサイトマップの行も自分で書きます。

### 更新を検索エンジンに知らせる（Crawler Hints）

![Caching の構成画面で、Crawler Hints のスイッチがオンになっている](https://okinako.dev/_emdash/api/media/file/01M5SNC5YKSC1MV18VC8YN71XX.png)

Crawler Hints をオンにすると、記事を公開・更新したときに、IndexNow で Bing などに通知されます。ChatGPT の検索や Copilot は Bing の索引も使っているとされるので、AI に早く拾われることを期待しています。オンにすると補足条項に同意したことになります。

### AI エージェントには Markdown を返す

![AI Crawl Control の概要画面。エージェント用 Markdown は Pro プラン限定で、Bot Preference Sync はオンになっている](https://okinako.dev/_emdash/api/media/file/01M5SNC8CSWSSHJQ7A7GSZ9E4D.png)

Cloudflare の「エージェント用 Markdown」は Pro プラン限定なので、同じことをサイト側で自前で作りました。

![Accept: text/markdown を付けて記事を取得すると、タイトル・URL・公開日などのフロントマター付きの Markdown が返ってくる](https://okinako.dev/_emdash/api/media/file/01M5SPHXGGHDSSV6B9C1599BSS.png)

- リクエストの `Accept` に `text/markdown` があるか、URL の末尾が `.md` なら、HTML の代わりに Markdown を返します。
- 本文は EmDash の `portableTextToMarkdown` で変換し、先頭にタイトル・URL・公開日・更新日・要約を付けます。
- HTML 側には `<link rel="alternate" type="text/markdown">` で Markdown 版の場所を書き、`Vary: Accept` も付けます（キャッシュで HTML と取り違えないように）。

記事ページ（Astro）の先頭で、次のように分岐しています（要点だけ）。

```ts
import { portableTextToMarkdown } from "emdash/client";

const wantsMarkdown =
  Astro.url.pathname.endsWith(".md") ||
  (Astro.request.headers.get("accept") ?? "").includes("text/markdown");

if (wantsMarkdown) {
  const body = portableTextToMarkdown(post.data.content);
  return new Response(`# ${post.data.title}\n\n${body}`, {
    headers: { "Content-Type": "text/markdown; charset=utf-8", Vary: "Accept" },
  });
}
// HTML を返すときも、同じ URL で中身が変わることをキャッシュに伝える
Astro.response.headers.set("Vary", "Accept");
```

### 診断で確かめる

![エージェントの準備状況の診断で、レベル1が5/5完了。Content Signals と Markdown Negotiation も完了になっている](https://okinako.dev/_emdash/api/media/file/01M5SNCAMQPBQG8MBF0XNZB7MY.png)

エージェントの準備状況の診断では、レベル 1「すぐに実行できる改善」が 5/5 完了になりました。レベル 2 以降（API の一覧、AI 向けのログイン手順、コマースなど）は、サービスを提供するサイト向けなので、ブログでは対応していません。

## 6. EmDash 側でやったこと

- **初期設定:** サンプル記事は入れずに「Empty site」で始める（英語のダミー記事が公開されないように）。管理者はパスキーで登録する。
- **コメント:** 既定は `first_time` で、初めてコメントする人の投稿だけが承認待ちになります（承認済みの人は次から即公開）。全件を承認制にしたいなら `all` にします。
- **日本語向けの調整:**
  - `<html lang="ja">` と、文節での折り返し（`word-break: auto-phrase` など）をサイト全体に当てる（[折り返しの記事](https://okinako.dev/posts/japanese-line-breaking-css)）
  - 日付を日本語の書式・日本時間で表示する（Workers は UTC で動くため）
  - 読了時間を「分」表記にする
- **コードブロックの色付け:** Astro に入っている Shiki で色を付け、`light-dark()` でライト／ダークに追従させる。
- **Ko-fi のボタン:** 公式のボタン画像をサイトに置いて配信する（外部のスクリプトを読み込まない）。
