How to integrate Site Pipeline with Roq
Keep the hand-off boundary clear
Site Pipeline and Roq have complementary jobs:
- Site Pipeline validates component inputs, resolves publication paths, and
writes a renderer-neutral stage under
site/.stage/. - Roq applies Qute layouts and themes, provides the development server, and generates the deployable static site.
The corresponding directory mapping is:
| Site Pipeline stage | Roq input | Meaning |
|---|---|---|
site/.stage/content/ |
content/ |
Page content after the path adaptation described below |
site/.stage/data/ |
data/ |
JSON data available to templates |
site/.stage/static/ |
public/ |
Files published at their stage-relative paths |
The static/ to public/ rename is intentional. Roq’s public/ directory
serves files without adding a path prefix. Its legacy static/ directory adds
a static/ prefix and therefore does not preserve Site Pipeline asset routes.
See Roq’s directory and data contracts for
the renderer side of this mapping.
Roq should not read provider snapshots, infer routes from repository layout, or reimplement pipeline planning. The staged pages and renderer-facing data are the hand-off contract.
Why this guide uses an adapter
Roq supports one site root and one directory name for each of its content and
public roots. It does not provide Hugo-style mounts that merge three arbitrary
external directory trees into an existing project. Roq’s
configuration reference
documents that single-root model. Pointing Roq’s site root at site/.stage/
also leaves three problems:
- Site Pipeline calls the asset root
static/, while Roq expects root-served files inpublic/. - Roq-owned templates, configuration, and any renderer-only inputs must remain outside the pipeline-owned stage, which is replaced on a successful build.
- Site Pipeline keeps consumer-owned pages under
content/site/and preserves source names such as_index.md. Roq requires the site landing page directly undercontent/and ignores page files whose names start with_.
Use a small consumer-owned synchronization step instead. It makes the rename explicit and keeps the renderer boundary easy to test. Symlinks can work in some local environments, but they are not the recommended portable contract: their behavior varies across operating systems and file watchers, and the stage directory is atomically replaced.
Start with a separate Roq project directory
Keep the Roq project beside the stage:
1site/
2 .stage/
3 content/
4 data/
5 static/
6 manifest.json
7 roq/
8 config/
9 application.properties
10 content/ # generated and path-adapted by the adapter
11 data/ # generated by the adapter
12 public/ # generated by the adapter
13 templates/ # consumer-owned Qute layouts and partials
14 pom.xml
Create or maintain site/roq/ as a normal Roq project. Follow the official
Roq getting-started guide for the
current CLI and project setup instead of copying a version number from this
guide.
Treat site/roq/content/, site/roq/data/, and site/roq/public/ as generated
adapter outputs. Do not put hand-authored files in those three directories,
because a correct synchronization removes files that disappeared from the
newest stage. Put site-owned pages and assets into the Site Pipeline inputs so
they become part of the stage. Keep Roq-specific templates, theme configuration,
and Java code in the Roq project.
Roq requires a site index. Make sure the stage contains the consumer site’s
root page under content/site/; if it does not, add that page to the
consumer-owned Site Pipeline inputs before integrating the renderer. The
adapter must publish that page as content/index.<extension> for Roq.
Adapt staged content paths for Roq
The content hand-off needs a small, deterministic filename adaptation. It does not change page bytes or front matter:
- copy component and documentation pages from the staged
content/tree at their existing relative paths; - copy files below staged
content/site/into the root of Roq’scontent/directory, removing thesite/path segment; and - rename every
_index.<extension>page toindex.<extension>, including nested component and documentation section pages.
For example, the checked-in smoke workspace produces these adaptations:
| Site Pipeline stage | Roq content input | Published route |
|---|---|---|
content/site/_index.md |
content/index.md |
/ |
content/components/site-pipeline/_index.md |
content/components/site-pipeline/index.md |
/components/site-pipeline/ |
Plan all destination paths before replacing the generated directory. If
flattening content/site/ or renaming an _index page would collide with
another staged page, stop with an error instead of overwriting either input.
Also reject symlinks in the stage or generated roots; the adapter is copying a
completed, ordinary file tree, not following paths outside that tree.
This repository includes a
copyable Python reference adapter
that implements those rules using only the Python standard library. It reads
the generated-root names from manifest.json, prepares the new roots before
changing the Roq project, and rolls back roots already replaced if publication
fails partway through. It supports stage layout version 1 and fails closed on
other versions. Copy it into the consumer-owned part of the Roq project and
review it like any other build script:
1cp examples/roq/sync_stage.py site/roq/sync_stage.py
Preserve staged asset names
Roq slugifies public file names by default. Site Pipeline has already resolved
asset paths, so changing those names in the renderer can break authored links.
Add this to site/roq/config/application.properties:
1site.slugify-files=false
Staged Markdown can contain literal braces in code samples. Current Roq
projects support Qute’s alternative expression syntax, which makes ordinary
{text} literal while expressions use {=expression}. Enabling it reduces
surprises when rendering documentation:
1quarkus.qute.alt-expr-syntax=true
These are renderer settings. They do not change the Site Pipeline contract.
Check content-language compatibility
Site Pipeline deliberately stages several page formats and leaves rendering to the consumer. Roq documents support for Markdown, AsciiDoc, HTML, and several template formats, but that does not make every Site Pipeline page extension a Roq input automatically. In particular, do not assume that MDX content will be rendered as Markdown.
Inventory the extensions under site/.stage/content/ before adoption. Convert
or reject unsupported formats in a consumer-owned step, and add one fixture for
each format you intend to publish. The adapter should copy bytes; it should not
silently rewrite a language it does not understand.
Qute section syntax such as {#for} can also be meaningful inside page text,
including examples in technical documentation. Alternative expression syntax
does not change section syntax. If staged content contains literal Qute
sections, use Roq’s page escaping controls for the affected paths and verify
that layouts still receive the expected front matter.
Synchronize one completed stage
Run Site Pipeline from the repository root, then invoke a consumer-owned adapter that applies the content rules above and replaces all three generated roots:
1site-pipeline check --workspace-root . --catalog site/catalog.yaml
2site-pipeline build --workspace-root . --catalog site/catalog.yaml
3python3 site/roq/sync_stage.py site/.stage site/roq
The reference file is an example rather than an installed Site Pipeline
command. A Gradle, Maven, Java, or shell adapter can implement the same
contract. Its data and static steps may use rsync on systems where it is
available:
1rsync -a --delete site/.stage/data/ site/roq/data/
2rsync -a --delete site/.stage/static/ site/roq/public/
Do not use the same direct rsync operation for content/: it would leave the
site root under content/site/ and _index pages hidden from Roq. Whatever
implementation you choose must remove files that disappeared from the newest
stage. That replacement is destructive by design, so target only the three
dedicated generated directories shown above.
Do not synchronize manifest.json into data/. It is a stage-completion and
discovery record for build tooling, not normal renderer data.
Preview with roq start
After the first build and synchronization, start Roq from its project:
1cd site/roq
2roq start
Roq owns the preview server and live reload. Site Pipeline does not have a
serve command.
For occasional content changes, rerun the build and adapter. For a continuous development loop, use a consumer-owned wrapper:
- start
site-pipeline watch; - wait for a successful
readyevent; - synchronize all three roots;
- let the already-running Roq development server observe the stable Roq input directories.
When consuming machine-readable watch events, request the JSONL format explicitly:
1events_file="$(mktemp)"
2trap 'rm -f "$events_file"' EXIT
3site-pipeline watch \
4 --workspace-root . \
5 --catalog site/catalog.yaml \
6 --unstable-events jsonl \
7 --unstable-events-output "$events_file"
The wrapper should synchronize only after a successful ready event, not while Site Pipeline is still building. It should also stop both processes and remove its temporary event file when the preview session ends. The event interface is currently marked unstable, so keep that parsing in one small adapter rather than spreading it through build scripts.
Generate the production site
Use the same hand-off in CI:
1site-pipeline build --workspace-root . --catalog site/catalog.yaml
2python3 site/roq/sync_stage.py site/.stage site/roq
3
4cd site/roq
5roq generate
Roq writes the generated site under target/roq by default. Publish that
renderer output, not site/.stage/. The stage is an input tree and includes
metadata intended for build coordination as well as renderer-facing files.
See Roq’s publishing guide for its current
generation and deployment contract.
Read page and aggregate data in Qute
Staged pages carry normalized pipeline front matter. Roq exposes front matter
through page.data, so layouts can read values such as:
page.data.pipeline.component.slugpage.data.pipeline.component.displayNamepage.data.pipeline.page.kindpage.data.pipeline.page.path
Keep fallback handling in one Qute partial because not every page kind has the same optional metadata.
Roq turns JSON and YAML files under data/ into named data beans. That makes
pipeline aggregates such as components.json, routes.json, and
content-index.json available to templates through Roq’s cdi namespace.
Pipeline aggregate arrays are Vert.x JsonArray values in the untyped data
bean. Convert arrays of objects through Roq’s template extension before using
Qute collection access, for example:
{=cdi:components.items.asJsonObjects.first.slug}
File names containing a hyphen may require map-style lookup rather than dotted property syntax. Treat the aggregate files as versioned input contracts and do not derive equivalent metadata by scanning file paths.
Check routes and redirects explicitly
Copying staged files does not make Roq interpret routes.json or
redirects.json. They remain data until a consumer template, plugin, or
deployment adapter uses them.
Before adopting the integration, verify at least these cases in the generated
target/roq tree:
- a component landing page and a nested documentation page use the expected pretty routes;
- root-absolute and relative links still resolve;
- an asset keeps the exact path and filename declared by Site Pipeline;
- a template can read one staged page’s
pipelinefront matter; - a template can read one staged aggregate;
- the deployment layer implements any required redirects.
If Roq’s default file-derived route differs from the path declared in
routes.json, adapt the consumer’s Roq route configuration or front matter.
Do not change the staged path silently or teach the pipeline about Roq-specific
templates.
What is and is not verified here
This integration boundary follows Roq’s current documented directory, configuration, development, and generation contracts. The repository’s tests build the checked-in smoke workspace with the real Site Pipeline, apply the content adaptation, and protect the directory mappings and command examples from accidental documentation drift.
The ordinary Site Pipeline test suite does not download or execute Roq. The
pinned fixture has additionally been validated with Roq 2.1.6 and Quarkus
3.35.1 in an isolated renderer run. Before making Roq a required production
renderer, add a consumer-level smoke test using the versions selected by that
consumer. It should build a small stage, adapt it, run roq generate, and
assert the route, metadata, data, and asset cases listed above.