diff --git a/core/src/wildpc_core/generators/caddyfile.py b/core/src/wildpc_core/generators/caddyfile.py index bf29420..1851519 100644 --- a/core/src/wildpc_core/generators/caddyfile.py +++ b/core/src/wildpc_core/generators/caddyfile.py @@ -261,12 +261,15 @@ def _host_remote_block(label: str, host: str, target: str) -> list[str]: def _static_serve_lines(serve_dir: str, spa: bool, indent: str) -> list[str]: - """The `root`/`file_server` body for a static site. SPA sites get a fallback to - the root `index.html` (client-side routing); content sites (Hugo) get plain - `file_server`, which resolves directory indexes and 404s missing paths.""" + """The `root`/`file_server` body for a static site. SPA sites try a nested + directory index (`{path}/index.html`) before falling back to the root + `index.html` (client-side routing) — so a multi-page static site keeps working + at `/sub/dir/` URLs while single-page apps still resolve deep routes. Content + sites (Hugo) get plain `file_server`, which resolves directory indexes and + 404s missing paths.""" lines = [f"{indent}root * {serve_dir}"] if spa: - lines.append(indent + "try_files {path} /index.html") + lines.append(indent + "try_files {path} {path}/index.html /index.html") lines.append(f"{indent}file_server") return lines @@ -456,7 +459,7 @@ def generate_caddyfile_from_registry( lines += [ " handle {", f" root * {root}", - " try_files {path} /index.html", + " try_files {path} {path}/index.html /index.html", " file_server", " }", "}", diff --git a/core/tests/test_caddyfile.py b/core/tests/test_caddyfile.py index adecbe2..073e0cd 100644 --- a/core/tests/test_caddyfile.py +++ b/core/tests/test_caddyfile.py @@ -145,7 +145,7 @@ class TestAcmeMode: cf = generate_caddyfile_from_registry(_acme({})) assert "@host_wildpc host wildpc.example.com" in cf assert "root * /data/repos/wildpc/app/dist" in cf - assert "try_files {path} /index.html" in cf + assert "try_files {path} {path}/index.html /index.html" in cf assert "file_server" in cf diff --git a/docs/stacks/react-vite.md b/docs/stacks/react-vite.md index 8528283..d8b6d65 100644 --- a/docs/stacks/react-vite.md +++ b/docs/stacks/react-vite.md @@ -213,15 +213,16 @@ The build is run with `VITE_BASE=/`, so a `vite.config` that reads it (see @host_my_frontend host my-frontend.example.com handle @host_my_frontend { root * /data/repos/my-frontend/dist - try_files {path} /index.html + try_files {path} {path}/index.html /index.html file_server } ``` -The `try_files {path} /index.html` directive is essential for SPA routing — -it falls back to `index.html` for any path that doesn't match a static file, -letting React Router handle client-side routes. The subdomain is the program name, -not hand-configured. +The `try_files {path} {path}/index.html /index.html` directive handles both SPA +routing and multi-page static sites: it serves a matched static file, then a +nested directory's own `index.html` (so `/sub/dir/` URLs work), and otherwise +falls back to the root `index.html` for any unmatched path — letting React Router +handle client-side routes. The subdomain is the program name, not hand-configured. ## API integration