react-router 8.3.1 → 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 (218) hide show
  1. package/CHANGELOG.md +83 -0
  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 +601 -189
  6. package/dist/development/index-react-server.js +100 -48
  7. package/dist/development/index.d.ts +10 -9
  8. package/dist/development/index.js +6 -5
  9. package/dist/development/lib/actions.js +1 -1
  10. package/dist/development/lib/components.js +66 -10
  11. package/dist/development/lib/context.d.ts +19 -6
  12. package/dist/development/lib/context.js +11 -3
  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 +30 -37
  18. package/dist/development/lib/dom/server.d.ts +6 -1
  19. package/dist/development/lib/dom/server.js +41 -15
  20. package/dist/development/lib/dom/ssr/components.js +26 -31
  21. package/dist/development/lib/dom/ssr/data.js +1 -1
  22. package/dist/development/lib/dom/ssr/entry.d.ts +13 -2
  23. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  24. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  25. package/dist/development/lib/dom/ssr/fog-of-war.d.ts +3 -2
  26. package/dist/development/lib/dom/ssr/fog-of-war.js +46 -22
  27. package/dist/development/lib/dom/ssr/hydration.d.ts +3 -1
  28. package/dist/development/lib/dom/ssr/hydration.js +6 -4
  29. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  30. package/dist/development/lib/dom/ssr/links.js +1 -1
  31. package/dist/development/lib/dom/ssr/markup.js +5 -2
  32. package/dist/development/lib/dom/ssr/route-discovery.d.ts +120 -0
  33. package/dist/development/lib/dom/ssr/route-discovery.js +415 -0
  34. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  35. package/dist/development/lib/dom/ssr/routes-test-stub.js +2 -2
  36. package/dist/development/lib/dom/ssr/routes.d.ts +2 -2
  37. package/dist/development/lib/dom/ssr/routes.js +8 -8
  38. package/dist/development/lib/dom/ssr/server.js +4 -2
  39. package/dist/development/lib/dom/ssr/single-fetch.d.ts +28 -2
  40. package/dist/development/lib/dom/ssr/single-fetch.js +80 -71
  41. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  42. package/dist/development/lib/dom-export/hydrated-router.d.ts +28 -0
  43. package/dist/development/lib/dom-export/hydrated-router.js +33 -12
  44. package/dist/development/lib/errors.js +1 -1
  45. package/dist/development/lib/hooks.d.ts +3 -3
  46. package/dist/development/lib/hooks.js +52 -47
  47. package/dist/development/lib/href.js +1 -1
  48. package/dist/development/lib/router/history.js +1 -1
  49. package/dist/development/lib/router/instrumentation.js +1 -1
  50. package/dist/development/lib/router/matcher-route-pattern.d.ts +2 -0
  51. package/dist/development/lib/router/matcher-route-pattern.js +216 -0
  52. package/dist/development/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  53. package/dist/development/lib/router/matcher-route-pattern.preload.js +36 -0
  54. package/dist/development/lib/router/matcher.d.ts +2 -0
  55. package/dist/development/lib/router/matcher.js +29 -0
  56. package/dist/development/lib/router/navigation.js +1 -1
  57. package/dist/development/lib/router/router.d.ts +19 -7
  58. package/dist/development/lib/router/router.js +235 -128
  59. package/dist/development/lib/router/url.js +1 -1
  60. package/dist/development/lib/router/utils.d.ts +30 -5
  61. package/dist/development/lib/router/utils.js +8 -5
  62. package/dist/development/lib/rsc/browser.js +3 -2
  63. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  64. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  65. package/dist/development/lib/rsc/html-stream/server.js +25 -15
  66. package/dist/development/lib/rsc/route-modules.js +1 -1
  67. package/dist/development/lib/rsc/server.rsc.d.ts +10 -2
  68. package/dist/development/lib/rsc/server.ssr.js +25 -21
  69. package/dist/development/lib/server-runtime/build.d.ts +14 -0
  70. package/dist/development/lib/server-runtime/cookies.js +1 -1
  71. package/dist/development/lib/server-runtime/crypto.js +1 -1
  72. package/dist/development/lib/server-runtime/data.js +1 -1
  73. package/dist/development/lib/server-runtime/dev.js +1 -1
  74. package/dist/development/lib/server-runtime/entry.js +1 -1
  75. package/dist/development/lib/server-runtime/errors.js +1 -1
  76. package/dist/development/lib/server-runtime/headers.js +1 -1
  77. package/dist/development/lib/server-runtime/invariant.js +1 -1
  78. package/dist/development/lib/server-runtime/mode.js +1 -1
  79. package/dist/development/lib/server-runtime/routeMatching.js +3 -4
  80. package/dist/development/lib/server-runtime/routes.d.ts +1 -1
  81. package/dist/development/lib/server-runtime/routes.js +1 -1
  82. package/dist/development/lib/server-runtime/server.js +18 -9
  83. package/dist/development/lib/server-runtime/serverHandoff.d.ts +17 -0
  84. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  85. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
  86. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
  87. package/dist/development/lib/server-runtime/sessions.js +1 -1
  88. package/dist/development/lib/server-runtime/single-fetch.js +1 -1
  89. package/dist/development/lib/server-runtime/urls.js +1 -1
  90. package/dist/development/lib/server-runtime/warnings.js +1 -1
  91. package/dist/development/lib/types/internal.js +1 -1
  92. package/dist/development/lib/types/route-data.d.ts +2 -2
  93. package/dist/development/lib/types/route-module-annotations.d.ts +1 -1
  94. package/dist/development/route-pattern.d.ts +3 -0
  95. package/dist/development/route-pattern.js +12 -0
  96. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  97. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +1 -1
  98. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  99. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  100. package/dist/production/dom-export.d.ts +2 -1
  101. package/dist/production/dom-export.js +1 -1
  102. package/dist/production/index-react-server-client.js +2 -2
  103. package/dist/production/index-react-server.d.ts +601 -189
  104. package/dist/production/index-react-server.js +100 -48
  105. package/dist/production/index.d.ts +10 -9
  106. package/dist/production/index.js +6 -5
  107. package/dist/production/lib/actions.js +1 -1
  108. package/dist/production/lib/components.js +66 -10
  109. package/dist/production/lib/context.d.ts +19 -6
  110. package/dist/production/lib/context.js +11 -3
  111. package/dist/production/lib/dom/dom.d.ts +3 -0
  112. package/dist/production/lib/dom/dom.js +1 -1
  113. package/dist/production/lib/dom/global.d.ts +35 -0
  114. package/dist/production/lib/dom/lib.d.ts +34 -9
  115. package/dist/production/lib/dom/lib.js +30 -37
  116. package/dist/production/lib/dom/server.d.ts +6 -1
  117. package/dist/production/lib/dom/server.js +41 -15
  118. package/dist/production/lib/dom/ssr/components.js +26 -31
  119. package/dist/production/lib/dom/ssr/data.js +1 -1
  120. package/dist/production/lib/dom/ssr/entry.d.ts +13 -2
  121. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  122. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  123. package/dist/production/lib/dom/ssr/fog-of-war.d.ts +3 -2
  124. package/dist/production/lib/dom/ssr/fog-of-war.js +46 -22
  125. package/dist/production/lib/dom/ssr/hydration.d.ts +3 -1
  126. package/dist/production/lib/dom/ssr/hydration.js +6 -4
  127. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  128. package/dist/production/lib/dom/ssr/links.js +1 -1
  129. package/dist/production/lib/dom/ssr/markup.js +5 -2
  130. package/dist/production/lib/dom/ssr/route-discovery.d.ts +120 -0
  131. package/dist/production/lib/dom/ssr/route-discovery.js +415 -0
  132. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  133. package/dist/production/lib/dom/ssr/routes-test-stub.js +2 -2
  134. package/dist/production/lib/dom/ssr/routes.d.ts +2 -2
  135. package/dist/production/lib/dom/ssr/routes.js +8 -8
  136. package/dist/production/lib/dom/ssr/server.js +4 -2
  137. package/dist/production/lib/dom/ssr/single-fetch.d.ts +28 -2
  138. package/dist/production/lib/dom/ssr/single-fetch.js +80 -71
  139. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  140. package/dist/production/lib/dom-export/hydrated-router.d.ts +28 -0
  141. package/dist/production/lib/dom-export/hydrated-router.js +33 -12
  142. package/dist/production/lib/errors.js +1 -1
  143. package/dist/production/lib/hooks.d.ts +3 -3
  144. package/dist/production/lib/hooks.js +51 -46
  145. package/dist/production/lib/href.js +1 -1
  146. package/dist/production/lib/router/history.js +1 -1
  147. package/dist/production/lib/router/instrumentation.js +1 -1
  148. package/dist/production/lib/router/matcher-route-pattern.d.ts +2 -0
  149. package/dist/production/lib/router/matcher-route-pattern.js +216 -0
  150. package/dist/production/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  151. package/dist/production/lib/router/matcher-route-pattern.preload.js +36 -0
  152. package/dist/production/lib/router/matcher.d.ts +2 -0
  153. package/dist/production/lib/router/matcher.js +29 -0
  154. package/dist/production/lib/router/navigation.js +1 -1
  155. package/dist/production/lib/router/router.d.ts +19 -7
  156. package/dist/production/lib/router/router.js +235 -128
  157. package/dist/production/lib/router/url.js +1 -1
  158. package/dist/production/lib/router/utils.d.ts +30 -5
  159. package/dist/production/lib/router/utils.js +8 -5
  160. package/dist/production/lib/rsc/browser.js +3 -2
  161. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  162. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  163. package/dist/production/lib/rsc/html-stream/server.js +25 -15
  164. package/dist/production/lib/rsc/route-modules.js +1 -1
  165. package/dist/production/lib/rsc/server.rsc.d.ts +10 -2
  166. package/dist/production/lib/rsc/server.ssr.js +25 -21
  167. package/dist/production/lib/server-runtime/build.d.ts +14 -0
  168. package/dist/production/lib/server-runtime/cookies.js +1 -1
  169. package/dist/production/lib/server-runtime/crypto.js +1 -1
  170. package/dist/production/lib/server-runtime/data.js +1 -1
  171. package/dist/production/lib/server-runtime/dev.js +1 -1
  172. package/dist/production/lib/server-runtime/entry.js +1 -1
  173. package/dist/production/lib/server-runtime/errors.js +1 -1
  174. package/dist/production/lib/server-runtime/headers.js +1 -1
  175. package/dist/production/lib/server-runtime/invariant.js +1 -1
  176. package/dist/production/lib/server-runtime/mode.js +1 -1
  177. package/dist/production/lib/server-runtime/routeMatching.js +3 -4
  178. package/dist/production/lib/server-runtime/routes.d.ts +1 -1
  179. package/dist/production/lib/server-runtime/routes.js +1 -1
  180. package/dist/production/lib/server-runtime/server.js +18 -9
  181. package/dist/production/lib/server-runtime/serverHandoff.d.ts +17 -0
  182. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  183. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
  184. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
  185. package/dist/production/lib/server-runtime/sessions.js +1 -1
  186. package/dist/production/lib/server-runtime/single-fetch.js +1 -1
  187. package/dist/production/lib/server-runtime/urls.js +1 -1
  188. package/dist/production/lib/server-runtime/warnings.js +1 -1
  189. package/dist/production/lib/types/internal.js +1 -1
  190. package/dist/production/lib/types/route-data.d.ts +2 -2
  191. package/dist/production/lib/types/route-module-annotations.d.ts +1 -1
  192. package/dist/production/route-pattern.d.ts +3 -0
  193. package/dist/production/route-pattern.js +12 -0
  194. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  195. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +1 -1
  196. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  197. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  198. package/docs/explanation/lazy-route-discovery.md +4 -0
  199. package/docs/explanation/sessions-and-cookies.md +108 -4
  200. package/docs/how-to/custom-fetch.md +218 -0
  201. package/docs/how-to/data-strategy.md +23 -1
  202. package/docs/how-to/fetchers.md +6 -1
  203. package/docs/how-to/middleware.md +4 -1
  204. package/docs/how-to/optimize-revalidation.md +178 -5
  205. package/docs/how-to/pre-rendering.md +23 -1
  206. package/docs/how-to/react-server-components.md +26 -3
  207. package/docs/how-to/resource-routes.md +3 -0
  208. package/docs/how-to/route-discovery.md +145 -0
  209. package/docs/how-to/spa.md +2 -0
  210. package/docs/how-to/view-transitions.md +80 -13
  211. package/docs/start/data/actions.md +2 -1
  212. package/docs/start/data/route-object.md +21 -6
  213. package/docs/start/framework/actions.md +4 -2
  214. package/docs/start/framework/navigating.md +5 -9
  215. package/docs/start/framework/rendering.md +37 -0
  216. package/docs/start/framework/route-module.md +32 -5
  217. package/docs/upgrading/future.md +68 -0
  218. package/package.json +19 -1
