ota2000
3 min read

Cloudflare Pages + Astro に Decap CMS を導入する

出先でスマホからブログ記事を書きたい。GitHub のモバイルアプリで Markdown を編集するのは辛い。

Decap CMS(旧 Netlify CMS)を入れた。ブラウザ上で記事を書いて、そのまま GitHub にコミットしてくれる。/admin/ にアクセスするだけ。

セットアップ

public/admin/ に2ファイル置くだけ。

index.html で Decap CMS の JS を読み込む。

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="robots" content="noindex" />
    <title>Content Manager</title>
  </head>
  <body>
    <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
  </body>
</html>

config.yml でコレクションを定義する。

backend:
  name: github
  repo: ota2000/ota2000.com
  branch: main
  base_url: https://ota2000.com
  auth_endpoint: api/auth

collections:
  - name: blog
    label: Blog
    folder: site/src/content/blog
    create: true
    slug: "{{slug}}"
    extension: md
    fields:
      - { label: Title, name: title, widget: string }
      - { label: Description, name: description, widget: string }
      - { label: Date, name: date, widget: datetime, format: "YYYY-MM-DD" }
      - { label: Tags, name: tags, widget: list }
      - { label: Body, name: body, widget: markdown }

OAuth プロキシ

ここがハマった。Decap CMS の GitHub 認証は OAuth サーバーが必要。Netlify にホストしていれば自動で使えるが、Cloudflare Pages では自前で用意する必要がある。

PKCE 認証を試したが、Decap CMS の GitHub 向け PKCE は未完成(Issue #6597 がオープンのまま)。Netlify の OAuth プロキシにフォールバックして動かない。

結局 Cloudflare Pages Functions で OAuth プロキシを書いた。/api/auth/api/callback の2つ。

/api/auth は GitHub の OAuth 認可画面にリダイレクトするだけ。

export async function onRequest(context) {
  const clientId = context.env.GITHUB_OAUTH_CLIENT_ID;
  const scope = 'repo';
  const url = `https://github.com/login/oauth/authorize?client_id=${clientId}&scope=${scope}`;
  return Response.redirect(url, 302);
}

/api/callback がトークン交換と Decap CMS への受け渡しを担う。ポイントは2段階の postMessage ハンドシェイク。

function renderBody(status, content) {
  return `<script>
    const receiveMessage = (message) => {
      window.opener.postMessage(
        'authorization:github:${status}:${JSON.stringify(content)}',
        message.origin
      );
      window.removeEventListener("message", receiveMessage, false);
    }
    window.addEventListener("message", receiveMessage, false);
    window.opener.postMessage("authorizing:github", "*");
  </script>`;
}

最初に authorizing:github を親ウィンドウに送り、親が応答したらその origin を使ってトークンを返す。この2段階が必要だと気づくまでに時間がかかった。直接 postMessage でトークンを送っても Decap CMS は受け取ってくれない。

環境変数

GitHub OAuth App の Client ID と Client Secret を Cloudflare Pages の環境変数に設定する。Terraform で管理。

GITHUB_OAUTH_CLIENT_ID = {
  type  = "plain_text"
  value = var.github_oauth_client_id
}
GITHUB_OAUTH_CLIENT_SECRET = {
  type  = "plain_text"
  value = var.github_oauth_client_secret
}

使い勝手

ota2000.com/admin/ にアクセスして GitHub ログインすれば記事一覧が出る。新規作成・編集どちらも Markdown エディタで書ける。スマホからでも使える。保存すると GitHub にコミットされ、Cloudflare Pages が自動デプロイ。