react-router 8.4.0 → 8.5.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.
Files changed (198) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/dist/development/dom-export.d.ts +2 -1
  3. package/dist/development/dom-export.js +1 -1
  4. package/dist/development/index-react-server-client.js +2 -2
  5. package/dist/development/index-react-server.d.ts +583 -165
  6. package/dist/development/index-react-server.js +27 -19
  7. package/dist/development/index.d.ts +9 -8
  8. package/dist/development/index.js +5 -4
  9. package/dist/development/lib/actions.js +1 -1
  10. package/dist/development/lib/components.js +22 -3
  11. package/dist/development/lib/context.d.ts +6 -1
  12. package/dist/development/lib/context.js +1 -1
  13. package/dist/development/lib/dom/dom.d.ts +3 -0
  14. package/dist/development/lib/dom/dom.js +1 -1
  15. package/dist/development/lib/dom/global.d.ts +35 -0
  16. package/dist/development/lib/dom/lib.d.ts +34 -9
  17. package/dist/development/lib/dom/lib.js +17 -6
  18. package/dist/development/lib/dom/server.js +2 -2
  19. package/dist/development/lib/dom/ssr/components.js +12 -9
  20. package/dist/development/lib/dom/ssr/data.js +1 -1
  21. package/dist/development/lib/dom/ssr/entry.d.ts +9 -2
  22. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  23. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  24. package/dist/development/lib/dom/ssr/fog-of-war.d.ts +3 -2
  25. package/dist/development/lib/dom/ssr/fog-of-war.js +14 -11
  26. package/dist/development/lib/dom/ssr/hydration.js +1 -1
  27. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  28. package/dist/development/lib/dom/ssr/links.js +1 -1
  29. package/dist/development/lib/dom/ssr/markup.js +1 -1
  30. package/dist/development/lib/dom/ssr/route-discovery.d.ts +120 -0
  31. package/dist/development/lib/dom/ssr/route-discovery.js +415 -0
  32. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  33. package/dist/development/lib/dom/ssr/routes-test-stub.js +2 -2
  34. package/dist/development/lib/dom/ssr/routes.d.ts +2 -2
  35. package/dist/development/lib/dom/ssr/routes.js +8 -8
  36. package/dist/development/lib/dom/ssr/server.js +3 -1
  37. package/dist/development/lib/dom/ssr/single-fetch.d.ts +28 -2
  38. package/dist/development/lib/dom/ssr/single-fetch.js +73 -55
  39. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  40. package/dist/development/lib/dom-export/hydrated-router.d.ts +28 -0
  41. package/dist/development/lib/dom-export/hydrated-router.js +32 -12
  42. package/dist/development/lib/errors.js +1 -1
  43. package/dist/development/lib/hooks.d.ts +3 -3
  44. package/dist/development/lib/hooks.js +4 -4
  45. package/dist/development/lib/href.js +1 -1
  46. package/dist/development/lib/router/history.js +1 -1
  47. package/dist/development/lib/router/instrumentation.js +1 -1
  48. package/dist/development/lib/router/matcher-route-pattern.js +1 -1
  49. package/dist/development/lib/router/matcher-route-pattern.preload.js +1 -1
  50. package/dist/development/lib/router/matcher.js +1 -1
  51. package/dist/development/lib/router/navigation.js +1 -1
  52. package/dist/development/lib/router/router.d.ts +6 -0
  53. package/dist/development/lib/router/router.js +157 -65
  54. package/dist/development/lib/router/url.js +1 -1
  55. package/dist/development/lib/router/utils.d.ts +24 -4
  56. package/dist/development/lib/router/utils.js +4 -3
  57. package/dist/development/lib/rsc/browser.js +2 -2
  58. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  59. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  60. package/dist/development/lib/rsc/html-stream/server.js +24 -12
  61. package/dist/development/lib/rsc/route-modules.js +1 -1
  62. package/dist/development/lib/rsc/server.rsc.d.ts +3 -2
  63. package/dist/development/lib/rsc/server.ssr.js +1 -1
  64. package/dist/development/lib/server-runtime/build.d.ts +14 -0
  65. package/dist/development/lib/server-runtime/cookies.js +1 -1
  66. package/dist/development/lib/server-runtime/crypto.js +1 -1
  67. package/dist/development/lib/server-runtime/data.js +1 -1
  68. package/dist/development/lib/server-runtime/dev.js +1 -1
  69. package/dist/development/lib/server-runtime/entry.js +1 -1
  70. package/dist/development/lib/server-runtime/errors.js +1 -1
  71. package/dist/development/lib/server-runtime/headers.js +1 -1
  72. package/dist/development/lib/server-runtime/invariant.js +1 -1
  73. package/dist/development/lib/server-runtime/mode.js +1 -1
  74. package/dist/development/lib/server-runtime/routeMatching.js +1 -1
  75. package/dist/development/lib/server-runtime/routes.d.ts +1 -1
  76. package/dist/development/lib/server-runtime/routes.js +1 -1
  77. package/dist/development/lib/server-runtime/server.js +12 -4
  78. package/dist/development/lib/server-runtime/serverHandoff.d.ts +17 -0
  79. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  80. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
  81. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
  82. package/dist/development/lib/server-runtime/sessions.js +1 -1
  83. package/dist/development/lib/server-runtime/single-fetch.js +1 -1
  84. package/dist/development/lib/server-runtime/urls.js +1 -1
  85. package/dist/development/lib/server-runtime/warnings.js +1 -1
  86. package/dist/development/lib/types/internal.js +1 -1
  87. package/dist/development/lib/types/route-data.d.ts +2 -2
  88. package/dist/development/lib/types/route-module-annotations.d.ts +1 -1
  89. package/dist/development/route-pattern.js +1 -1
  90. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  91. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +1 -1
  92. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  93. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  94. package/dist/production/dom-export.d.ts +2 -1
  95. package/dist/production/dom-export.js +1 -1
  96. package/dist/production/index-react-server-client.js +2 -2
  97. package/dist/production/index-react-server.d.ts +583 -165
  98. package/dist/production/index-react-server.js +27 -19
  99. package/dist/production/index.d.ts +9 -8
  100. package/dist/production/index.js +5 -4
  101. package/dist/production/lib/actions.js +1 -1
  102. package/dist/production/lib/components.js +22 -3
  103. package/dist/production/lib/context.d.ts +6 -1
  104. package/dist/production/lib/context.js +1 -1
  105. package/dist/production/lib/dom/dom.d.ts +3 -0
  106. package/dist/production/lib/dom/dom.js +1 -1
  107. package/dist/production/lib/dom/global.d.ts +35 -0
  108. package/dist/production/lib/dom/lib.d.ts +34 -9
  109. package/dist/production/lib/dom/lib.js +17 -6
  110. package/dist/production/lib/dom/server.js +2 -2
  111. package/dist/production/lib/dom/ssr/components.js +12 -9
  112. package/dist/production/lib/dom/ssr/data.js +1 -1
  113. package/dist/production/lib/dom/ssr/entry.d.ts +9 -2
  114. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  115. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  116. package/dist/production/lib/dom/ssr/fog-of-war.d.ts +3 -2
  117. package/dist/production/lib/dom/ssr/fog-of-war.js +14 -11
  118. package/dist/production/lib/dom/ssr/hydration.js +1 -1
  119. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  120. package/dist/production/lib/dom/ssr/links.js +1 -1
  121. package/dist/production/lib/dom/ssr/markup.js +1 -1
  122. package/dist/production/lib/dom/ssr/route-discovery.d.ts +120 -0
  123. package/dist/production/lib/dom/ssr/route-discovery.js +415 -0
  124. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  125. package/dist/production/lib/dom/ssr/routes-test-stub.js +2 -2
  126. package/dist/production/lib/dom/ssr/routes.d.ts +2 -2
  127. package/dist/production/lib/dom/ssr/routes.js +8 -8
  128. package/dist/production/lib/dom/ssr/server.js +3 -1
  129. package/dist/production/lib/dom/ssr/single-fetch.d.ts +28 -2
  130. package/dist/production/lib/dom/ssr/single-fetch.js +73 -55
  131. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  132. package/dist/production/lib/dom-export/hydrated-router.d.ts +28 -0
  133. package/dist/production/lib/dom-export/hydrated-router.js +32 -12
  134. package/dist/production/lib/errors.js +1 -1
  135. package/dist/production/lib/hooks.d.ts +3 -3
  136. package/dist/production/lib/hooks.js +3 -3
  137. package/dist/production/lib/href.js +1 -1
  138. package/dist/production/lib/router/history.js +1 -1
  139. package/dist/production/lib/router/instrumentation.js +1 -1
  140. package/dist/production/lib/router/matcher-route-pattern.js +1 -1
  141. package/dist/production/lib/router/matcher-route-pattern.preload.js +1 -1
  142. package/dist/production/lib/router/matcher.js +1 -1
  143. package/dist/production/lib/router/navigation.js +1 -1
  144. package/dist/production/lib/router/router.d.ts +6 -0
  145. package/dist/production/lib/router/router.js +157 -65
  146. package/dist/production/lib/router/url.js +1 -1
  147. package/dist/production/lib/router/utils.d.ts +24 -4
  148. package/dist/production/lib/router/utils.js +4 -3
  149. package/dist/production/lib/rsc/browser.js +2 -2
  150. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  151. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  152. package/dist/production/lib/rsc/html-stream/server.js +24 -12
  153. package/dist/production/lib/rsc/route-modules.js +1 -1
  154. package/dist/production/lib/rsc/server.rsc.d.ts +3 -2
  155. package/dist/production/lib/rsc/server.ssr.js +1 -1
  156. package/dist/production/lib/server-runtime/build.d.ts +14 -0
  157. package/dist/production/lib/server-runtime/cookies.js +1 -1
  158. package/dist/production/lib/server-runtime/crypto.js +1 -1
  159. package/dist/production/lib/server-runtime/data.js +1 -1
  160. package/dist/production/lib/server-runtime/dev.js +1 -1
  161. package/dist/production/lib/server-runtime/entry.js +1 -1
  162. package/dist/production/lib/server-runtime/errors.js +1 -1
  163. package/dist/production/lib/server-runtime/headers.js +1 -1
  164. package/dist/production/lib/server-runtime/invariant.js +1 -1
  165. package/dist/production/lib/server-runtime/mode.js +1 -1
  166. package/dist/production/lib/server-runtime/routeMatching.js +1 -1
  167. package/dist/production/lib/server-runtime/routes.d.ts +1 -1
  168. package/dist/production/lib/server-runtime/routes.js +1 -1
  169. package/dist/production/lib/server-runtime/server.js +12 -4
  170. package/dist/production/lib/server-runtime/serverHandoff.d.ts +17 -0
  171. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  172. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
  173. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
  174. package/dist/production/lib/server-runtime/sessions.js +1 -1
  175. package/dist/production/lib/server-runtime/single-fetch.js +1 -1
  176. package/dist/production/lib/server-runtime/urls.js +1 -1
  177. package/dist/production/lib/server-runtime/warnings.js +1 -1
  178. package/dist/production/lib/types/internal.js +1 -1
  179. package/dist/production/lib/types/route-data.d.ts +2 -2
  180. package/dist/production/lib/types/route-module-annotations.d.ts +1 -1
  181. package/dist/production/route-pattern.js +1 -1
  182. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  183. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +1 -1
  184. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  185. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  186. package/docs/explanation/lazy-route-discovery.md +4 -0
  187. package/docs/explanation/sessions-and-cookies.md +108 -4
  188. package/docs/how-to/custom-fetch.md +218 -0
  189. package/docs/how-to/data-strategy.md +23 -1
  190. package/docs/how-to/middleware.md +1 -0
  191. package/docs/how-to/optimize-revalidation.md +2 -2
  192. package/docs/how-to/pre-rendering.md +23 -1
  193. package/docs/how-to/route-discovery.md +145 -0
  194. package/docs/how-to/spa.md +2 -0
  195. package/docs/how-to/view-transitions.md +80 -13
  196. package/docs/start/framework/navigating.md +5 -9
  197. package/docs/start/framework/rendering.md +37 -0
  198. package/package.json +9 -1
