Skip to content

Commit 91f06ce

Browse files
committed
fix: handle redirects for unmatched routes
1 parent 9b0cedf commit 91f06ce

10 files changed

Lines changed: 171 additions & 2 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "react-router";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "react-router";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "react-router";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "react-router";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "react-router";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};

packages/cli/src/prebuild.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import {
55
readdir,
66
readFile,
77
rm,
8+
symlink,
89
writeFile,
910
} from "node:fs/promises";
1011
import { join } from "node:path";
12+
import { pathToFileURL } from "node:url";
1113
import { tmpdir } from "node:os";
1214
import { bundleVersion } from "@webstudio-is/protocol";
1315
import { generateRedirectsModule, prebuild } from "./prebuild";
@@ -20,6 +22,35 @@ const rootFolderId = "root";
2022
const elementComponent = "ws:element";
2123
const slowPrebuildTestTimeout = 15_000;
2224
type Redirects = Array<{ old: string; new: string; status?: "301" | "302" }>;
25+
type GeneratedRouteModule = {
26+
loader: (args: { request: Request }) => Response | Promise<Response>;
27+
};
28+
29+
const importGeneratedRoute = async (path: string) => {
30+
await symlink(join(originalCwd, "node_modules"), "node_modules", "dir");
31+
return (await import(
32+
`${pathToFileURL(join(tempDir, path)).href}?test=${crypto.randomUUID()}`
33+
)) as GeneratedRouteModule;
34+
};
35+
36+
const expectGeneratedRedirectFallback = async (path: string) => {
37+
const routeModule = await importGeneratedRoute(path);
38+
const redirectResponse = await routeModule.loader({
39+
request: new Request("https://example.com/dl.php?filename=file.pdf"),
40+
});
41+
expect(redirectResponse.status).toBe(301);
42+
expect(redirectResponse.headers.get("Location")).toBe("/downloads/file.pdf");
43+
44+
try {
45+
await routeModule.loader({
46+
request: new Request("https://example.com/not-a-redirect"),
47+
});
48+
throw new Error("Expected unmatched request to throw a 404 response.");
49+
} catch (error) {
50+
expect(error).toBeInstanceOf(Response);
51+
expect((error as Response).status).toBe(404);
52+
}
53+
};
2354

2455
const getFilePaths = async (dir: string): Promise<string[]> => {
2556
const entries = await readdir(dir, { withFileTypes: true });
@@ -302,6 +333,7 @@ describe("prebuild", () => {
302333
expect(routeTemplate).toContain("../__generated__/_index.server");
303334
expect(routeTemplate).not.toContain("__CLIENT__");
304335
expect(routeTemplate).not.toContain("__SERVER__");
336+
await expectGeneratedRedirectFallback("app/routes/$.tsx");
305337

306338
await expect(
307339
readFile("app/__generated__/stale.ts", "utf8")

packages/cli/src/redirect-url.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe("redirect-url fixture copies", () => {
5353
"react-router-vercel",
5454
"webstudio-features",
5555
])(
56-
"keeps %s redirect helper synced with react-router template",
56+
"keeps %s redirect files synced with react-router template",
5757
async (fixture) => {
5858
await expect(
5959
readFile(
@@ -66,10 +66,21 @@ describe("redirect-url fixture copies", () => {
6666
"utf8"
6767
)
6868
);
69+
await expect(
70+
readFile(
71+
join(repoRoot, "fixtures", fixture, "app/routes/$.tsx"),
72+
"utf8"
73+
)
74+
).resolves.toEqual(
75+
await readFile(
76+
join(cliRoot, "templates/react-router/app/routes/$.tsx"),
77+
"utf8"
78+
)
79+
);
6980
}
7081
);
7182

72-
test("keeps webstudio-cloudflare-template redirect helper synced with defaults template", async () => {
83+
test("keeps webstudio-cloudflare-template redirect files synced with defaults template", async () => {
7384
await expect(
7485
readFile(
7586
join(
@@ -84,6 +95,20 @@ describe("redirect-url fixture copies", () => {
8495
"utf8"
8596
)
8697
);
98+
await expect(
99+
readFile(
100+
join(
101+
repoRoot,
102+
"fixtures/webstudio-cloudflare-template/app/routes/$.tsx"
103+
),
104+
"utf8"
105+
)
106+
).resolves.toEqual(
107+
await readFile(
108+
join(cliRoot, "templates/defaults/app/routes/$.tsx"),
109+
"utf8"
110+
)
111+
);
87112
});
88113
});
89114

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type LoaderFunctionArgs } from "react-router";
2+
import { redirectRequest } from "../redirect-url";
3+
// @todo think about how to make __generated__ typeable
4+
// @ts-ignore
5+
import { redirects } from "../__generated__/$resources.redirects";
6+
7+
export const loader = ({ request }: LoaderFunctionArgs) => {
8+
const redirectResponse = redirectRequest(request, redirects);
9+
if (redirectResponse !== undefined) {
10+
return redirectResponse;
11+
}
12+
13+
throw new Response("Not Found", { status: 404 });
14+
};

0 commit comments

Comments
 (0)