Contents
  1. Downloads
  2. Preface
  3. Part I Writing
  4. 1 Quickstart
  5. 2 Writing in Markdown
  6. 3 Authoring in Hiccup
  7. Part II Configuration and Theming
  8. 4 Configuring the Book
  9. 5 Theming and Editions
  10. 6 Commands
  11. Part III Producing and Publishing
  12. 7 Producing a Book
  13. Structure lives in book.edn
  14. 8 Editions
  15. 9 Distributing Your Book
  16. 10 How It Works
  17. A Error Catalog
  18. B Clojure for Authors
  19. C A Chapter Written in Code
  20. A table the book writes for itself
  21. Documentation that cannot drift
  22. Down to a single formatting object
  23. When to reach for code
  24. Glossary
  25. List of Figures
  26. List of Tables
  27. List of Listings
  28. Bibliography
  29. Index
  30. Search

Chapter 9

Distributing Your Book

A built book is a directory of files; getting it in front of readers is a separate step. This chapter shows one way to do it with a Git host’s built-in pages and releases, using this manual as the worked example: the copy you are reading was published this way. Everything here builds on the editions chapter. Distribution is choosing where each edition goes.

The split: read online, download to keep

The five editions fall into two roles. The site edition is for reading in a browser, so it goes to a web host. The screen, print, print-x, and EPUB editions are files to download and keep, so they attach to a tagged release. The site carries a Downloads page that links to those release files, which gives a reader a single chain to follow:

a link in the README → the manual online (the site edition) → its Downloads page → the release assets.

Because the manual’s repository is hosted on GitHub, the rest of this chapter names GitHub’s facilities: Pages for the site, Releases for the files, and Actions for the automation. The shape is the same on any host with equivalent features.

Declaring the downloads

The Downloads page is data, not handwritten HTML. Add a :book/downloads map to book.edn: a :base URL and an :assets list, one entry per downloadable edition. This is the manual’s own block:

book.edn
:book/downloads
{:base   "https://github.com/leifericf/smia/releases/latest/download"
 :assets [{:label   "Screen PDF"
           :file    "smia-manual-screen.pdf"
           :note    "Symmetric margins, for reading on screen."
           :default true}
          {:label "Print PDF"
           :file  "smia-manual-print.pdf"
           :note  "Mirrored margins and a binding gutter, for printing."}
          {:label "Print-ready (PDF/X)"
           :file  "smia-manual-print-x.pdf"
           :note  "PDF/X-4 with embedded fonts, for a press."}
          {:label "EPUB"
           :file  "smia-manual.epub"
           :note  "Reflowable, for e-readers."}]}
Listing 3. Declaring the downloadable editions

Each asset’s link is :base joined to its :file. The asset flagged :default true renders as the primary link at the top of the page; here that is the screen PDF, the right choice for most readers. The rest follow as a list, each with its :note. At most one asset may be the default, and a malformed block fails the build with :smia.book.config/invalid-downloads.

The :base points at releases/latest/download, which always resolves to the newest release, so the site needs no rebuild when you cut a new one. The Downloads page is also site-only by construction: only the site edition reads :book/downloads, so the PDFs and the EPUB never carry a page of links to themselves. Build the site and a downloads.html sits beside index.html, reachable from the contents.

Publishing on a date tag

The manual ships a single GitHub Actions workflow, .github/workflows/release.yml, that does the whole job when you push a tag shaped like a calendar date. Smia uses date tags rather than version numbers; the tag is the day you publish.

.github/workflows/release.yml
on:
  push:
    # YYYY-MM-DD (glob, not regex)
    tags: ['20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]']
Listing 4. Triggering on a YYYY-MM-DD tag

On such a push the workflow builds all five editions into one output root, then does three things with them:

  • attaches the screen, print, print-x, and EPUB files to a GitHub Release for the tag (softprops/action-gh-release);
  • uploads the site directory as a Pages artifact and deploys it (actions/upload-pages-artifact then actions/deploy-pages);
  • verifies every promised download exists before publishing, so a Downloads page can never link to a missing file.

The build step is the same build command you run locally; the workflow has no private knowledge of the book. The CI runner provides a Java runtime but not the installed smia binary, so the workflow invokes Clojure directly to build Smia from source before running it:

clojure -M:run:cljs:math:diagrams build manual \
  --edition screen --edition print --edition print-x \
  --edition epub --edition site \
  --output-root build/release

To publish, tag the current date and push it:

git tag 2026-06-04 && git push origin 2026-06-04

The site URL and a custom domain

This manual serves from a custom domain, smia.leifericf.com, under a /manual/ path, so a page like the quickstart lives at smia.leifericf.com/manual/part-1/quickstart/. Every internal link in the site edition is relative, so mounting the whole site under a sub-path needs no rebuild: the deploy step stages it there and writes the CNAME at the domain root. The step has a default domain baked in, so it works without any repository configuration:

.github/workflows/release.yml
- name: Stage the Pages site under /manual
  run: |
    root=build/release/site-root
    mkdir -p "$root/manual"
    cp -R build/release/smia-manual/site/. "$root/manual/"
    domain="${{ vars.PAGES_CUSTOM_DOMAIN || 'smia.leifericf.com' }}"
    echo "$domain" > "$root/CNAME"
Listing 5. Staging the site under /manual with a default domain

At the bare domain root the workflow drops a one-line redirect into /manual/. A fork overrides the domain by setting the PAGES_CUSTOM_DOMAIN repository variable, whose value wins over the default. Serving at the domain root instead is a one-line change: stage the site as the artifact root rather than under manual/.

With the public address in book.edn as :book/site-url (this manual sets "https://smia.leifericf.com/manual"), the built site already contains a sitemap.xml listing every canonical page and a robots.txt pointing search engines at it; nothing to add in the workflow. When a published chapter later moves, :book/redirects keeps its old URL alive; both keys are described in the configuration chapter.

One-time repository setup

Three things are configured once, in the repository, outside the manuscript:

  • In Settings → Pages, set the source to GitHub Actions, and set the custom domain to smia.leifericf.com with Enforce HTTPS on.
  • Point the domain’s DNS at GitHub Pages (a CNAME record for the subdomain to leifericf.github.io), or set the PAGES_CUSTOM_DOMAIN Actions variable to your own domain.
  • Push the first date tag to trigger the first publish.

From then on, publishing a new edition of the book is one push of a date tag. The manuscript itself carries everything else: the chapters, book.edn, the theme, and the :book/downloads block.