tkc310.com

技術学習ログ更新

AI駆動開発でブログをNext.jsからAstroにリプレイスした

目次
  1. なぜ作り直したか
  2. 構成
  3. 導入手順
  4. 1. Astro を静的サイトとして作る
  5. 2. Cloudflare Workers にデプロイする
  6. 3. 1 ページだけ Worker で描画してキャッシュする
  7. 4. Sveltia CMS を置く
  8. 5. GitHub の OAuth App と認証の Worker を作る
  9. 6. DNS を Cloudflare に切り替える
  10. 7. プレビュー URL を Cloudflare Access で守る
  11. 8. 管理画面の本体を自分で配り、CSP を付ける
  12. 移行: Claude に旧サイトをスクレイピングさせて Markdown を作る
  13. 公開ページの __NEXT_DATA__ を読む
  14. 本文を変換する
  15. 結果
  16. ハマったところ
  17. Workers Builds が .tool-versions を asdf で読もうとする
  18. ビルドのキャッシュでリンクカードの画像が落ちる
  19. 応答しないサイトの OGP を 5 分待つ
  20. 管理画面の「公開」を押してもマージされない
  21. アセットの画面からの画像のアップロードが失敗する
  22. 結果
  23. おわりに

microCMS + Next.js + Vercel で作っていたこのブログを、Astro + Sveltia CMS + Cloudflare Workers に作り直しました。

この記事では、新しい構成の導入手順と、旧ブログの記事を Claude(Claude Code)にスクレイピングさせて Markdown に移した方法をまとめます。

なぜ作り直したか

旧ブログは 2021 年に作ったもので、次のような構成でした。

  • フレームワーク: Next.js 12(SSG)
  • UI: Chakra UI
  • CMS: microCMS(本文は MDX のテキスト)
  • ホスティング: Vercel

数年放っておいたあいだに Next.js も Chakra UI も大きく変わり、手を入れるには依存の更新から始める必要がありました。作り直すにあたって決めた目的は 3 つです。

  • 表示を速くする: 記事を読むだけのページに、クライアントの JavaScript はほとんど要らない
  • 構造化データ(JSON-LD)を充実させる: 検索エンジンと AI の要約に、記事の中身を正しく伝える
  • 新しい道具に触る: Cloudflare Workers・Astro・Vite+ のキャッチアップ、Claude Codeの素振り (普段はCursor)

構成

役割使ったもの
フレームワークAstro 7(SSG / 1 ページだけ Worker で描画)
記事リポジトリの Markdown
管理画面Sveltia CMS(Git-based)
配信Cloudflare Workers の静的アセット + Workers Builds
CMS の認証sveltia-cms-auth(別の Cloudflare Worker)
ツールチェーンVite+(検査・テスト・フォーマット)、pnpm、mise

記事の正本は GitHub のリポジトリの Markdown です。管理画面で書いた記事も、GitHub にコミットされるだけなので、データベースも外部の CMS もありません。

導入手順

1. Astro を静的サイトとして作る

旧ブログの記事の URL(/articles/<id>、末尾スラッシュなし)をそのまま保ちたかったので、Astro の出力を次のようにしました。

export default defineConfig({
site: "https://tkc310.com",
output: "static",
trailingSlash: "never",
build: {
// /articles/<id>.html を出し、Workers の静的アセットが /articles/<id> を 200 で返す
format: "file",
},
});

build.format: "file" にすると、/articles/foo/index.html ではなく /articles/foo.html が出ます。Cloudflare Workers の静的アセットは、/articles/foo へのリクエストに foo.html をリダイレクトなしで返してくれます。

記事はコンテンツコレクションで読み、frontmatter を Zod のスキーマで検査します。項目が足りない・余計な項目がある記事は、ビルドで止まります。

const posts = defineCollection({
loader: glob({ base: "./src/content/posts", pattern: "**/*.md" }),
schema: z
.object({
title: z.string().min(1).max(110),
description: z.string().min(1).max(200),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
category: reference("categories"),
draft: z.boolean().default(false),
})
.strict(),
});

Markdown の処理は unified() にして、リンクカード(remark-link-card-plus)・コードブロックの装飾(Expressive Code)・外部リンクを別タブで開く設定(rehype-external-links)を入れています。リンクカードの OGP はビルド時に取るので、読み込んだあとでレイアウトがずれることはありません。

2. Cloudflare Workers にデプロイする

Cloudflare のダッシュボードで Worker を作り、GitHub のリポジトリとつなぎます(Workers Builds)。

