Serve prerendered blog posts, not the SPA shell, from the website container
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run

The personal-website repo now prerenders each post to
dist/blog/<slug>/index.html. Caddy try_files does not resolve a directory
to its index on its own, so with the single-term directive every post URL
fell through to the SPA shell: right content once React booted, wrong
<title>, and no link preview. Verified against caddy:2.8 in Docker both
ways before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7BaCSV8FEtcNgVDVsRAJj
This commit is contained in:
Krzysztof Rudnicki 2026-08-29 16:19:28 +02:00
parent d255288e06
commit 516acc243f
2 changed files with 44 additions and 1 deletions

View File

@ -0,0 +1,36 @@
{
"intent": "Make kuhy.duckdns.org serve each prerendered blog post's own HTML file instead of falling through to the SPA shell, so a shared post URL has the right title and link preview.",
"scope": [
"linux_configuration/features/lib/website_build.sh (the inner Caddyfile template only)",
"Non-goal: no change to the reverse-proxy snippet, the compose stack, ports, or the build command"
],
"changes": [
"try_files gains the middle term {path}/index.html, so a request for /blog/<slug> resolves to dist/blog/<slug>/index.html",
"Added a comment recording why the middle term is load-bearing, because its absence fails silently"
],
"verification": [
{
"command": "docker run caddy:2.8 with the old directive, curl each route",
"result": "fail",
"evidence": "/, /blog, /blog/hello-world and /blog/nope ALL returned <title>Krzysztof Rudnicki</title> — every post served as the landing page"
},
{
"command": "docker run caddy:2.8 with the new directive, curl each route",
"result": "pass",
"evidence": "/blog -> 'Blog — Krzysztof Rudnicki'; /blog/hello-world -> 'Hello, world — Krzysztof Rudnicki'; /blog/nope still falls back to the SPA shell as intended"
},
{
"command": "shellcheck linux_configuration/features/lib/website_build.sh",
"result": "pass",
"evidence": "no findings"
}
],
"risks": [
"A directory in dist/ that shares a name with a client-side route would now win over the SPA fallback; today only /blog/<slug> directories exist, and they are exactly the ones that should win",
"The change only takes effect after setup_personal_website.sh rewrites the managed Caddyfile and restarts the container"
],
"rollback": [
"Restore the single-term try_files line and rerun setup_personal_website.sh",
"After rollback, confirm / still serves and accept that blog posts revert to SPA-shell titles"
]
}

View File

@ -49,7 +49,14 @@ EOF
:${WEBSITE_PORT} {
root * /srv
try_files {path} /index.html
# {path}/index.html is the load-bearing middle term: the website
# prerenders each blog post to dist/blog/<slug>/index.html, and
# try_files does NOT resolve a directory to its index on its own.
# Without it every post silently falls through to the SPA shell and
# is served as the landing page -- right content once React boots,
# wrong <title> and no link preview, which looks like nothing is
# wrong until you check the tab.
try_files {path} {path}/index.html /index.html
file_server
encode gzip
}