Cloudsteading
Product image still needed. This listing has source documentation, but no reviewed screenshot yet.

Monolith

A Markdown blog with a Cloudflare API, media storage and administration.

Monolith is a self-hosted Ghost/WordPress alternative built on Cloudflare (Analytics Engine, D1, Pages, R2, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.

Source & license

Upstream license: MIT

License TL;DR

You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.

Explain MIT in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@one-ea

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Free tier eligible within limits

The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply.

Hosting requirements
  • Deploy the documented Pages frontend and Worker API with your own resource IDs and secrets; the collected active config covers the API.
  • Keep one-minute cron and dynamic traffic under Workers Free request/10 ms CPU limits, D1 queries/storage and Standard R2 within included quotas.
  • Analytics Engine currently includes 100,000 written points/day and 10,000 read queries/day on Workers Free; its official pricing may change when billing begins.
  • Optional Resend mail, custom domains and alternative Turso/PostgreSQL/S3 services have separate costs.
  • Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured.
  • Dynamic Pages Functions requests share the Workers account allowance; only requests that do not invoke Functions count as free unlimited static asset requests.
  • D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota.
  • R2 Standard allowance: 10 GB-month storage, 1 million Class A and 10 million Class B operations/month; account activation may require billing setup, and other storage classes are excluded.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: 98759aa. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • ghost ↗

    50ms 的访问延迟。 设计哲学:**内容优先 · 边缘原生 · 沉浸式阅读 · 一切皆可控制台配置**。 --- ## 🌟 核心特性 ### ✍️ 创作体验 - **沉浸式编辑器** — Markdown + 实时预览,KaTeX 数学公式,代码高亮一键复制 - **多平台导入** — 一键迁移 WordPress / Ghost / Hexo / Hugo / Jekyll / Halo - **内容编排** — 草稿、定时发布、置顶、系列合集、独立页动态导航 ### 🎨 阅读体验(GitHub Primer 设计语言) - **日/夜双主题** — 亮暗两套完整 token 对齐 GitHub "浅色/深色 - 默认"(`@primer/primitives`),蓝色链接与 focus ring、官方语法高亮配色、六色 GitHub 风格标签 - **站点级主题管控** — 明暗模式 × 视觉风格(简洁 / 液态玻璃)由控制台统一设置,支持跟随系统,首屏 cookie 防闪烁 - **文章导航** — 自动 TOC、阅读进度条、IntersectionObserver 章节追踪 - **⌘K 全站搜索** — 防抖检索、键盘导航、关键词高亮 - **Reaction 表情** — 文末轻互动,无需登录即

  • wordpress ↗

    --- ## 🌟 核心特性 ### ✍️ 创作体验 - **沉浸式编辑器** — Markdown + 实时预览,KaTeX 数学公式,代码高亮一键复制 - **多平台导入** — 一键迁移 WordPress / Ghost / Hexo / Hugo / Jekyll / Halo - **内容编排** — 草稿、定时发布、置顶、系列合集、独立页动态导航 ### 🎨 阅读体验(GitHub Primer 设计语言) - **日/夜双主题** — 亮暗两套完整 token 对齐 GitHub "浅色/深色 - 默认"(`@primer/primitives`),蓝色链接与 focus ring、官方语法高亮配色、六色 GitHub 风格标签 - **站点级主题管控** — 明暗模式 × 视觉风格(简洁 / 液态玻璃)由控制台统一设置,支持跟随系统,首屏 cookie 防闪烁 - **文章导航** — 自动 TOC、阅读进度条、IntersectionObserver 章节追踪 - **⌘K 全站搜索** — 防抖检索、键盘导航、关键词高亮 - **Reaction 表情** — 文末轻互动,无需登录即可表态 ### 🎛️ 控制台全站管控 行为配置全部进 D1 设置键值,控制台改完即生效(最长 15 秒),

  • workers ↗

    name = "monolith-server" main = "src/index.ts" account_id = "9d9474f286c406f50848ac46c7a15877" compatibility_date = "2024-12-30" compatibility_flags = ["nodejs_compat"] [vars] # 非敏感配置变量(敏感的用 wrangler secret put) BLOG_NAME = "Monolith" # 对外公开域名(用于 sitemap.xml / robots.txt 的绝对 URL) SITE_ORIGIN = "https://monolith-client.pages.dev" # 运行时默认不做建表/补列;部署流程负责迁移与 schema reconcile。 AUTO_SCHEMA_MIGRATION = "false" # AE GraphQL 查询所需的账户 ID(非敏感;查询令牌走 CLOUDFLARE_API_TOKEN secret) CLOUDFLARE_ACCOUNT_ID = "

  • d1 ↗

    CLOUDFLARE_ACCOUNT_ID = "9d9474f286c406f50848ac46c7a15877" # D1 数据库绑定 [[d1_databases]] binding = "DB" database_name = "monolith-db" database_id = "260856eb-760d-4a36-aad7-31f8aa1291e9" migrations_dir = "src/migrations" # R2 存储桶绑定 [[r2_buckets]] binding = "BUCKET" bucket_name = "monolith-assets" # Analytics Engine 数据集绑定(CF 专属增强分析模块) # 灵感来源: HanAnalytics (MIT) — https://github.com/uxiaohan/HanAnalytics # 仅在 Cloudflare 部署时生效,Turso/PG 后端会自动跳过 AE 写入与查询 [[analytics_engine_datasets]] binding = "AE" dataset = "monolith_analytics" # 定时触发器 (每 1 分钟执行一次) [triggers] crons

  • r2 ↗

    60d-4a36-aad7-31f8aa1291e9" migrations_dir = "src/migrations" # R2 存储桶绑定 [[r2_buckets]] binding = "BUCKET" bucket_name = "monolith-assets" # Analytics Engine 数据集绑定(CF 专属增强分析模块) # 灵感来源: HanAnalytics (MIT) — https://github.com/uxiaohan/HanAnalytics # 仅在 Cloudflare 部署时生效,Turso/PG 后端会自动跳过 AE 写入与查询 [[analytics_engine_datasets]] binding = "AE" dataset = "monolith_analytics" # 定时触发器 (每 1 分钟执行一次) [triggers] crons = ["* * * * *"]

  • analytics-engine ↗

    lith-client.pages.dev" # 运行时默认不做建表/补列;部署流程负责迁移与 schema reconcile。 AUTO_SCHEMA_MIGRATION = "false" # AE GraphQL 查询所需的账户 ID(非敏感;查询令牌走 CLOUDFLARE_API_TOKEN secret) CLOUDFLARE_ACCOUNT_ID = "9d9474f286c406f50848ac46c7a15877" # D1 数据库绑定 [[d1_databases]] binding = "DB" database_name = "monolith-db" database_id = "260856eb-760d-4a36-aad7-31f8aa1291e9" migrations_dir = "src/migrations" # R2 存储桶绑定 [[r2_buckets]] binding = "BUCKET" bucket_name = "monolith-assets" # Analytics Engine 数据集绑定(CF 专属增强分析模块) # 灵感来源: HanAnalytics (MIT) — https://github.com/uxiaohan/HanAnalytics #

  • pages ↗

    outer │ │ 媒体 / 备份 │ │ + PWA │ │ Auth · Admin │ └──────────────┘ │ Pages Functions │ │ Storage Factory │ └──────────────────┘ │ D1/Turso/PG │ │ R2/S3 │ └──────────────────┘ ``` **分层职责** | 层级 | 模块 | 关键路径 | |------|------|---------| | 边缘网络 | Cloudflare 全球 anycast | 200+ PoPs · 自动 TLS · DDoS 防护 | | 前端 | React SPA + Pages Functions | `client/src` · `client/functions` | | 后端 | Hono Workers + Storage Factory | `server/src/index.ts` ·

  • free-tier-eligible ↗

    name = "monolith-server" main = "src/index.ts" account_id = "9d9474f286c406f50848ac46c7a15877" compatibility_date = "2024-12-30" compatibility_flags = ["nodejs_compat"] [vars] # 非敏感配置变量(敏感的用 wrangler secret put) BLOG_NAME = "Monolith" # 对外公开域名(用于 sitemap.xml / robots.txt 的绝对 URL) SITE_ORIGIN = "https://monolith-client.pages.dev" # 运行时默认不做建表/补列;部署流程负责迁移与 schema reconcile。 AUTO_SCHEMA_MIGRATION = "false" # AE GraphQL 查询所需的账户 ID(非敏感;查询令牌走 CLOUDFLARE_API_TOKEN secret)

  • free-tier-eligible ↗

    ount Manager. | | Requests<sup>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 second

  • free-tier-eligible ↗

    rs Paid](https://developers.cloudflare.com/workers/platform/pricing/#workers) | | --- | --- | --- | | Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows | | Storage (per GB stored) | 5 GB (total) | First 5 GB included + $0.75 / GB-mo | Track your D1 usage To accurately track your usage, use the [meta object](https://developers.cloudflare.com/d1/worker-api/return-object/), [GraphQL Analytics API](https://developers.cloudflare.com/d1/obs

  • free-tier-eligible ↗

    f you have retrieved data (for infrequent access storage) for 1.1 GB, you will be billed for 2 GB. ### Free tier You can use the following amount of storage and operations each month for free. | | Free | | --- | --- | | Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month | | Egress (data transfer to Internet) | Free <sup>[1](#user-content-fn-1)</sup> | Caution The free tier only applies to Standard storage, and does not apply to Infrequent Access storage. ### Storage usage S

  • free-tier-eligible ↗

    r> (+$0.25 per additional million) | 1 million included per month (+$1.00 per additional million) | | **Workers Free** | 100,000 included per day | 10,000 included per day | Pricing availability Currently, you will not be billed for your use of Workers Analytics Engine. Pricing information here is shared in advance, so that you can estimate what your costs will be once Cloudflare starts billing for usage in the coming months. If you are an Enterprise customer, contact your account team for information about Workers Analytics Engine pricing and billing. ### Dat

  • free-tier-eligible ↗

    s.cloudflare.com/workers/platform/pricing/#how-to-switch-usage-models). ### Static asset requests On both free and paid plans, requests to static assets are free and unlimited. A request is considered static when it does not invoke Functions. Refer to [Functions invocation routes](https://developers.cloudflare.com/pages/functions/routing/#functions-invocation-routes) to learn more about when Functions are invoked. ## Free Plan Requests to your Pages Functions count towards your quota for the Workers Free plan. For example, you could use 50,000 Functions reques

  • MIT ↗

    MIT License Copyright (c) 2026 Monolith Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do action so, subject to the following conditions: The above copyright notice an

  • architecture ↗

    name = "monolith-server" main = "src/index.ts" account_id = "9d9474f286c406f50848ac46c7a15877" compatibility_date = "2024-12-30" compatibility_flags = ["nodejs_compat"] [vars] # 非敏感配置变量(敏感的用 wrangler secret put) BLOG_NAME = "Monolith" # 对外公开域名(用于 sitemap.xml / robots.txt 的绝对 URL) SITE_ORIGIN = "https://monolith-client.pages.dev" # 运行时默认不做建表/补列;部署流程负责迁移与 schema reconcile。 AUTO_SCHEMA_MIGRATION = "false" # AE GraphQL 查询所需的账户 ID(非敏感;查询令牌走 CLOUDFLARE_API_TOKEN secret) CLOUDFLARE_ACCOUNT_ID = "

  • architecture ↗

    outer │ │ 媒体 / 备份 │ │ + PWA │ │ Auth · Admin │ └──────────────┘ │ Pages Functions │ │ Storage Factory │ └──────────────────┘ │ D1/Turso/PG │ │ R2/S3 │ └──────────────────┘ ``` **分层职责** | 层级 | 模块 | 关键路径 | |------|------|---------| | 边缘网络 | Cloudflare 全球 anycast | 200+ PoPs · 自动 TLS · DDoS 防护 | | 前端 | React SPA + Pages Functions | `client/src` · `client/functions` | | 后端 | Hono Workers + Storage Factory | `server/src/index.ts` ·

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

Ghost logoGhost ↗

Editorial workflow alternative: Markdown blog administration, drafts and scheduled publishing; membership billing and managed-service parity are not claimed.

See supporting source ↗
external SaaS target
varies
external SaaS target
varies

How it works

The shape of Monolith on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram based on the linked repository documentation. See the sources and hosting assumptions above.

View upstream source ↗
Public interface
Browser and documented clients1
Monolith
Conceptual entry; runtime not tested
↓
App
React frontend
entry
Pages and API proxy (README)
monolith-server
entry
Cloudflare Worker
Cron * * * * *
↓

Configuration and workflow sources

Reviewed commit 98759aa1bf1f. Files were read as data; upstream applications and CI jobs were not executed.

Deployment configuration · 1 files
server/wrangler.toml ↗

Cloudflare Workers · compatibility 2024-12-30

monolith-server · default

Entrypoint: src/index.ts

Cron triggers (UTC): * * * * *

  • DB → D1
  • BUCKET → R2
  • AE → Analytics Engine

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

server/src/index.ts ↗
  • L2385 · fetch handler exported
  • L2386 · scheduled handler exported · calls createDatabase, db.publishScheduledPosts, console.log
  • L50 · app.use("*")
  • L56 · app.use("*")
  • L69 · app.use("*")
  • L76 · app.use("*")
  • L494 · app.get("/api/health")
  • L520 · app.post("/api/track")
  • L570 · app.get("/api/posts")
  • L578 · app.get("/api/search")
  • L610 · app.get("/api/posts/:slug")
  • L657 · app.get("/api/series/:slug")
  • L665 · app.get("/api/tags")
  • L672 · app.get("/api/categories")
  • L679 · app.get("/api/posts/:slug/comments")
  • L690 · app.post("/api/posts/:slug/comments")
  • L741 · app.get("/api/guestbook")
  • L757 · app.post("/api/guestbook")
  • L796 · app.get("/api/posts/:slug/reactions")
  • L806 · app.post("/api/posts/:slug/reactions")
  • L836 · app.get("/api/settings/public")
  • L874 · app.get("/api/friends")
  • L893 · app.post("/api/friends/apply")
  • L941 · app.get("/api/stats/traffic")
  • L955 · app.get("/rss.xml")
  • L1003 · app.get("/sitemap.xml")
  • L1089 · app.get("/robots.txt")
  • L1223 · app.post("/api/auth/login")
  • L1283 · app.get("/api/auth/me")
  • L1300 · app.use("/api/admin/*")
  • L1315 · app.get("/api/admin/posts")
  • L1322 · app.get("/api/admin/stats")
  • L1329 · app.get("/api/admin/analytics")
  • L1351 · app.get("/api/admin/analytics/ae")
  • L1386 · app.get("/api/admin/comments")
  • L1393 · app.post("/api/admin/comments/:id/approve")
  • L1403 · app.delete("/api/admin/comments/:id")
  • L1413 · app.get("/api/admin/guestbook")
  • L1429 · app.post("/api/admin/guestbook/:id/approve")
  • L1439 · app.delete("/api/admin/guestbook/:id")

Environment references: c.env.WEBHOOK_URLS · env.SITE_ORIGIN · c.env.RESEND_FROM · c.env.ADMIN_EMAIL · c.env.RESEND_API_KEY · c.env.ANALYTICS_WEBSITE_WHITELIST · c.env.AE · c.env.REACTION_SALT · env.ADMIN_PASSWORD · env.JWT_SECRET · c.env.TURNSTILE_SECRET · c.env.ADMIN_PASSWORD · c.env.JWT_SECRET · c.env.DB_PROVIDER · c.env.CLOUDFLARE_ACCOUNT_ID · c.env.CLOUDFLARE_API_TOKEN

server/src/storage/factory.ts ↗
  • L20 · createDatabase calls (conditional paths may differ): d1Adapter.ensureSchema, d1Adapter.ensureSchemaBaseline, adapter.ensureCoreTables

Environment references: env.DB_PROVIDER · env.AUTO_SCHEMA_MIGRATION · env.DB · env.TURSO_URL · env.TURSO_AUTH_TOKEN · env.DATABASE_URL · env.STORAGE_PROVIDER · env.BUCKET · env.S3_ENDPOINT · env.S3_ACCESS_KEY · env.S3_SECRET_KEY · env.S3_BUCKET_NAME · env.S3_REGION · env.S3_PUBLIC_URL

server/src/analytics/ae-tracker.ts ↗
  • L53 · writeAnalyticsPoint calls (conditional paths may differ): parseUserAgent, slice, ctx.ae.writeDataPoint, payload.path.slice, refererHost, bucketResultCount, Math.max, Math.min
  • L81 · bucketResultCount calls (conditional paths may differ): Math.max, Math.floor
  • L93 · isWebsiteAllowed calls (conditional paths may differ): whitelist.trim, filter, map, whitelist.split, toLowerCase, s.trim, allowed.some, host.toLowerCase, endsWith
server/src/analytics/ae-query.ts ↗
  • L47 · runSql calls (conditional paths may differ): fetch, AE_SQL_ENDPOINT, catch, res.text, text.slice, res.json
  • L72 · safeDays calls (conditional paths may differ): Math.max, Math.min, Math.floor
  • L78 · queryAEAnalytics calls (conditional paths may differ): safeDays, Promise.all, runSql, Number, referersFull.map, referersFullList.push, referersFullList.sort, referersFullList.splice, byDay.map, byCountry.map, byReferer.map, byDevice.map, byBrowser.map, byOs.map, byPage.map, byScreen.map, byLang.map, Math.round, heatmap.map, entryPagesRows.map

Environment references: env.CLOUDFLARE_ACCOUNT_ID · env.CLOUDFLARE_API_TOKEN

server/src/analytics/insights.ts ↗
  • L151 · sum calls (conditional paths may differ): items.reduce, Number
  • L160 · formatPercent calls (conditional paths may differ): Math.round
  • L165 · shareItems calls (conditional paths may differ): items.map, Number
  • L182 · rankRisingPages calls (conditional paths may differ): previous.map, Number, slice, sort, current.map, previousMap.get, changePercent
  • L203 · trendWithSignals calls (conditional paths may differ): current.reduce, Math.max, Number, sum, Math.pow, Math.sqrt, current.map, Math.abs
  • L225 · qualityScore calls (conditional paths may differ): Math.min, Math.max, Math.round
  • L240 · pageFilterKey calls (conditional paths may differ): path.startsWith
  • L247 · buildPageFilters calls (conditional paths may differ): sum, pageFilterKey, counts.set, counts.get, Number, filter, map
  • L277 · buildLifecycle calls (conditional paths may differ): posts.map, rising.map, Date.now, map, topPages.filter, page.path.startsWith, page.path.replace, postMap.get, Math.max, Math.floor, getTime, risingMap.get, slice, items.filter
  • L333 · buildReport calls (conditional paths may differ): map, insights.slice, filter, contentSuggestions.slice, Math.abs, join, Math.round, highlights.map, nextActions.map
  • L363 · attachAnalyticsInsights calls (conditional paths may differ): sum, Math.round, changePercent, trendWithSignals, map, slice, trend.filter, item.date.slice, Math.abs, rankRisingPages, qualityScore, buildPageFilters, buildLifecycle, emptySearchAnalytics, insights.push, formatPercent, toFixed, topRisingPages.slice, contentSuggestions.push, search.suggestions.slice
server/src/analytics/ua-parser.ts ↗
  • L19 · parseUserAgent calls (conditional paths may differ): toLowerCase, BOT_RE.test, TABLET_RE.test, MOBILE_RE.test, test
  • L49 · refererHost calls (conditional paths may differ): hostname.toLowerCase
server/src/storage/db/card-layout.ts ↗
  • L8 · normalizeCardWidth calls (conditional paths may differ): Number.isFinite, Math.round, Math.min, Math.max
  • L15 · normalizeCardHeight calls (conditional paths may differ): Number.isFinite, Math.round, Math.min, Math.max
Build and deployment pipeline · 5 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

CI · .github/workflows/ci.yml ↗

Triggers: pull_request, push

Gitleaks · no job dependencies declared

  1. actions/checkout@v7actions/checkout@v7
  2. gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1egitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e

Lint · no job dependencies declared

  1. actions/checkout@v7actions/checkout@v7
  2. actions/setup-node@v7actions/setup-node@v7
  3. Shell commandnpm ci
  4. ESLint (workspaces, fail on warning)npm run lint

Typecheck · no job dependencies declared

  1. actions/checkout@v7actions/checkout@v7
  2. actions/setup-node@v7actions/setup-node@v7
  3. Shell commandnpm ci
  4. Server tsc --noEmitnpx tsc --noEmit
  5. Client tsc --noEmitnpx tsc --noEmit

Build · no job dependencies declared

  1. actions/checkout@v7actions/checkout@v7
  2. actions/setup-node@v7actions/setup-node@v7
  3. Shell commandnpm ci
  4. Build clientnpm run build
Dependabot Auto-Merge · .github/workflows/dependabot-auto-merge.yml ↗

Triggers: pull_request

dependabot · no job dependencies declared

Condition: github.event.pull_request.user.login == 'dependabot[bot]' && !github.event.pull_request.draft

  1. Fetch Dependabot metadatadependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98
  2. Mark major updates for manual reviewgh pr edit "$PR_URL" --add-label "major-update" || trueCondition: steps.dependabot-metadata.outputs.update-type == 'version-update:semver-major'
  3. Auto-Approve minor/patch updatesgh pr review --approve "$PR_URL"Condition: steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' || steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor'
  4. Enable Auto-Merge (Squash) for minor/patchgh pr merge --auto --squash "$PR_URL"Condition: steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' || steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor'
Cloudflare Deploy · .github/workflows/deploy-cloudflare.yml ↗

Triggers: workflow_dispatch

Deploy to Cloudflare (no-secret-overwrite) · no job dependencies declared

  1. Checkoutactions/checkout@v7
  2. Setup Node.jsactions/setup-node@v7
  3. Install dependenciesnpm ci
  4. Preflight (token & inputs)if [ -z "${CLOUDFLARE_API_TOKEN}" ]; then echo "::error::缺少 Actions secret CLOUDFLARE_API_TOKEN(Settings → Secrets and variables → Actions)" exit 1 fi echo "pages_branch=${{ inputs.pages_branch }}" npx wrangler whoami
  5. Apply remote D1 migrations & reconcileprintf 'y\n' | npx wrangler d1 migrations apply monolith-db --remote node ../scripts/reconcile-d1-schema.mjs --remoteCondition: ${{ !inputs.skip_migrate }}
  6. Deploy Workers backendnpm run deploy:server 2>&1 | tee /tmp/deploy-server.log WORKERS_URL="$(grep -oE 'https://[a-zA-Z0-9.-]+\.workers\.dev' /tmp/deploy-server.log | tail -1 || true)" if [ -n "${WORKERS_URL}" ]; then echo "WORKERS_URL=${WORKERS_URL}" >> "$GITHUB_ENV" echo "Workers URL: ${WORKERS_URL}" else echo "::warning::未能从部署输出解析 workers.dev URL" fiCondition: ${{ !inputs.skip_server }}
  7. Resolve API_BASEAPI_BASE="${{ inputs.api_base }}" if [ -z "${API_BASE}" ]; then API_BASE="${WORKERS_URL}"; fi if [ -z "${API_BASE}" ]; then echo "::error::无法确定 API_BASE:请手动指定 api_base 输入,或不要跳过 Workers 部署" exit 1 fi echo "API_BASE=${API_BASE}" >> "$GITHUB_ENV" echo "API_BASE=${API_BASE}"
  8. Set Pages API_BASE secretif [ "${{ inputs.pages_branch }}" = "main" ]; then PAGES_ENV="production"; else PAGES_ENV="preview"; fi printf '%s\n' "${API_BASE}" | npx wrangler pages secret put API_BASE --project-name monolith-client --env "${PAGES_ENV}"Condition: ${{ !inputs.skip_client }}
  9. Build frontendnpm run buildCondition: ${{ !inputs.skip_client }}
  10. Deploy Pages frontendnpx wrangler pages deploy dist --project-name monolith-client \ --branch "${{ inputs.pages_branch }}" \ --commit-dirty=true \ --commit-message "deploy ${{ github.sha }}" 2>&1 | tee /tmp/pages-deploy.log PAGES_URL="$(grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev' /tmp/pages-deploy.log | tail -1 || true)" if [ -n "${PAGES_URL}" ]; then echo "PAGES_URL=${PAGES_URL}" >> "$GITHUB_ENV"; fiCondition: ${{ !inputs.skip_client }}
  11. Post-deploy health checkBASE="${PAGES_URL:-https://monolith-client.pages.dev}" fail=0 for path in / /robots.txt /sitemap.xml /rss.xml /api/health /api/friends; do code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 "${BASE}${path}")" echo "${code} ${BASE}${path}" case "${code}" in 2*|3*) ;; *) fail=1 ;; esac done if [ -n "${WORKERS_URL}" ]; then code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 "${WORKERS_URL}/api/health")" echo "${code} ${WORKERS_URL}/api/health" case "${code}" in 2*|3*) ;; *) fai…Condition: ${{ !inputs.skip_client }}
