Skip to content

Sveltia CMS

Sveltia CMS is a fully client-side CMS that edits markdown and JSON files directly in GitHub. No server-side code, no adapter changes — the Astro site stays fully static. The admin UI lives at /admin on the production domain.


  1. public/admin/index.html loads the Sveltia CMS script from unpkg
  2. public/admin/config.yml tells it which files to manage and what fields each has
  3. Editors log in via GitHub OAuth (handled by a separate Cloudflare Worker)
  4. Edits are committed directly to the GitHub repo — Cloudflare Pages redeploys automatically

All editable content lives in src/data/*.json and src/content/blog/. The .astro pages import from these files.

File What’s editable
src/data/pricing.json Website plans, SEO packages, SEO audit price
src/data/faqs.json FAQs for homepage, web design, SEO, hosting pages
src/data/about.json Team members, stats, how-we-work section
src/data/homepage.json Clients list, process steps
src/data/comparison.json Comparison table rows
src/content/blog/ Blog posts including FAQs per post for Google rich results

Things that stay hardcoded in .astro (contain inline SVG strings — no widget type for these): services cards, who-we-help grid, values section.


juju-template ships with both files pre-configured:

File Purpose
public/admin/index.html Loads Sveltia CMS script
public/admin/config.yml CMS collection and field definitions

Every new client project needs these four things configured:

Change the two backend fields at the top:

backend:
name: github
repo: ctrl07/<client-repo-name> # ← change to client's GitHub repo
branch: main
base_url: https://sveltia-cms-auth.kurlekarsuyog.workers.dev

Go to github.com/settings/developers → OAuth Apps → New OAuth App:

Field Value
Application name <Client Name> CMS
Homepage URL https://<client-domain>
Authorization callback URL https://sveltia-cms-auth.kurlekarsuyog.workers.dev/callback

Copy the Client ID and generate a Client Secret.

In Cloudflare dashboard → Workers → sveltia-cms-auth → Settings → Variables:

Variable Value
GITHUB_CLIENT_ID from step 2
GITHUB_CLIENT_SECRET from step 2
ALLOWED_DOMAINS add <client-domain> (comma-separate if multiple)

The same Worker can serve multiple client sites — just extend ALLOWED_DOMAINS.

Only GitHub accounts with write access to the repo can log in. Go to repo Settings → Collaborators → add the client’s GitHub username.


Visit https://<client-domain>/admin → click Login with GitHub → authorise the OAuth app.


Cloudflare Pages merges all matching _headers rules — same header names are comma-joined, not overridden. The global /* CSP will be appended to any /admin/* CSP, making the combined value invalid.

Fix: use ! HeaderName to negate the global rule before setting your own:

/*
Content-Security-Policy: default-src 'self'; ...
/admin/*
! Content-Security-Policy
Content-Security-Policy: default-src * blob: data: 'unsafe-inline' 'unsafe-eval'; frame-ancestors 'none'

This is already in juju-template’s public/_headers. Don’t remove it.


publish_mode: editorial_workflow is enabled in config.yml. Blog posts go through three stages before reaching the live site:

Stage What it means
Draft Work in progress — stored in a git branch, invisible to the site
In Review Ready for feedback — still in a branch
Ready Approved — merging this branch triggers a Cloudflare Pages rebuild

Nothing appears on the live site until a “Ready” post is merged to main.


  1. Editor changes a field and clicks Save
  2. Sveltia CMS commits the updated file to GitHub
  3. Cloudflare Pages detects the commit and rebuilds (2–3 minutes)
  4. Change is live on the site

For Draft and In Review posts, the commit goes to a separate branch — nothing changes on the live site until the post is Published (merged to main).


  1. Add the field to the relevant src/data/*.json file
  2. Add the corresponding widget to public/admin/config.yml
  3. Import and use the new field in the relevant .astro file
  4. Commit and push