項目値
ビルドコマンドpnpm run build
デプロイコマンドnpx wrangler deploy(既定のまま)
プレビュービルド有効

プレビュービルドを有効にすると、main 以外のブランチを push するたびに、ブランチごとのプレビュー URL にデプロイされます。下書きの記事はプレビュー URL にだけ出し、本番には出しません。Workers Builds がビルド中に入れる WORKERS_CI_BRANCH を見て切り替えています。

プレビュー URL はそのままだと誰でも開けるので、あとで Cloudflare Access で認証をかけました(手順 7)。

export function shouldShowDrafts(env: Record<string, string | undefined>): boolean {
if (env.SHOW_DRAFTS === "true") return true;
if (env.SHOW_DRAFTS === "false") return false;
const branch = env.WORKERS_CI_BRANCH;
return branch !== undefined && branch !== "" && branch !== "main";
}

wrangler.jsonc に書くのは名前と静的アセットの扱いくらいです。Worker の入口とアセットの置き場所は、Astro の Cloudflare アダプターがビルドのときに足してくれます。

{
"name": "astro-blog",
"compatibility_date": "2026-10-19",
"assets": {
"html_handling": "auto-trailing-slash",
},
"observability": { "enabled": true },
}

旧ブログの URL のうち、無くしたページ(タグ・ノート・寄付など)は public/_redirects で 301 にしました。旧サイトのサイトマップにあった URL を一覧にしておき、すべてが 200 か 301 を返すことを CI で確かめています。

3. 1 ページだけ Worker で描画してキャッシュする

ほぼ静的サイトですが、私について(/about)だけは例外です。LAPRAS の公開 API から最近の活動を取って載せたかったのに、ブラウザから fetch すると CORS で止まってしまいました。

そこで /about だけを prerender = false にしてサーバ(Worker)から API を呼び、Astro のルートキャッシュで Cloudflare にキャッシュしています。Vercel の ISR と同じ動き(期限が切れたら、古いものを返しながら裏で描き直す)です。

---
export const prerender = false;
const activities = await fetchActivities("tkc310", { limit: 10 });
// 開発中は cache.enabled が false なので、毎回描画される
if (Astro.cache.enabled) {
Astro.cache.set(
activities.ok
? { maxAge: 60 * 60, swr: 60 * 60 * 24, tags: ["lapras"] }
: { maxAge: 60 * 5, tags: ["lapras"] },
);
}
---
import cloudflare from "@astrojs/cloudflare";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";
export default defineConfig({
output: "static",
adapter: cloudflare({ prerenderEnvironment: "node", imageService: "compile" }),
session: false, // セッションを使わないので KV を作らない
cache: { provider: cacheCloudflare() },
});

LAPRAS の API は 3 秒で打ち切り、取れなくてもプロフィールへのリンクを出してページを返すようにしています。

4. Sveltia CMS を置く

Sveltia CMS は、Decap CMS(旧 Netlify CMS)の後継として作り直された Git-based の CMS です。管理画面のページと設定ファイルを置くだけで、/admin が管理画面になります。サーバもデータベースも要らず、ブラウザの中で動く JavaScript が GitHub の API でリポジトリの Markdown を直接読み書きします。

はじめは公式の手順どおり、public/admin/index.html から unpkg の本体(<script src="https://unpkg.com/@sveltia/cms@0.228.0/dist/sveltia-cms.js">)を読み込んでいました。今は npm のパッケージを Astro でまとめて、自分のサイトから配っています(理由は手順 8)。

src/pages/admin.astro
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex" />
<title>管理画面 | tkc310.com</title>
<link href="/admin/config.yml" type="application/yaml" rel="cms-config-url" />
</head>
<body>
<script>
import { init } from "@sveltia/cms";
void init();
</script>
</body>
</html>

設定では、保存の方式を Editorial Workflow にしています。管理画面で保存すると、記事ごとのブランチとプルリクエストができます。プレビュー URL で見た目を確かめてからマージすると公開される流れです。

backend:
name: github
repo: tkc310/astro-blog
branch: main
base_url: https://astro-blog-sveltia-cms-auth.tkc310.workers.dev
publish_mode: editorial_workflow
site_url: https://tkc310.com
collections:
- name: posts
label: 記事
folder: src/content/posts
extension: md
format: yaml-frontmatter
create: true
media_folder: /src/assets/posts/{{filename}}
public_folder: ../../assets/posts/{{filename}}
fields:
- { name: title, label: タイトル, widget: string }
- { name: description, label: 説明, widget: text }
- { name: pubDate, label: 公開日, widget: datetime }
- { name: category, label: カテゴリ, widget: relation, collection: categories, value_field: "{{slug}}", display_fields: [name] }
- { name: draft, label: 下書き, widget: boolean, required: false }
- { name: body, label: 本文, widget: markdown }