ESLint Security Scan · .github/workflows/eslint.yml ↗

Triggers: push, pull_request

ESLint 安全扫描 · no job dependencies declared

  1. Checkoutactions/checkout@v7
  2. Setup Node.jsactions/setup-node@v7
  3. Install root dependenciesnpm ci
  4. Install SARIF formatternpm install --no-save @microsoft/eslint-formatter-sarif
  5. Run ESLint on client (SARIF)npx eslint "src/**/*.{ts,tsx}" --format @microsoft/eslint-formatter-sarif --output-file ../eslint-client.sarif
  6. Run ESLint on server (SARIF)npx eslint "src/**/*.ts" --format @microsoft/eslint-formatter-sarif --output-file ../eslint-server.sarif
  7. Upload client SARIFgithub/codeql-action/upload-sarif@v4Condition: always()
  8. Upload server SARIFgithub/codeql-action/upload-sarif@v4Condition: always()
release-please · .github/workflows/release-please.yml ↗

Triggers: push

release-please · no job dependencies declared

  1. googleapis/release-please-action@0dfd8538845b8e92600d271a895a5372865d4062googleapis/release-please-action@0dfd8538845b8e92600d271a895a5372865d4062
package.json ↗
  • build: npm -w monolith-client run build
  • deploy:server: npm -w monolith-server run deploy
  • deploy:client: node -e "const {spawnSync}=require('node:child_process'); const result=spawnSync('npx',['wrangler','pages','deploy','dist','--project-name','monolith-client','--branch','main','--commit-dirty=true'],{cwd:'client',stdio:'inherit',shell:process.platform==='win32'}); process.exit(result.status ?? 1);"
  • deploy:cloudflare: node scripts/deploy-cloudflare.mjs