@@ -1,12 +1,185 @@
1
1
  ---
2
2
  title: Revalidation Optimization
3
- hidden: true
4
3
  ---
5
4
 
6
- [copy pasted]
5
+ # Revalidation Optimization
7
6
 
8
- During client-side transitions, React Router will optimize reloading of routes that are already rendering, like not reloading layout routes that aren't changing. In other cases, like form submissions or search param changes, React Router doesn't know which routes need to be reloaded, so it reloads them all to be safe. This ensures your UI always stays in sync with the state on your server.
7
+ [MODES: framework, data]
9
8
 
10
- This function lets apps further optimize by returning `false` when React Router is about to reload a route. If you define this function on a route module, React Router will defer to your function on every navigation and every revalidation after an action is called. Again, this makes it possible for your UI to get out of sync with your server if you do it wrong, so be careful.
9
+ <br/>
10
+ <br/>
11
11
 
12
- `fetcher.load` calls also revalidate, but because they load a specific URL, they don't have to worry about route param or URL search param revalidations. `fetcher.load`'s only revalidate by default after action submissions and explicit revalidation requests via [`useRevalidator`][use-revalidator].
12
+ After a mutation or some navigations, React Router re-runs loaders so
13
+ the UI stays in sync with the server. That default is the right
14
+ starting point. When a loader is expensive, or a mutation cannot
15
+ affect that route's data, you can skip the reload.
16
+
17
+ <docs-warning>
18
+ Skipping revalidation can leave the UI out of sync with the server.
19
+ Prefer targeting a specific action or navigation, and fall back to
20
+ `defaultShouldRevalidate` instead of always returning `false`.
21
+ </docs-warning>
22
+
23
+ ## Default behavior
24
+
25
+ The default behavior differs between Framework and Data Modes:
26
+
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`][use-fetcher])
29
+ - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
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`][use-fetcher])
32
+ - Failed submissions returning a 4xx/5xx status do not trigger revalidations by default
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
+ - A GET navigation to the _exact_ same URL is treated like a page refresh and all loaders are revalidated.
35
+
36
+ Matched matched routes are handled independently - A child that skips
37
+ revalidation does not skip any ancestor routes.
38
+
39
+ [`fetcher.load`][use-fetcher] only revalidates by default after action
40
+ submissions and explicit [`useRevalidator`][use-revalidator] calls, not
41
+ on search-param or param-driven navigations.
42
+
43
+ A plain `fetch()` to a [resource route][resource-routes] does not
44
+ go through the router, so it does not revalidate loaders.
45
+
46
+ ## Skip a route with `shouldRevalidate`
47
+
48
+ Export `shouldRevalidate` from the [route module][route-module]
49
+ (Framework Mode) or set it on the [route object][data-mode]
50
+ (Data Mode). Returning `false` skips **that route's** loader.
51
+
52
+ ```tsx filename=app/routes/dashboard.tsx
53
+ // Framework Mode
54
+ export function shouldRevalidate() {
55
+ return false;
56
+ }
57
+ ```
58
+
59
+ ```tsx src/main.tsx
60
+ // Data Mode
61
+ createBrowserRouter([
62
+ {
63
+ path: "/dashboard",
64
+ loader: dashboardLoader,
65
+ shouldRevalidate: () => false,
66
+ Component: Dashboard,
67
+ },
68
+ ]);
69
+ ```
70
+
71
+ Always returning `false` opts that route out of the default
72
+ behavior completely, including cases you usually still want
73
+ (param changes, explicit [`useRevalidator`][use-revalidator]).
74
+ Prefer the conditional form below.
75
+
76
+ ## Opt out of specific requests
77
+
78
+ Inspect
79
+ [`ShouldRevalidateFunctionArgs`][should-revalidate-args]
80
+ and return `defaultShouldRevalidate` for everything else.
81
+
82
+ ```tsx
83
+ import type { ShouldRevalidateFunctionArgs } from "react-router";
84
+
85
+ export function shouldRevalidate({
86
+ formMethod,
87
+ formAction,
88
+ defaultShouldRevalidate,
89
+ }: ShouldRevalidateFunctionArgs) {
90
+ if (
91
+ formMethod === "POST" &&
92
+ formAction?.endsWith("/analytics")
93
+ ) {
94
+ return false;
95
+ }
96
+
97
+ return defaultShouldRevalidate;
98
+ }
99
+ ```
100
+
101
+ Other useful fields:
102
+
103
+ - `formData`, `json`, `text` — the submission body
104
+ - `actionResult`, `actionStatus` — the action's return value
105
+ - `currentUrl`, `nextUrl`, `currentParams`, `nextParams` —
106
+ the navigation
107
+
108
+ You can ignore search-param-only updates while still
109
+ revalidating when the pathname changes:
110
+
111
+ ```tsx
112
+ export function shouldRevalidate({
113
+ currentUrl,
114
+ nextUrl,
115
+ defaultShouldRevalidate,
116
+ }: ShouldRevalidateFunctionArgs) {
117
+ if (currentUrl.pathname === nextUrl.pathname) {
118
+ return false;
119
+ }
120
+
121
+ return defaultShouldRevalidate;
122
+ }
123
+ ```
124
+
125
+ ## Skip revalidation for one event
126
+
127
+ Pass `defaultShouldRevalidate={false}` at the call site so you
128
+ do not have to change every route file. This works on
129
+ [`<Form>`][form], [`<Link>`][link], `<fetcher.Form>`, and as an
130
+ option to [`useSubmit`][use-submit], `fetcher.submit`,
131
+ [`useNavigate`][use-navigate], and
132
+ [`useSearchParams`][use-search-params].
133
+
134
+ ```tsx
135
+ import { Form, Link } from "react-router";
136
+
137
+ <Link
138
+ to="/search?q=shoes"
139
+ defaultShouldRevalidate={false}
140
+ >
141
+ Search Shoes
142
+ </Link>
143
+
144
+ <Form
145
+ method="post"
146
+ action="/analytics"
147
+ defaultShouldRevalidate={false}
148
+ >
149
+ <button>Track Click</button>
150
+ </Form>
151
+ ```
152
+
153
+ ```tsx
154
+ fetcher.submit(
155
+ { intent: "save-progress" },
156
+ {
157
+ method: "post",
158
+ action: "/save-progress",
159
+ defaultShouldRevalidate: false,
160
+ },
161
+ );
162
+ ```
163
+
164
+ If a matched route does **not** export `shouldRevalidate`, this
165
+ value is used directly for that loader. If it **does** export
166
+ `shouldRevalidate`, the value is passed in as
167
+ `defaultShouldRevalidate` and the route still has the final say.
168
+
169
+ That is why a child `shouldRevalidate` that always returns
170
+ `false` cannot hide a root reload after `fetcher.submit`. Either
171
+ also opt `root` out for that case, or pass
172
+ `defaultShouldRevalidate: false` at the call site when `root`
173
+ has no `shouldRevalidate` of its own.
174
+
175
+ [data-mode]: ../start/data/route-object#shouldrevalidate
176
+ [form]: ../api/components/Form
177
+ [link]: ../api/components/Link
178
+ [resource-routes]: ./resource-routes
179
+ [route-module]: ../start/framework/route-module#shouldrevalidate
180
+ [should-revalidate-args]: https://api.reactrouter.com/v8/interfaces/react-router.ShouldRevalidateFunctionArgs.html
181
+ [use-fetcher]: ../api/hooks/useFetcher
182
+ [use-navigate]: ../api/hooks/useNavigate
183
+ [use-revalidator]: ../api/hooks/useRevalidator
184
+ [use-search-params]: ../api/hooks/useSearchParams
185
+ [use-submit]: ../api/hooks/useSubmit
@@ -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.
@@ -498,16 +498,39 @@ Using Server Components in loaders can be helpful for incremental adoption of RS
498
498
 
