How to create HTTP server config from staged metadata
Site Pipeline resolves redirects, but deliberately stops before choosing a web server, CDN, or edge-platform syntax. A deployment adapter reads the completed stage and turns its server-neutral redirect inventory into configuration for one deployment target.
This guide gives you a tested Apache httpd adapter and explains what changes when the deployment target is GitHub Pages.
Before you start
You need:
- a successful
site-pipeline buildand its completed stage directory; - Python 3.13 or newer to run the copyable reference adapter; and
- for the Apache path, control of an Apache httpd 2.4 server or virtual-host
configuration with
mod_rewriteenabled.
The example emits a config fragment. Your deployment still owns where that
fragment is installed, how httpd configuration is tested, and when the server
is reloaded.
Understand the adapter boundary
The adapter starts at manifest.json. It follows
manifest.dataFiles.redirects instead of assuming that the aggregate always
lives at data/redirects.json.
For example, the manifest may contain:
1{
2 "schemaVersion": 1,
3 "stageLayoutVersion": 1,
4 "aggregateFormat": "json",
5 "dataFiles": {
6 "redirects": "data/redirects.json"
7 }
8}
The referenced aggregate contains already-resolved absolute URLs:
1{
2 "items": [
3 {
4 "fromUrl": "https://docs.example.org/spark/development/docs/",
5 "toUrl": "https://docs.example.org/spark/releases/4.0.0/",
6 "status": 308,
7 "reason": "The released documentation has a stable URL.",
8 "sourceKind": "catalog"
9 }
10 ]
11}
routes.json remains useful for route ownership, origin grouping, and
canonical-route inspection. Concrete redirect behavior comes from
redirects.json; the adapter does not infer redirects from aliases in the route
inventory.
Generate an Apache httpd fragment
The repository contains a copyable standard-library adapter. From a Site Pipeline source checkout, run it once per deployed source origin. The origin includes the scheme and optional non-default port, so HTTP and HTTPS redirect inventories cannot be collapsed accidentally:
1uv run --frozen python examples/apache_httpd/generate_redirects.py \
2 /workspace/site/.stage \
3 https://docs.example.org \
4 /workspace/build/httpd/docs-example-redirects.conf
The output for the aggregate above is:
1# Generated from Site Pipeline staged redirect metadata.
2# Target source origin: https://docs.example.org
3# Include in server or VirtualHost context; do not use in .htaccess.
4RewriteEngine On
5
6RewriteCond "%{HTTPS}" "^on$" [NC]
7RewriteCond "%{HTTP_HOST}" "^docs\.example\.org(?::443)?$" [NC]
8RewriteRule "^/spark/development/docs/$" "https://docs.example.org/spark/releases/4.0.0/" [R=308,L,NE,QSD]
Include the generated file from server or VirtualHost context, for example:
1<VirtualHost *:443>
2 ServerName docs.example.org
3 Include /workspace/build/httpd/docs-example-redirects.conf
4
5 # TLS and document-root configuration stay consumer-owned.
6</VirtualHost>
Then use your installation’s configuration test before deployment, commonly:
1apachectl configtest
The generated rules are deliberately exact:
- scheme, hostname, effective port, and source path are all preserved;
- the
%{HTTPS}guard separates HTTP from HTTPS, while theHTTP_HOSTguard accepts either an omitted default port or that origin’s exact default port; - a non-default source port is matched exactly;
- each staged
301,302,307, or308status is retained; QSDprevents an incoming query string from being appended when the staged destination has no query; andNEkeeps an already-resolved destination URL from being escaped again.
Apache documents that a RewriteRule in server or VirtualHost context sees
the URL path with its leading slash. That differs from per-directory and
.htaccess matching, which is why the generated fragment must not be moved to
.htaccess. See the
Apache RewriteRule matching documentation
and the mod_rewrite flag reference.
The HTTPS guard describes the connection Apache sees. If TLS terminates at a proxy and Apache receives plain HTTP, this reference fragment intentionally does not guess from forwarded headers. Adapt the scheme condition to your trusted proxy setup and test it end to end before deployment.
The simpler Apache Redirect
directive
uses path-prefix mapping in its common form. Emitting that form for a staged
exact path could redirect additional requests, so the reference adapter uses
anchored rewrite rules instead.
Know what the reference adapter rejects
The adapter validates the entire redirect aggregate before it writes output. It fails without replacing an existing output file when it encounters:
- a missing, malformed, symlinked, or escaping manifest/aggregate path;
- an unsupported manifest schema, stage layout, or aggregate format;
- a redirect status other than
301,302,307, or308; - duplicate source paths for the same scheme, hostname, and effective port;
- a source URL with a query, fragment, non-normalized path, non-ASCII path, or percent-encoded path;
- a malformed requested source origin or one with no redirects; or
- URL text that could be interpreted as Apache configuration, a rewrite backreference, or a rewrite-map/server-variable expansion.
The ASCII and percent-encoding restrictions are conservative. Apache matches a decoded URL path in server context; guessing how an encoded source path should map could broaden or break a rule. If a real deployment needs such paths, extend the adapter together with target-server integration tests.
Destinations may point to a different host. They remain fixed absolute URLs from staged metadata; the adapter never reconstructs them from request headers or request variables.
Use the same contract with another server or CDN
Keep the discovery and validation steps the same:
- Read
manifest.json. - Resolve
manifest.dataFiles.redirectsbeneath the stage root. - Validate the redirect aggregate.
- Select entries for one deployment source origin, including scheme and port.
- Preserve each source path, destination URL, and status.
- Render only syntax whose exact-match behavior is understood and tested for the target.
Do not discard schemes or ports before selecting the deployment target,
silently rewrite status codes, invent wildcard rules, or rebuild destinations
from Host or other request-derived values.
Deploying on GitHub Pages
GitHub Pages deploys a static site artifact. A custom Actions workflow can run a renderer or adapter while building that artifact, but it does not add an Apache configuration layer to the Pages service. GitHub documents the artifact-based custom workflow in Using custom workflows with GitHub Pages.
Choose between two materially different outcomes:
Generate static fallback pages
A build adapter can create an HTML file at every staged source path. The file can contain a canonical link, a visible destination link, and optional browser navigation through a refresh element or script.
This is a fallback page, not an HTTP redirect. The initial response is a normal
static-page response, so it cannot preserve the staged 301, 302, 307, or
308 status or their method-handling semantics. Browsers, crawlers, caches, and
API clients may therefore behave differently. A Pages adapter should report
that loss explicitly, and a deployment that requires status fidelity should
reject this mode.
Put redirect rules at an external edge
If the status codes are part of the publication contract, place a CDN, reverse
proxy, or other programmable edge in front of the static site. That adapter can
consume the same redirects.json, return the declared status, and send all
non-redirect requests to Pages.
GitHub Pages’ automatic custom-domain redirects cover paired domain forms such
as an apex domain and its www variant. They are not a replacement for the
per-path redirect inventory described here. See Managing a custom domain for
your GitHub Pages
site.
Check the result
For an Apache deployment, verify at least:
apachectl configtestsucceeds;- every generated rule appears under the intended source origin;
- a representative redirect returns its staged status and exact
Location; - a longer path sharing the same prefix does not redirect; and
- an unrelated scheme, hostname, or port does not receive the rule.
For GitHub Pages, document whether the deployment chose degraded static fallback pages or an external status-preserving edge. Do not describe the first option as equivalent to the second.