@@ -0,0 +1,218 @@
1
+ ---
2
+ title: Custom Fetch
3
+ unstable: true
4
+ ---
5
+
6
+ # Custom Fetch
7
+
8
+ [MODES: framework]
9
+
10
+ <docs-warning>This feature is experimental and may change in minor or patch releases. It is not supported in RSC Framework Mode.</docs-warning>
11
+
12
+ The `unstable_fetch` prop on [`HydratedRouter`](../api/framework-routers/HydratedRouter) lets you customize the JavaScript-issued requests React Router makes for server loader/action data and lazy route discovery. Use it to add headers, customize fetch options, or apply a request policy based on the operation that initiated the request.
13
+
14
+ This is a Framework Mode API. In Data Mode, you control fetching in your loaders/actions or through a [`dataStrategy`](./data-strategy#request-metadata-unstable).
15
+
16
+ ## Setup
17
+
18
+ If you don't have an [`app/entry.client.tsx`](../api/framework-conventions/entry.client.tsx), reveal the default entry:
19
+
20
+ ```shellscript nonumber
21
+ npx react-router reveal entry.client
22
+ ```
23
+
24
+ Pass a custom implementation to `HydratedRouter`:
25
+
26
+ ```tsx filename=app/entry.client.tsx
27
+ import { startTransition, StrictMode } from "react";
28
+ import { hydrateRoot } from "react-dom/client";
29
+ import {
30
+ HydratedRouter,
31
+ type unstable_RouterFetch,
32
+ } from "react-router/dom";
33
+
34
+ const customFetch: unstable_RouterFetch = (
35
+ request,
36
+ context,
37
+ ) => {
38
+ console.log(context.type, request.method, request.url);
39
+ return fetch(request);
40
+ };
41
+
42
+ startTransition(() => {
43
+ hydrateRoot(
44
+ document,
45
+ <StrictMode>
46
+ <HydratedRouter unstable_fetch={customFetch} />
47
+ </StrictMode>,
48
+ );
49
+ });
50
+ ```
51
+
52
+ The callback receives a constructed `Request`, including its URL, headers, body, and abort signal, and must return a `Promise<Response>`. Pass that request to the browser's `fetch` to preserve those settings, including cancellation.
53
+
54
+ `HydratedRouter` creates a singleton router and reads `unstable_fetch` only during initialization. Changing the prop later has no effect. If your implementation needs changing values such as authentication tokens, read current application state inside the callback instead of capturing values from a component render.
55
+
56
+ ## Request Context
57
+
58
+ The [`unstable_RouterFetchContext`](https://api.reactrouter.com/v8/types/react-router.dom.unstable_RouterFetchContext.html) type is exported from `react-router/dom` and has the following shape:
59
+
60
+ ```ts
61
+ import type { NavigationType } from "react-router";
62
+
63
+ type unstable_RouterFetchContext =
64
+ | { type: "manifest" }
65
+ | {
66
+ type: "navigation";
67
+ fetcherKey: string | null;
68
+ navigationType: NavigationType;
69
+ }
70
+ | {
71
+ type: "initialization" | "fetcher" | "revalidation";
72
+ fetcherKey: string | null;
73
+ navigationType: null;
74
+ };
75
+ ```
76
+
77
+ `type` identifies the operation that initiated the request:
78
+
79
+ - `"manifest"`: Lazy route discovery requests. These have no `fetcherKey` or `navigationType`.
80
+ - `"initialization"`: Data loading during router initialization, such as initial loader requests in API-only Mode.
81
+ - `"navigation"`: Data requests initiated by a navigation, including action submissions and the subsequent loader revalidation.
82
+ - `"fetcher"`: Requests initiated by a fetcher, including the loader revalidation after a fetcher action.
83
+ - `"revalidation"`: Requests initiated by an explicit revalidation, such as `useRevalidator().revalidate()`.
84
+
85
+ `fetcherKey` identifies the fetcher targeted by a data request, or is `null` for route loader/action requests. A fetcher reloaded as part of a navigation or explicit revalidation retains that initiating `type` and also includes its fetcher key. Fetchers reloaded after an initialization redirect likewise retain the `"initialization"` type and include their fetcher key.
86
+
87
+ `navigationType` is `"PUSH"`, `"REPLACE"`, or `"POP"` for navigation-initiated data requests, including fetchers reloaded by that navigation. It is `null` for initialization, standalone fetcher operations, and explicit revalidation. This is the history action for the in-flight navigation, not the previously committed location.
88
+
89
+ ## Examples
90
+
91
+ Each example below can be passed to `HydratedRouter` as its `unstable_fetch` prop.
92
+
93
+ ### Authentication and Custom Headers
94
+
95
+ Read the current token at request time so token changes don't require replacing the callback:
96
+
97
+ ```ts
98
+ import type { unstable_RouterFetch } from "react-router/dom";
99
+ import { authStore } from "./auth";
100
+
101
+ const customFetch: unstable_RouterFetch = (request) => {
102
+ let headers = new Headers(request.headers);
103
+ let token = authStore.getAccessToken();
104
+
105
+ if (token) {
106
+ headers.set("Authorization", `Bearer ${token}`);
107
+ }
108
+
109
+ headers.set("X-App-Version", "1.0");
110
+ return fetch(request, { headers });
111
+ };
112
+ ```
113
+
114
+ Here, `authStore` is your application's authentication store. Only send credentials to trusted origins. Adding custom headers to cross-origin requests may require a CORS preflight and corresponding server configuration.
115
+
116
+ ### Cross-Origin Credentials
117
+
118
+ In [API-only Mode](../start/framework/rendering#using-a-separate-api-origin), use `unstable_apiServerOrigin` to configure a separate API origin. React Router applies that origin before calling your custom fetch, and also uses it for native data prefetches.
119
+
120
+ The origin setting does not enable cross-origin cookies. If you need them, customize the credential policy for the trusted API origin:
121
+
122
+ ```ts
123
+ import type { unstable_RouterFetch } from "react-router/dom";
124
+
125
+ const customFetch: unstable_RouterFetch = (request) => {
126
+ let isApiRequest =
127
+ new URL(request.url).origin ===
128
+ "https://api.example.com";
129
+
130
+ return fetch(request, {
131
+ credentials: isApiRequest
132
+ ? "include"
133
+ : request.credentials,
134
+ });
135
+ };
136
+ ```
137
+
138
+ The API server must allow credentialed CORS requests from your client origin. Browser cookie policies still apply. Native data prefetches do not use this callback, so don't rely on it to configure their credentials.
139
+
140
+ ### Back/Forward Traversal Cache
141
+
142
+ Use `navigationType` to prefer the browser's HTTP cache when navigating back or forward, while retaining the normal cache policy for other requests:
143
+
144
+ ```ts
145
+ import type { unstable_RouterFetch } from "react-router/dom";
146
+
147
+ const customFetch: unstable_RouterFetch = (
148
+ request,
149
+ context,
150
+ ) => {
151
+ let isTraversal =
152
+ import.meta.env.PROD &&
153
+ request.method === "GET" &&
154
+ context.type === "navigation" &&
155
+ context.navigationType === "POP";
156
+
157
+ return fetch(request, {
158
+ cache: isTraversal ? "force-cache" : "default",
159
+ });
160
+ };
161
+ ```
162
+
163
+ This example enables the policy in production only. It includes GET requests for fetchers reloaded by a back/forward navigation, but not standalone fetcher loads or explicit revalidations.
164
+
165
+ The [`force-cache` policy](https://fetch.spec.whatwg.org/#concept-request-cache-mode) uses a matching HTTP cache entry even if it is stale, falling back to the network when no entry exists. Your server's responses must be cacheable; `Cache-Control: no-store` responses aren't stored.
166
+
167
+ This is not a router-managed data cache. React Router still performs its request and response decoding, and this policy adds no mutation or authentication invalidation guarantees. Only use it when serving potentially stale data is appropriate for your application.
168
+
169
+ ### Retrying Safe Requests
170
+
171
+ You can retry a safe GET after a network failure without retrying action submissions or canceled requests:
172
+
173
+ ```ts
174
+ import type { unstable_RouterFetch } from "react-router/dom";
175
+
176
+ const customFetch: unstable_RouterFetch = async (
177
+ request,
178
+ ) => {
179
+ if (request.method !== "GET") {
180
+ return fetch(request);
181
+ }
182
+
183
+ try {
184
+ return await fetch(request.clone());
185
+ } catch (error) {
186
+ if (
187
+ request.signal.aborted ||
188
+ (error instanceof Error &&
189
+ error.name === "AbortError")
190
+ ) {
191
+ throw error;
192
+ }
193
+
194
+ // Retry once, retaining the original abort signal.
195
+ return fetch(request);
196
+ }
197
+ };
198
+ ```
199
+
200
+ This assumes your GET endpoints are safe to repeat. It retries a rejected fetch, not HTTP error responses such as `500`. Don't automatically retry mutations: the server may have processed an action even when the client didn't receive its response.
201
+
202
+ ## Limitations
203
+
204
+ ### Requests Outside Custom Fetch
205
+
206
+ Custom fetch does not intercept the initial document request, server-side loader execution, static assets, or fetches you write in your own application code, including in `clientLoader`/`clientAction`. Calls to their `serverLoader`/`serverAction` helpers do use custom fetch when they issue server data requests.
207
+
208
+ Data prefetches from `<Link prefetch>`, `<NavLink prefetch>`, and [`PrefetchPageLinks`](../api/components/PrefetchPageLinks) use native `<link rel="prefetch">` elements and bypass custom fetch. If a request requires custom headers or other transformations, use `prefetch="none"` on links and avoid rendering `PrefetchPageLinks` for those routes.
209
+
210
+ ### Inspecting Request Bodies
211
+
212
+ If you need a form field such as `intent` to choose request headers, read it from `await request.clone().formData()` for form-encoded submissions, then send the original request. Reading `request.formData()` directly consumes the body so it can't subsequently be sent unchanged.
213
+
214
+ Only parse bodies with a compatible encoding. Cloning and reading a body can buffer data, so avoid doing this unnecessarily, especially for large file uploads. Adding headers or changing fetch options does not require reading the body.
215
+
216
+ ### Response Compatibility
217
+
218
+ Return the response from `fetch` unchanged. You can inspect its status and headers, but you should not consume its body or try to decode, modify, or re-encode it. React Router handles response decoding and expects its manifest/data protocol, including Turbo Stream-encoded loader/action data.
@@ -23,7 +23,9 @@ A custom `dataStrategy` receives the `loader`/`action` arguments (`request`, `pa
23
23
 
24
24
  - `matches`: An array of `DataStrategyMatch` instances for the routes matched by the current `request`
25
25
  - `runClientMiddleware`: A helper function to run the middleware for the matched routes
26
- - `fetcherKey`: The fetcher key if this is for a fetcher request and not a navigation
26
+ - `fetcherKey`: The key of the fetcher targeted by this execution, or `null` for route loader/action executions. Fetchers reloaded by a navigation or explicit revalidation also provide their key.
27
+ - `unstable_initiator`: The operation that initiated this execution (see [Request Metadata](#request-metadata-unstable))
28
+ - `unstable_navigationType`: The history action for a navigation-initiated execution, or `null` for other executions
27
29
 
28
30
  A `DataStrategyMatch` is a normal route match plus a few additional fields:
29
31
 
@@ -77,6 +79,26 @@ interface DataStrategyResult {
77
79
  }
78
80
  ```
79
81
 
82
+ ### Request Metadata (unstable)
83
+
84
+ <docs-warning>`unstable_initiator` and `unstable_navigationType` are experimental and may change in minor or patch releases.</docs-warning>
85
+
86
+ `unstable_initiator` identifies why the data strategy is running:
87
+
88
+ - `"initialization"`: Initial router data loading
89
+ - `"navigation"`: A navigation, including action submissions and subsequent loader revalidation
90
+ - `"fetcher"`: A fetcher operation, including loader revalidation after a fetcher action
91
+ - `"revalidation"`: Explicit revalidation, such as `useRevalidator().revalidate()`
92
+ - `"static"`: Execution by `createStaticHandler` on the server
93
+
94
+ Requests that reload fetchers during a navigation or explicit revalidation retain that initiator while providing the targeted `fetcherKey`. Don't use `fetcherKey` alone to determine what initiated the execution.
95
+
96
+ `unstable_navigationType` is `"PUSH"`, `"REPLACE"`, or `"POP"` for navigation-initiated executions, including fetchers reloaded by that navigation. It is `null` for initialization, standalone fetcher operations, explicit revalidation, and static handler executions. It describes the in-flight navigation rather than the previously committed location.
97
+
98
+ You can use this metadata to choose a loading or caching policy. For example, a Data Mode `dataStrategy` can distinguish back/forward navigations with `unstable_initiator === "navigation" && unstable_navigationType === "POP"`. Your strategy remains responsible for executing the appropriate handlers and returning their results.
99
+
100
+ Framework Mode exposes equivalent request metadata through [`unstable_fetch`](./custom-fetch#request-context), which customizes server requests without replacing loader/action execution.
101
+
80
102
  ### Calling Route Middleware
81
103
 
82
104
  If you are using `middleware` on your routes, you need to leverage the `callClientMiddleware` helper function to execute `middleware` around your handlers:
@@ -635,6 +635,7 @@ export async function loader({
635
635
  [server-client]: #server-vs-client-middleware
636
636
  [framework-action]: ../start/framework/route-module#action
637
637
  [framework-loader]: ../start/framework/route-module#loader
638
+ [getloadcontext]: ../api/other-api/adapter#react-routerexpress
638
639
  [datastrategy]: ../api/data-routers/createBrowserRouter#optsdatastrategy
639
640
  [cms-redirect]: #cms-redirect-on-404
640
641
  [createContext]: ../api/utils/createContext
@@ -25,10 +25,10 @@ Prefer targeting a specific action or navigation, and fall back to
25
25
  The default behavior differs between Framework and Data Modes:
26
26
 
27
27
  - **Framework Mode with SSR**
28
- - Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions ([`Link`][link], [`Form`][form], [`fetcher.submit`](fetcher-submit))
28
+ - Defaults to opt-out behavior - active loaders are revalidated on navigations and successful submissions ([`Link`][link], [`Form`][form], [`fetcher.submit`][use-fetcher])
29
29
  - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
30
30
  - **Framework "SPA Mode" and Data Mode**
31
- - Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][form], [`fetcher.submit`])
31
+ - Defaults to opt-out behavior on successful submissions - active loaders are revalidated on successful submissions ([`Form`][form], [`fetcher.submit`][use-fetcher])
32
32
  - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
33
33
  - Defaults to opt-in behavior for GET navigations ([`Link`][link]) - active loaders are only revalidated if their dynamic params changed, or if any search params changed
34
34
  - A GET navigation to the _exact_ same URL is treated like a page refresh and all loaders are revalidated.
@@ -84,10 +84,11 @@ export default {
84
84
 
85
85
  ## Pre-Rendering with/without a Runtime Server
86
86
 
87
- Pre-Rendering can be used in two ways based on the `ssr` config value:
87
+ Pre-Rendering can be used with these `ssr` config values:
88
88
 
89
89
  - Alongside a runtime SSR server with `ssr:true` (the default value)
90
90
  - Deployed to a static file server with `ssr:false`
91
+ - Alongside a runtime API server with `ssr:"unstable_api-only"` (experimental)
91
92
 
92
93
  ### Pre-rendering with `ssr:true`
93
94
 
@@ -223,3 +224,24 @@ When pre-rendering with `ssr:false`, React Router will error at build time if yo
223
224
  - If you are using a `loader` on a pre-rendered route that has child routes, you will need to make sure the parent `loaderData` can be determined at run-time properly by either:
224
225
  - Pre-rendering all child routes so that the parent `loader` can be called at build-time for each child route path and rendered into a `.data` file, or
225
226
  - Use a `clientLoader` on the parent that can be called at run-time for non-pre-rendered child paths
227
+
228
+ ### Pre-rendering with API-only Mode (unstable)
229
+
230
+ <docs-warning>API-only Mode is experimental and may change in minor or patch releases. It is not supported in RSC Framework Mode.</docs-warning>
231
+
232
+ Use `ssr: "unstable_api-only"` with `prerender` to generate documents at build time while keeping a runtime server for loaders, actions, and route discovery:
233
+
234
+ ```ts filename=react-router.config.ts
235
+ import type { Config } from "@react-router/dev/config";
236
+
237
+ export default {
238
+ ssr: "unstable_api-only",
239
+ prerender: ["/"],
240
+ } satisfies Config;
241
+ ```
242
+
243
+ Document generation follows the same behavior as `ssr:false`: selected paths produce HTML and `.data` files, and other paths use a SPA fallback. The fallback is `__spa-fallback.html` when `/` is prerendered, or `index.html` otherwise.
244
+
245
+ Prerendered pages hydrate using their build-time data. Pages using the fallback fetch server loader data during initialization. Server loaders and actions are available on all routes, including those outside the prerender list, and normal loader revalidation remains enabled.
246
+
247
+ See [API-only Mode](../start/framework/rendering#api-only-mode-unstable) for deployment routing and configuring a separate API origin.
@@ -0,0 +1,145 @@
1
+ ---
2
+ title: Application-Controlled Route Discovery
3
+ unstable: true
4
+ ---
5
+
6
+ # Application-Controlled Route Discovery
7
+
8
+ [MODES: framework]
9
+
10
+ <docs-warning>This API is unstable and may change without a major release. It supports conventional Framework Mode; RSC modes are not supported.</docs-warning>
11
+
12
+ Enable the future flag in `react-router.config.ts`:
13
+
14
+ ```ts
15
+ import type { Config } from "@react-router/dev/config";
16
+
17
+ export default {
18
+ future: { unstable_customRouteDiscovery: true },
19
+ } satisfies Config;
20
+ ```
21
+
22
+ Without this flag, React Router keeps its existing discovery implementation and the hook is unavailable. The flag supports conventional Framework Mode only.
23
+
24
+ Keep `routeDiscovery.mode: "lazy"` (the default for SSR) and use `unstable_useRouteDiscovery` to choose when to discover destinations or load the complete route manifest.
25
+
26
+ ```tsx
27
+ let { state, discoverRoutes, loadAllRoutes } =
28
+ unstable_useRouteDiscovery({
29
+ onManifestMismatch: async (event) => {
30
+ // Handle mismatch, potentially by loading the entire manifest
31
+ await loadAllRoutes();
32
+ },
33
+ });
34
+
35
+ // state: "partial" | "loading" | "complete"
36
+
37
+ // Eagerly discover specific paths
38
+ let result = await discoverRoutes(paths);
39
+ ```
40
+
41
+ ## Imperative Route Discovery
42
+
43
+ You can control route discovery through `discoverRoutes()` without having to render links/forms on the page - this allows you to eagerly discover routes that are not directly linked from the current UI. Note that discovery only loads route metadata - it does not navigate, run loaders/actions, or prefetch route modules/stylesheets. Paths are batched within the manifest request URL limit and previously discovered paths are skipped.
44
+
45
+ `discoverRoutes(paths, { signal })` accepts application-root paths beginning with `/`. React Router applies the configured `basename` and ignores query strings and hashes. Relative paths and external URLs are not supported.
46
+
47
+ The result is `{ type: "success" }`, `{ type: "version-mismatch" }`, or `{ type: "aborted" }`. Success means the discovery operation completed successfully; it does not guarantee that a request was sent or that each pathname matches a route. Cached paths need no request, and a concurrent full-manifest load can supersede an incremental request failure. Unrecovered request failures and unrelated handler errors reject. Imperative mismatches do not automatically trigger a reload.
48
+
49
+ For example, the cart's checkout link automatically discovers the shipping route. Use the hook to discover later checkout steps when the cart mounts, even though those steps aren't linked from the cart:
50
+
51
+ ```tsx
52
+ import { useEffect } from "react";
53
+ import {
54
+ Link,
55
+ unstable_useRouteDiscovery,
56
+ } from "react-router";
57
+
58
+ function Cart() {
59
+ let { discoverRoutes } = unstable_useRouteDiscovery();
60
+
61
+ useEffect(() => {
62
+ let controller = new AbortController();
63
+
64
+ discoverRoutes(
65
+ ["/checkout/billing", "/checkout/review"],
66
+ { signal: controller.signal },
67
+ ).catch(console.error);
68
+
69
+ return () => controller.abort();
70
+ }, [discoverRoutes]);
71
+
72
+ return (
73
+ <>
74
+ {/* Render cart items and totals here. */}
75
+ <Link to="/checkout/shipping">
76
+ Proceed to checkout
77
+ </Link>
78
+ </>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ## Completing discovery
84
+
85
+ `loadAllRoutes()` loads the running client's versioned manifest asset to complete its route tree, even if that client is stale. This merges all missing routes into the running router and thus prevents further incremental discovery. The exposed `version` remains the version of the running client. With `routeDiscovery.mode: "initial"`, SPA Mode, or development's full manifest, the state is already `complete` and the operations are successful no-ops.
86
+
87
+ The shared `state` moves from `partial` to `loading` to `complete`. Concurrent calls share a load, and calls after completion resolve immediately. Failures reject and return the state to `partial`; applications can catch the error and choose whether to reload.
88
+
89
+ Retain the client's versioned manifest **and its referenced assets** for as long as that client is supported. Server routing and loader/action contracts must also remain compatible. Loading build A's manifest does not make an incompatible build B backend safe for build A's client.
90
+
91
+ ## Handling manifest mismatches
92
+
93
+ You can provide custom handling for manifest version mismatches to avoid hard reloads, giving you a chance to complete the route tree, or cancel the navigation and provide a prompt for the user to reload (see below). A mismatch indicates different versions, not which version is newer.
94
+
95
+ ```tsx
96
+ unstable_useRouteDiscovery({
97
+ onManifestMismatch: async (event) => {
98
+ await event.loadAllRoutes();
99
+ },
100
+ });
101
+ ```
102
+
103
+ After mismatch handlers settle, surviving operations continue only if the tree is complete. Otherwise navigations and fetchers are canceled and their pending state settles. Canceling a navigation also cancels any pending revalidation - both return to idle and the current page and data remain in place without another loader pass. Similarly, interrupted fetcher loads settle with their existing data. If an action already changed server data, the displayed data may remain stale until a later navigation or explicit revalidation.
104
+
105
+ Router-managed Back/Forward cancellations restore the prior history position. Additional browser Back/Forward presses during restoration can leave the URL and rendered route out of sync or navigate to another entry; preserving the current screen is not guaranteed in that case.
106
+
107
+ Full-manifest load failures remain catchable and do not render route error boundaries, whether the handler calls `event.loadAllRoutes()` or the function returned by the hook. Uncaught full-load failures are reported through `<HydratedRouter onError>` or `console.error`, and the incomplete operation is canceled. Unrelated handler errors follow normal discovery error handling.
108
+
109
+ ## Temporary overrides
110
+
111
+ Custom mismatch handlers override built-in recovery. Calling the hook without a handler does not override anything. Removing the last custom mismatch handler restores automatic recovery.
112
+
113
+ Call `event.defaultBehavior()` to explicitly run built-in recovery: eager mismatches warn, navigation mismatches reload the destination, fetcher mismatches reload the current document, and imperative mismatches return without reloading. Repeated calls for one shared mismatch event reuse the same promise. It does not invoke other custom handlers or stop their propagation.
114
+
115
+ ```tsx
116
+ unstable_useRouteDiscovery({
117
+ async onManifestMismatch(event) {
118
+ if (hasUnsavedChanges) {
119
+ event.stopPropagation();
120
+ setShowReloadDialog(true);
121
+ return;
122
+ }
123
+ await event.defaultBehavior();
124
+ },
125
+ });
126
+ ```
127
+
128
+ Custom handlers run in reverse registration order, with each awaited before the next. `stopPropagation()` prevents earlier handlers from running; it does not complete discovery or allow an operation to continue against an incomplete tree.
129
+
130
+ ```tsx
131
+ unstable_useRouteDiscovery({
132
+ onManifestMismatch(event) {
133
+ if (hasUnsavedChanges) {
134
+ event.stopPropagation();
135
+ setShowReloadDialog(true);
136
+ }
137
+ },
138
+ });
139
+ ```
140
+
141
+ An editor mounted after a root recovery handler can use this to preserve a draft. Unmounting removes its registration and reveals earlier handlers. Callback updates preserve registration priority. This is registration order, not a guarantee that the deepest component runs first.
142
+
143
+ Eager discovery waits for the initialized route tree to commit so a root handler can intercept the first request. It does not wait for every suspended descendant or guarantee an idle main thread. Discovery needed before handlers can mount retains normal lazy behavior.
144
+
145
+ Without mismatch handlers, existing automatic recovery for eager/navigation/fetcher discovery remains unchanged. Deployment polling and staleness policy remain application-owned.
@@ -22,6 +22,8 @@ Typical Single Page apps send a mostly blank `index.html` template with little m
22
22
 
23
23
  <docs-info>SPA Mode is a special form of "Pre-Rendering" that allows you to serve all paths in your application from the same HTML file. Please refer to the [Pre-Rendering](./pre-rendering) guide if you want to do more extensive pre-rendering.</docs-info>
24
24
 
25
+ <docs-info>If you want a SPA frontend backed by React Router server loaders and actions, see experimental [API-only Mode](../start/framework/rendering#api-only-mode-unstable).</docs-info>
26
+
25
27
  ## 1. Disable Runtime Server Rendering
26
28
 
27
29
  Server rendering is enabled by default. Set the `ssr` flag to `false` in `react-router.config.ts` to disable it.
@@ -4,16 +4,78 @@ title: View Transitions
4
4
 
5
5
  # View Transitions
6
6
 
7
- [MODES: framework, data]
7
+ [MODES: framework, data, declarative]
8
8
 
9
9
  <br/>
10
10
  <br/>
11
11
 
12
- Enable smooth animations between page transitions in your React Router applications using the [View Transitions API][view-transitions-api]. This feature allows you to create seamless visual transitions during client-side navigation.
12
+ Use React's [`<ViewTransition>`][react-view-transition] component to animate client-side navigations. This requires React and React DOM 19.3 or later.
13
13
 
14
- ## Basic View Transition
14
+ <docs-warning>
15
15
 
16
- ### 1. Enable view transitions on navigation
16
+ React Router's `viewTransition` props/options, `useViewTransitionState` hook, and `NavLink`'s `isTransitioning` render prop and `transitioning` class are deprecated in favor of React's `<ViewTransition>` component. The existing APIs continue to work, including with older React versions.
17
+
18
+ </docs-warning>
19
+
20
+ ## Navigation View Transitions with React
21
+
22
+ Wrap the outlet in a persistent `<ViewTransition>` boundary in a shared layout route. In Framework Mode, this can be your root route's default export; in Data and Declarative modes, use it as the component for a parent route with children:
23
+
24
+ ```tsx
25
+ import { ViewTransition } from "react";
26
+ import { Link, Outlet } from "react-router";
27
+
28
+ export default function PageLayout() {
29
+ return (
30
+ <>
31
+ <nav>
32
+ <Link to="/">Home</Link>
33
+ <Link to="/about">About</Link>
34
+ </nav>
35
+ <ViewTransition>
36
+ <main>
37
+ <Outlet />
38
+ </main>
39
+ </ViewTransition>
40
+ </>
41
+ );
42
+ }
43
+ ```
44
+
45
+ Navigations that update the outlet cross-fade its contents. Keep the boundary mounted across navigations; you don't need to key it by location or remount the route tree. The navigation stays outside the animated region. Other updates inside the boundary can also animate when they run in a React Transition.
46
+
47
+ React Router wraps router state updates in [`React.startTransition`][start-transition] by default, which lets React activate `<ViewTransition>` without additional configuration. React Transitions must remain enabled for navigation view transitions to work: do not set `useTransitions={false}` on your router.
48
+
49
+ This applies to navigations from `<Link>`, `<NavLink>`, and `<Form>`, as well as programmatic navigation with `useNavigate` and `useSubmit`. No additional `startTransition` wrapper is needed.
50
+
51
+ ### Migrating from React Router's view transitions
52
+
53
+ - Remove `viewTransition` from `<Link>`, `<NavLink>`, and `<Form>`, and remove `viewTransition: true` from navigation/submission options. React coordinates `document.startViewTransition()` itself; enabling both implementations can interrupt animations.
54
+ - Replace `useViewTransitionState`, `isTransitioning`, and `.transitioning` styles with React's [View Transition classes][react-view-transition-classes]. Those router APIs only track the legacy transitions.
55
+ - For shared elements such as an image moving from a list to a detail route, wrap each side in `<ViewTransition name="...">` with the same unique name. See React's [shared element example][react-shared-elements].
56
+ - Keep React Transitions enabled. `useTransitions={false}` and synchronous updates such as `flushSync` can prevent React's animations.
57
+
58
+ React's component is not a drop-in replacement for every legacy animation. In particular, React [skips view transitions for synchronous updates from `popstate`][react-router-transitions], so browser Back/Forward navigations may not animate. Browsers without View Transitions support still navigate normally.
59
+
60
+ Respect reduced-motion preferences when adding animations:
61
+
62
+ ```css
63
+ @media (prefers-reduced-motion: reduce) {
64
+ ::view-transition-group(*),
65
+ ::view-transition-old(*),
66
+ ::view-transition-new(*) {
67
+ animation: none !important;
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Legacy View Transitions (Deprecated)
73
+
74
+ The following examples document the deprecated APIs for existing applications. These APIs are available in Framework and Data modes.
75
+
76
+ ### Basic View Transition
77
+
78
+ #### 1. Enable view transitions on navigation
17
79
 
18
80
  The simplest way to enable view transitions is by adding the `viewTransition` prop to your `Link`, `NavLink`, or `Form` components. This automatically wraps the navigation update in `document.startViewTransition()`.
19
81
 
@@ -25,7 +87,7 @@ The simplest way to enable view transitions is by adding the `viewTransition` pr
25
87
 
26
88
  Without any additional CSS, this provides a basic cross-fade animation between pages.
27
89
 
28
- ### 2. Enable view transitions with programmatic navigation
90
+ #### 2. Enable view transitions with programmatic navigation
29
91
 
30
92
  When using programmatic navigation with the `useNavigate` hook, you can enable view transitions by passing the `viewTransition: true` option:
31
93
 
@@ -51,11 +113,11 @@ This provides the same cross-fade animation as using the `viewTransition` prop o
51
113
 
52
114
  For more information on using the View Transitions API, please refer to the ["Smooth transitions with the View Transition API" guide][view-transitions-guide] from the Google Chrome team.
53
115
 
54
- ## Image Gallery Example
116
+ ### Image Gallery Example
55
117
 
56
118
  Let's build an image gallery that demonstrates how to trigger and use view transitions. We'll create a list of images that expand into a detail view with smooth animations.
57
119
 
58
- ### 1. Create the image gallery route
120
+ #### 1. Create the image gallery route
59
121
 
60
122
  ```tsx filename=routes/image-gallery.tsx
61
123
  import { NavLink } from "react-router";
@@ -91,7 +153,7 @@ export default function ImageGalleryRoute() {
91
153
  }
92
154
  ```
93
155
 
94
- ### 2. Add transition styles
156
+ #### 2. Add transition styles
95
157
 
96
158
  Define view transition names and animations for elements that should transition smoothly between routes.
97
159
 
@@ -127,7 +189,7 @@ Define view transition names and animations for elements that should transition
127
189
  }
128
190
  ```
129
191
 
130
- ### 3. Create the image detail route
192
+ #### 3. Create the image detail route
131
193
 
132
194
  The detail view needs to use the same view transition names to create a seamless animation.
133
195
 
@@ -151,7 +213,7 @@ export default function ImageDetailsRoute({
151
213
  }
152
214
  ```
153
215
 
154
- ### 4. Add matching transition styles for the detail view
216
+ #### 4. Add matching transition styles for the detail view
155
217
 
156
218
  ```css filename=app.css
157
219
  /* Match transition names from the list view */
@@ -169,11 +231,11 @@ export default function ImageDetailsRoute({
169
231
  }
170
232
  ```
171
233
 
172
- ## Advanced Usage
234
+ ### Advanced Usage
173
235
 
174
236
  You can control view transitions more precisely using either render props or the `useViewTransitionState` hook.
175
237
 
176
- ### 1. Using render props
238
+ #### 1. Using render props
177
239
 
178
240
  ```tsx filename=routes/image-gallery.tsx
179
241
  <NavLink to={`/image/${idx}`} viewTransition>
@@ -201,7 +263,7 @@ You can control view transitions more precisely using either render props or the
201
263
  </NavLink>
202
264
  ```
203
265
 
204
- ### 2. Using the `useViewTransitionState` hook
266
+ #### 2. Using the `useViewTransitionState` hook
205
267
 
206
268
  ```tsx filename=routes/image-gallery.tsx
207
269
  function NavImage(props: { src: string; idx: number }) {
@@ -234,4 +296,9 @@ function NavImage(props: { src: string; idx: number }) {
234
296
  ```
235
297
 
236
298
  [view-transitions-api]: https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition
299
+ [react-view-transition]: https://react.dev/reference/react/ViewTransition
300
+ [start-transition]: https://react.dev/reference/react/startTransition
301
+ [react-view-transition-classes]: https://react.dev/reference/react/ViewTransition#view-transition-class
302
+ [react-shared-elements]: https://react.dev/reference/react/ViewTransition#animating-a-shared-element
303
+ [react-router-transitions]: https://react.dev/reference/react/ViewTransition#building-view-transition-enabled-routers
237
304
  [view-transitions-guide]: https://developer.chrome.com/docs/web-platform/view-transitions