Static (manager: caddy) deployments always emitted the SPA fallback
`try_files {path} /index.html`, which serves the root index.html for
every unmatched path. That's correct for React/Vite apps but swallows a
multi-page content site's in-page links back to the homepage, so a Hugo
site (payne.io) couldn't navigate off its landing page.
Add an explicit `spa: bool` on CaddyDeployment (default True, preserving
existing SPA behavior). When False, the gateway serves plain file_server
— resolving directory indexes (/posts/ -> /posts/index.html) and 404ing
missing paths. Threaded through the registry snapshot + mesh and the
route computation so both the internal wildcard and public apex blocks
honor it. `castle program create --stack hugo` now sets spa: false.
3.9 KiB
Hugo static sites in Castle
This is a stack — creation-time guidance for writing new sites. A stack is a template + conventions, not a runtime requirement.
castle program create --stack hugoscaffolds from it (via Hugo's ownhugo new site) and seeds the program's default build verb. An existing Hugo site adopted withcastle program adddoesn't need this stack — it declares its owncommands:/build:. See @docs/registry.md forcommands:,stack:(optional), andrepo:.
How to build, serve, and manage Hugo sites as castle programs.
Stack
- Generator: Hugo (extended recommended — needed for SCSS/asset processing)
- Build:
hugo --gc --minify→public/ - Served:
manager: caddystatic deployment, in place at<name>.<gateway.domain>, withspa: false— the gateway resolves directory indexes (/posts/→/posts/index.html) and 404s missing paths. (The defaultspa: trueis a single-page-app fallback that serves the rootindex.htmlfor every unmatched path — correct for React/Vite, but it swallows a Hugo site's in-page links back to the homepage.castle program create --stack hugosetsspa: falsefor you.) - Package manager (only if a theme needs an asset pipeline): pnpm
Hugo has one meaningful dev verb — build. It has no native lint/test/type-check,
so the stack advertises only build / install / uninstall; castle check and
friends aren't offered (a site can still declare its own, e.g. an HTML linter, under
commands: — a declared verb always wins over the stack).
Create a new site
castle program create my-site --stack hugo --description "My site"
cd /data/repos/my-site
castle program build my-site # hugo --gc --minify -> public/
castle apply my-site # serve at my-site.<gateway.domain>
The scaffold delegates the canonical skeleton to hugo new site (archetypes/,
content/, layouts/, static/, themes/, hugo.toml) and overlays the pieces a bare
skeleton lacks: minimal layouts/ so the site builds and serves without a
theme, an example content/posts/hello.md, a castle-flavored hugo.toml
(baseURL = "/", so assets resolve at the root of the site's own subdomain), and a
.gitignore for the regenerated public/ and resources/.
Develop with the live server:
hugo server -D # http://localhost:1313, rebuilds on save
Adding a theme
Drop a theme under themes/ (usually a git submodule) and set theme in
hugo.toml:
git submodule add https://github.com/<owner>/<theme>.git themes/<theme>
Themes with an asset pipeline (e.g. Blowfish + Tailwind) need a pre-build step
before hugo. Declare it as a two-step build in programs/<name>.yaml — a
declared build.commands overrides the stack's single-step default:
build:
commands:
- [pnpm, build] # compile the theme's CSS/JS
- [hugo, --gc, --minify] # render the site -> public/
outputs: [public]
One-time setup those themes expect (run once in the source tree):
git submodule update --init --recursive
cd themes/<theme> && pnpm install
Deployment shape
castle program create --stack hugo writes:
programs/<name>.yaml—source,stack: hugo,build.outputs: [public].deployments/statics/<name>.yaml—manager: caddy,root: public,reach: internal(flip topublicto also expose over the tunnel).
The gateway serves <source>/public in place — no copy, no Node/Hugo process at
runtime. castle program build regenerates public/; castle apply renders the
route and reloads the gateway.
Adopting an existing Hugo site
No stack needed — adopt the repo and declare how it builds:
castle program add /path/to/site --name my-site
Then set build.commands (as above) and add a manager: caddy deployment. This is
how a site with a bespoke build (submodule theme + Tailwind) is wired without the
scaffold. See @docs/registry.md.