Monolith is the kind of project that makes the Cloudflare ecosystem feel less like a collection of primitives and more like an application platform.
It is not another static site generator. It is a dynamic blog/CMS designed around Cloudflare Pages, Workers, D1, R2, and an admin console.
That makes it a useful follow-up to the Cloudflare Workers, D1, KV, and R2 serverless stack post and the CloudflareBase post. Monolith is narrower than CloudflareBase, but more immediately usable if your goal is simply: “I want a blog with a dashboard, media, comments, backups, and analytics on Cloudflare.”
What is Monolith?
Monolith is an open-source edge-native blog system.
Its README describes the philosophy as:
content first, edge native, immersive reading, everything configurable from the console
The practical feature set is closer to a small CMS than a static blog:
- Markdown editor with live preview;
- KaTeX math;
- syntax highlighting and code-copy buttons;
- imports from WordPress, Ghost, Hexo, Hugo, Jekyll, and Halo;
- drafts, scheduled posts, pinned posts, series, custom pages, comments, guestbook, friend links, and reactions;
- admin console for settings, theme, snippets, SEO, backups, media, comments, pages, and analytics;
- RSS, sitemap, robots.txt, JSON-LD, OpenGraph, and Twitter cards;
- PWA offline support;
- built-in D1 analytics plus optional Cloudflare Analytics Engine dashboards.
The project is MIT licensed.
The Important Self-Hosting Detail
Monolith is self-hostable, but not in the classic Docker Compose sense.
Self-hosting here means:
deploy the Pages frontend, Workers backend, D1 database, and R2 bucket into your own Cloudflare account
There is no docker-compose.yml in the repository, and that is not an accident. The app is written for Cloudflare bindings and edge deployment.
So the decision is simple:
| If you want… | Pick… |
|---|---|
| A static Markdown site with Git-based publishing | Hugo, Jekyll, Astro, or another SSG |
| A traditional server CMS with a normal database | WordPress, Ghost, Directus, Strapi |
| A Cloudflare-native blog CMS with admin UI and edge APIs | Monolith |
Architecture Overview
The stack is cleanly split:
| Layer | Monolith Uses |
|---|---|
| Frontend | React 19 SPA, Vite, Tailwind CSS 4 |
| Hosting | Cloudflare Pages |
| Frontend proxy | Cloudflare Pages Functions |
| Backend API | Hono on Cloudflare Workers |
| ORM | Drizzle |
| Default database | Cloudflare D1 |
| Database alternatives | Turso/libSQL, PostgreSQL |
| Default object storage | Cloudflare R2 |
| Object storage alternative | S3-compatible storage |
| PWA | vite-plugin-pwa |
| Bot protection | Optional Cloudflare Turnstile |
| Optional Resend | |
| Advanced analytics | Optional Cloudflare Analytics Engine |
The frontend calls same-origin paths such as /api/*, /cdn/*, /rss.xml, /sitemap.xml, and /robots.txt.
Pages Functions then proxy those requests to the Worker backend through the API_BASE binding. That keeps the public site domain clean and avoids making the browser talk directly to a separate API domain.
What Runs on Cloudflare?
The default Cloudflare deployment uses:
| Cloudflare Primitive | What Monolith Uses It For |
|---|---|
| Pages | Serves the React/Vite frontend |
| Pages Functions | Proxies frontend routes to the Worker API |
| Workers | Runs the Hono backend API |
| D1 | Stores posts, pages, settings, comments, reactions, traffic stats, versions, guestbook messages, and links |
| R2 | Stores uploaded media and backup files |
| Analytics Engine | Optional advanced visitor analytics |
| Turnstile | Optional login bot protection |
| Wrangler secrets | Admin password, JWT secret, Resend key, Turnstile secret, Analytics token |
| Cron triggers | Scheduled Worker tasks |
That puts Monolith close to the pure Cloudflare serverless model.
The caveat is that Monolith also has adapters:
DB_PROVIDER=tursocan move the database to Turso/libSQL;DB_PROVIDER=postgrescan move the database to PostgreSQL;STORAGE_PROVIDER=s3can move object storage to an S3-compatible provider.
Those adapters are good for portability, but the default deployment path is Cloudflare Pages + Workers + D1 + R2.
Console and Content Workflow
The admin console is the main reason to look at Monolith instead of a plain SSG.
From the code and UI routes, the console covers:
- dashboard;
- post editor;
- post version restore;
- comments moderation;
- guestbook moderation;
- friend links;
- media upload and deletion;
- public pages;
- backup export/restore;
- SEO configuration;
- site settings;
- Cloudflare Analytics Engine status and dashboard.
Behavior settings are stored in the database and cached briefly, so many changes can be made from the console without redeploying the site.
That is the practical CMS tradeoff:
SSG = simpler deployment, Git is the editor
Monolith = more moving parts, browser admin console
Security and Privacy Notes
Monolith uses an admin-password login with JWT sessions.
Important security pieces in the repo:
ADMIN_PASSWORDandJWT_SECRETare required secrets;- optional
TURNSTILE_SECRETprotects the login form when enabled; - rate limits exist for login, friend-link submissions, and guestbook/comment style flows;
- Markdown rendering blocks dangerous URI schemes;
- DOMPurify is used for user-provided rich text;
- SSRF protection blocks private, loopback, and link-local target ranges;
- the Worker sets security headers such as HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy;
- commenter emails are not returned in the public comments API;
- third-party script injection is gated by consent logic.
The privacy model is also Cloudflare-centered. Normal traffic data and content state live in the edge database/object storage you configure, while optional third-party scripts and Resend email are separate integrations.
Deployment Notes
The README’s Cloudflare deploy path is:
git clone https://github.com/one-ea/Monolith.git
cd Monolith
npm ci
npx wrangler login
npm run deploy:cloudflare
That deploy script is not just a frontend upload. It orchestrates:
- remote D1 migrations;
- D1 schema reconciliation;
- Worker secrets for
ADMIN_PASSWORDandJWT_SECRET; - Worker deployment;
- Pages
API_BASEsecret; - frontend build;
- Pages deployment.
For daily production updates, the GitHub Actions workflow is more conservative. It deploys through a no-secret-overwrite path, so existing business secrets such as ADMIN_PASSWORD, JWT_SECRET, and REACTION_SALT are not touched during a normal release.
Local Trial Notes
I did not deploy Monolith to Cloudflare from this machine because that would require real account bindings and secrets.
I did validate the code path that can be tested safely:
npx -y -p node@22 node -v
npx -y -p node@22 -p npm@11 npm ci
NODE22_DIR=$(dirname "$(npx -y -p node@22 which node)") && PATH="$NODE22_DIR:$PATH" npm run build
NODE22_DIR=$(dirname "$(npx -y -p node@22 which node)") && PATH="$NODE22_DIR:$PATH" npm run check
Results:
| Check | Result |
|---|---|
| Install | Passed in a temporary clone |
| Frontend production build | Passed |
| PWA generation | Passed; dist/sw.js and Workbox assets generated |
| Workspace type checks | Passed for client and server |
| Local doctor | Failed because server/.dev.vars was intentionally missing |
One repo detail to notice: .nvmrc currently says 20, while the root package.json requires node >=22, and the GitHub Actions deployment uses Node 22. I would use Node 22 for installs and CI parity.
npm ci also reported nine audit findings at the time of testing. I did not run npm audit fix, because that would mutate dependency resolution beyond the scope of reviewing the project.
Homelab Note
I added a Home-Lab note for Monolith, but not a Docker Compose stack:
That folder is intentionally documentation-only.
For Monolith, a fake compose file would be misleading. The correct local/private deployment path is:
Node 22 + Wrangler + Cloudflare account + D1 + R2 + Pages + Workers
If you want a blog/CMS that runs as a single local container, use a different project.
How I Would Use It
I would consider Monolith for:
- a personal technical blog where I want browser-based editing;
- a project journal with comments and simple reactions;
- a Cloudflare-first publishing stack;
- a blog migration from WordPress, Ghost, Hugo, Jekyll, Hexo, or Halo;
- a site where global edge latency matters but I do not want to run a VPS.
I would not choose it for:
- a pure static docs site;
- a WordPress plugin ecosystem;
- multi-author editorial workflows with mature role management;
- a homelab-only Docker stack;
- environments where Cloudflare platform coupling is unacceptable.
FAQ
Is Monolith fully serverless?
Yes, when deployed on the default Cloudflare path.
The app uses Cloudflare Pages for the frontend, Pages Functions for proxying, Workers for API compute, D1 for relational data, and R2 for files/backups. That means there is no VM, no Docker daemon, no OS patching, and no database server to administer.
The tradeoff is platform coupling. The app depends on Cloudflare bindings such as DB, BUCKET, AE, and Pages API_BASE.
Can I deploy Monolith with Docker Compose?
Not as shipped.
The repository does not include a Dockerfile or compose stack. It is designed around Wrangler and Cloudflare deployment.
You can run development locally with Node and Wrangler, but production self-hosting means deploying into your own Cloudflare account, not running a portable container stack on a VPS.
What Cloudflare services are required and what are optional?
Required for the normal Cloudflare deployment:
| Service | Purpose |
|---|---|
| Workers | Hono backend API |
| Pages | React/Vite frontend hosting |
| Pages Functions | Same-origin proxy to the Worker |
| D1 | Default application database |
| R2 | Default media and backup object storage |
| Wrangler secrets | Admin password and JWT secret |
Optional or feature-specific:
| Service / Integration | Purpose |
|---|---|
| Turnstile | Login anti-bot challenge |
| Analytics Engine | Advanced analytics dashboard |
| Resend | Email notifications |
| Webhooks | External notification fan-out |
| Turso | Alternative database backend |
| PostgreSQL | Alternative database backend |
| S3-compatible storage | Alternative object storage backend |
| WebDAV | Backup target |
Which variables and secrets do I need to configure?
Minimum production:
export ADMIN_PASSWORD='replace-with-a-strong-password'
export JWT_SECRET="$(openssl rand -base64 32)"
npx wrangler login
npm run deploy:cloudflare
Core bindings in server/wrangler.toml:
| Name | Type | Purpose |
|---|---|---|
DB |
D1 binding | Main database |
BUCKET |
R2 binding | Media and backups |
AE |
Analytics Engine binding | Optional enhanced analytics |
SITE_ORIGIN |
Worker var | Absolute site URL for RSS/sitemap/robots |
API_BASE |
Pages secret | Worker URL consumed by Pages Functions |
Common optional secrets:
npx wrangler secret put TURNSTILE_SECRET --name monolith-server
npx wrangler secret put RESEND_API_KEY --name monolith-server
npx wrangler secret put CLOUDFLARE_API_TOKEN --name monolith-server
npx wrangler secret put REACTION_SALT --name monolith-server
If using adapters instead of Cloudflare defaults, configure DB_PROVIDER, STORAGE_PROVIDER, and the matching Turso/Postgres/S3 variables.
Can it run on Cloudflare only, or does it need external databases?
The default path can run on Cloudflare services: Pages, Workers, D1, R2, and optional Analytics Engine.
External databases are not required for the default deployment.
They are adapter options. You can move the database to Turso or PostgreSQL, and object storage to S3-compatible storage, but doing that makes the deployment less Cloudflare-contained.
How does Monolith compare with Hugo or Jekyll?
Hugo and Jekyll generate static files. They are excellent when Git is the publishing workflow and you want minimal runtime state.
Monolith is dynamic. It keeps posts, settings, comments, reactions, media, pages, and analytics in runtime services and exposes a browser admin console.
So the comparison is:
| Tool | Best For |
|---|---|
| Hugo/Jekyll | Static Markdown sites with simple hosting |
| Monolith | Cloudflare-hosted blog CMS with admin UI and dynamic features |
What kind of CMS is Monolith?
Monolith is best described as a database-backed blog CMS for Cloudflare, not a traditional static-site CMS.
It has a Markdown-first writing experience, but the source of truth is not a folder of .md files in Git. Posts and pages are stored in the database through the app’s storage adapter: D1 by default, or Turso/PostgreSQL if you choose another provider.
That puts it somewhere between:
| Category | Example | How Monolith Relates |
|---|---|---|
| Static site generator | Hugo, Jekyll, Astro | Monolith can import Markdown from these tools, but does not publish by committing .md files back to Git |
| Traditional CMS | WordPress, Ghost | Monolith has admin editing, comments, pages, media, and settings, but runs on Cloudflare edge services |
| Headless CMS | Directus, Strapi, Payload | Monolith has an API-backed content model, but it is opinionated around a blog/public site rather than being a generic content platform |
| Edge-native blog CMS | Monolith | The intended category: Cloudflare Pages + Workers + D1 + R2 with a browser admin console |
So I would not treat it as a direct replacement for a Git-backed Hugo workflow. I would treat it as a dynamic blog CMS that happens to use Markdown as the authoring format.
How does Monolith compare with WordPress or Ghost?
WordPress and Ghost are mature CMS/blog systems built around a server process and database.
Monolith is much younger and more Cloudflare-specific, but avoids running a VPS or container host. It is attractive if you want a modern edge-native blog CMS and are comfortable with Wrangler, D1, R2, and Cloudflare platform limits.
WordPress still wins for plugin ecosystem. Ghost still wins for polished publication/member workflows. Monolith wins when the Cloudflare-native architecture is the feature.
How does this relate to CloudflareBase?
CloudflareBase is a Firebase-style backend platform on Cloudflare.
Monolith is an actual blog/CMS application.
The relationship is:
CloudflareBase = platform/backend layer for building apps
Monolith = finished publishing app built directly on Cloudflare primitives
Both are part of the same broader pattern: Workers plus Cloudflare storage primitives can replace a local server stack for certain web apps.
What is Hono, and why is Monolith using it?
Hono is a small TypeScript web framework commonly used on edge runtimes such as Cloudflare Workers.
In Monolith, Hono is the backend router layer. It receives requests such as:
GET /api/posts
GET /api/posts/:slug
POST /api/auth/login
POST /api/admin/posts
PUT /api/admin/posts/:slug
GET /rss.xml
GET /sitemap.xml
Why it fits here:
- it is lightweight enough for Workers;
- it gives clean routing and middleware without Express-style Node server assumptions;
- it works well with Web Fetch APIs, which Workers use natively;
- it keeps the backend portable across edge-like runtimes more easily than a full Node server framework.
So Hono is not the CMS. Hono is the HTTP framework that lets the Worker expose the CMS API.
How does Hono compare with Better Auth?
They solve different layers of the stack.
Hono is a web framework. It gives the Worker a clean way to define routes, middleware, request parsing, responses, and API endpoints.
Better Auth is an authentication framework. It handles users, sessions, sign-in methods, OAuth providers, organization/team models, password flows, and auth-related database tables.
So they are not competitors:
| Layer | Hono | Better Auth |
|---|---|---|
| Main job | HTTP routing and middleware | User authentication and sessions |
| Similar to | Express/Fastify-style routing, but edge-friendly | Auth.js, Lucia-style app auth, custom user/session systems |
| Used for | /api/posts, /api/admin/posts, /rss.xml, /sitemap.xml |
Login, users, sessions, OAuth, roles, API auth |
| Does it store users? | No | Yes, through an adapter/database |
| Does it replace the CMS database? | No | No |
| Cloudflare fit | Very good for Workers | Good if paired with a supported adapter/runtime setup |
Monolith currently uses Hono, but it does not use Better Auth. Its admin authentication is simpler: admin password plus JWT session, with optional Turnstile.
If Monolith wanted multi-user editing, invitations, editor/admin roles, Git commit attribution, OAuth login, or per-user publishing permissions, Better Auth would be a natural piece to add. Hono would still remain the API framework underneath.
Does the PWA mode mean the whole CMS works offline?
No.
The Vite PWA setup generates a service worker, precaches frontend assets, caches /cdn/* images with a cache-first strategy, and uses network-first caching for non-admin API calls.
That means Monolith can be useful offline for already-loaded public reading paths:
- the app shell can load after a previous visit;
- cached images can still render;
- some previously fetched public API responses may be available if the network is down;
- installed-PWA behavior can make the site feel app-like on mobile/desktop.
But the admin CMS is not offline-first.
The service worker intentionally excludes admin/auth routes from runtime API caching. Editing, publishing, media uploads, comments moderation, backups, login, and settings changes still need the Worker API and database/storage services online.
So the short version is:
offline reading cache: yes, partly
offline CMS editing/publishing: no
How far is Monolith from offline PWA editing and Git-backed static publishing?
Your target architecture is:
authenticated users edit locally in a PWA
PWA syncs Markdown files to Git
PWA uploads images to Cloudflare R2
Astro builds a static site from .md files
Cloudflare Pages deploys the generated static site
That is a solid architecture, but it is not Monolith’s current architecture.
Monolith already has some useful pieces:
| Needed Capability | Monolith Today |
|---|---|
| Markdown editor | Yes |
| Markdown rendering/preview | Yes |
| Image upload pipeline | Yes, via Worker API to R2/S3-compatible storage |
| Cloudflare Worker backend | Yes, Hono |
| Admin authentication | Yes, simple admin password + JWT |
| PWA install/cache shell | Yes |
| Import from Hugo/Jekyll/Hexo Markdown | Yes |
| Backup/export | Yes, JSON/R2/WebDAV |
But the parts that make your desired workflow safe and static are missing:
| Needed Capability | Current Gap |
|---|---|
| Offline draft database | Needs IndexedDB/local-first storage for posts, pages, assets, metadata, and sync state |
| Sync queue | Needs an outbox for create/update/delete operations that can retry when online |
| Conflict handling | Needs merge/conflict UI when two users edit the same Markdown file |
| Git provider integration | Needs GitHub/GitLab API or a GitHub App to create commits or pull requests |
| Static content mapping | Needs deterministic mapping from CMS state to Astro paths like src/content/blog/my-post.md |
| Frontmatter writer | Needs Markdown + YAML/TOML frontmatter serialization |
| R2 asset flow | Needs direct/signed upload or Worker-mediated upload, then stable public URLs inserted into Markdown |
| Authenticated multi-user model | Needs real users, roles, permissions, and audit logs; Better Auth would fit here better than a single admin password |
| Build trigger | Needs Cloudflare Pages deploy hook or commit-triggered Pages build |
| Safe publishing workflow | Needs branch/PR mode, draft mode, path allowlists, commit signing strategy, and rollback |
So Monolith is maybe close in editor feel, but far in publishing model.
The current model is:
React admin editor -> Worker API -> D1 database + R2 media -> dynamic Cloudflare site
Your desired model is:
PWA editor -> local IndexedDB drafts -> Git commits/PRs with .md files -> Astro static build -> Cloudflare Pages
\-> R2 media uploads -> image URLs in Markdown
That is closer to a Git-backed static CMS than to Monolith’s current database-backed CMS.
How would I design the offline Astro + R2 + Cloudflare Pages version safely?
I would split it into three surfaces.
First, the editor PWA:
- stores drafts, images-to-upload, frontmatter, and sync state in IndexedDB;
- works offline for writing and previewing Markdown;
- has an explicit “Sync” button instead of relying only on browser background sync;
- shows per-file sync status: local draft, uploaded asset, committed Markdown, build pending, live;
- never treats “saved locally” as “published”.
Second, the authenticated sync API:
- uses real user accounts and roles, probably Better Auth rather than a single shared admin password;
- uploads images to R2 through signed URLs or a Worker upload endpoint;
- returns stable public R2 URLs for Markdown insertion;
- writes Markdown/frontmatter to a Git branch through GitHub/GitLab APIs or a GitHub App;
- creates pull requests for non-admin users, while trusted admins may commit directly;
- only allows writes under approved paths such as
src/content/blog/andpublic/metadata/; - records audit events: who changed what, when, and which commit/build was produced.
Third, the static build path:
- Astro reads committed
.mdor.mdxfiles from Git; - images referenced in Markdown point to R2 public URLs or a Cloudflare image proxy;
- Cloudflare Pages builds on commit or deploy hook;
- rollback is normal Git rollback: revert commit or restore previous branch state;
- production output remains static, so readers do not depend on the CMS database being online.
That gives a strong separation:
editing system can be dynamic
published site stays static
Git remains the durable content history
R2 stores large binary assets
Cloudflare Pages serves the final site safely
This would be a different product from Monolith, but Monolith provides useful reference code for the editor, Markdown preview, R2 media handling, Workers API shape, and Cloudflare deployment ergonomics.
Can Monolith modify an existing static page hosted on Cloudflare Pages?
Not directly.
Monolith does not edit the files of an existing Cloudflare Pages static site. It does not open your Pages project’s Git repository, change Markdown files, commit to Git, and trigger a Pages rebuild.
Instead, Monolith deploys its own React frontend to Pages and stores content in the backend database. Its “pages” feature means CMS-managed pages stored in the pages database table, written in Markdown and served through Monolith’s routes such as /page/:slug.
That is a very different workflow from:
edit content/posts/example.md
git commit
git push
Cloudflare Pages rebuilds the static site
If your goal is to keep the current static Pages site and edit its .md files from a UI, Monolith is not the right fit as-is. You would want a Git-backed CMS such as Decap CMS, Pages CMS, TinaCMS, or a custom GitHub/GitLab-editing workflow.
Is the content Markdown-centered and Git-backed?
Markdown-centered: yes.
Git-backed: no, not in the current architecture.
Monolith stores content as text in the database for posts and pages. The editor is Markdown-oriented, public rendering turns Markdown into HTML, and importers understand Markdown/frontmatter from Hugo, Hexo, Jekyll, and other systems.
But after import, the canonical copy lives in the configured database:
D1 / Turso / PostgreSQL = content source of truth
R2 / S3 = media and backup object storage
JSON / R2 / WebDAV = backup/export paths
Git = app source code, not content source of truth
So backup is JSON/R2/WebDAV-centered, not Git-centered.
You could build a Git export pipeline around Monolith’s backup JSON or admin API, but that would be an extra integration. It is not the default publishing model.
What did the local tests show?
The safe local checks passed:
npm ci passed
npm run build passed
npm run check passed
PWA service worker build passed
The runtime doctor correctly failed because server/.dev.vars was missing. That file would need at least ADMIN_PASSWORD and JWT_SECRET for local development.
I did not create that file in the repo and did not deploy to Cloudflare.
Prompt Used for the Cover
Text-free editorial cover for a Cloudflare-native blog CMS called Monolith. Show a polished publishing console, markdown document panels, media bucket, edge worker node, small database cylinder, and global edge arcs. Clean high-end technical illustration, dark neutral background with orange, cyan, white, and green accents, no logos, no readable text, no fake UI labels, no watermarks.
Comments