client/package.json ↗
  • build: tsc && vite build
server/package.json ↗
  • deploy: wrangler deploy

Full upstream document by @one-ea · README.md · snapshot 98759aa

Monolith

Monolith

高质感无服务器边缘博客系统

GitHub Primer 设计语言 · 边缘计算 · 控制台全站管控 · 零运维成本


License: MIT Cloudflare Workers React Hono TypeScript


📚 文档 · ☁️ 在线预览 · 🐛 反馈 · 🛡️ 安全 · 🔒 隐私


✨ 简介

Monolith 是一套运行在 Cloudflare 全球边缘网络上的现代化博客系统,前后端通过适配器模式解耦,零运维即可获得全球 < 50ms 的访问延迟。

设计哲学:内容优先 · 边缘原生 · 沉浸式阅读 · 一切皆可控制台配置。


🌟 核心特性

✍️ 创作体验

  • 沉浸式编辑器 — Markdown + 实时预览,KaTeX 数学公式,代码高亮一键复制
  • 多平台导入 — 一键迁移 WordPress / Ghost / Hexo / Hugo / Jekyll / Halo
  • 内容编排 — 草稿、定时发布、置顶、系列合集、独立页动态导航

🎨 阅读体验(GitHub Primer 设计语言)

  • 日/夜双主题 — 亮暗两套完整 token 对齐 GitHub "浅色/深色 - 默认"(@primer/primitives),蓝色链接与 focus ring、官方语法高亮配色、六色 GitHub 风格标签
  • 站点级主题管控 — 明暗模式 × 视觉风格(简洁 / 液态玻璃)由控制台统一设置,支持跟随系统,首屏 cookie 防闪烁
  • 文章导航 — 自动 TOC、阅读进度条、IntersectionObserver 章节追踪
  • ⌘K 全站搜索 — 防抖检索、键盘导航、关键词高亮
  • Reaction 表情 — 文末轻互动,无需登录即可表态

