メインコンテンツへスキップ
ブログサイトのロゴsui Tech Blog

Cloudflare が WordPress の後継 CMS「EmDash」を作ったので触ってみる

Cloudflare EmDashのマーケティングテンプレートをベースに、ブログ機能の移植、seed反映の確認、デザイン調整、Cloudflare Accessによる管理画面保護まで試した内容をまとめました。

WordPress は 2003 年の登場以来、インターネット上のWebサイトの 43% 以上を支え続けています1。これほどの規模で普及したソフトウェアは他にほとんどなく、「誰でもサイトを作れる」という体験を多くの人に届けた偉大なプロジェクトです。

ただ、23 年という年月は技術の世界では相当な重みがあります。WordPress が生まれたころ、AWS EC2 は存在せず、サーバレスなどという概念もありませんでした。プラグインは PHP スクリプトがそのままデータベースやファイルシステムに触れる設計のまま今に至り、96% のセキュリティ問題がプラグイン起因という状況は変わっていません。

WordPress のこの設計課題に正面から向き合う形で、Cloudflare が 2026年4月1日に「EmDash」を発表しました。WordPress の後継を名乗る TypeScript 製のオープンソース CMS です。発表日がエイプリルフール当日だったので、最初は半信半疑でしたが、GitHub リポジトリを確認すると本物でした。セットアップから、マーケティングテンプレートにブログを乗せるところまで試してみたので、詰まったポイントも含めて解説していきます。

EmDash とは何か

EmDash は Astro2 を中心に据えた TypeScript 製の CMS です。MIT ライセンスで、WordPress との互換性を目指しながらも「WordPress のコードは一切使わずにゼロから作り直した」と明言しています。

WordPress との最大の違いはプラグインの動作モデルです。WordPress のプラグインはサイト全体と同じ実行コンテキストで動くため、プラグインがデータベースやファイルシステムに自由にアクセスできます。EmDash では各プラグインが独立した Dynamic Worker のサンドボックスで動作し、プラグインが宣言したケイパビリティ3以外には一切アクセスできません。

たとえば「記事が公開されたときにメールを送るプラグイン」を書くとこうなります。

import { definePlugin } from "emdash";
 
export default () =>
  definePlugin({
    id: "notify-on-publish",
    version: "1.0.0",
    capabilities: ["read:content", "email:send"],
    hooks: {
      "content:afterSave": async (event, ctx) => {
        if (event.collection !== "posts" || event.content.status !== "published") return;
        await ctx.email!.send({
          to: "editors@example.com",
          subject: `New post published: ${event.content.title}`,
          text: `"${event.content.title}" is now live.`,
        });
      },
    },
  });

このプラグインは read:contentemail:send の 2 つしか宣言していないため、物理的にそれ以外の操作は一切できません。外部ネットワークへのアクセスも、許可されたホスト名だけに絞れます。WordPress のプラグインが「何でもできる」のと対極にある設計だと思います。

WARNING

Dynamic Workers を使ったプラグイン機能は 2026年4月4日時点、Cloudflare の有料プランが必要です。

他にも x4024 による有料コンテンツのしくみや、内蔵の MCP サーバ5、WordPress からのコンテンツ移行機能など、「昨今の生成AI時代に作るならこうする」という思想があちこちに垣間見えます。

セットアップ

今回は pnpm を使ってプロジェクトを作成します。pnpm create emdash@latest を実行すると、対話形式でテンプレートとデプロイ先を選べます。
今回はマーケティングページのテンプレートで Cloudflare Workers へのデプロイを選択しました。

 E M D A S H
 
  Create a new EmDash project

  Project name?
  sui-corporate-example

  Where will you deploy?
  Cloudflare Workers

  Which template?
  Marketing

  Which package manager?
  pnpm

  Install dependencies?
  Yes

  Project created!

  Dependencies installed!

  Next steps ───────────────╮

  cd sui-corporate-example
  pnpm dev

├────────────────────────────╯

  Done! Your EmDash project is ready at sui-corporate-example

pnpm run dev を実行するとコンテンツが何もないまっさらなマーケティングページが表示されます。

初期状態のマーケティングページ

殺風景なので公式が用意しているシードデータを流し込みましょう。

