Astro Static Route 404 Behind Caddy: Align build.format and Trailing Slashes
If an Astro route works in development but returns 404 behind Caddy, inspect the generated dist/ tree before adding rewrites. A static route is only reachable when Astro’s output format, the URL’s trailing slash, Caddy’s document root, and the deployed files all describe the same path.
For most pre-rendered Astro sites, the simplest contract is build.format: 'directory', trailingSlash: 'always', and a Caddy file_server rooted at the directory that contains dist/. A route such as /guide/ then maps to dist/guide/index.html.
Inspect the generated path first
Build the site and compare the failing URL with the actual files:
pnpm buildfind dist -maxdepth 3 -type f | sortAstro’s configuration reference defines these formats:
| Astro setting | Example output | URL contract to test |
|---|---|---|
build.format: 'directory' | dist/guide/index.html | /guide/ |
build.format: 'file' | dist/guide.html | /guide.html, or an explicit host mapping |
build.format: 'preserve' | Follows the source file shape | Test each generated path directly |
The default format is directory. Astro recommends pairing it with trailingSlash: 'always'; file pairs with trailingSlash: 'never'. For prerendered pages, the hosting server handles the final slash behavior, so a successful astro dev or astro preview request does not prove that Caddy will resolve the production path.
Use a consistent Astro configuration
For a static site whose public URLs end in /, make the contract explicit:
import { defineConfig } from 'astro/config';
export default defineConfig({ output: 'static', trailingSlash: 'always', build: { format: 'directory', },});This does not create redirects for a prerendered page by itself. It makes Astro generate a directory-shaped artifact and keeps development URLs aligned with the intended production shape. Your static server still needs to serve directories and their index.html files.
If you deliberately use build.format: 'file', do not expect /guide/ to find dist/guide.html. Either publish the extension-bearing URL or add a tested, explicit rewrite for the extensionless route. Switching to directory output is usually easier to reason about for a Caddy static site.
Point Caddy at the actual dist root
The Caddy root must be the directory containing the generated files, not the repository parent or a directory that contains a second dist folder:
example.com { root * /usr/share/caddy file_server}In a container, copy the contents of dist into /usr/share/caddy:
FROM caddy:2-alpineCOPY dist/ /usr/share/caddy/The existing Astro static site Docker and Caddy guide covers the multi-stage build boundary. This article focuses on the route contract after the files reach the server.
When the request is /guide/, Caddy’s static file server can resolve the directory and its index file. If you use try_files elsewhere in the site, Caddy’s documented directory form includes the trailing slash:
try_files {path} {path}/ =404Do not replace a pre-rendered site’s real 404 handling with try_files {path} /index.html unless a client-side SPA router owns every unmatched route. That fallback can make a broken Astro path appear to work while returning the wrong document and canonical URL.
Test both slash variants and a real 404
Run the checks against the same container or host configuration that serves production:
curl --head http://localhost:8080/guide/curl --head http://localhost:8080/guidecurl --head http://localhost:8080/does-not-exist/Interpret the results with the generated tree:
/guide/should servedist/guide/index.htmlfor directory output./guidemay redirect, depending on the server configuration; it should not be silently rewritten to an unrelated page.- A missing route should remain a real 404 rather than being swallowed by an SPA fallback.
If the site is deployed below a subpath such as /docs, also set Astro’s base option and verify that assets are copied under that prefix. A correct Caddy root cannot compensate for links generated with the wrong base path.
FAQ
Q: Why does /guide/ work locally but 404 in Caddy?
A: Local preview knows Astro’s route model, while Caddy sees only files. Check whether dist/guide/index.html exists and whether Caddy’s root points directly at the directory containing it.
Q: Should I add a Caddy SPA fallback for every Astro 404?
A: No. A pre-rendered Astro site should serve its generated files and return 404 for missing routes. Use an SPA fallback only when a client-side router intentionally owns those paths.
Q: Is trailingSlash enough to configure a static host?
A: No. Astro documents the URL preference, but prerendered page slash handling is performed by the hosting platform. Align the setting with build.format and verify the host with curl.
References:
Astro Docs: Configuration Reference
Report a typo or broken link, or suggest a related topic.