frontmatter の項目は、Astro の Zod スキーマと Sveltia の設定の 2 か所に書くことになります。ずれるとビルドで止まるので、両方がそろっているかをテストで確かめています。

5. GitHub の OAuth App と認証の Worker を作る

Sveltia CMS で GitHub にログインするには、OAuth のやり取りを中継するサーバが要ります。公式の sveltia-cms-auth を、ブログとは別の Cloudflare Worker として動かしました。

  1. 認証の Worker をデプロイする: README の「Deploy to Cloudflare Workers」ボタンから作ります。ビルドコマンドは空欄、プレビュービルドと Cloudflare Access は不要です。できた Worker の URL(https://<名前>.<サブドメイン>.workers.dev)を控えます
  2. GitHub の OAuth App を作る: GitHub の Settings → Developer settings → OAuth Apps から作ります
    • Redirect URIs: <Worker の URL>/callback
    • Allow wildcard matching: オフ(戻り先は登録した 1 件だけなので不要。認証の Worker は GitHub に redirect_uri を送らず、GitHub は登録した URL に戻す)
    • Enable Device Flow: オフ
  3. Worker に値を入れる: Worker の Settings → Variables and Secrets に、GITHUB_CLIENT_ID・GITHUB_CLIENT_SECRET(シークレットとして)・ALLOWED_DOMAINS(tkc310.com)を入れます
  4. CMS の設定に Worker の URL を書く: config.yml の backend.base_url に書きます

2026 年 8 月から、GitHub の OAuth App では「Authorization callback URL」が「Redirect URIs」になり、複数の URL を登録できるようになりました。「Allow wildcard matching」は、以前は URL が 1 つのアプリで暗黙にオンだったものが見えるようになっただけなので、要らなければオフにしておくのがよさそうです。

6. DNS を Cloudflare に切り替える

ドメインはお名前.com で取っていて、ネームサーバーは Vercel を向いていました。切り替えは次の順で行いました。

  1. Cloudflare にドメインを足し、Vercel の DNS から読み込んだレコードを整理する(このときは Vercel を向いた A レコードをプロキシなしで残す)
  2. お名前.com のネームサーバーを Cloudflare のものに変える(この間も旧サイトが見える)
  3. ゾーンが有効になったら、A レコードを消し、Worker のカスタムドメインに tkc310.com を足す
  4. www は Redirect Rule で tkc310.com へ 301 にする
  5. 旧ブログの URL の一覧がすべて 200 か 301 を返すことを確かめる

7. プレビュー URL を Cloudflare Access で守る

プレビュー URL(<ブランチ名>-astro-blog.<サブドメイン>.workers.dev)には下書きの記事も出るので、そのままだと公開前の記事を誰でも読めてしまいます。Cloudflare Access で、プレビュー URL にだけ認証をかけました。

  1. Workers & Pages で Worker を開き、Access タブの Protect this Worker behind Access を選ぶ
  2. 範囲は Previews only にする
  3. 認証のポリシーを選ぶ(1 人で使うなら、Cloudflare のアカウントのメンバーだけを通すのが簡単)
  4. Apply Access を押す

範囲を All traffic にすると、Worker につながるすべてのドメインが対象になり、カスタムドメインの本番(tkc310.com)まで認証が必要になります。プレビューだけを守りたいときは Previews only を選びます。

これでプレビュー URL を開くと、Cloudflare のログイン画面が出るようになりました。気をつけたのは次の 3 点です。

  • ブランチの URL は 1 つのラベル: プレビュー URL は cms-posts-foo-astro-blog.<サブドメイン>.workers.dev のように、ブランチ名と Worker の名前が 1 つのラベルにつながります。Zero Trust 側で自分でアプリを作り、*.astro-blog.<サブドメイン>.workers.dev のようなホスト名を指定しても当たりません。Worker の Access タブから設定するのが確実です
  • ポリシーの動作は Allow: Bypass にすると、誰でも認証なしで通ります
  • CMS の認証の Worker には掛けない: 管理画面のログイン(GitHub)の途中で Access の画面が挟まると、ログインできなくなります

CI の Lighthouse とスクリーンショットは、CI の中で astro preview を起動して localhost に対して動かしているので、Access の影響を受けません。

8. 管理画面の本体を自分で配り、CSP を付ける

管理画面のスクリプトは、ログインした人の GitHub のアクセストークン(repo 権限)を持って動きます。unpkg から本体を読み込む形だと、版を固定していても、配信元が乗っ取られたり途中で中身が変わったりしたときに、ブラウザはそれを確かめずに実行してしまいます。差し替えられた本体は、トークンを外に送ることも、リポジトリに書き込んで本番に出すこともできます。

さらに CDN 版の本体は、日本語の翻訳やコードのハイライト(Shiki)なども実行中に unpkg から追加で読み込むので、unpkg が止まると管理画面も開けません。

そこで次のように変えました。

  1. @sveltia/cms を package.json に正確な版で入れる。中身はロックファイルのハッシュで確かめられ、版の更新もほかの依存と同じく PR と CI を通る
  2. 管理画面を Astro のページ(src/pages/admin.astro)にし、パッケージの init() を呼ぶ。Astro(Vite)がまとめて /_astro/ から配るので、翻訳なども含めて外部の CDN を読まない
  3. public/_headers で /admin に Content-Security-Policy を付け、スクリプトは自分のサイトのものだけ、通信は自分のサイトと GitHub だけに絞る
public/_headers
/admin
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data: https://avatars.githubusercontent.com https://*.githubusercontent.com; connect-src 'self' blob: data: https://api.github.com https://*.githubusercontent.com https://www.githubstatus.com; frame-ancestors 'none'; ...

script-src は 'self' だけで、インラインのスクリプトも許していません。Astro は小さなスクリプトを HTML に埋め込むことがありますが、管理画面のスクリプトはパッケージを読み込むので埋め込まれず、別のファイルとして配られます。スタイルだけは、Sveltia CMS が部品のスタイルを差し込むので 'unsafe-inline' を許しています。

壊れていないことは CI で確かめています。ビルド結果の検査で管理画面に外部やインラインのスクリプトが無いことを、スクリーンショットの撮影で管理画面を実際に開き、CSP のヘッダーが付いていること・CSP に止められた読み込みが無いこと・ログインの画面まで描かれることを見ています。

ログインから記事の保存までの流れをシーケンス図付きでリポジトリの文書にまとめました。

CSP以外にも色々とできそうです。

移行: Claude に旧サイトをスクレイピングさせて Markdown を作る

記事の移行は、Claude Code に任せました。はじめは microCMS の API から記事を取る予定でしたが、API キーをエージェントの作業環境に渡したくなかったので、公開しているサイトから取る方法に変えています。

公開ページの __NEXT_DATA__ を読む

旧サイトは Next.js の SSG(getStaticProps)で作っていたので、各ページの HTML に __NEXT_DATA__ として、microCMS の API の応答がそのまま埋め込まれています。HTML から本文を逆算しなくても、記事の本文(MDX のテキスト)・カテゴリ・日付・抜粋がそのまま取れます。

これに気づいたのは Claude でした。旧ブログのリポジトリと公開ページを読ませたところ、API を使わない方法としてこれを提案してきました。

async function nextData(path: string): Promise<Record<string, unknown>> {
const response = await fetch(`${LEGACY_SITE}${path}`);
if (!response.ok) throw new Error(`${path}: ${response.status}`);
const html = await response.text();
const json = /<script id="__NEXT_DATA__" type="application\/json">([\s\S]*?)<\/script>/.exec(
html,
)?.[1];
if (!json) throw new Error(`${path}: __NEXT_DATA__ がありません`);
return (JSON.parse(json) as { props: { pageProps: Record<string, unknown> } }).props.pageProps;
}

記事の ID は、トップと /articles/page/<n> の一覧を順にたどって集めます。サイトマップには 16 本中 10 本しか載っていなかったので使えませんでした。

本文を変換する

旧ブログは、描画の前に本文を書き換えていました。新しいブログで同じ見た目になるよう、移行のときに Markdown を直します。変換のルールは先に指示書に書き、Claude にスクリプトとテストを書かせました。

旧ブログでの書き方移行後
<https://...> だけの行(実行時に OGP を取るリンクカード)URL だけの行(ビルド時にリンクカードになる)
microCMS の画像の URL画像をダウンロードしてリポジトリに置き、相対パスに直す
<img src="..." alt="..." />![alt](...) に直す
本文中の #(h1)全体を 1 段下げる(ページの h1 はタイトルだけ)
言語名の無いコードブロックtext を付けて一覧に出す
JSX のコンポーネント自動では直さず、一覧に出して下書きにする

スクリプトは何度実行しても同じ結果になるようにしてあります。出力先を消してから書き、最後に整形までするので、旧サイトに新しい記事が無ければ差分は出ません。切り替えの直前にもう一度実行して、取りこぼしが無いことを確かめました。

結果

記事 15 本・カテゴリ 5 個・画像 12 枚を移せました。手で直す必要があったのは、JSX のコンポーネントが残ったテスト用の記事 1 本だけで、これは移さないことにしました。

AI に任せてよかったのは、移行のような「一度きりだが、正確さが要る」作業を、指示書とテスト付きのスクリプトにして残してくれたことです。手作業でコピーしていたら、画像の取りこぼしや見出しのレベルのずれに気づけなかったと思います。

一方で、旧サイトの作り(__NEXT_DATA__)に頼る方法なので、旧サイトを消したら再実行できません。移行が終わるまで、旧サイトは残しておく必要があります。

ハマったところ

Workers Builds が .tool-versions を asdf で読もうとする

ツールの版を .tool-versions に書いていたところ、Workers Builds がこれを asdf で読もうとし、pnpm の行を入れられずにビルドが止まりました。mise には mise.toml、GitHub Actions と Workers Builds には .node-version で版を渡すようにして解決しました。

ビルドのキャッシュでリンクカードの画像が落ちる

本番でリンクカードの画像だけが 404 になりました。Workers Builds が Astro のキャッシュを戻してビルドするため、描画済みの本文が使い回され、画像をダウンロードする処理が走っていなかったのが原因です。ビルドを astro build --force にして、キャッシュを捨てて回すようにしました。

応答しないサイトの OGP を 5 分待つ

CI のビルドが 1 分から 6 分に延びました。リンクカードのプラグインが、OGP を取るライブラリに秒で渡すべきタイムアウトをミリ秒で渡していて、応答しないサイトを Node の既定の 5 分まで待っていました。pnpm の patch で 10 秒に直しています。

管理画面の「公開」を押してもマージされない

管理画面で記事を保存するとプルリクエストができ、「公開」を押すとそれが main にマージされる……はずが、「公開」が失敗しました。原因は 2 つありました。

  1. マージの方式が合っていない: main は保護(ruleset)でスクイズマージだけを許し、履歴を一直線に保っています。Sveltia CMS は設定が無いと普通のマージ(マージコミット)を試みるので、GitHub に拒まれていました
  2. 必須の検査が終わっていない: CI の check を必須にしているので、保存の直後で検査が実行中のあいだは、GitHub がマージを受け付けません

対策は次の 3 つです。

  • config.yml の backend に squash_merges: true を足し、「公開」をスクイズマージにする
  • Sveltia CMS を 0.248.0 以上に上げる。この版から、「公開」を押したときに必須の検査が実行中なだけなら、GitHub の自動マージ(auto-merge)を予約するようになりました。検査が通った時点で GitHub がマージするので、管理画面を閉じてもかまいません
  • リポジトリの設定で「Allow auto-merge」を有効にする
public/admin/config.yml
backend:
name: github
repo: tkc310/astro-blog
branch: main
base_url: https://astro-blog-sveltia-cms-auth.tkc310.workers.dev
squash_merges: true
Terminal window
gh api -X PATCH repos/tkc310/astro-blog -F allow_auto_merge=true

check を必須から外せば「公開」はすぐ通りますが、壊れた記事がそのまま本番に出てしまいます。実際、記事が 1 本増えただけで記事の一覧に 4 ページ目ができ、旧ブログ用の 301 がそのページを隠していたことがあり、ビルド結果の検査がそれを見つけてくれました。検査は残し、待つのは GitHub に任せる形にしました。

そのかわり、記事と記事の画像(src/content/・src/assets/posts/)だけを変えたプルリクエストでは、検査を最低限に絞っています。型・lint・テストはコードの検査なので飛ばし、ビルドとビルド結果の検査だけを回します。Lighthouse とスクリーンショットの撮影も回しません。必須の check は、飛ばしたステップがあっても結果が付くので、自動マージは止まりません。

ちなみに、はじめは Sveltia CMS の最新の 0.248.1 に上げようとしたのですが、pnpm 12 の minimumReleaseAge(公開から 1 日たっていない版は入れない)に止められました。サプライチェーン攻撃への備えとしてありがたい既定値なので、そのまま 0.248.0 にしています。

アセットの画面からの画像のアップロードが失敗する

管理画面の「すべてのアセット」「グローバルアセット」から画像を上げると失敗しました。Sveltia CMS のソースを読むと、アセットの画面からのアップロードは Editorial Workflow を通らず、main へ直接コミットしようとしていました。main はプルリクエストを必須にしているので、拒まれて当然です。

記事の編集画面から本文に画像を足すと、記事のブランチとプルリクエストに入るので、こちらは問題なく保存できます。置き場所も記事ごとのフォルダ(src/assets/posts/<記事の ID>/)になるので、画像は記事の編集画面から足すことにしました。

管理画面の仕組み(ログインから保存・公開までの流れ)は、こちらにまとめています。

結果

本番の Lighthouse(モバイル)は、測った 5 ページとも、性能・アクセシビリティ・ベストプラクティス・SEO の 4 項目が 100 点になりました。

CI でも Lighthouse を回していて、点が下がる変更はマージできないようにしています。完成したあとにClaudeに指示するだけで、30~1時間ほどで仕組みも含めてチューニングされていました。

Lighthouseの測定結果 - 4項目が100点

おわりに

作り直しのほとんどは、Claude Code と会話しながら進めました。

Opus5.5を使っていますが、コスト的もパフォーマンスも今までと次元が違くて驚いています。

いままで、Cursorから色々なモデルを使っていましたが、外部APIと定額サブスクリプションでは圧倒的に後者が有利だとやっと気づきました。

仕事でもレガシーコードやレビューなどは安いモデルだと使い物にならないため、ClaudeやCodexのモデルを使っていましたが、忙しいときは半月で上限までクレジットを消費していました。

GrokBot / Cursor Projects / Cloude Agents / pstackなど、エコシステムはまだCursorに利がありますが、モデルの賢さ的に併用するしかなさそうです。

しかしながら、そんなにスキルやコマンドなどを知らなくても、対話的にプロンプトを与えるだけでやりたい事をシニアクラスの品質で出力してくれるので本当に驚きです。

エンジニアが不要になるにはもう少し掛かるかも知れませんが、まず急激にやることは変わっていきそうです。

このリプレイスもこの記事の通り、AIが全てのコードを書いており、私はインフラ設定とレビューとプロンプトやコマンドを叩いていただけです。

こだわったりキャッチアップしながらでなければ、マイグレーション含めて数時間で完成していたと思います。

インフラすらもcloudflareに特化したvoidなどのフレームワークを使えば、AIに任せられそうです。

コードを書くのは楽しかったですが、悲観的にばかりならず、この歴史的な時代に生まれたことに感謝して楽しむマインドを忘れないようにしたいです。

一番近くにいるエンジニアから影響を受けていますが、他のホワイトワーカー、次にブルーワーカーも業態が変わっていき1-2年以内には世界が様変わりしていそうです。

一応、AI駆動開発のハーネスとして、技術の選定・要件・設計の判断を先に文書(指示書と ADR)にしてから実装させ、直した内容と再発防止をログに残す流れにしたので、あとから「なぜこうなっているのか」をリポジトリからたどれます。

sveltiaについては仕組みも複雑でカスタマイズもyamlを通じて行われるため、正直流行らなそうな気がしました笑

サーバーが不要で管理画面が使えるのか不思議で使ってみましたが、ざっくりと下記の仕組みで動いているようでした。

  • /adminにアクセス
  • sveltiaがconfig.ymlから画面を組み立てる (デフォルトだとsveltiaのスクリプトをCDNからダウンロードして動かしている)
  • Github APIを通じてリポジトリのファイルを取得 (マークダウンや画像)
  • 取得データをindexedDBなどブラウザのメモリに保持して描画
  • 記事を変更・保存 (indexedDBに差分を保持)
  • Github APIを通じて差分をコミット・PR作成
  • 管理画面から公開
  • PRマージ
  • WebhookでCloudflare Build上でSSGしてCloudflare workerにデプロイ

スキル・コマンド化しておいて、スマホからGrokBotに依頼してクラウド上でビルドしてもらった成果物をPRでマージするなどの方が校正もしてくれるので、今っぽいかもしれません。(クラウド上でプレビュー)

ちなみにこの記事は、sveltiaの管理画面から投稿しています。



Sveltia CMSの管理画面