From c78253aaf3df7b4c6a70e41b889aac231319085f Mon Sep 17 00:00:00 2001 From: Brenley Dueck Date: Thu, 10 Sep 2026 17:25:12 -0500 Subject: [PATCH] fix: only strip a whole `index` segment in routePathFromFile Route files whose names merely end in "index" (e.g. `routes/reindex.tsx`) were mapped to `/re` instead of `/reindex` because the nested convention used an unanchored `/index$/` replace. Anchor the strip to a segment boundary so only a real `index` file maps to its directory's path. Same fix as solidjs/solid-start#2315 for solidjs/solid-start#2314. Co-Authored-By: Claude Fable 5.1 --- .changeset/index-suffix-segment.md | 5 +++++ src/convention.ts | 3 ++- test/convention.spec.ts | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/index-suffix-segment.md diff --git a/.changeset/index-suffix-segment.md b/.changeset/index-suffix-segment.md new file mode 100644 index 0000000..28567ea --- /dev/null +++ b/.changeset/index-suffix-segment.md @@ -0,0 +1,5 @@ +--- +"filesystem-routing": patch +--- + +Only strip a whole `index` segment when mapping route files to paths. Route files whose names merely end in "index", such as `routes/reindex.tsx`, were previously mapped to `/re` instead of `/reindex`. diff --git a/src/convention.ts b/src/convention.ts index 5ea36b8..3693db8 100644 --- a/src/convention.ts +++ b/src/convention.ts @@ -14,7 +14,8 @@ export function routePathFromFile(routeFile: string): string { const routePath = routeFile // remove the initial slash .slice(1) - .replace(/index$/, "") + // only strip a whole `index` segment, not a name that merely ends in "index" (e.g. `reindex`) + .replace(/(^|\/)index$/, "$1") .replace(/\[([^/]+)\]/g, (_, m) => { if (m.length > 3 && m.startsWith("...")) { return `*${m.slice(3)}`; diff --git a/test/convention.spec.ts b/test/convention.spec.ts index 2dcd943..8ac4a7f 100644 --- a/test/convention.spec.ts +++ b/test/convention.spec.ts @@ -28,6 +28,16 @@ describe("routePathFromFile", () => { it("retains group segments for emission adapters", () => { expect(routePathFromFile("/(marketing)/about")).toBe("/(marketing)/about"); }); + + // https://github.com/solidjs/solid-start/issues/2314 + it("only strips index as a whole segment, not as a filename suffix", () => { + expect(routePathFromFile("/reindex")).toBe("/reindex"); + expect(routePathFromFile("/myindex")).toBe("/myindex"); + expect(routePathFromFile("/appendix")).toBe("/appendix"); + expect(routePathFromFile("/blog/reindex")).toBe("/blog/reindex"); + expect(routePathFromFile("/reindex/index")).toBe("/reindex/"); + expect(routePathFromFile("/[id]/index")).toBe("/:id/"); + }); }); const temporaryDirectories: string[] = []; @@ -75,6 +85,19 @@ describe("PageFileSystemRouter", () => { expect(post.$$route?.pick).toEqual(["route"]); }); + it("keeps route files whose names end in index at their own path", async () => { + const dir = createRouteTree({ + "reindex.tsx": "export default () =>

Reindex

;", + "blog/index.tsx": "export default () =>

Blog

;", + "blog/myindex.tsx": "export default () =>

My index

;" + }); + const router = new PageFileSystemRouter({ dir, extensions: ["tsx"] }); + + const paths = (await router.getRoutes()).map(route => route.path).sort(); + + expect(paths).toEqual(["/blog/", "/blog/myindex", "/reindex"]); + }); + it("resolves a relative dir against the current working directory", async () => { const dir = createRouteTree({ "index.tsx": "export default () =>

Home

;",