499
499
  [Server Functions][react-server-functions-doc] are a React feature that allow you to call async functions executed on the server. They're defined with the [`"use server"`][use-server-docs] directive.
500
500
 
501
+ <docs-warning>
502
+
503
+ Treat every Server Function as a public endpoint. The client controls both the
504
+ Server Function identifier and request URL, so do not rely on route middleware
505
+ for authentication or authorization. Server Functions must perform their own
506
+ access control and input validation; use a route `action` when access control
507
+ should be middleware-driven.
508
+
509
+ </docs-warning>
510
+
501
511
  ```tsx
502
512
  "use server";
503
513
 
514
+ import { unstable_getRequest as getRequest } from "react-router";
515
+ import { requireUser } from "./auth.ts";
516
+
504
517
  export async function updateFavorite(formData: FormData) {
505
- let movieId = formData.get("id");
518
+ let user = await requireUser(getRequest());
519
+ let movieId = Number(formData.get("id"));
506
520
  let intent = formData.get("intent");
521
+
522
+ if (
523
+ !Number.isSafeInteger(movieId) ||
524
+ movieId <= 0 ||
525
+ (intent !== "add" && intent !== "remove")
526
+ ) {
527
+ throw new Error("Invalid form submission");
528
+ }
529
+
507
530
  if (intent === "add") {
508
- await addFavorite(Number(movieId));
531
+ await addFavorite(user.id, movieId);
509
532
  } else {
510
- await removeFavorite(Number(movieId));
533
+ await removeFavorite(user.id, movieId);
511
534
  }
512
535
  }
513
536
  ```
