Skip to content

Commit 705a6c6

Browse files
authored
feat(react-router): support unencoded UTF-8 routes in prerender config with ssr set to false (#13699)
1 parent f153b19 commit 705a6c6

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

.changeset/polite-fans-rhyme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Support unencoded UTF-8 routes in prerender config with `ssr` set to `false`

integration/vite-prerender-test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,6 +2370,86 @@ test.describe("Prerendering", () => {
23702370
expect(requests).toEqual(["/page.data"]);
23712371
});
23722372

2373+
test("Navigates prerendered multibyte path routes", async ({ page }) => {
2374+
fixture = await createFixture({
2375+
prerender: true,
2376+
files: {
2377+
"react-router.config.ts": reactRouterConfig({
2378+
ssr: false, // turn off fog of war since we're serving with a static server
2379+
prerender: ["/", "/page", "/ページ"],
2380+
}),
2381+
"vite.config.ts": files["vite.config.ts"],
2382+
"app/root.tsx": js`
2383+
import * as React from "react";
2384+
import { Link, Outlet, Scripts } from "react-router";
2385+
2386+
export function Layout({ children }) {
2387+
return (
2388+
<html lang="en">
2389+
<head />
2390+
<body>
2391+
<nav>
2392+
<Link to="/page">Page</Link><br/>
2393+
<Link to="/ページ">ページ</Link><br/>
2394+
</nav>
2395+
{children}
2396+
<Scripts />
2397+
</body>
2398+
</html>
2399+
);
2400+
}
2401+
2402+
export default function Root({ loaderData }) {
2403+
return <Outlet />
2404+
}
2405+
`,
2406+
"app/routes/_index.tsx": js`
2407+
export default function Index() {
2408+
return <h1 data-index>Index</h1>
2409+
}
2410+
`,
2411+
"app/routes/page.tsx": js`
2412+
export function loader() {
2413+
return "PAGE DATA"
2414+
}
2415+
export default function Page({ loaderData }) {
2416+
return <h1 data-page>{loaderData}</h1>;
2417+
}
2418+
`,
2419+
"app/routes/ページ.tsx": js`
2420+
export function loader() {
2421+
return "ページ データ";
2422+
}
2423+
export default function Page({ loaderData }) {
2424+
return <h1 data-multibyte-page>{loaderData}</h1>;
2425+
}
2426+
`,
2427+
},
2428+
});
2429+
appFixture = await createAppFixture(fixture);
2430+
2431+
let encodedMultibytePath = encodeURIComponent("ページ");
2432+
let requests = captureRequests(page);
2433+
let app = new PlaywrightFixture(appFixture, page);
2434+
await app.goto("/", true);
2435+
await page.waitForSelector("[data-index]");
2436+
2437+
await app.clickLink("/page");
2438+
await page.waitForSelector("[data-page]");
2439+
expect(await (await page.$("[data-page]"))?.innerText()).toBe(
2440+
"PAGE DATA"
2441+
);
2442+
expect(requests).toEqual(["/page.data"]);
2443+
clearRequests(requests);
2444+
2445+
await app.clickLink("/ページ");
2446+
await page.waitForSelector("[data-multibyte-page]");
2447+
expect(await (await page.$("[data-multibyte-page]"))?.innerText()).toBe(
2448+
"ページ データ"
2449+
);
2450+
expect(requests).toEqual([`/${encodedMultibytePath}.data`]);
2451+
})
2452+
23732453
test("Returns a 404 if navigating to a non-prerendered param value", async ({
23742454
page,
23752455
}) => {

packages/react-router/lib/server-runtime/server.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,22 +172,25 @@ export const createRequestHandler: CreateRequestHandlerFunction = (
172172
// When runtime SSR is disabled, make our dev server behave like the deployed
173173
// pre-rendered site would
174174
if (!_build.ssr) {
175+
// Decode the URL path before checking against the prerender config
176+
let decodedPath = decodeURI(normalizedPath);
177+
175178
// When SSR is disabled this, file can only ever run during dev because we
176179
// delete the server build at the end of the build
177180
if (_build.prerender.length === 0) {
178181
// ssr:false and no prerender config indicates "SPA Mode"
179182
isSpaMode = true;
180183
} else if (
181-
!_build.prerender.includes(normalizedPath) &&
182-
!_build.prerender.includes(normalizedPath + "/")
184+
!_build.prerender.includes(decodedPath) &&
185+
!_build.prerender.includes(decodedPath + "/")
183186
) {
184187
if (url.pathname.endsWith(".data")) {
185188
// 404 on non-pre-rendered `.data` requests
186189
errorHandler(
187190
new ErrorResponseImpl(
188191
404,
189192
"Not Found",
190-
`Refusing to SSR the path \`${normalizedPath}\` because \`ssr:false\` is set and the path is not included in the \`prerender\` config, so in production the path will be a 404.`
193+
`Refusing to SSR the path \`${decodedPath}\` because \`ssr:false\` is set and the path is not included in the \`prerender\` config, so in production the path will be a 404.`
191194
),
192195
{
193196
context: loadContext,

0 commit comments

Comments
 (0)