Static export module: publish content as files so a CDN can serve them without the CMS
- Dominant language
- C#
- Stars
- 6
- Forks
- 7
- Avg merge
- 4h 42m
- Merged PRs (30d)
- 307
Description
A site that serves the same published content to everyone does not need a database in the request path. Today it has one: every public read reaches Postgres, and the only thing between them is a 60 second `Cache-Control` (`barakoCMS/Features/Public/Endpoints.cs:275`).
A static export module makes barakoCMS an origin that produces files. A CDN, Netlify, Cloudflare Pages or plain nginx serves those files, and the CMS is not in the path at all.
## What it does
Walk every entry that is deliverable, `Published` and `Public`, and write it to a target as files:
- one JSON document per entry, at a stable path
- one index per content type
- `sitemap.xml` and the feed
Targets: a local directory first, then S3 through the existing `BarakoCMS.Files.S3` credentials.
Triggers: an endpoint, a schedule, and a content event.
## What already exists
Most of the machinery is there, which is why this is a module and not a rewrite.
- `PublicDelivery.ToPublic` is the masking projection the REST reads use. The export must go through it, not around it, so a field the API masks is masked in the export by construction rather than by a second copy of the rules.
- `SitemapEndpoint` and `FeedEndpoint` already build the two derived files.
- `ContentChangeListener` (`barakoCMS/Features/Public/Events/ContentChangeListener.cs`) already fires after commit on the writing instance.
The module contract covers this without change: services, a hosted service, endpoints and no middleware.
## The two things that will be wrong if not decided up front
**Multi-instance.** `ContentChangeListener` is a session listener, so it fires only on the instance that committed. An export triggered that way runs on one instance and writes to shared storage. Decide whether that is acceptable or whether the trigger belongs on the async daemon, which HotCold already pins to one instance.
**Full versus incremental.** `SitemapEndpoint` caps at `.Take(50000)`. A full export at that size is not something to run on every publish. See the changed-since issue.
## Done when
- `dotnet add package BarakoCMS.Static`, set a target directory, and a publish produces files.
- A test proves an export of an entry with a masked field does not contain that field.
- The export is idempotent: running it twice with no content change produces identical bytes.
Contributor guide
Research direction
Start with PublicDelivery.ToPublic, SitemapEndpoint, FeedEndpoint, and ContentChangeListener.cs, then review the module contract and the current endpoint at barakoCMS/Features/Public/Endpoints.cs:275. Determine the export trigger and scope decisions before implementing; done means the package can export published public entries to a configured directory, masked fields are absent, and repeated unchanged exports produce identical bytes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, postgresql
- Domain
- backend, cloud
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100