CTAO Science Portal — static site + git-based CMS
Public portal for CTAO news, built as a fully static site. Editors get a browser WYSIWYG editor (Sveltia CMS) that commits Markdown to a Gitea repo; a build job turns commits into static HTML. No application or database runs on the public path.
Live demo: https://astro.isl-dev.grid.cyfronet.pl · Gitea on the demo machine: https://astro-git.isl-dev.grid.cyfronet.pl
Repository layout & deployment
Code and content are SEPARATE repositories, so editorial commits never mix with development:
| Repo | Holds | Source of truth | Who commits |
|---|---|---|---|
ctao-portal (this one, Bitbucket) |
Templates, CSS, deploy | development | the team |
ctao/content (Gitea on the demo machine) |
news/, pages/, uploads/ |
editorial | Sveltia CMS / editors |
The demo machine polls ITS Gitea (ctao/portal mirror + ctao/content) and
republishes on a push to either — so a push to Bitbucket does NOT deploy;
deploying code = pushing it to the machine's Gitea (a deliberate step). The
build overlays content onto code (deploy/build.sh); the content paths are
gitignored here.
Stack
- Astro (static output). Pages are
.astrotemplates (plain HTML with a JS frontmatter block). No React/Vue/Svelte components, no client-side framework. - Plain CSS — everything lives in
src/styles/global.cssas design tokens (custom properties) + rules. No Tailwind, no preprocessor. The design system (tokens, type scale, spacing, motion policy) is documented inDESIGN.md— read it before touching styles. - Sveltia CMS — a single prebuilt JS bundle, vendored in
public/vendor/sveltia-cms.jsand loaded on/admin/(src/pages/admin/index.astro), configured bypublic/admin/config.yml. It runs entirely in the editor's browser and talks to the Gitea API (OAuth PKCE). There is no CMS server. To update it:curl -sL https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js -o public/vendor/sveltia-cms.js - Runtime dependencies: none beyond Astro. This is deliberate — keep it that way.
Develop
npm ci
git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ../ctao-content
./scripts/link-content.sh # copies news/pages/uploads into the dev tree
npm run dev # http://localhost:4321
npm run build # static output in dist/
npm run check # deterministic gate: astro check (types) + build + internal-link check
tsconfig.json excludes public/ on purpose: the vendored CMS bundle there is
2 MB of minified JS and would make astro check run out of memory.
Layout
| Path | What |
|---|---|
src/pages/ |
Routes (.astro templates), incl. news/, search, RSS/sitemap |
src/layouts/Base.astro |
HTML shell: head, header/nav, footer |
src/content/news/*.md |
Articles (from the content repo — gitignored here; schema in src/content.config.ts) |
src/styles/global.css |
All CSS: tokens + components + prose |
DESIGN.md |
Design handoff: where the tokens live, brand basics, practical notes |
public/admin/ |
CMS config (config.yml) + editor preview styles (preview.css) |
public/uploads/ |
Editor-uploaded media (from the content repo — gitignored here) |
deploy/ |
Runbook + container/systemd units for the demo machine — see deploy/README.md |
Editing content
Editors use /admin/ (link in the footer) and sign in with a Gitea account.
Saving commits to main; the machine polls and republishes automatically
(seconds). Full history/rollback = git history in Gitea. Articles with
draft: true are excluded from the build.
Note for styling work: public/admin/preview.css mirrors the .prose rules
from global.css so the editor preview matches the site 1:1 — keep them in
sync (both files carry a KEEP IN SYNC comment).
Upgrading
Versions are pinned exactly; an upgrade is a deliberate change, never a side
effect of npm install. Astro ships security fixes only for the current and
one previous major, so plan one upgrade per major rather than skipping several.
npx @astrojs/upgrade # moves astro and official integrations together
npm run check # types + build + links must pass
npm run preview # verify the real dist/ output, not the dev server
No experimental flags in astro.config.mjs: they can change in minor releases.