Publishing
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page was written against Antora 3.2.1 and checked on 2026-10-26. Antora produces a folder of static files
(build/site by default) and does not publish it: that is the job of a hosting service and a CI job. The three
services below cover most cases. What differs between them is who serves the files, whether Jekyll touches them, and
how page redirects are expressed. The CLI options used in the examples are explained in
CLI, Docker, cache and private repositories.
The figure shows the same output folder going to each target; only the last step changes.
What every target needs from the playbook
-
site.urlshould be set to the final address, including any subpath such ashttps://user.github.io/docs, without a trailing slash. When it is not set, features that depend on it are switched off silently. Anysite.urlenables the 404 page, thesite-urlattribute andsite.robots; only an absolute URL enables the sitemap files and the canonical link of pages. -
ui.bundle.urlis mandatory in every playbook. -
output.dir(or--to-dir) must match the folder that the CI job publishes:build/sitefor GitHub Pages as documented,publicfor GitLab Pages. -
Antora writes the UI files in a folder named
_and the images in_images. Hosts that run Jekyll drop folders whose names start with an underscore, which is the origin of the.nojekyllfile described below.
GitHub Pages with actions/deploy-pages
Set the Pages source of the repository to GitHub Actions (Settings, Pages). The official workflow builds the site and deploys the artifact with the Pages actions, so no branch is involved:
.github/workflows/publish.ymlname: Publish to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: github-pages
cancel-in-progress: false
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Install Node.js
uses: actions/setup-node@v5
with:
node-version: '20'
- name: Install Antora
run: npm i antora@3.2.1
- name: Generate Site
run: npx antora antora-playbook.yml
- name: Upload Artifacts
uses: actions/upload-pages-artifact@v4
with:
path: build/site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
This is the workflow of the Antora documentation with only the Antora version pinned. The action versions
(checkout@v5, configure-pages@v5, setup-node@v5, upload-pages-artifact@v4, deploy-pages@v4) are the ones in
the official example; newer major versions of these actions exist (the workflow that Zensical generates uses
checkout@v7, configure-pages@v6, upload-pages-artifact@v5 and deploy-pages@v5), so check the action
repositories before copying. The three permissions are what the deployment needs, concurrency makes deployments run
one at a time, and Antora 3.2 needs Node.js 20 or newer. The path of the upload step must match the playbook’s
output.dir. This workflow was not executed here because it needs a GitHub
repository; the Antora build it runs was executed with the scratch project.
When the artifact flow is used, the action takes care of the Jekyll problem and .nojekyll is not needed.
GitHub Pages from a branch with .nojekyll
The alternative is to commit the generated site to a branch (usually gh-pages) and set that branch as the Pages
source. GitHub then passes the files through Jekyll, which would delete _ and _images, so the published site loses
its style sheets and images. A file named .nojekyll at the root of the site turns Jekyll off. There are two ways to
create it. The first is a build step:
npx antora antora-playbook.yml
touch build/site/.nojekyll
The second is a supplemental UI file, which makes the file part of every build and keeps it out of the CI script. The
ui.yml file lists .nojekyll as a static file so that Antora publishes it at the site root, and the second entry
creates the empty file:
site:
title: Pocket Units Docs
url: https://docs.example.com/pocket-units
start_page: pocket-units::index.adoc
robots: allow
content:
sources:
- url: ./src/pocket-units
branches: v2.0
ui:
bundle:
url: ../ui/antora-ui-default/build/ui-bundle.zip
supplemental_files:
- path: ui.yml
contents: |
static_files:
- .nojekyll
- path: .nojekyll
This playbook was built; the output folder contained .nojekyll, 404.html, index.html, robots.txt,
sitemap.xml and the _ and pocket-units folders. The deployment of the branch needs a job that pushes the folder
to the branch. This site (Irurueta Docs) does it with the third-party action crazy-max/ghaction-github-pages, which
pushes build/site to the gh-pages branch from a nightly workflow, and its playbook ships .nojekyll as a
supplemental file exactly as above. A custom domain is set in the repository settings, or with the fqdn input of
that action (this site sets it).
GitLab Pages
GitLab publishes the artifact of a job named pages, taken from a folder named public. The official example uses a
Node.js image and a locally installed Antora:
.gitlab-ci.ymlimage:
name: node:24-alpine
variables:
ANTORA_CACHE_DIR: .cache/antora
NODE_OPTIONS: --max-old-space-size=4096
before_script:
- npm ci
pages:
stage: deploy
interruptible: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
cache:
paths:
- .cache
script:
- npx antora --fetch --redirect-facility gitlab --to-dir public antora-playbook.yml
artifacts:
paths:
- public
ANTORA_CACHE_DIR and the cache entry keep the cloned repositories between pipelines, --fetch refreshes them,
--redirect-facility gitlab writes the _redirects file that GitLab Pages understands, and --to-dir public must
match the artifact path. npm ci requires package.json and package-lock.json to be committed. For private content
repositories define a GIT_CREDENTIALS CI/CD variable, preferably holding a read-only deploy token; to fail the
pipeline on warnings add --log-failure-level warn. The pipeline was not executed here because it needs a GitLab
project.
Netlify
Netlify builds from the repository, so the build command and the publish folder go in netlify.toml. Antora does not
document a Netlify setup of its own, so the file below is assembled from the Netlify configuration reference and the
Antora options already described:
netlify.toml[build]
command = "npm ci && npx antora --fetch --redirect-facility netlify antora-playbook.yml"
publish = "build/site"
[build.environment]
NODE_VERSION = "24"
publish is relative to the base directory and holds the files that Netlify uploads. This file was not executed: it
needs a Netlify account and site. The netlify redirect facility writes a _redirects file in the site root, which
is where Netlify reads it; Netlify applies the rules from top to bottom and the first match wins.
Page redirects: urls.redirect_facility
Antora creates redirects for the start page, for pages that declare page-aliases (see
Versioning and prerelease) and for latest-version
aliases. A static site cannot answer with an HTTP status, so Antora can emit the redirects in the format of the server
that will host the site. The key is urls.redirect_facility, overridden by --redirect-facility. When the site has no
redirects no file is written.
| Facility | Output | Use for |
|---|---|---|
|
A small HTML page with a meta refresh at the old URL, and an |
Hosts that cannot read redirect files: GitHub Pages, GitLab Pages without the gitlab facility, local preview. Only this facility writes bounce pages |
|
|
Netlify |
|
|
GitLab Pages |
|
|
Apache httpd that honours |
|
|
nginx; the file must be moved or included by the nginx configuration |
|
nothing |
Redirects handled elsewhere |
Source: Antora docs: Redirect facility
Start page and alias redirects are permanent (301); latest-version aliases are temporary (302). A redirect path depends
on urls.html_extension_style: with indexify it ends in a slash and not in .html. The drop style needs server
support (for nginx a try_files line). The Pocket Units scratch repository has a page broken.adoc with
:page-aliases: old-broken.adoc. Building it with each facility gave:
netlify _redirects
/index.html /pocket-units/2.0/index.html 301!
/ /pocket-units/2.0/index.html 301!
/pocket-units/2.0/old-broken.html /pocket-units/2.0/broken.html 301!
gitlab _redirects
/index.html /pocket-units/2.0/index.html 301
/ /pocket-units/2.0/index.html 301
/pocket-units/2.0/old-broken.html /pocket-units/2.0/broken.html 301
httpd .htaccess
Redirect 301 /index.html /pocket-units/2.0/index.html
Redirect 301 /pocket-units/2.0/old-broken.html /pocket-units/2.0/broken.html
nginx .etc/nginx/rewrite.conf
location = /index.html { return 301 /pocket-units/2.0/index.html; }
location = /pocket-units/2.0/old-broken.html { return 301 /pocket-units/2.0/broken.html; }
The static facility wrote pocket-units/2.0/old-broken.html and a root index.html that contain
<meta http-equiv="refresh" content="0; url=pocket-units/2.0/index.html">, a canonical link and a
robots noindex meta tag; disabled wrote nothing.
Sitemaps and robots.txt
With an absolute site.url Antora writes sitemap.xml after the pages are composed. In the scratch project with two
components (pocket-units and shared) the file was a sitemap index pointing to sitemap-pocket-units.xml and
sitemap-shared.xml, each a urlset with one url per page and a lastmod timestamp:
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>http://localhost:5252/sitemap-pocket-units.xml</loc>
</sitemap>
<sitemap>
<loc>http://localhost:5252/sitemap-shared.xml</loc>
</sitemap>
</sitemapindex>
With a single component the build wrote one sitemap.xml holding the urlset directly. The sitemap lists absolute
URLs including the subpath of site.url: with https://docs.example.com/pocket-units the pages appeared as
https://docs.example.com/pocket-units/pocket-units/2.0/broken.html. A relative site.url such as /docs does not
produce a sitemap. robots.txt is written only when site.robots is set; site.robots: allow gave:
User-agent: *
Allow: /
Source: Antora docs: Site URL
The official page on site.url does not describe the names of the sitemap files, so the structure above is what was
observed with Antora 3.2.1. Submit the sitemap to the search engines or reference it in a hand-written robots.txt
if the site is large.
Pitfalls
-
Branch deployment without
.nojekyllloses the_folder: the pages render without style and images. -
output.dirand the folder uploaded by the CI job must be the same (build/site,public). -
Without
site.urlthere is no 404 page, no sitemap and no canonical links, and no error says so. -
The
staticfacility is the only one that works on GitHub Pages; thenetlifyandgitlabfacilities write_redirects, a file those two hosts read. -
A custom domain on GitHub Pages is configured in the repository settings, and
site.urlmust be updated to match. -
Scheduled builds publish whatever release branches exist at that time; combine them with
--fetch.