npx emdash seed seed/seed.json
 
 Loading seed file...
 Seed file is valid
 Running migrations...
 Applied 31 migrations
 Applying seed...
 Seed applied successfully!
 
 Settings: 2 applied
 Collections: 1 created, 0 skipped, 0 updated
 Fields: 2 created, 0 skipped, 0 updated
 Menus: 1 created, 3 items
 Content: 3 created, 0 skipped, 0 updated
 Done!

実行したあと「完了した」と出ているのに、ブラウザを見てもコンテンツが反映されていません。
試しに管理画面(/_emdash/admin)へ遷移してサイト名を入力したところ、そこで初めてセットアップの API ログが流れているのが見えて、反映されることが分かりました。

なお管理画面の初期セットアップを完了した後は、シードデータが反映されてこのような状態になります。

シードデータ反映後のマーケティングページ

Seed と DB のしくみでハマる

EmDash は管理画面の初期セットアップを完了するまで seed が実 DB へ反映されません。
http://localhost:4321/_emdash/admin/setup にアクセスして、サイト名と管理者を作成する初回セットアップを終えて初めてコンテンツが見えます。

ログを見ると理由が分かります。POST /_emdash/api/setup が呼ばれた直後に画像のダウンロードとアップロードが走っています。初期セットアップの完了が seed 適用のトリガになっている設計です。

[302] /_emdash/admin
[200] /_emdash/admin/setup
[200] /_emdash/api/setup/status
  📥 Downloading: https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=1200&h=800&fit=crop
  ✅ Uploaded: building-long-term.jpg
  📥 Downloading: https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=1200&h=800&fit=crop
  ✅ Uploaded: case-for-static.jpg
[200] POST /_emdash/api/setup 501ms

「サイト名を設定しないとコンテンツが反映されない」ではなく、「初期セットアップを完了しないと、その環境の DB への seed 適用が終わらない」というしくみになっています。
EmDash における seed は設定ファイルではなく、DB 初期化のための投入データです。seed.json を置くだけでは不十分で、その内容が実際に対象 DB へ適用されていなければなりません。

テンプレートを移植する

EmDash の公式テンプレートはマーケティング・ブログ・ポートフォリオの 3 種類があります。
実際のマーケティングページでは、製品への理解を深めてもらうためにブログも必要になるケースが多いと思います。
今回選んだマーケティングテンプレートにはブログページが含まれていないので、ブログテンプレートからコンポーネントを移植してみましょう。

移植作業はシンプルで、以下のファイルをブログテンプレートからコピーして src/pages/posts/ ディレクトリを作るだけで基本的な構成が動きます。

  • src/components/PostCard.astro
  • src/components/TagList.astro
  • src/pages/posts/index.astro
  • src/pages/posts/[slug].astro
  • src/pages/category/[slug].astro
  • src/pages/tag/[slug].astro
  • src/pages/search.astro
  • src/pages/rss.xml.ts
  • src/utils/reading-time.ts

詳細な移植内容はこちらのコミット履歴を見てください。
CSS 変数(--color-accent など)はマーケティングテンプレートのものが使われるので、レイアウトが崩れることなくテーマが統一された状態でブログが表示されます。

実際にブログを公開してみる

移植後は管理画面(/_emdash/admin)からブログ記事を追加できます。ログインはパスキー6ベースなのでパスワードがいりません。Cloudflare の発表ブログでも、passkey-based authentication by default と説明されています。管理画面も SPA7 として作られていて、コレクション(投稿タイプ)の作成からフィールド定義まで、すべてブラウザ上で完結するようになっています。

管理画面ダッシュボード

コレクション一覧からブログ記事の管理画面を開くと、seed.json で投入済みの記事が並んでいます。

記事一覧画面

エディタはマークダウン記法に対応していて、記入しながらプレビューが確認できます。

ブログエディタ

公開すると、こんな感じでブログページに反映されます。思ったよりスムーズに移植できましたね。

公開後のブログページ

公開後のブログ URL はこちらです。

sui-corporate-example.ayasnppk00.workers.dev のアイコン
sui-corporate-example.ayasnppk00.workers.dev

Page not found

気になるところとしては、Markdown をそのままペーストすると書式が自動で反映されないケースがありました。
α版ですので、こういった細かい課題はまだ残っているところだと思います。

