Skip to content

Commit aa8f75e

Browse files
ScriptedAlchemychenjiahan
authored andcommitted
fix: route feature detection
1 parent 6ba5657 commit aa8f75e

27 files changed

+2245
-481
lines changed

README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,116 @@ The custom server setup allows you to:
307307
- Add server-side caching
308308
- And more!
309309
310+
## Cloudflare Workers Deployment
311+
312+
To deploy your React Router app to Cloudflare Workers:
313+
314+
1. **Configure Rsbuild** (`rsbuild.config.ts`):
315+
```ts
316+
import { defineConfig } from '@rsbuild/core';
317+
import { pluginReact } from '@rsbuild/plugin-react';
318+
import { pluginReactRouter } from '@rsbuild/plugin-react-router';
319+
320+
export default defineConfig({
321+
environments: {
322+
node: {
323+
performance: {
324+
chunkSplit: { strategy: 'all-in-one' },
325+
},
326+
tools: {
327+
rspack: {
328+
experiments: { outputModule: true },
329+
externalsType: 'module',
330+
output: {
331+
chunkFormat: 'module',
332+
chunkLoading: 'import',
333+
workerChunkLoading: 'import',
334+
wasmLoading: 'fetch',
335+
library: { type: 'module' },
336+
module: true,
337+
},
338+
resolve: {
339+
conditionNames: ['workerd', 'worker', 'browser', 'import', 'require'],
340+
},
341+
},
342+
},
343+
},
344+
},
345+
plugins: [pluginReactRouter({customServer: true}), pluginReact()],
346+
});
347+
```
348+
349+
2. **Create Worker Entry** (`server/app.ts`):
350+
```ts
351+
import { createRequestHandler } from 'react-router';
352+
353+
declare global {
354+
interface CloudflareEnvironment extends Env {}
355+
interface ImportMeta {
356+
env: {
357+
MODE: string;
358+
};
359+
}
360+
}
361+
362+
declare module 'react-router' {
363+
export interface AppLoadContext {
364+
cloudflare: {
365+
env: CloudflareEnvironment;
366+
ctx: ExecutionContext;
367+
};
368+
}
369+
}
370+
371+
// @ts-expect-error - virtual module provided by React Router at build time
372+
import * as serverBuild from 'virtual/react-router/server-build';
373+
374+
const requestHandler = createRequestHandler(serverBuild, import.meta.env.MODE);
375+
376+
export default {
377+
fetch(request, env, ctx) {
378+
return requestHandler(request, {
379+
cloudflare: { env, ctx },
380+
});
381+
},
382+
} satisfies ExportedHandler<CloudflareEnvironment>;
383+
```
384+
385+
3. **Configure Wrangler** (`wrangler.toml`):
386+
```toml
387+
workers_dev = true
388+
name = "my-react-router-worker"
389+
compatibility_date = "2024-11-18"
390+
main = "./build/server/static/js/app.js"
391+
assets = { directory = "./build/client/" }
392+
393+
[vars]
394+
VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare"
395+
```
396+
397+
4. **Update Package Scripts** (`package.json`):
398+
```json
399+
{
400+
"scripts": {
401+
"build": "rsbuild build",
402+
"deploy": "npm run build && wrangler deploy",
403+
"dev": "rsbuild dev",
404+
"start": "wrangler dev"
405+
},
406+
"dependencies": {
407+
"@react-router/node": "^7.1.3",
408+
"@react-router/serve": "^7.1.3",
409+
"react-router": "^7.1.3"
410+
},
411+
"devDependencies": {
412+
"@cloudflare/workers-types": "^4.20241112.0",
413+
"@react-router/cloudflare": "^7.1.3",
414+
"@react-router/dev": "^7.1.3",
415+
"wrangler": "^3.106.0"
416+
}
417+
}
418+
```
419+
310420
## Development
311421
312422
The plugin automatically:

examples/cloudflare/rsbuild.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ export default defineConfig({
3737
},
3838
},
3939
},
40-
plugins: [pluginReactRouter(), pluginReact()],
40+
plugins: [pluginReactRouter({customServer: true}), pluginReact()],
4141
});

examples/cloudflare/wrangler.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
workers_dev = true
2+
23
name = "my-react-router-worker"
34
compatibility_date = "2024-11-18"
45
main = "./build/server/static/js/app.js"
56
assets = { directory = "./build/client/" }
67

8+
[build]
9+
#command = "npm run build"
10+
#watch_dir = "app"
11+
712
[vars]
813
VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare"
Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
1-
@import 'tailwindcss';
2-
3-
@theme {
4-
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif,
5-
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
6-
}
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
74

