diff --git a/.changeset/tidy-zebras-route.md b/.changeset/tidy-zebras-route.md new file mode 100644 index 000000000..2e2e522f8 --- /dev/null +++ b/.changeset/tidy-zebras-route.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Fix API route matching when `server.baseURL` is configured. diff --git a/packages/start/src/config/index.ts b/packages/start/src/config/index.ts index c13493a0f..9b766bf0a 100644 --- a/packages/start/src/config/index.ts +++ b/packages/start/src/config/index.ts @@ -181,6 +181,9 @@ export function solidStart(options?: SolidStartOptions): Array { "import.meta.env.START_CLIENT_ENTRY": JSON.stringify(handlers.client), "import.meta.env.START_CLIENT_ENTRY_URL": JSON.stringify(clientEntryUrl), "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), + "import.meta.env.SERVER_BASE_URL": JSON.stringify( + (config.server as { baseURL?: string } | undefined)?.baseURL ?? "", + ), "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || "json"), }, builder: { diff --git a/packages/start/src/env.d.ts b/packages/start/src/env.d.ts index 0a3c5c5cb..43617f4ea 100644 --- a/packages/start/src/env.d.ts +++ b/packages/start/src/env.d.ts @@ -14,5 +14,5 @@ interface SolidStartMetaEnv { START_CLIENT_ENTRY_URL: string; START_ISLANDS: boolean; // START_DEV_OVERLAY: boolean; - // SERVER_BASE_URL: string; + SERVER_BASE_URL: string; } diff --git a/packages/start/src/server/handler.ts b/packages/start/src/server/handler.ts index bbf703ebc..e195d42c7 100644 --- a/packages/start/src/server/handler.ts +++ b/packages/start/src/server/handler.ts @@ -13,6 +13,7 @@ import { handleServerFunction } from "../fns/handler.ts"; import type { APIEvent, FetchEvent, HandlerOptions, PageEvent } from "./types.ts"; import { getExpectedRedirectStatus } from "./util.ts"; import { toWebReadableStream } from "./web-stream.ts"; +import { stripPathBase } from "./strip-path-base.ts"; const SERVER_FN_BASE = "/_server"; @@ -252,6 +253,6 @@ function produceResponseWithEventHeaders(res: Response) { } function stripBaseUrl(path: string) { - if (import.meta.env.BASE_URL === "/" || import.meta.env.BASE_URL === "") return path; - return path.slice(import.meta.env.BASE_URL.length); + const base = import.meta.env.SERVER_BASE_URL || import.meta.env.BASE_URL || "/"; + return stripPathBase(path, base); } diff --git a/packages/start/src/server/strip-path-base.spec.ts b/packages/start/src/server/strip-path-base.spec.ts new file mode 100644 index 000000000..3cec4f142 --- /dev/null +++ b/packages/start/src/server/strip-path-base.spec.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; + +import { stripPathBase } from "./strip-path-base.ts"; + +describe("stripPathBase", () => { + it("leaves paths unchanged for an empty or root base", () => { + expect(stripPathBase("/api/hello", "")).toBe("/api/hello"); + expect(stripPathBase("/api/hello", "/")).toBe("/api/hello"); + }); + + it("strips the base from paths below it", () => { + expect(stripPathBase("/app/api/hello", "/app")).toBe("/api/hello"); + expect(stripPathBase("/app/api/hello", "/app/")).toBe("/api/hello"); + }); + + it("maps an exact base path to root", () => { + expect(stripPathBase("/app", "/app")).toBe("/"); + }); + + it("does not strip a partial path segment match", () => { + expect(stripPathBase("/application/api/hello", "/app")).toBe("/application/api/hello"); + }); +}); diff --git a/packages/start/src/server/strip-path-base.ts b/packages/start/src/server/strip-path-base.ts new file mode 100644 index 000000000..a587aa82d --- /dev/null +++ b/packages/start/src/server/strip-path-base.ts @@ -0,0 +1,9 @@ +export function stripPathBase(path: string, base: string) { + if (!base || base === "/") return path; + + const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base; + if (path === normalizedBase) return "/"; + if (path.startsWith(`${normalizedBase}/`)) return path.slice(normalizedBase.length); + + return path; +}