Skip to content

Commit 273496b

Browse files
lxsmnsycclaude
andauthored
fix: serve pages in SPA mode when the route also has an API handler (1.x) (#2355)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent e36e23c commit 273496b

5 files changed

Lines changed: 51 additions & 2 deletions

File tree

‎.changeset/spa-page-api-route.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solidjs/start": patch
3+
---
4+
5+
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.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
const GET = { import: () => Promise.resolve({}) };
4+
5+
// What the server file router produces for `routes/[...404].tsx` exporting
6+
// both a page and `GET`, with and without the page component.
7+
vi.mock("vinxi/routes", () => ({
8+
default: [
9+
{
10+
path: "/*404",
11+
page: true,
12+
$component: { src: "routes/[...404].tsx", import: () => Promise.resolve({}) },
13+
$GET: GET
14+
},
15+
{ path: "/spa/*404", page: true, $GET: GET },
16+
{ path: "/api/*rest", $GET: GET }
17+
]
18+
}));
19+
20+
const { matchAPIRoute } = await import("./routes");
21+
22+
describe("matchAPIRoute", () => {
23+
it("marks a route with a page and an API handler as a page", () => {
24+
expect(matchAPIRoute("/missing", "GET")?.isPage).toBe(true);
25+
});
26+
27+
it("marks it as a page in SPA builds, where the server routes have no page components", () => {
28+
expect(matchAPIRoute("/spa/missing", "GET")?.isPage).toBe(true);
29+
});
30+
31+
it("does not mark an API-only route as a page", () => {
32+
expect(matchAPIRoute("/api/missing", "GET")?.isPage).toBe(false);
33+
});
34+
});

‎packages/start/src/router/routes.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ export function matchAPIRoute(path: string, method: string) {
6969

7070
if (handler === undefined) return;
7171

72-
// Check if this is a page route
73-
const isPage = route.page === true && route.$component !== undefined;
72+
// Check if this is a page route. SPA builds leave page components out of
73+
// the server routes, so this can't depend on `$component`.
74+
const isPage = route.page === true;
7475

7576
// Return comprehensive route information
7677
return {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default [];

‎packages/start/vitest.config.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import { fileURLToPath } from "node:url";
12
import { defineConfig } from "vitest/config";
23

34
export default defineConfig({
5+
resolve: {
6+
alias: {
7+
// vinxi serves its route manifest only inside an app build. Specs that
8+
// need it mock this stub with `vi.mock("vinxi/routes")`.
9+
"vinxi/routes": fileURLToPath(new URL("./test/vinxi-routes.stub.ts", import.meta.url))
10+
}
11+
},
412
test: {
513
globals: true,
614
environment: "node",

0 commit comments

Comments
 (0)