85
html,
96
body {
10-
@apply bg-white dark:bg-gray-950;
7+
@apply bg-white dark:bg-gray-950 min-h-screen font-sans;
118

129
@media (prefers-color-scheme: dark) {
1310
color-scheme: dark;
1411
}
1512
}
13+
14+
.nav-link {
15+
@apply px-4 py-2 rounded-lg transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800;
16+
}
17+
18+
.nav-link.active {
19+
@apply bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-100;
20+
}
21+
22+
.page-container {
23+
@apply max-w-7xl mx-auto px-4 py-8;
24+
}
25+
26+
.card {
27+
@apply bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6;
28+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/** @jsxRuntime automatic */
2+
/** @jsxImportSource react */
3+
4+
import { Link } from 'react-router';
5+
6+
// import logoDark from "./logo-dark.svg";
7+
// import logoLight from "./logo-light.svg";
8+
9+
const resources = [
10+
{
11+
href: 'https://reactrouter.com/docs',
12+
text: 'React Router Documentation',
13+
description: 'Learn everything about React Router v6 and its features.',
14+
icon: (
15+
<svg
16+
xmlns="http://www.w3.org/2000/svg"
17+
className="w-6 h-6 text-blue-600 dark:text-blue-400"
18+
fill="none"
19+
viewBox="0 0 24 24"
20+
stroke="currentColor"
21+
aria-hidden="true"
22+
>
23+
<title>Documentation Icon</title>
24+
<path
25+
strokeLinecap="round"
26+
strokeLinejoin="round"
27+
strokeWidth={2}
28+
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
29+
/>
30+
</svg>
31+
),
32+
},
33+
{
34+
href: 'https://github.com/remix-run/react-router',
35+
text: 'GitHub Repository',
36+
description: 'Explore the source code and contribute to React Router.',
37+
icon: (
38+
<svg
39+
xmlns="http://www.w3.org/2000/svg"
40+
className="w-6 h-6 text-blue-600 dark:text-blue-400"
41+
fill="none"
42+
viewBox="0 0 24 24"
43+
stroke="currentColor"
44+
aria-hidden="true"
45+
>
46+
<title>GitHub Icon</title>
47+
<path
48+
strokeLinecap="round"
49+
strokeLinejoin="round"
50+
strokeWidth={2}
51+
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
52+
/>
53+
</svg>
54+
),
55+
},
56+
{
57+
href: 'https://reactrouter.com/blog',
58+
text: 'React Router Blog',
59+
description: 'Stay updated with the latest news and updates.',
60+
icon: (
61+
<svg
62+
xmlns="http://www.w3.org/2000/svg"
63+
className="w-6 h-6 text-blue-600 dark:text-blue-400"
64+
fill="none"
65+
viewBox="0 0 24 24"
66+
stroke="currentColor"
67+
aria-hidden="true"
68+
>
69+
<title>Blog Icon</title>
70+
<path
71+
strokeLinecap="round"
72+
strokeLinejoin="round"
73+
strokeWidth={2}
74+
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2.5 2.5 0 00-2.5-2.5H15"
75+
/>
76+
</svg>
77+
),
78+
},
79+
];
80+
81+
export function Welcome({ message }: { message: string }) {
82+
return (
83+
<div className="page-container">
84+
<div className="text-center mb-12">
85+
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
86+
{message}
87+
</h1>
88+
<p className="text-xl text-gray-600 dark:text-gray-300">
89+
Get started with React Router and explore its powerful features
90+
</p>
91+
</div>
92+
93+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
94+
{resources.map(({ href, text, description, icon }) => (
95+
<a
96+
key={href}
97+
className="card group hover:ring-2 hover:ring-blue-500 transition-all"
98+
href={href}
99+
target="_blank"
100+
rel="noreferrer"
101+
>
102+
<div className="flex items-center gap-4 mb-4">
103+
{icon}
104+
<h2 className="text-xl font-semibold text-gray-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400">
105+
{text}
106+
</h2>
107+
</div>
108+
<p className="text-gray-600 dark:text-gray-300">{description}</p>
109+
</a>
110+
))}
111+
</div>
112+
113+
<div className="card text-center">
114+
<h2 className="text-2xl font-semibold mb-4 text-gray-900 dark:text-white">
115+
Ready to explore more?
116+
</h2>
117+
<p className="text-gray-600 dark:text-gray-300 mb-6">
118+
Check out our about page to learn more about the technologies used in
119+
this demo.
120+
</p>
121+
<Link
122+
to="/about"
123+
className="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
124+
>
125+
View About Page
126+
</Link>
127+
</div>
128+
</div>
129+
);
130+
}

0 commit comments

Comments
 (0)