diff --git a/.changeset/fs-router-index-suffix.md b/.changeset/fs-router-index-suffix.md new file mode 100644 index 000000000..06f32731e --- /dev/null +++ b/.changeset/fs-router-index-suffix.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": 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 served at `/re` instead of `/reindex`. diff --git a/packages/start/src/config/fs-router.ts b/packages/start/src/config/fs-router.ts index 3bea17361..7179d5197 100644 --- a/packages/start/src/config/fs-router.ts +++ b/packages/start/src/config/fs-router.ts @@ -7,23 +7,33 @@ import { type FileSystemRouterConfig, } from "./fs-routes/router.ts"; +/** + * Maps a route file to its router path: strips the routes directory and extension, + * drops a trailing `index` segment (`blog/index` -> `/blog/`, `index` -> `/`) and + * converts `[param]`, `[[optional]]` and `[...rest]` segments to router syntax. + */ +function toRoutePath(src: string, config: FileSystemRouterConfig) { + const routePath = cleanPath(src, config) + // remove the initial slash + .slice(1) + // 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)}`; + } + if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { + return `:${m.slice(1, -1)}?`; + } + return `:${m}`; + }); + + return routePath.length > 0 ? `/${routePath}` : "/"; +} + export class SolidStartClientFileRouter extends BaseFileSystemRouter { toPath(src: string) { - const routePath = cleanPath(src, this.config) - // remove the initial slash - .slice(1) - .replace(/index$/, "") - .replace(/\[([^/]+)\]/g, (_, m) => { - if (m.length > 3 && m.startsWith("...")) { - return `*${m.slice(3)}`; - } - if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { - return `:${m.slice(1, -1)}?`; - } - return `:${m}`; - }); - - return routePath?.length > 0 ? `/${routePath}` : "/"; + return toRoutePath(src, this.config); } toRoute(src: string) { @@ -107,21 +117,7 @@ export class SolidStartServerFileRouter extends BaseFileSystemRouter { } toPath(src: string) { - const routePath = cleanPath(src, this.config) - // remove the initial slash - .slice(1) - .replace(/index$/, "") - .replace(/\[([^/]+)\]/g, (_, m) => { - if (m.length > 3 && m.startsWith("...")) { - return `*${m.slice(3)}`; - } - if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { - return `:${m.slice(1, -1)}?`; - } - return `:${m}`; - }); - - return routePath?.length > 0 ? `/${routePath}` : "/"; + return toRoutePath(src, this.config); } toRoute(src: string) { diff --git a/packages/start/src/config/fs-routes/router.spec.ts b/packages/start/src/config/fs-routes/router.spec.ts index ab858d7e6..0ae5a9124 100644 --- a/packages/start/src/config/fs-routes/router.spec.ts +++ b/packages/start/src/config/fs-routes/router.spec.ts @@ -3,7 +3,7 @@ import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { SolidStartClientFileRouter } from "../fs-router.ts"; +import { SolidStartClientFileRouter, SolidStartServerFileRouter } from "../fs-router.ts"; import { analyzeModule } from "./router.ts"; const temporaryDirectories: string[] = []; @@ -83,3 +83,37 @@ describe("analyzeModule", () => { expect(router.toRoute(route)?.$component.pick).toEqual(["default", "$css"]); }); }); + +describe("toPath", () => { + const dir = "/app/src/routes"; + const routers = [SolidStartClientFileRouter, SolidStartServerFileRouter]; + + it.each([ + ["index.tsx", "/"], + ["blog/index.tsx", "/blog/"], + ["[id]/index.tsx", "/:id/"], + ["about.tsx", "/about"], + ["blog/[slug].tsx", "/blog/:slug"], + ["docs/[[version]].tsx", "/docs/:version?"], + ["[...404].tsx", "/*404"], + ])("maps %s to %s", (file, expected) => { + for (const Router of routers) { + const router = new Router({ dir, extensions: ["tsx"] }); + expect(router.toPath(`${dir}/${file}`)).toBe(expected); + } + }); + + // https://github.com/solidjs/solid-start/issues/2314 + it.each([ + ["reindex.tsx", "/reindex"], + ["myindex.tsx", "/myindex"], + ["appendix.tsx", "/appendix"], + ["reindex/index.tsx", "/reindex/"], + ["blog/reindex.tsx", "/blog/reindex"], + ])("does not strip a trailing index substring from %s", (file, expected) => { + for (const Router of routers) { + const router = new Router({ dir, extensions: ["tsx"] }); + expect(router.toPath(`${dir}/${file}`)).toBe(expected); + } + }); +});