Skip to content

Commit 5c8fa80

Browse files
committed
fix(rsc): normalize double slashes in manifest URLs
Fixes #14583 by using joinPaths utility to normalize double slashes in getManifestUrl function, preventing ERR_NAME_NOT_RESOLVED errors
1 parent a947b03 commit 5c8fa80

File tree

3 files changed

+142
-3
lines changed

3 files changed

+142
-3
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
test,
3+
expect,
4+
} from "@playwright/test";
5+
import getPort from "get-port";
6+
7+
import { implementations, js, setupRscTest, validateRSCHtml } from "./rsc/utils";
8+
9+
implementations.forEach((implementation) => {
10+
test.describe(`RSC Double Slashes Fix (${implementation.name})`, () => {
11+
test.describe("Production", () => {
12+
let port: number;
13+
let stopAfterAll: () => void;
14+
15+
test.afterAll(() => {
16+
stopAfterAll?.();
17+
});
18+
19+
test.beforeAll(async () => {
20+
port = await getPort();
21+
stopAfterAll = await setupRscTest({
22+
implementation,
23+
port,
24+
files: {
25+
"src/routes.ts": js`
26+
import type { unstable_RSCRouteConfig as RSCRouteConfig } from "react-router";
27+
28+
export const routes = [
29+
{
30+
id: "root",
31+
path: "",
32+
lazy: () => import("./routes/root"),
33+
children: [
34+
{
35+
id: "double-slash-test",
36+
path: "en/test2/test",
37+
lazy: () => import("./routes/double-slash-test/home"),
38+
},
39+
],
40+
},
41+
] satisfies RSCRouteConfig;
42+
`,
43+
44+
"src/routes/double-slash-test/home.tsx": js`
45+
export function loader() {
46+
return { message: "Double slash test successful" };
47+
}
48+
49+
export default function HomeRoute({ loaderData }) {
50+
return (
51+
<div>
52+
<h2 data-test-result>Double Slash Test</h2>
53+
<p data-test-message>{loaderData.message}</p>
54+
</div>
55+
);
56+
}
57+
`,
58+
},
59+
});
60+
});
61+
62+
test("should handle URLs with double slashes correctly", async ({ page }) => {
63+
// Test the problematic URL pattern from the issue
64+
await page.goto(`http://localhost:${port}//en//test2/test`);
65+
66+
// Should successfully load the page despite double slashes
67+
await page.waitForSelector("[data-test-result]");
68+
expect(await page.locator("[data-test-result]").textContent()).toBe(
69+
"Double Slash Test",
70+
);
71+
expect(await page.locator("[data-test-message]").textContent()).toBe(
72+
"Double slash test successful",
73+
);
74+
75+
// Ensure this is using RSC
76+
validateRSCHtml(await page.content());
77+
});
78+
79+
test("should normalize URLs and not cause manifest loading errors", async ({ page }) => {
80+
// Monitor network requests to ensure no ERR_NAME_NOT_RESOLVED errors
81+
const failedRequests: string[] = [];
82+
83+
page.on('requestfailed', (request) => {
84+
failedRequests.push(request.url());
85+
});
86+
87+
// Navigate to a URL with double slashes
88+
await page.goto(`http://localhost:${port}//en//test2/test`);
89+
90+
// Wait for the page to load
91+
await page.waitForSelector("[data-test-result]");
92+
93+
// Check that no manifest requests failed with name resolution errors
94+
const manifestFailures = failedRequests.filter(url =>
95+
url.includes('.manifest') && url.includes('//')
96+
);
97+
98+
expect(manifestFailures).toHaveLength(0);
99+
100+
// Ensure this is using RSC
101+
validateRSCHtml(await page.content());
102+
});
103+
});
104+
});
105+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
import { joinPaths } from "../lib/router/utils";
6+
7+
describe("RSC Double Slashes Issue - URL Normalization", () => {
8+
test("joinPaths should normalize double slashes", () => {
9+
expect(joinPaths(["//en//test2/test"])).toBe("/en/test2/test");
10+
expect(joinPaths(["//en//test1", "//fr//test2"])).toBe("/en/test1/fr/test2");
11+
expect(joinPaths(["/app//base/"])).toBe("/app/base");
12+
expect(joinPaths(["/normal/path"])).toBe("/normal/path");
13+
expect(joinPaths([""])).toBe("/");
14+
});
15+
16+
test("should handle various double slash scenarios", () => {
17+
// Test cases that would cause the original issue
18+
expect(joinPaths(["//en//test2/test"])).toBe("/en/test2/test");
19+
expect(joinPaths(["///multiple///slashes"])).toBe("/multiple/slashes");
20+
expect(joinPaths(["path//with//double//slashes"])).toBe("path/with/double/slashes");
21+
expect(joinPaths(["//"])).toBe("/");
22+
});
23+
24+
test("should preserve single slashes", () => {
25+
expect(joinPaths(["/normal/path"])).toBe("/normal/path");
26+
expect(joinPaths(["path/without/leading/slash"])).toBe("path/without/leading/slash");
27+
});
28+
});

packages/react-router/lib/rsc/browser.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type {
2222
DataStrategyFunctionArgs,
2323
RouterContextProvider,
2424
} from "../router/utils";
25-
import { ErrorResponseImpl, createContext } from "../router/utils";
25+
import { ErrorResponseImpl, createContext, joinPaths } from "../router/utils";
2626
import type {
2727
DecodedSingleFetchResults,
2828
FetchAndDecodeFunction,
@@ -972,16 +972,22 @@ function getManifestUrl(paths: string[]): URL | null {
972972
}
973973

974974
if (paths.length === 1) {
975-
return new URL(`${paths[0]}.manifest`, window.location.origin);
975+
// Normalize double slashes in the single path
976+
const normalizedPath = joinPaths([paths[0]]);
977+
return new URL(`${normalizedPath}.manifest`, window.location.origin);
976978
}
977979

978980
const globalVar = window as WindowWithRouterGlobals;
979981
let basename = (globalVar.__reactRouterDataRouter.basename ?? "").replace(
980982
/^\/|\/$/g,
981983
"",
982984
);
985+
// Normalize double slashes in basename
986+
basename = joinPaths([basename]);
983987
let url = new URL(`${basename}/.manifest`, window.location.origin);
984-
url.searchParams.set("paths", paths.sort().join(","));
988+
// Normalize double slashes in all paths before joining
989+
const normalizedPaths = paths.map(path => joinPaths([path]));
990+
url.searchParams.set("paths", normalizedPaths.sort().join(","));
985991

986992
return url;
987993
}

0 commit comments

Comments
 (0)