diff --git a/deno.lock b/deno.lock index 8492715..147e117 100644 --- a/deno.lock +++ b/deno.lock @@ -2308,7 +2308,7 @@ "jsr:@std/testing@^1.0.17", "jsr:@std/uuid@^1.1.0", "jsr:@udibo/esbuild-plugin-postcss@0.3", - "jsr:@udibo/juniper@0.3", + "jsr:@udibo/juniper@~0.3.1", "npm:@opentelemetry/api@^1.9.0", "npm:@tailwindcss/postcss@^4.1.18", "npm:@testing-library/react@^16.3.2", @@ -2358,7 +2358,7 @@ "jsr:@std/path@^1.1.4", "jsr:@std/streams@^1.0.17", "jsr:@std/testing@^1.0.17", - "jsr:@udibo/juniper@0.3", + "jsr:@udibo/juniper@~0.3.1", "npm:@opentelemetry/api@^1.9.0", "npm:@testing-library/react@^16.3.2", "npm:@types/react@^19.2.9", @@ -2374,7 +2374,7 @@ "jsr:@std/streams@^1.0.17", "jsr:@std/testing@^1.0.17", "jsr:@udibo/esbuild-plugin-postcss@0.3.0", - "jsr:@udibo/juniper@0.3", + "jsr:@udibo/juniper@~0.3.1", "npm:@opentelemetry/api@^1.9.0", "npm:@tailwindcss/postcss@^4.1.18", "npm:@testing-library/react@^16.3.2", @@ -2392,7 +2392,7 @@ "jsr:@std/path@^1.1.4", "jsr:@std/streams@^1.0.17", "jsr:@std/testing@^1.0.17", - "jsr:@udibo/juniper@0.3", + "jsr:@udibo/juniper@~0.3.1", "npm:@opentelemetry/api@^1.9.0", "npm:@tanstack/react-query@^5.90.20", "npm:@testing-library/react@^16.3.2", @@ -2409,7 +2409,7 @@ "jsr:@std/path@^1.1.4", "jsr:@std/streams@^1.0.17", "jsr:@std/testing@^1.0.17", - "jsr:@udibo/juniper@0.3", + "jsr:@udibo/juniper@~0.3.1", "npm:@opentelemetry/api@^1.9.0", "npm:@testing-library/react@^16.3.2", "npm:@types/react@^19.2.9", diff --git a/src/_server.tsx b/src/_server.tsx index cbad763..03b5a8c 100644 --- a/src/_server.tsx +++ b/src/_server.tsx @@ -733,6 +733,16 @@ export function createHandlers< dataOrResponse.status >= 300 && dataOrResponse.status < 400 && location ) { + for (const [key, value] of dataOrResponse.headers.entries()) { + const lowerKey = key.toLowerCase(); + if ( + lowerKey !== "location" && + lowerKey !== "content-type" && + lowerKey !== "content-length" + ) { + c.header(key, value); + } + } c.header("X-Juniper", "redirect"); return c.json({ location }); } diff --git a/src/server.test.tsx b/src/server.test.tsx index 8a210c4..d97d11a 100644 --- a/src/server.test.tsx +++ b/src/server.test.tsx @@ -666,6 +666,54 @@ describe("mergeServerRoutes", () => { }); }); +describe("redirect header preservation", () => { + it("should preserve all headers from redirect response in data requests", async () => { + const client = new Client({ + path: "/", + main: { default: () =>
Home
}, + }); + + const server = createServer(import.meta.url, client, { + path: "/", + main: { + loader: () => + Promise.resolve( + new Response(null, { + status: 302, + headers: new Headers([ + ["Location", "/dashboard"], + ["Set-Cookie", "session=abc123; Path=/; HttpOnly"], + ["X-Custom-Header", "custom-value"], + ["Cache-Control", "no-store"], + ]), + }), + ), + }, + }); + + const res = await server.request("http://localhost/", { + headers: { "X-Juniper-Route-Id": "/" }, + }); + + assertEquals(res.status, 200); + assertEquals(res.headers.get("X-Juniper"), "redirect"); + + // All headers should be preserved except Location, Content-Type, Content-Length + assertEquals( + res.headers.get("Set-Cookie"), + "session=abc123; Path=/; HttpOnly", + ); + assertEquals(res.headers.get("X-Custom-Header"), "custom-value"); + assertEquals(res.headers.get("Cache-Control"), "no-store"); + + // Location should be in the JSON body, not as a header + assertEquals(res.headers.get("Location"), null); + + const data = await res.json(); + assertEquals(data, { location: "/dashboard" }); + }); +}); + describe("build artifact cache control", () => { it("should set no-cache headers with etag for /build/main.js", async () => { // Create a minimal public/build directory structure for serving