diff --git a/.changeset/spa-page-api-route.md b/.changeset/spa-page-api-route.md new file mode 100644 index 000000000..774a9b043 --- /dev/null +++ b/.changeset/spa-page-api-route.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Serve pages in SPA mode (`ssr: false`) when a route file exports both a page and an API handler, such as a `[...404].tsx` with a `GET`. The server treated those routes as API-only, so a `GET` that returned nothing gave a 404 for every URL the route matched. diff --git a/packages/start/src/server/routes.spec.ts b/packages/start/src/server/routes.spec.ts new file mode 100644 index 000000000..367601bc9 --- /dev/null +++ b/packages/start/src/server/routes.spec.ts @@ -0,0 +1,34 @@ +import { describe, expect, it, vi } from "vitest"; + +const GET = { import: () => Promise.resolve({}) }; + +// What the server file router produces for `routes/[...404].tsx` exporting +// both a page and `GET`, with and without the page component. +vi.mock("solid-start:routes", () => ({ + default: [ + { + path: "/*404", + page: true, + $component: { src: "routes/[...404].tsx", import: () => Promise.resolve({}) }, + $GET: GET, + }, + { path: "/spa/*404", page: true, $GET: GET }, + { path: "/api/*rest", $GET: GET }, + ], +})); + +const { matchAPIRoute } = await import("./routes.ts"); + +describe("matchAPIRoute", () => { + it("marks a route with a page and an API handler as a page", () => { + expect(matchAPIRoute("/missing", "GET")?.isPage).toBe(true); + }); + + it("marks it as a page in SPA builds, where the server routes have no page components", () => { + expect(matchAPIRoute("/spa/missing", "GET")?.isPage).toBe(true); + }); + + it("does not mark an API-only route as a page", () => { + expect(matchAPIRoute("/api/missing", "GET")?.isPage).toBe(false); + }); +}); diff --git a/packages/start/src/server/routes.ts b/packages/start/src/server/routes.ts index 0b44eb18d..4521cff8e 100644 --- a/packages/start/src/server/routes.ts +++ b/packages/start/src/server/routes.ts @@ -111,8 +111,9 @@ export function matchAPIRoute( if (handler === undefined) return; - // Check if this is a page route - const isPage = route.page === true && route.$component !== undefined; + // Check if this is a page route. SPA builds leave page components out of + // the server routes, so this can't depend on `$component`. + const isPage = route.page === true; // Return comprehensive route information return {