🎛️ 控制台全站管控

行为配置全部进 D1 设置键值,控制台改完即生效(最长 15 秒),不再依赖重部署:

配置面 能力
安全防护 登录页 Cloudflare Turnstile 人机验证、JWT 会话时长(1/7/30 天)、登录/友链/留言速率限制
通知与集成 Webhook 目标(多地址 + 一键测试)、Resend 邮件收发件人
主题外观 明暗模式、视觉风格,即时预览
发现与注入 片段化代码注入:位置/作用域/同意策略独立控制,常用统计服务预设模板,旧数据一键迁移
搜索优化 robots.txt 追加规则(白名单校验)、sitemap 归档开关与额外 URL(自动去重)、站点域名
AE 采集 访客统计开关、站点白名单(主域自动匹配子域)

⚡ 性能架构

  • 边缘原生 — Hono + Cloudflare Workers,无冷启动,全球 < 50ms
  • 存储适配 — 数据库 D1 / Turso / PostgreSQL,对象存储 R2 / S3 兼容
  • 轻量交付 — Pages 上传 2.2MB / 97 个文件(旧格式字体经 .assetsignore 裁剪),PWA 离线可用
  • 访客分析 — 内置 D1 轻量统计;Cloudflare 部署额外解锁 Analytics Engine 增强仪表板(UV/停留时长/浏览器/系统/分辨率/语言)

