From ec9f05bf955a2c1f4f65263ec011dd28df189137 Mon Sep 17 00:00:00 2001 From: Seth Raphael Date: Sun, 10 May 2026 21:52:32 -0600 Subject: [PATCH] fix(client): serve /foo/index.html for /foo and /foo/ requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a static bundle is laid out as a directory tree (e.g. Next.js with `output: 'export'` produces `out/about/index.html`), the only stored asset for the route is `/about/index.html`. Requests for `/about` or `/about/` miss the exact-path lookup, then trigger the SPA fallback and serve the root `/index.html` instead of the directory's own page. Add a directory-index fallback step between the exact-path lookup and the SPA fallback: if the request has no file extension, try `${path}/index.html` (or `${path}index.html` when the path ends with `/`). If that exists, serve it. Otherwise the SPA fallback runs unchanged. This is non-breaking — for Vite-style flat outputs the new lookup misses and behaviour is identical to before. Co-Authored-By: Claude Opus 4.7 --- src/client/index.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/client/index.ts b/src/client/index.ts index 6e4ccf5..56d47b7 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -218,7 +218,17 @@ export function registerStaticRoutes( let asset: AssetDoc = await ctx.runQuery(component.lib.getByPath, { path }); - // SPA fallback: if not found and no file extension, serve index.html + // Directory-index fallback: serve /foo/index.html when /foo or /foo/ is + // requested. Supports frameworks like Next.js `output: 'export'` that emit + // directory-based static trees (e.g. `out/about/index.html`). + if (!asset && !hasFileExtension(path)) { + const indexPath = path.endsWith("/") + ? `${path}index.html` + : `${path}/index.html`; + asset = await ctx.runQuery(component.lib.getByPath, { path: indexPath }); + } + + // SPA fallback: if still not found and no file extension, serve index.html if (!asset && spaFallback && !hasFileExtension(path)) { asset = await ctx.runQuery(component.lib.getByPath, { path: "/index.html",