Workflow
How the workflow flows
The generated deployment workflow roughly processes in this order:
Diagram source
flowchart TD
Start["Workflow starts"]
CheckoutSite["1. Check out the site"]
CheckoutContent["2. Check out external content<br/>only when needed"]
Node["3. Node.js 22"]
Install["4. npm ci"]
Cache["5. Restore Riebeckite cache"]
Check["6. riebeckite check"]
Build["7. riebeckite build"]
Deploy["8. Deploy to Cloudflare"]
Start --> CheckoutSite
CheckoutSite --> CheckoutContent
CheckoutContent --> Node
Node --> Install
Install --> Cache
Cache --> Check
Check --> Build
Build --> Deploy
1. Check out the site repository
First the site repository is fetched.
GitHub runner
↓
Site repository
This contains the Riebeckite config, the application, plugin/theme settings, package.json, and package-lock.json.
2. Check out external content
When the content is in the same repository, this extra step is not needed.
When you use a separate repository, the content repository is checked out into:
content/
Runner
Site repository
├─ app/
├─ riebeckite.config.ts
├─ package.json
└─ content/ ← external content repository
In that case Riebeckite can read it as:
content: {
directory: "content",
},
3. Set up Node.js
The generated workflow sets up Node.js 22.
GitHub runner
↓
Node.js 22
The subsequent npm ci and Riebeckite CLI run in this environment.
4. Install packages
It runs:
npm ci
npm ci installs dependencies according to package-lock.json, so when you use the generated workflow you must commit package-lock.json.
5. Restore Riebeckite build state
The generated workflow restores everything incremental SSG needs between runs with a single actions/cache step:
.riebeckite/cache— the Markdown and plugin processing cache.riebeckite/build/content-state.json— incremental build state.riebeckite/ssg-output-cache.jsonand.riebeckite/ssg-output-cache.entries— the SSG output cache (rendered routes and files, plus their entry blobs).riebeckite/ssg-output-cache.fast-path.json— the no-change fast-path validation sidecar
The cache key is scoped by the runner OS, the Node.js version, the package-lock.json hash, and the riebeckite.config.ts hash, then saved under a per-run generation:
riebeckite-build-v2-<os>-node<node>-<lockfile hash>-<config hash>-<run id>-<run attempt>
restore-keys fall back to the newest compatible generation in two steps: first a generation with the same lockfile and config hashes, then one with the same lockfile hash. The lockfile hash covers the installed Riebeckite version and the config hash covers preset and plugin settings, so changing either starts a new cache lineage, while Riebeckite's own fingerprint checks (schema version, app/pipeline/content fingerprints, plugin cacheVersion) decide the actual reuse. An entry is never overwritten in place: each run saves a new generation and GitHub evicts old ones automatically. Caches are not shared across operating systems or Node.js versions, so a different environment starts cold instead of reusing an incompatible entry. dist/ is not cached.
When Riebeckite rejects a restored output cache — a missing or malformed cache, a changed application fingerprint, an unknown output dependency, a full-regeneration reason, or an output without a cached entry — it renders every output and rewrites dist/ from scratch, so a stale or partial cache never reaches the deployment. A cache miss is safe and simply performs a cold build.
After the build, a new cache generation is saved automatically. The hits, misses, and bypasses on the build log's Persistent content cache line show whether Riebeckite reused content processing, and the SSG outputs line's rendered, reused, and removed counts show whether it reused rendered outputs. Deleting .riebeckite/ or removing the workflow's cache step makes the next run cold, but does not affect output correctness. To keep the content cache but skip the output-cache transfer, remove the three .riebeckite/ssg-output-cache* paths from the cache step; the next run re-renders every output. The workflow also prints the restored and saved cache sizes so you can watch the transfer cost. If saving the cache fails or is skipped, the deploy still runs.
6. Validate the site
Then it runs:
npm exec riebeckite check
If config or plugin resolution has a problem, it fails here before deploying.
Checkout
↓
Install
↓
check
↓
stop if there is a problem
This is a guard against deploying a broken configuration.
7. Build the site
Once check succeeds, it runs:
npm exec riebeckite build
Riebeckite generates the publishable:
dist/
Content
Config
Plugin
Theme
↓
riebeckite build
↓
dist/
8. Deploy to Cloudflare Workers
Finally it deploys to Cloudflare Workers using:
cloudflare/wrangler-action@v3
Diagram source
flowchart LR
Dist["dist/"]
Wrangler["wrangler-action"]
Workers["Cloudflare Workers"]
Site["Public site"]
Dist --> Wrangler
Wrangler --> Workers
Workers --> Site
Here:
CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_ID
are used.
Publication state
The workflow stores the last successful publication snapshot on a dedicated publication-state branch, not on main. Before the build it copies that branch's state.json into the runner's temporary directory and points RIEBECKITE_PUBLICATION_STATE there. After a successful deploy it pushes one commit to the branch; a failed deploy writes nothing. The source branch is never advanced by the workflow, so your next git push does not need a pull or rebase. Do not merge publication-state into a development branch. If you migrate from an older workflow that committed publication/state.json, see Publication workflow.
Using a private content repository
Checkout authentication differs between a public and a private content repository.
To read a private content repository, set this on the site repository:
RIEBECKITE_CONTENT_READ_TOKEN
Diagram source
flowchart LR
Site["Site repository"]
Token["RIEBECKITE_CONTENT_READ_TOKEN"]
Content["Private content repository"]
Site --> Token
Token -->|"read"| Content
This token's role is to read the private content repository from the site's workflow.
Do not confuse the two tokens
In a split layout, two similarly named tokens appear.
| Secret | Where to store it | Role |
|---|---|---|
RIEBECKITE_CONTENT_READ_TOKEN |
Site repository | Read the private content repository |
SITE_DISPATCH_TOKEN |
Content repository | Notify the site repository of an update |
It helps to think in terms of direction.
RIEBECKITE_CONTENT_READ_TOKEN
Site ─────read─────> Content
SITE_DISPATCH_TOKEN
Content ───notify───> Site
That is:
Diagram source
flowchart LR
Content["Content repository"]
Site["Site repository"]
Content -->|"SITE_DISPATCH_TOKEN<br/>notify update"| Site
Site -->|"RIEBECKITE_CONTENT_READ_TOKEN<br/>fetch content"| Content
Site pushes vs. content pushes
With separate repositories there are two deployment paths.
When you change the site
Site repository
↓
push to main
↓
publish.yml
↓
check out content
↓
Build
↓
Deploy
The site's push starts the workflow directly.
When you change the content
Content repository
↓
push to main
↓
notify-site.yml
↓
repository_dispatch
↓
Site publish.yml
↓
check out content
↓
Build
↓
Deploy
Here one extra step — the notification from the content repository to the site repository — is added.
In both cases, the build ultimately runs on the site repository side.
Running the deploy workflow manually
The generated workflow also supports:
workflow_dispatch
so you can run the workflow manually from GitHub.
GitHub
↓
Actions
↓
Deploy workflow
↓
Run workflow
This is useful when you want to deploy the current state again without making a new commit to the content or site.