@@ -66,6 +66,8 @@ export function action(_: Route.ActionArgs) {
66
66
  }
67
67
  ```
68
68
 
69
+ Calling this `action` through [`<Form>`][form] or [`useFetcher`][fetcher] still revalidates matched UI loaders. A plain `fetch()` to the resource URL does not. See [Revalidation Optimization][optimize-revalidation] for more info.
70
+
69
71
  ## Return Types
70
72
 
71
73
  Resource Routes are flexible when it comes to the return type - you can return [`Response`][Response] instances or [`data()`][data] objects. A good general rule of thumb when deciding which type to use is:
@@ -124,3 +126,4 @@ export function action() {
124
126
  [form]: ../api/components/Form
125
127
  [await]: ../api/components/Await
126
128
  [error-boundary]: ../start/framework/route-module#errorboundary
129
+ [optimize-revalidation]: ./optimize-revalidation
@@ -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
@@ -9,7 +9,7 @@ order: 5
9
9
 
10
10
  ## Defining Actions
11
11
 
12
- Data mutations are done through Route actions defined on the `action` property of a route object. 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.
12
+ Data mutations are done through Route actions defined on the `action` property of a route object. 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. To skip some or all of those reloads, see [Revalidation Optimization][optimize-revalidation].
13
13
 
14
14
  ```tsx
15
15
  import { createBrowserRouter } from "react-router";
@@ -136,3 +136,4 @@ function Project() {
136
136
  Next: [Navigating](./navigating)
137
137
 
138
138
  [fetchers]: ../../how-to/fetchers
139
+ [optimize-revalidation]: ../../how-to/optimize-revalidation