サイトのレイアウトを修正する

テンプレートの移植が完了したら、サイト全体のレイアウトを調整していきましょう。
ヘッダやフッタの構成を変える場合は Astro ファイルの変更が必要になりますが、今回は全体のフォントや色合いなどを CSS の修正だけで調整していきます。
CSSは theme.css が本来のカスタマイズ用ファイルになりますが、現状は Base.astro<style is:global> が後優先で反映されます。
そのため、src/layouts/Base.astro に設定されている CSS 変数を上書きする形で、全体のテーマカラーやフォントを変更するのが良さそうです。

このように柔らかい雰囲気の色にしてみました。実際の変更はこちらのコミット履歴を見てください。

カスタマイズ後のサイト全体の雰囲気

管理画面のアクセス制限

ひとつ気になったのは管理パスが /_emdash/admin で固定されている点です。URL を直打ちすれば、誰でもログインページに到達できてしまいます。

URL直打ちで管理画面のログインページが表示される

そこで今回は、/_emdash/admin に到達できるユーザーを指定したメールアドレスだけに絞るために Cloudflare Access を設定していきましょう。
Cloudflare のダッシュボードからアプリケーションを追加して、セルフホストを選びます。

セルフホストを選択

選択後は、アプリケーション名を emdash-admin、セッション時間を 24 hours に設定します。合わせてパブリック ホスト名のパスには /_emdash/admin* を指定します。

アプリケーションの設定画面

次に Access ポリシーを追加して、許可したいメールアドレスを 包含 ルールの Emails セレクタに設定します。

ポリシーの作成画面

保存後、allow-my-email がポリシー一覧に表示されていれば設定完了です。

ポリシー一覧

設定後に /_emdash/admin へ直アクセスすると、Cloudflare のログイン画面が割り込んできます。

Cloudflare Accessのログイン画面

許可していないメールアドレスでワンタイムコードを送信しても、UI 上は「送信しました」と表示されますが実際にはメールは届きません。これは Cloudflare の One-time PIN ドキュメント にある通り、ブロック状態を外部から判別しにくくするための仕様です。