🛡️ 安全合规

  • 认证与防护 — JWT + Turnstile 人机验证 + 可调限流,CSP/HSTS 全套头,SSRF 拦截
  • 平台安全线 — GitHub secret scanning + push protection、CodeQL extended 查询集、CI Gitleaks 全历史扫描(含 AI 密钥等非提供商模式自定义规则)
  • 隐私优先 — Cookie 同意横幅,第三方脚本按片段门控,GDPR 数据导出
  • 多端备份 — JSON / R2-S3 / WebDAV 自由切换

🔍 SEO 与洞察

  • SEO 友好 — sitemap、RSS 2.0、JSON-LD、OG/Twitter Card
  • 数据洞察 — 浏览量、14 日趋势、热门 Top 10

🏗️ 架构

        ┌──────────────────────────────────────────┐
        │            Cloudflare Edge               │
        │       (200+ PoPs · global anycast)       │
        └──────────────────────────────────────────┘
                                          │
        ┌─────────────────────────────────┼──────────────────┐
        ▼                                 ▼                  ▼
┌──────────────────┐            ┌──────────────────┐  ┌──────────────┐
│  Cloudflare      │  /api/*    │  Cloudflare      │  │ Cloudflare   │
│  Pages           │ ─────────▶ │  Workers         │  │ R2 / S3      │
│  React 19 SPA    │  反向代理  │  Hono Router     │  │ 媒体 / 备份  │
│  + PWA           │            │  Auth · Admin    │  └──────────────┘
│  Pages Functions │            │  Storage Factory │
└──────────────────┘            │  D1/Turso/PG     │
                                │  R2/S3           │
                                └──────────────────┘

分层职责

层级 模块 关键路径
边缘网络 Cloudflare 全球 anycast 200+ PoPs · 自动 TLS · DDoS 防护
前端 React SPA + Pages Functions client/src · client/functions
后端 Hono Workers + Storage Factory server/src/index.ts · server/src/storage
持久层 D1 / Turso / PostgreSQL · R2 / S3 server/src/storage/db · server/src/storage/object

关键设计决策

  • 适配器模式 — IDatabase / IObjectStorage 统一接口,切换后端零侵入
  • 配置分层 — 行为配置进 D1 settings(控制台管理);Workers secret 只放凭据与基础设施拓扑
  • Pages Functions 反向代理 — 前端域名直连 /api/*,规避 CORS 复杂度
  • Drizzle ORM — 所有 SQL 参数化,Schema 一处定义、三端同步
  • Monorepo 单脚本部署 — npm run deploy:cloudflare 串起迁移 → Workers → Pages 全链路

详细架构与设计决策参阅 Wiki · 架构概览。


🚀 快速开始

git clone https://github.com/one-ea/Monolith.git && cd Monolith
source "$HOME/.nvm/nvm.sh"  # bash/zsh 按需加载 nvm
nvm install && nvm use
npm ci
npm run doctor:local
npm run dev

完整环境准备、密钥配置与本地数据库初始化参阅 Wiki · 快速开始。

☁️ 部署

npx wrangler login          # 首次部署一次即可
npm run deploy:cloudflare   # 远程迁移 → Workers → API_BASE 注入 → Pages

生产 secret 已存在而本地无明文时,请改走不覆盖 secret 的分步部署链路,业务密钥全程不被触碰。

方案 状态 适用场景
本机 CLI npm run deploy:cloudflare ✅ 生产验证 首次部署推荐
不覆盖 secret 分步链路 ✅ 生产验证 已上线站点日常更新
GitHub Actions Cloudflare Deploy ✅ 生产验证(含部署后自动健康检查) 手动触发的一键 CI 部署

📚 文档导航

入口 内容
Wiki · 部署指南 部署完整指南(速通 + 密钥清单 + 分步链路 + 排错)
Wiki · 控制台配置 控制台可配置项与 secret 边界总览
Wiki 架构、API、二次开发
SECURITY.md 安全策略与漏洞披露
PRIVACY.md 隐私政策
LICENSE MIT 开源协议

🤝 贡献

欢迎通过 Issue 反馈问题,或通过 Pull Request 贡献代码。提交前请阅读 Wiki · 贡献指南。

📄 License

基于 MIT License 开源发布。

Crafted with ♡ on the edge.

Frequently asked about Monolith

What is Monolith?+

Monolith is a self-hosted Ghost/WordPress alternative built on the Cloudflare developer platform. A Markdown blog with a Cloudflare API, media storage and administration.

What does Monolith replace?+

Monolith is listed as an alternative to Ghost, WordPress. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does Monolith use?+

Monolith is built on Analytics Engine, D1, Pages, R2, Workers.

How much does Monolith cost to run?+

The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply. Deploy the documented Pages frontend and Worker API with your own resource IDs and secrets; the collected active config covers the API. Keep one-minute cron and dynamic traffic under Workers Free request/10 ms CPU limits, D1 queries/storage and Standard R2 within included quotas. Analytics Engine currently includes 100,000 written points/day and 10,000 read queries/day on Workers Free; its official pricing may change when billing begins. Optional Resend mail, custom domains and alternative Turso/PostgreSQL/S3 services have separate costs. Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured. Dynamic Pages Functions requests share the Workers account allowance; only requests that do not invoke Functions count as free unlimited static asset requests. D1 Free allowance: 5 million rows read/day, 100,000 rows written/day and 5 GB total storage; unindexed scans and history retention consume quota. R2 Standard allowance: 10 GB-month storage, 1 million Class A and 10 million Class B operations/month; account activation may require billing setup, and other storage classes are excluded. Check current Cloudflare pricing before deploying.

Is Monolith open source?+

The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/one-ea/Monolith/98759aa1bf1f82dba77cecb47858b1dfae644caa/LICENSE. Source code and contributor credit are available at https://github.com/one-ea/Monolith.

Discussion · 0

sign in to comment →
No comments yet — be the first.