Docs
Architecture
How Seedly Sites is put together - a multi-tenant CMS and builder on your app host, publishing static client sites to Cloudflare Pages.
Last updated
Seedly Sites splits cleanly into two halves: the platform you and your clients log into, and the static sites your clients' visitors see. Understanding the split explains why published sites are fast, cheap to host, and safe to edit.
The Platform Half#
The CMS and the visual builder run as an application on your app host (Railway is the supported setup), backed by a Postgres database.
- The CMS holds every tenant's pages, blog posts, media records, redirects, settings, and users, with strict per-tenant scoping. Operators see all tenants; a client user is scoped to their own site.
- The visual builder runs alongside the CMS on the same origin. Pages are stored as a structured element tree (Section, Row, Column, Element), not as rendered HTML, which is what makes them editable, restyleable, and portable.
- Media uploads land in R2 object storage and are served to published sites from each site's own domain.
This half is the only part with a server and a database, and only logged-in users ever touch it.
The Published Half#
When you press Deploy, the platform renders the tenant's published content into plain static files - HTML and CSS with no runtime framework - and pushes them to that client's own Cloudflare Pages project, created automatically the first time. Each deploy also regenerates the site's sitemap, robots rules, and redirects.
- One project per client site. Every tenant gets its own Pages project and its own custom domain. Client sites never share a URL namespace.
- Static by architecture. A visitor request is served from Cloudflare's edge as a prebuilt file. There is no database call, no origin server, and no plugin stack in the request path, which is why the output is fast and Core Web Vitals friendly by default.
- Portable output. The published site is self-contained static markup. If a client ever leaves, their site is exportable in a form any static host can serve.
Why the Split Matters#
- Editing is decoupled from serving. The CMS can be updated, restarted, or even down without affecting any live client site - the static files keep serving.
- Publish is not Deploy. Publishing marks content live in the CMS; the static site changes only when you deploy. Every change to a live site is a deliberate, operator-controlled step with a readiness check in front of it. See Core Concepts.
- Costs stay flat. You pay for one app host and media storage regardless of how many sites you run. The static hosting side scales on Cloudflare's free static tier. Hosting is not zero-cost, but it does not grow per site the way per-site subscriptions do. See Hosting & domains.
Knowing an Update Actually Landed#
A deploy finishing is not the same thing as an update being complete. When a release changes the database, the code can be live while the migration has not been run, and the platform will keep serving in that state.
There is a deep health check for exactly this: it reports whether the running code and the database agree, so you can confirm an update rather than infer it from a green deploy. Use it after any update that lists migrations. See Updating.
Domains#
Each tenant serves on its own custom domain, connected in Cloudflare. The platform's preview URLs are kept out of search engines so only the client's real domain ranks. See Hosting & domains for the setup walkthrough.
Where Your Data Lives#
Everything is on infrastructure you control: content and settings in your Postgres database, media in your R2 bucket, published files in your Cloudflare account, and the publish pipeline in your private GitHub repository. Backups and exports are built in - see Backups & disaster recovery.