注意点として、この設定で保護されるのは /_emdash/admin 配下のみです。/_emdash/api/* は保護対象になりません。ただし EmDash 側で認証必須エンドポイントは弾く設計ですので、最低限の防御は備わっています。

その他気になったところ

CMS の都合上、毎回のリクエストで DB へのクエリが走る設計になっています。そのためキャッシュが効いていないとき、画面表示で数秒待たされることがありました。
コードを細かく見ているわけではないので断言できないですが、まだまだパフォーマンスを改善できる余地はありそうです。

まとめ

  • EmDash は WordPress の後継を名乗るだけあって、WordPress でありがちな課題を解決するための工夫が随所に見られる
  • 特にプラグインの動作モデルは、WordPress の「何でもできる」から対極の「宣言したことしかできない」設計になっていて、セキュリティ面で大きな改善が期待できる
  • seed を実行しても管理画面の初期セットアップを完了するまでコンテンツが反映されないため、管理画面のセットアップ完了が seed 適用のトリガになっていることを理解しておく必要がある
  • マーケティングテンプレートへのブログ追加は、ブログテンプレートからコンポーネントを移植するだけでおおむね動く
  • 管理パス /_emdash/admin は固定になっているため、公開環境では Cloudflare Access で /_emdash/admin* を保護するとよい
  • プラグイン機能(Dynamic Workers によるサンドボックス)を使う場合は Cloudflare の有料プランがいる

v0.1.0 のプレビュー段階なので荒削りな部分もありますが、WordPress が解決できなかった問題を本気で直そうとしている意思は感じました。
TypeScript と Astro で小規模なサイトを作りたいなら、EmDash は魅力的な選択肢です。
特に個人事業主やマイクロ法人のような小規模な組織で製品紹介とコーポレートサイトを両立したい場合、マーケティングテンプレートにブログテンプレートを組み合わせる構成はよく合うと思います。
WordPress でありがちな課題を本気で解決しようとしている意思は伝わってきたので、今後のアップデートも楽しみです。

今回は以上になります✊️

参考

blog.cloudflare.com のアイコン
blog.cloudflare.com

Introducing EmDash — the spiritual successor to WordPress that solves plugin security

Today we are launching the beta of EmDash, a full-stack serverless JavaScript CMS built on Astro 6.0. It combines the features of a traditional CMS with modern security, running plugins in sandboxed Worker isolates.

github.com のアイコン
github.com

GitHub - emdash-cms/emdash: EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress - emdash-cms/emdash

developers.cloudflare.com のアイコン
developers.cloudflare.com

Dynamic Workers

Spin up isolated Workers on demand to execute code.

developers.cloudflare.com のアイコン
developers.cloudflare.com

Policies

Configure Policies in Access.

developers.cloudflare.com のアイコン
developers.cloudflare.com

One-time PIN login

One-time PIN login in Zero Trust integrations.

patchstack.com のアイコン
patchstack.com

State of WordPress Security 2025

An open-source security whitepaper, covering the most important security-related challenges, trends and statistics in WordPress.

Footnotes

  1. 43% という数字は 3Techs より引用。https://w3techs.com/technologies/details/cm-wordpress

  2. コンテンツ重視のWebサイト向けフレームワーク。静的サイト生成と SSR を組み合わせた設計で、フロントエンド開発者の間で採用が増えています。

  3. 「何ができるか」を明示的に宣言するセキュリティモデル。宣言していない操作は物理的に実行できないよう制限されます。

  4. HTTP/402 ステータスコードを活用したインターネットネイティブの決済標準。エージェントやクライアントがオンデマンドでコンテンツの料金を支払えるようにします。

  5. Model Context Protocol の略。AI ツールがサービスと直接やりとりするための標準プロトコルです。

  6. パスワードの代わりに生体認証やデバイスの PIN を使う認証方式。フィッシングやブルートフォース攻撃への耐性があります。

  7. Single Page Application の略。ページ遷移の代わりに JavaScript で画面を動的に切り替えるWebアプリケーションの設計手法です。

理解度チェック

問題1: EmDash のプラグインが WordPress のプラグインと最も大きく異なる点はどれですか?

  • TypeScript で書かれている

    不正解もう一度考えてみましょう!

  • 各プラグインが宣言したケイパビリティ以外には一切アクセスできないサンドボックスで動作する

    正解正解です!

    EmDash では各プラグインが独立した Dynamic Worker のサンドボックスで動作し、宣言していないデータベースやファイルシステムへのアクセスが物理的に不可能です。WordPress のプラグインはサイト全体と同じ実行コンテキストで動くため、何でもアクセスできます。

  • プラグインが無料で公開されている

    不正解もう一度考えてみましょう!

  • プラグインのインストールにパスキー認証が必要

    不正解もう一度考えてみましょう!

問題2: EmDash で `npx emdash seed` を実行してもコンテンツが反映されない場合、何をすれば反映されますか?

  • 開発サーバーを再起動する

    不正解もう一度考えてみましょう!

  • seed.json を再編集して再実行する

    不正解もう一度考えてみましょう!

  • 管理画面(/_emdash/admin)の初期セットアップを完了させる

    正解正解です!

    EmDash の seed は管理画面の初期セットアップ完了をトリガーとして実 DB へ適用されます。POST /_emdash/api/setup が呼ばれた直後に画像のダウンロード・アップロードと seed 適用が走る設計です。

  • pnpm install を再実行する

    不正解もう一度考えてみましょう!

問題3: Cloudflare Access で EmDash の管理画面を保護する際、パスに指定すべき正しい値はどれですか?

  • /_emdash/*

    不正解もう一度考えてみましょう!

    このパスでは API エンドポイントを含む /_emdash/ 配下すべてが保護対象になります。管理画面のみを絞り込むには admin パスを指定する必要があります。

  • /admin*

    不正解もう一度考えてみましょう!

  • /_emdash/admin

    不正解もう一度考えてみましょう!

    末尾にワイルドカードがないと、/_emdash/admin 直下のページのみが対象になり、管理画面内のサブパスが保護されません。

  • /_emdash/admin*

    正解正解です!

    末尾にワイルドカード * を付けることで、/_emdash/admin 配下のすべてのパスが保護対象になります。

関連記事