So you have grown a beautiful Forester forest on your machine. It is structured, it is evergreen, and the mathematical diagrams render perfectly. Naturally, you want to share it with the world via GitHub Pages. Despite it being quite easy in the end, I spent a lovely afternoon untangling 404 errors, mysterious XSLT parsing failures, and OCaml version mismatches. So here is a short guide to share what I learned in the process, for when I will inevitably forget everything.
The website is deployed by a GitHub Actions workflow that installs a minimal TeX Live (for those sweet diagrams), set up OCaml 5.3+ (required for Forester 5.0), installs forester, builds the forest, and ship it to Pages.
I used the zauguin/install-texlive action for TeX to keep things lightweight, and the standard ocaml/setup-ocaml with caching enabled. You can see my full workflow file here: .github/workflows/forester.yml.
Installing the full TeX Live distribution on CI is a massive waste of bandwidth and
time. The good thing about zauguin/install-texlive is that you can easily instruct it to pull down a minimal installation.
The package list was built by grepping around the repo for package names, and scheme-basic takes care of most of the essentials. Also keep in mind a package name in LaTeX is
not necessarily the package name tlmgr expects—gotta confess that Gemini was really helpful in figuring out this one.
- name: Install TeX Live
uses: zauguin/install-texlive@v4
with:
texlive_version: 2025
packages: >
scheme-basic standalone preview dvisvgm amsfonts amsmath
amscls cclicenses enumitem etoolbox hyperref mathpartir
ebproof quiver pgf spath3 mathtools microtype stmaryrd
thmtools tikz-cd tikz-nfold xcolor
The first real pitfall I encountered was that the latest Forester (5.0+) strictly
requires OCaml 5.3 or newer. If you just ask for ocaml-compiler: 5.x, you might get 5.2, which causes Opam to quietly fall back to Forester 4.3.1. Suddenly
your new features vanish and you are debugging ghost bugs.
Thus we have to pin the version we need:
- name: Setup OCaml
uses: ocaml/setup-ocaml@v3
with:
# Forester 5.0 requires OCaml >= 5.3.0
ocaml-compiler: 5.3
dune-cache: true
- name: Install Forester
# Pinning the version ensures we get 5.0 or fail if incompatible
run: opam install forester.5.0
Finally, remember to add this block to give the action permission to deploy.
permissions: contents: read pages: write id-token: write
The last hurdle I had to jump was a bit of delicate configuration regarding base URLs. A good piece of debugging advice is that if you end up with a site that loads but throws "XSLT parsing errors" there is a good chance your XSLTs are 404ing, and the culprit is likely how Forester calculates base URLs when deploying to a project subdirectory. Then using browser networking tools you can see which XSL files the browser is trying to load.
All in all, keep in mind Forester is sensitive to the url setting in forest.toml and its trailing slashes. If you look at my configuration in forest.toml, you will see this:
[forest] trees = ["trees"] assets = ["assets"] url = "https://mattecapu.github.io/website/" home = "matteo-capucci/"
It's important the slashes are where they are.
Because I set the URL to include /website/, Forester infers that the base path is /website/. Consequently, it builds everything into an ulterior subfolder named after that path inside the output directory.
In practice, this means that in the workflow we simply account for that:
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'output/website/'
And that's how you are reading this!