react-router 7.16.0 → 7.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/dist/development/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
- package/dist/development/{chunk-QUQL4437.mjs → chunk-6CSD65Y2.mjs} +2 -2
- package/dist/{production/chunk-NALGHHKE.mjs → development/chunk-ASILSGTR.mjs} +2 -2
- package/dist/development/{chunk-SRID2YZ2.js → chunk-KFNXW4AL.js} +1 -1
- package/dist/development/{chunk-XEJDWL2B.js → chunk-PBLBZ3QU.js} +7 -7
- package/dist/{production/chunk-SKEDDLRM.js → development/chunk-PULC7NLK.js} +99 -99
- package/dist/development/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
- package/dist/development/dom-export.d.mts +3 -3
- package/dist/development/dom-export.d.ts +1 -1
- package/dist/development/dom-export.js +28 -28
- package/dist/development/dom-export.mjs +3 -3
- package/dist/development/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
- package/dist/development/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
- package/dist/development/index-react-server-client.d.mts +2 -2
- package/dist/development/index-react-server-client.d.ts +1 -1
- package/dist/development/index-react-server-client.js +4 -4
- package/dist/development/index-react-server-client.mjs +2 -2
- package/dist/development/index-react-server.js +1 -1
- package/dist/development/index-react-server.mjs +1 -1
- package/dist/development/index.d.mts +6 -6
- package/dist/development/index.d.ts +2 -2
- package/dist/development/index.js +85 -85
- package/dist/development/index.mjs +3 -3
- package/dist/development/lib/types/internal.js +1 -1
- package/dist/development/lib/types/internal.mjs +1 -1
- package/dist/production/{browser-nIQ4Nsyi.d.mts → browser-CGcs-0pD.d.mts} +1 -1
- package/dist/{development/chunk-S54KXAEJ.mjs → production/chunk-5TQZEVD5.mjs} +2 -2
- package/dist/production/{chunk-EAQNHM3N.js → chunk-CTIXC7EV.js} +7 -7
- package/dist/{development/chunk-IBI7OMNB.js → production/chunk-EN242BO4.js} +99 -99
- package/dist/production/{chunk-Q65P7S7Y.mjs → chunk-OSYEOCBT.mjs} +2 -2
- package/dist/production/{chunk-Y7DNFQZP.js → chunk-RTRY3JFT.js} +1 -1
- package/dist/production/{context-m8rizgnE.d.mts → context-CmHpk1Ws.d.mts} +1 -1
- package/dist/production/dom-export.d.mts +3 -3
- package/dist/production/dom-export.d.ts +1 -1
- package/dist/production/dom-export.js +28 -28
- package/dist/production/dom-export.mjs +3 -3
- package/dist/production/{index-react-server-client-BLiUx67a.d.ts → index-react-server-client-CwU9bE5R.d.ts} +1 -1
- package/dist/production/{index-react-server-client-CdKROblb.d.mts → index-react-server-client-DPrDrCew.d.mts} +1 -1
- package/dist/production/index-react-server-client.d.mts +2 -2
- package/dist/production/index-react-server-client.d.ts +1 -1
- package/dist/production/index-react-server-client.js +4 -4
- package/dist/production/index-react-server-client.mjs +2 -2
- package/dist/production/index-react-server.js +1 -1
- package/dist/production/index-react-server.mjs +1 -1
- package/dist/production/index.d.mts +6 -6
- package/dist/production/index.d.ts +2 -2
- package/dist/production/index.js +85 -85
- package/dist/production/index.mjs +3 -3
- package/dist/production/lib/types/internal.js +1 -1
- package/dist/production/lib/types/internal.mjs +1 -1
- package/docs/explanation/backend-for-frontend.md +50 -0
- package/docs/explanation/code-splitting.md +61 -0
- package/docs/explanation/concurrency.md +135 -0
- package/docs/explanation/form-vs-fetcher.md +292 -0
- package/docs/explanation/hot-module-replacement.md +137 -0
- package/docs/explanation/hydration.md +14 -0
- package/docs/explanation/index-query-param.md +86 -0
- package/docs/explanation/index.md +4 -0
- package/docs/explanation/lazy-route-discovery.md +78 -0
- package/docs/explanation/location.md +6 -0
- package/docs/explanation/progressive-enhancement.md +150 -0
- package/docs/explanation/race-conditions.md +88 -0
- package/docs/explanation/react-transitions.md +160 -0
- package/docs/explanation/route-matching.md +7 -0
- package/docs/explanation/server-client-execution.md +4 -0
- package/docs/explanation/sessions-and-cookies.md +465 -0
- package/docs/explanation/special-files.md +16 -0
- package/docs/explanation/state-management.md +524 -0
- package/docs/explanation/styling.md +87 -0
- package/docs/explanation/type-safety.md +82 -0
- package/docs/how-to/accessibility.md +44 -0
- package/docs/how-to/client-data.md +199 -0
- package/docs/how-to/data-strategy.md +317 -0
- package/docs/how-to/error-boundary.md +231 -0
- package/docs/how-to/error-reporting.md +142 -0
- package/docs/how-to/fetchers.md +307 -0
- package/docs/how-to/file-route-conventions.md +410 -0
- package/docs/how-to/file-uploads.md +217 -0
- package/docs/how-to/form-validation.md +120 -0
- package/docs/how-to/headers.md +164 -0
- package/docs/how-to/index.md +4 -0
- package/docs/how-to/instrumentation.md +556 -0
- package/docs/how-to/meta.md +40 -0
- package/docs/how-to/middleware.md +763 -0
- package/docs/how-to/navigation-blocking.md +233 -0
- package/docs/how-to/optimize-revalidation.md +12 -0
- package/docs/how-to/pre-rendering.md +225 -0
- package/docs/how-to/presets.md +103 -0
- package/docs/how-to/react-server-components.md +899 -0
- package/docs/how-to/resource-routes.md +126 -0
- package/docs/how-to/route-module-type-safety.md +100 -0
- package/docs/how-to/search-params.md +4 -0
- package/docs/how-to/security.md +30 -0
- package/docs/how-to/server-bundles.md +66 -0
- package/docs/how-to/spa.md +120 -0
- package/docs/how-to/status.md +63 -0
- package/docs/how-to/suspense.md +132 -0
- package/docs/how-to/using-handle.md +117 -0
- package/docs/how-to/view-transitions.md +237 -0
- package/docs/how-to/webhook.md +50 -0
- package/docs/index.md +39 -0
- package/docs/start/data/actions.md +138 -0
- package/docs/start/data/custom.md +198 -0
- package/docs/start/data/data-loading.md +44 -0
- package/docs/start/data/index.md +4 -0
- package/docs/start/data/installation.md +52 -0
- package/docs/start/data/navigating.md +12 -0
- package/docs/start/data/pending-ui.md +12 -0
- package/docs/start/data/route-object.md +268 -0
- package/docs/start/data/routing.md +281 -0
- package/docs/start/data/testing.md +8 -0
- package/docs/start/declarative/index.md +4 -0
- package/docs/start/declarative/installation.md +43 -0
- package/docs/start/declarative/navigating.md +133 -0
- package/docs/start/declarative/routing.md +237 -0
- package/docs/start/declarative/url-values.md +65 -0
- package/docs/start/framework/actions.md +174 -0
- package/docs/start/framework/data-loading.md +201 -0
- package/docs/start/framework/deploying.md +96 -0
- package/docs/start/framework/index.md +4 -0
- package/docs/start/framework/installation.md +41 -0
- package/docs/start/framework/navigating.md +182 -0
- package/docs/start/framework/pending-ui.md +142 -0
- package/docs/start/framework/rendering.md +59 -0
- package/docs/start/framework/route-module.md +527 -0
- package/docs/start/framework/routing.md +362 -0
- package/docs/start/framework/testing.md +133 -0
- package/docs/start/index.md +4 -0
- package/docs/start/modes.md +201 -0
- package/docs/upgrading/component-routes.md +363 -0
- package/docs/upgrading/future.md +280 -0
- package/docs/upgrading/index.md +4 -0
- package/docs/upgrading/remix.md +403 -0
- package/docs/upgrading/router-provider.md +442 -0
- package/docs/upgrading/v6.md +382 -0
- package/package.json +2 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: URL Values
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# URL Values
|
|
6
|
+
|
|
7
|
+
[MODES: declarative]
|
|
8
|
+
|
|
9
|
+
## Route Params
|
|
10
|
+
|
|
11
|
+
Route params are the parsed values from a dynamic segment.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Route path="/concerts/:city" element={<City />} />
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
In this case, `:city` is the dynamic segment. The parsed value for that city will be available from `useParams`
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useParams } from "react-router";
|
|
21
|
+
|
|
22
|
+
function City() {
|
|
23
|
+
let { city } = useParams();
|
|
24
|
+
let data = useFakeDataLibrary(`/api/v2/cities/${city}`);
|
|
25
|
+
// ...
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## URL Search Params
|
|
30
|
+
|
|
31
|
+
Search params are the values after a `?` in the URL. They are accessible from `useSearchParams`, which returns an instance of [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
function SearchResults() {
|
|
35
|
+
let [searchParams] = useSearchParams();
|
|
36
|
+
return (
|
|
37
|
+
<div>
|
|
38
|
+
<p>
|
|
39
|
+
You searched for <i>{searchParams.get("q")}</i>
|
|
40
|
+
</p>
|
|
41
|
+
<FakeSearchResults />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Location Object
|
|
48
|
+
|
|
49
|
+
React Router creates a custom `location` object with some useful information on it accessible with `useLocation`.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
function useAnalytics() {
|
|
53
|
+
let location = useLocation();
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
sendFakeAnalytics(location.pathname);
|
|
56
|
+
}, [location]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function useScrollRestoration() {
|
|
60
|
+
let location = useLocation();
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
fakeRestoreScroll(location.key);
|
|
63
|
+
}, [location]);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Actions
|
|
3
|
+
order: 6
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Actions
|
|
7
|
+
|
|
8
|
+
[MODES: framework]
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
Data mutations are done through Route actions. When the action completes, all loader data on the page is revalidated to keep your UI in sync with the data without writing any code to do it.
|
|
13
|
+
|
|
14
|
+
Route actions defined with `action` are only called on the server while actions defined with `clientAction` are run in the browser.
|
|
15
|
+
|
|
16
|
+
## Client Actions
|
|
17
|
+
|
|
18
|
+
Client actions only run in the browser and take priority over a server action when both are defined.
|
|
19
|
+
|
|
20
|
+
```tsx filename=app/project.tsx
|
|
21
|
+
// route('/projects/:projectId', './project.tsx')
|
|
22
|
+
import type { Route } from "./+types/project";
|
|
23
|
+
import { Form } from "react-router";
|
|
24
|
+
import { someApi } from "./api";
|
|
25
|
+
|
|
26
|
+
export async function clientAction({
|
|
27
|
+
request,
|
|
28
|
+
}: Route.ClientActionArgs) {
|
|
29
|
+
let formData = await request.formData();
|
|
30
|
+
let title = formData.get("title");
|
|
31
|
+
let project = await someApi.updateProject({ title });
|
|
32
|
+
return project;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function Project({
|
|
36
|
+
actionData,
|
|
37
|
+
}: Route.ComponentProps) {
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<h1>Project</h1>
|
|
41
|
+
<Form method="post">
|
|
42
|
+
<input type="text" name="title" />
|
|
43
|
+
<button type="submit">Submit</button>
|
|
44
|
+
</Form>
|
|
45
|
+
{actionData ? (
|
|
46
|
+
<p>{actionData.title} updated</p>
|
|
47
|
+
) : null}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Server Actions
|
|
54
|
+
|
|
55
|
+
Server actions only run on the server and are removed from client bundles.
|
|
56
|
+
|
|
57
|
+
```tsx filename=app/project.tsx
|
|
58
|
+
// route('/projects/:projectId', './project.tsx')
|
|
59
|
+
import type { Route } from "./+types/project";
|
|
60
|
+
import { Form } from "react-router";
|
|
61
|
+
import { fakeDb } from "../db";
|
|
62
|
+
|
|
63
|
+
export async function action({
|
|
64
|
+
request,
|
|
65
|
+
}: Route.ActionArgs) {
|
|
66
|
+
let formData = await request.formData();
|
|
67
|
+
let title = formData.get("title");
|
|
68
|
+
let project = await fakeDb.updateProject({ title });
|
|
69
|
+
return project;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default function Project({
|
|
73
|
+
actionData,
|
|
74
|
+
}: Route.ComponentProps) {
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
<h1>Project</h1>
|
|
78
|
+
<Form method="post">
|
|
79
|
+
<input type="text" name="title" />
|
|
80
|
+
<button type="submit">Submit</button>
|
|
81
|
+
</Form>
|
|
82
|
+
{actionData ? (
|
|
83
|
+
<p>{actionData.title} updated</p>
|
|
84
|
+
) : null}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Calling Actions
|
|
91
|
+
|
|
92
|
+
Actions are called declaratively through `<Form>` and imperatively through `useSubmit` (or `<fetcher.Form>` and `fetcher.submit`) by referencing the route's path and a "post" method.
|
|
93
|
+
|
|
94
|
+
### Calling actions with a Form
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { Form } from "react-router";
|
|
98
|
+
|
|
99
|
+
function SomeComponent() {
|
|
100
|
+
return (
|
|
101
|
+
<Form action="/projects/123" method="post">
|
|
102
|
+
<input type="text" name="title" />
|
|
103
|
+
<button type="submit">Submit</button>
|
|
104
|
+
</Form>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This will cause a navigation and a new entry will be added to the browser history.
|
|
110
|
+
|
|
111
|
+
### Calling actions with useSubmit
|
|
112
|
+
|
|
113
|
+
You can submit form data to an action imperatively with `useSubmit`.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { useCallback } from "react";
|
|
117
|
+
import { useSubmit } from "react-router";
|
|
118
|
+
import { useFakeTimer } from "fake-lib";
|
|
119
|
+
|
|
120
|
+
function useQuizTimer() {
|
|
121
|
+
let submit = useSubmit();
|
|
122
|
+
|
|
123
|
+
let cb = useCallback(() => {
|
|
124
|
+
submit(
|
|
125
|
+
{ quizTimedOut: true },
|
|
126
|
+
{ action: "/end-quiz", method: "post" },
|
|
127
|
+
);
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
let tenMinutes = 10 * 60 * 1000;
|
|
131
|
+
useFakeTimer(tenMinutes, cb);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This will cause a navigation and a new entry will be added to the browser history.
|
|
136
|
+
|
|
137
|
+
### Calling actions with a fetcher
|
|
138
|
+
|
|
139
|
+
Fetchers allow you to submit data to actions (and loaders) without causing a navigation (no new entries in the browser history).
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { useFetcher } from "react-router";
|
|
143
|
+
|
|
144
|
+
function Task() {
|
|
145
|
+
let fetcher = useFetcher();
|
|
146
|
+
let busy = fetcher.state !== "idle";
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<fetcher.Form method="post" action="/update-task/123">
|
|
150
|
+
<input type="text" name="title" />
|
|
151
|
+
<button type="submit">
|
|
152
|
+
{busy ? "Saving..." : "Save"}
|
|
153
|
+
</button>
|
|
154
|
+
</fetcher.Form>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
They also have the imperative `submit` method.
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
fetcher.submit(
|
|
163
|
+
{ title: "New Title" },
|
|
164
|
+
{ action: "/update-task/123", method: "post" },
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
See the [Using Fetchers][fetchers] guide for more information.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
Next: [Navigating](./navigating)
|
|
173
|
+
|
|
174
|
+
[fetchers]: ../../how-to/fetchers
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Data Loading
|
|
3
|
+
order: 5
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Data Loading
|
|
7
|
+
|
|
8
|
+
[MODES: framework]
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
Data is provided to the route component from `loader` and `clientLoader`.
|
|
13
|
+
|
|
14
|
+
Loader data is automatically serialized from loaders and deserialized in components. In addition to primitive values like strings and numbers, loaders can return promises, maps, sets, dates and more.
|
|
15
|
+
|
|
16
|
+
The type for the `loaderData` prop is [automatically generated][type-safety].
|
|
17
|
+
|
|
18
|
+
<docs-info>We try to support the same set of [serializable types][serializable-types] that React permits server components to pass as props to client components. This future proofs your application for any eventual migration to [RSC][rsc].</docs-info>
|
|
19
|
+
|
|
20
|
+
## Client Data Loading
|
|
21
|
+
|
|
22
|
+
`clientLoader` is used to fetch data on the client. This is useful for pages or full projects that you'd prefer to fetch data from the browser only.
|
|
23
|
+
|
|
24
|
+
```tsx filename=app/product.tsx
|
|
25
|
+
// route("products/:pid", "./product.tsx");
|
|
26
|
+
import type { Route } from "./+types/product";
|
|
27
|
+
|
|
28
|
+
export async function clientLoader({
|
|
29
|
+
params,
|
|
30
|
+
}: Route.ClientLoaderArgs) {
|
|
31
|
+
const res = await fetch(`/api/products/${params.pid}`);
|
|
32
|
+
const product = await res.json();
|
|
33
|
+
return product;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// HydrateFallback is rendered while the client loader is running
|
|
37
|
+
export function HydrateFallback() {
|
|
38
|
+
return <div>Loading...</div>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function Product({
|
|
42
|
+
loaderData,
|
|
43
|
+
}: Route.ComponentProps) {
|
|
44
|
+
const { name, description } = loaderData;
|
|
45
|
+
return (
|
|
46
|
+
<div>
|
|
47
|
+
<h1>{name}</h1>
|
|
48
|
+
<p>{description}</p>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Server Data Loading
|
|
55
|
+
|
|
56
|
+
When server rendering, `loader` is used for both initial page loads and client navigations. Client navigations call the loader through an automatic `fetch` by React Router from the browser to your server.
|
|
57
|
+
|
|
58
|
+
```tsx filename=app/product.tsx
|
|
59
|
+
// route("products/:pid", "./product.tsx");
|
|
60
|
+
import type { Route } from "./+types/product";
|
|
61
|
+
import { fakeDb } from "../db";
|
|
62
|
+
|
|
63
|
+
export async function loader({ params }: Route.LoaderArgs) {
|
|
64
|
+
const product = await fakeDb.getProduct(params.pid);
|
|
65
|
+
return product;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default function Product({
|
|
69
|
+
loaderData,
|
|
70
|
+
}: Route.ComponentProps) {
|
|
71
|
+
const { name, description } = loaderData;
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<h1>{name}</h1>
|
|
75
|
+
<p>{description}</p>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Note that the `loader` function is removed from client bundles so you can use server only APIs without worrying about them being included in the browser.
|
|
82
|
+
|
|
83
|
+
## Static Data Loading
|
|
84
|
+
|
|
85
|
+
When pre-rendering, loaders are used to fetch data during the production build.
|
|
86
|
+
|
|
87
|
+
```tsx filename=app/product.tsx
|
|
88
|
+
// route("products/:pid", "./product.tsx");
|
|
89
|
+
import type { Route } from "./+types/product";
|
|
90
|
+
|
|
91
|
+
export async function loader({ params }: Route.LoaderArgs) {
|
|
92
|
+
let product = await getProductFromCSVFile(params.pid);
|
|
93
|
+
return product;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default function Product({
|
|
97
|
+
loaderData,
|
|
98
|
+
}: Route.ComponentProps) {
|
|
99
|
+
const { name, description } = loaderData;
|
|
100
|
+
return (
|
|
101
|
+
<div>
|
|
102
|
+
<h1>{name}</h1>
|
|
103
|
+
<p>{description}</p>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The URLs to pre-render are specified in `react-router.config.ts`:
|
|
110
|
+
|
|
111
|
+
```ts filename=react-router.config.ts
|
|
112
|
+
import type { Config } from "@react-router/dev/config";
|
|
113
|
+
|
|
114
|
+
export default {
|
|
115
|
+
async prerender() {
|
|
116
|
+
let products = await readProductsFromCSVFile();
|
|
117
|
+
return products.map(
|
|
118
|
+
(product) => `/products/${product.id}`,
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
} satisfies Config;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Note that when server rendering, any URLs that aren't pre-rendered will be server rendered as usual, allowing you to pre-render some data at a single route while still server rendering the rest.
|
|
125
|
+
|
|
126
|
+
## Using Both Loaders
|
|
127
|
+
|
|
128
|
+
`loader` and `clientLoader` can be used together. The `loader` will be used on the server for initial SSR (or pre-rendering) and the `clientLoader` will be used on subsequent client-side navigations.
|
|
129
|
+
|
|
130
|
+
```tsx filename=app/product.tsx
|
|
131
|
+
// route("products/:pid", "./product.tsx");
|
|
132
|
+
import type { Route } from "./+types/product";
|
|
133
|
+
import { fakeDb } from "../db";
|
|
134
|
+
|
|
135
|
+
export async function loader({ params }: Route.LoaderArgs) {
|
|
136
|
+
return fakeDb.getProduct(params.pid);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function clientLoader({
|
|
140
|
+
serverLoader,
|
|
141
|
+
params,
|
|
142
|
+
}: Route.ClientLoaderArgs) {
|
|
143
|
+
const res = await fetch(`/api/products/${params.pid}`);
|
|
144
|
+
const serverData = await serverLoader();
|
|
145
|
+
return { ...serverData, ...(await res.json()) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default function Product({
|
|
149
|
+
loaderData,
|
|
150
|
+
}: Route.ComponentProps) {
|
|
151
|
+
const { name, description } = loaderData;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div>
|
|
155
|
+
<h1>{name}</h1>
|
|
156
|
+
<p>{description}</p>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
You can also force the client loader to run during hydration and before the page renders by setting the `hydrate` property on the function. In this situation you will want to render a `HydrateFallback` component to show a fallback UI while the client loader runs.
|
|
163
|
+
|
|
164
|
+
```tsx filename=app/product.tsx
|
|
165
|
+
export async function loader() {
|
|
166
|
+
/* ... */
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function clientLoader() {
|
|
170
|
+
/* ... */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// force the client loader to run during hydration
|
|
174
|
+
clientLoader.hydrate = true as const; // `as const` for type inference
|
|
175
|
+
|
|
176
|
+
export function HydrateFallback() {
|
|
177
|
+
return <div>Loading...</div>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default function Product() {
|
|
181
|
+
/* ... */
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
Next: [Actions][actions]
|
|
188
|
+
|
|
189
|
+
See also:
|
|
190
|
+
|
|
191
|
+
- [Streaming with Suspense][streaming]
|
|
192
|
+
- [Client Data][client-data]
|
|
193
|
+
- [Using Fetchers][fetchers]
|
|
194
|
+
|
|
195
|
+
[type-safety]: ../../explanation/type-safety
|
|
196
|
+
[serializable-types]: https://react.dev/reference/rsc/use-client#serializable-types
|
|
197
|
+
[rsc]: ../../how-to/react-server-components
|
|
198
|
+
[actions]: ./actions
|
|
199
|
+
[streaming]: ../../how-to/suspense
|
|
200
|
+
[client-data]: ../../how-to/client-data
|
|
201
|
+
[fetchers]: ../../how-to/fetchers#loading-data
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deploying
|
|
3
|
+
order: 10
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deploying
|
|
7
|
+
|
|
8
|
+
[MODES: framework]
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
React Router can be deployed two ways:
|
|
13
|
+
|
|
14
|
+
- Fullstack Hosting
|
|
15
|
+
- Static Hosting
|
|
16
|
+
|
|
17
|
+
The official [React Router templates](https://github.com/remix-run/react-router-templates) can help you bootstrap an application or be used as a reference for your own application.
|
|
18
|
+
|
|
19
|
+
When deploying to static hosting, you can deploy React Router the same as any other single page application with React.
|
|
20
|
+
|
|
21
|
+
## Templates
|
|
22
|
+
|
|
23
|
+
After running the `create-react-router` command, make sure to follow the instructions in the README.
|
|
24
|
+
|
|
25
|
+
### Node.js with Docker
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
npx create-react-router@latest --template remix-run/react-router-templates/default
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- Server Rendering
|
|
32
|
+
- Tailwind CSS
|
|
33
|
+
|
|
34
|
+
The containerized application can be deployed to any platform that supports Docker, including:
|
|
35
|
+
|
|
36
|
+
- AWS ECS
|
|
37
|
+
- Google Cloud Run
|
|
38
|
+
- Azure Container Apps
|
|
39
|
+
- Digital Ocean App Platform
|
|
40
|
+
- Fly.io
|
|
41
|
+
- Railway
|
|
42
|
+
|
|
43
|
+
### Node with Docker (Custom Server)
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
npx create-react-router@latest --template remix-run/react-router-templates/node-custom-server
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- Server Rendering
|
|
50
|
+
- Tailwind CSS
|
|
51
|
+
- Custom express server for more control
|
|
52
|
+
|
|
53
|
+
The containerized application can be deployed to any platform that supports Docker, including:
|
|
54
|
+
|
|
55
|
+
- AWS ECS
|
|
56
|
+
- Google Cloud Run
|
|
57
|
+
- Azure Container Apps
|
|
58
|
+
- Digital Ocean App Platform
|
|
59
|
+
- Fly.io
|
|
60
|
+
- Railway
|
|
61
|
+
|
|
62
|
+
### Node with Docker and Postgres
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
npx create-react-router@latest --template remix-run/react-router-templates/node-postgres
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- Server Rendering
|
|
69
|
+
- Postgres Database with Drizzle
|
|
70
|
+
- Tailwind CSS
|
|
71
|
+
- Custom express server for more control
|
|
72
|
+
|
|
73
|
+
The containerized application can be deployed to any platform that supports Docker, including:
|
|
74
|
+
|
|
75
|
+
- AWS ECS
|
|
76
|
+
- Google Cloud Run
|
|
77
|
+
- Azure Container Apps
|
|
78
|
+
- Digital Ocean App Platform
|
|
79
|
+
- Fly.io
|
|
80
|
+
- Railway
|
|
81
|
+
|
|
82
|
+
### Vercel
|
|
83
|
+
|
|
84
|
+
Vercel maintains their own template for React Router. Checkout the [Vercel Guide](https://vercel.com/templates/react-router/react-router-boilerplate) for more information.
|
|
85
|
+
|
|
86
|
+
### Cloudflare Workers
|
|
87
|
+
|
|
88
|
+
Cloudflare maintains their own template for React Router. Checkout the [Cloudflare Guide](https://developers.cloudflare.com/workers/framework-guides/web-apps/react-router/) for more information.
|
|
89
|
+
|
|
90
|
+
### Netlify
|
|
91
|
+
|
|
92
|
+
Netlify maintains their own template for React Router. Checkout the [Netlify Guide](https://docs.netlify.com/build/frameworks/framework-setup-guides/react-router/) for more information.
|
|
93
|
+
|
|
94
|
+
### EdgeOne Pages
|
|
95
|
+
|
|
96
|
+
EdgeOne Pages maintains their own template for React Router. Checkout the [EdgeOne Pages Guide](https://pages.edgeone.ai/document/framework-react-router) for more information.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Installation
|
|
3
|
+
order: 1
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Installation
|
|
7
|
+
|
|
8
|
+
[MODES: framework]
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
Most projects start with a template. Let's use a basic template maintained by React Router:
|
|
13
|
+
|
|
14
|
+
```shellscript nonumber
|
|
15
|
+
npx create-react-router@latest my-react-router-app
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Now change into the new directory and start the app
|
|
19
|
+
|
|
20
|
+
```shellscript nonumber
|
|
21
|
+
cd my-react-router-app
|
|
22
|
+
npm i
|
|
23
|
+
npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can now open your browser to `http://localhost:5173`
|
|
27
|
+
|
|
28
|
+
You can [view the template on GitHub][default-template] to see how to manually set up your project.
|
|
29
|
+
|
|
30
|
+
We also have a number of [ready to deploy templates][react-router-templates] available for you to get started with:
|
|
31
|
+
|
|
32
|
+
```shellscript nonumber
|
|
33
|
+
npx create-react-router@latest --template remix-run/react-router-templates/<template-name>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Next: [Routing](./routing)
|
|
39
|
+
|
|
40
|
+
[default-template]: https://github.com/remix-run/react-router-templates/tree/main/default
|
|
41
|
+
[react-router-templates]: https://github.com/remix-run/react-router-templates
|