react-router 8.3.1 → 8.4.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 (176) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/development/dom-export.js +1 -1
  3. package/dist/development/index-react-server-client.js +1 -1
  4. package/dist/development/index-react-server.d.ts +42 -48
  5. package/dist/development/index-react-server.js +75 -31
  6. package/dist/development/index.d.ts +2 -2
  7. package/dist/development/index.js +3 -3
  8. package/dist/development/lib/actions.js +1 -1
  9. package/dist/development/lib/components.js +45 -8
  10. package/dist/development/lib/context.d.ts +13 -5
  11. package/dist/development/lib/context.js +11 -3
  12. package/dist/development/lib/dom/dom.js +1 -1
  13. package/dist/development/lib/dom/lib.js +14 -32
  14. package/dist/development/lib/dom/server.d.ts +6 -1
  15. package/dist/development/lib/dom/server.js +40 -14
  16. package/dist/development/lib/dom/ssr/components.js +15 -23
  17. package/dist/development/lib/dom/ssr/data.js +1 -1
  18. package/dist/development/lib/dom/ssr/entry.d.ts +4 -0
  19. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  20. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  21. package/dist/development/lib/dom/ssr/fog-of-war.js +33 -12
  22. package/dist/development/lib/dom/ssr/hydration.d.ts +3 -1
  23. package/dist/development/lib/dom/ssr/hydration.js +6 -4
  24. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  25. package/dist/development/lib/dom/ssr/links.js +1 -1
  26. package/dist/development/lib/dom/ssr/markup.js +5 -2
  27. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  28. package/dist/development/lib/dom/ssr/routes-test-stub.js +1 -1
  29. package/dist/development/lib/dom/ssr/routes.js +1 -1
  30. package/dist/development/lib/dom/ssr/server.js +2 -2
  31. package/dist/development/lib/dom/ssr/single-fetch.js +10 -19
  32. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  33. package/dist/development/lib/dom-export/hydrated-router.js +2 -1
  34. package/dist/development/lib/errors.js +1 -1
  35. package/dist/development/lib/hooks.js +49 -44
  36. package/dist/development/lib/href.js +1 -1
  37. package/dist/development/lib/router/history.js +1 -1
  38. package/dist/development/lib/router/instrumentation.js +1 -1
  39. package/dist/development/lib/router/matcher-route-pattern.d.ts +2 -0
  40. package/dist/development/lib/router/matcher-route-pattern.js +216 -0
  41. package/dist/development/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  42. package/dist/development/lib/router/matcher-route-pattern.preload.js +36 -0
  43. package/dist/development/lib/router/matcher.d.ts +2 -0
  44. package/dist/development/lib/router/matcher.js +29 -0
  45. package/dist/development/lib/router/navigation.js +1 -1
  46. package/dist/development/lib/router/router.d.ts +13 -7
  47. package/dist/development/lib/router/router.js +81 -66
  48. package/dist/development/lib/router/url.js +1 -1
  49. package/dist/development/lib/router/utils.d.ts +6 -1
  50. package/dist/development/lib/router/utils.js +6 -4
  51. package/dist/development/lib/rsc/browser.js +2 -1
  52. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  53. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  54. package/dist/development/lib/rsc/html-stream/server.js +2 -4
  55. package/dist/development/lib/rsc/route-modules.js +1 -1
  56. package/dist/development/lib/rsc/server.rsc.d.ts +7 -0
  57. package/dist/development/lib/rsc/server.ssr.js +25 -21
  58. package/dist/development/lib/server-runtime/cookies.js +1 -1
  59. package/dist/development/lib/server-runtime/crypto.js +1 -1
  60. package/dist/development/lib/server-runtime/data.js +1 -1
  61. package/dist/development/lib/server-runtime/dev.js +1 -1
  62. package/dist/development/lib/server-runtime/entry.js +1 -1
  63. package/dist/development/lib/server-runtime/errors.js +1 -1
  64. package/dist/development/lib/server-runtime/headers.js +1 -1
  65. package/dist/development/lib/server-runtime/invariant.js +1 -1
  66. package/dist/development/lib/server-runtime/mode.js +1 -1
  67. package/dist/development/lib/server-runtime/routeMatching.js +3 -4
  68. package/dist/development/lib/server-runtime/routes.js +1 -1
  69. package/dist/development/lib/server-runtime/server.js +7 -6
  70. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  71. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
  72. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
  73. package/dist/development/lib/server-runtime/sessions.js +1 -1
  74. package/dist/development/lib/server-runtime/single-fetch.js +1 -1
  75. package/dist/development/lib/server-runtime/urls.js +1 -1
  76. package/dist/development/lib/server-runtime/warnings.js +1 -1
  77. package/dist/development/lib/types/internal.js +1 -1
  78. package/dist/development/route-pattern.d.ts +3 -0
  79. package/dist/development/route-pattern.js +12 -0
  80. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  81. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +1 -1
  82. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  83. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  84. package/dist/production/dom-export.js +1 -1
  85. package/dist/production/index-react-server-client.js +1 -1
  86. package/dist/production/index-react-server.d.ts +42 -48
  87. package/dist/production/index-react-server.js +75 -31
  88. package/dist/production/index.d.ts +2 -2
  89. package/dist/production/index.js +3 -3
  90. package/dist/production/lib/actions.js +1 -1
  91. package/dist/production/lib/components.js +45 -8
  92. package/dist/production/lib/context.d.ts +13 -5
  93. package/dist/production/lib/context.js +11 -3
  94. package/dist/production/lib/dom/dom.js +1 -1
  95. package/dist/production/lib/dom/lib.js +14 -32
  96. package/dist/production/lib/dom/server.d.ts +6 -1
  97. package/dist/production/lib/dom/server.js +40 -14
  98. package/dist/production/lib/dom/ssr/components.js +15 -23
  99. package/dist/production/lib/dom/ssr/data.js +1 -1
  100. package/dist/production/lib/dom/ssr/entry.d.ts +4 -0
  101. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  102. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  103. package/dist/production/lib/dom/ssr/fog-of-war.js +33 -12
  104. package/dist/production/lib/dom/ssr/hydration.d.ts +3 -1
  105. package/dist/production/lib/dom/ssr/hydration.js +6 -4
  106. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  107. package/dist/production/lib/dom/ssr/links.js +1 -1
  108. package/dist/production/lib/dom/ssr/markup.js +5 -2
  109. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  110. package/dist/production/lib/dom/ssr/routes-test-stub.js +1 -1
  111. package/dist/production/lib/dom/ssr/routes.js +1 -1
  112. package/dist/production/lib/dom/ssr/server.js +2 -2
  113. package/dist/production/lib/dom/ssr/single-fetch.js +10 -19
  114. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  115. package/dist/production/lib/dom-export/hydrated-router.js +2 -1
  116. package/dist/production/lib/errors.js +1 -1
  117. package/dist/production/lib/hooks.js +49 -44
  118. package/dist/production/lib/href.js +1 -1
  119. package/dist/production/lib/router/history.js +1 -1
  120. package/dist/production/lib/router/instrumentation.js +1 -1
  121. package/dist/production/lib/router/matcher-route-pattern.d.ts +2 -0
  122. package/dist/production/lib/router/matcher-route-pattern.js +216 -0
  123. package/dist/production/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  124. package/dist/production/lib/router/matcher-route-pattern.preload.js +36 -0
  125. package/dist/production/lib/router/matcher.d.ts +2 -0
  126. package/dist/production/lib/router/matcher.js +29 -0
  127. package/dist/production/lib/router/navigation.js +1 -1
  128. package/dist/production/lib/router/router.d.ts +13 -7
  129. package/dist/production/lib/router/router.js +81 -66
  130. package/dist/production/lib/router/url.js +1 -1
  131. package/dist/production/lib/router/utils.d.ts +6 -1
  132. package/dist/production/lib/router/utils.js +6 -4
  133. package/dist/production/lib/rsc/browser.js +2 -1
  134. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  135. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  136. package/dist/production/lib/rsc/html-stream/server.js +2 -4
  137. package/dist/production/lib/rsc/route-modules.js +1 -1
  138. package/dist/production/lib/rsc/server.rsc.d.ts +7 -0
  139. package/dist/production/lib/rsc/server.ssr.js +25 -21
  140. package/dist/production/lib/server-runtime/cookies.js +1 -1
  141. package/dist/production/lib/server-runtime/crypto.js +1 -1
  142. package/dist/production/lib/server-runtime/data.js +1 -1
  143. package/dist/production/lib/server-runtime/dev.js +1 -1
  144. package/dist/production/lib/server-runtime/entry.js +1 -1
  145. package/dist/production/lib/server-runtime/errors.js +1 -1
  146. package/dist/production/lib/server-runtime/headers.js +1 -1
  147. package/dist/production/lib/server-runtime/invariant.js +1 -1
  148. package/dist/production/lib/server-runtime/mode.js +1 -1
  149. package/dist/production/lib/server-runtime/routeMatching.js +3 -4
  150. package/dist/production/lib/server-runtime/routes.js +1 -1
  151. package/dist/production/lib/server-runtime/server.js +7 -6
  152. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  153. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
  154. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
  155. package/dist/production/lib/server-runtime/sessions.js +1 -1
  156. package/dist/production/lib/server-runtime/single-fetch.js +1 -1
  157. package/dist/production/lib/server-runtime/urls.js +1 -1
  158. package/dist/production/lib/server-runtime/warnings.js +1 -1
  159. package/dist/production/lib/types/internal.js +1 -1
  160. package/dist/production/route-pattern.d.ts +3 -0
  161. package/dist/production/route-pattern.js +12 -0
  162. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  163. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +1 -1
  164. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  165. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  166. package/docs/how-to/fetchers.md +6 -1
  167. package/docs/how-to/middleware.md +3 -1
  168. package/docs/how-to/optimize-revalidation.md +178 -5
  169. package/docs/how-to/react-server-components.md +26 -3
  170. package/docs/how-to/resource-routes.md +3 -0
  171. package/docs/start/data/actions.md +2 -1
  172. package/docs/start/data/route-object.md +21 -6
  173. package/docs/start/framework/actions.md +4 -2
  174. package/docs/start/framework/route-module.md +32 -5
  175. package/docs/upgrading/future.md +68 -0
  176. package/package.json +11 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -9,10 +9,10 @@
9
9
  * @license MIT
10
10
  */
11
11
  import { isRouteErrorResponse } from "../router/utils.js";
12
- import { hasInvalidProtocol } from "../router/router.js";
12
+ import { createDataRouteMatcher, hasInvalidProtocol } from "../router/router.js";
13
13
  import { RSCRouterContext } from "../context.js";
14
14
  import { decodeRedirectErrorDigest, decodeRouteErrorResponseDigest } from "../errors.js";
15
- import { escapeHtml } from "../dom/ssr/markup.js";
15
+ import { escapeAttribute } from "../dom/ssr/markup.js";
16
16
  import { shouldHydrateRouteLoader } from "../dom/ssr/routes.js";
17
17
  import { FrameworkContext } from "../dom/ssr/components.js";
18
18
  import { StaticRouterProvider, createStaticRouter } from "../dom/server.js";
@@ -179,7 +179,7 @@ async function routeRSCServerRequest({ request, serverResponse, createFromReadab
179
179
  const redirectTransform = new TransformStream({ flush(controller) {
180
180
  if (renderRedirect) {
181
181
  if (hasInvalidProtocol(renderRedirect.location)) return;
182
- controller.enqueue(new TextEncoder().encode(`<meta http-equiv="refresh" content="0;url=${escapeHtml(renderRedirect.location)}"/>`));
182
+ controller.enqueue(new TextEncoder().encode(`<meta http-equiv="refresh" content="0;url=${escapeAttribute(renderRedirect.location)}"/>`));
183
183
  }
184
184
  } });
185
185
  if (!hydrate) return new Response(html.pipeThrough(redirectTransform), {
@@ -258,7 +258,7 @@ async function routeRSCServerRequest({ request, serverResponse, createFromReadab
258
258
  const retryRedirectTransform = new TransformStream({ flush(controller) {
259
259
  if (retryRedirect) {
260
260
  if (hasInvalidProtocol(retryRedirect.location)) return;
261
- controller.enqueue(new TextEncoder().encode(`<meta http-equiv="refresh" content="0;url=${escapeHtml(retryRedirect.location)}"/>`));
261
+ controller.enqueue(new TextEncoder().encode(`<meta http-equiv="refresh" content="0;url=${escapeAttribute(retryRedirect.location)}"/>`));
262
262
  }
263
263
  } });
264
264
  if (!hydrate) return new Response(html.pipeThrough(retryRedirectTransform), {
@@ -333,6 +333,24 @@ function RSCStaticRouter({ getPayload, nonce }) {
333
333
  if (payload.type !== "render") return null;
334
334
  let patchedLoaderData = { ...payload.loaderData };
335
335
  for (const match of payload.matches) if (shouldHydrateRouteLoader(match.id, match.clientLoader, match.hasLoader, false) && (match.hydrateFallbackElement || !match.hasLoader)) delete patchedLoaderData[match.id];
336
+ const routes = payload.matches.reduceRight((previous, match) => {
337
+ const route = {
338
+ id: match.id,
339
+ action: match.hasAction || !!match.clientAction,
340
+ element: match.element,
341
+ errorElement: match.errorElement,
342
+ handle: match.handle,
343
+ hydrateFallbackElement: match.hydrateFallbackElement,
344
+ index: match.index,
345
+ loader: match.hasLoader || !!match.clientLoader,
346
+ path: match.path,
347
+ shouldRevalidate: match.shouldRevalidate
348
+ };
349
+ if (previous.length > 0) route.children = previous;
350
+ return [route];
351
+ }, []);
352
+ const dataRouteMatcher = createDataRouteMatcher({}, payload.basename || "/");
353
+ dataRouteMatcher.update(routes);
336
354
  const context = {
337
355
  get _deepestRenderedBoundaryId() {
338
356
  return decoded._deepestRenderedBoundaryId ?? null;
@@ -348,6 +366,7 @@ function RSCStaticRouter({ getPayload, nonce }) {
348
366
  loaderHeaders: {},
349
367
  location: payload.location,
350
368
  statusCode: 200,
369
+ _match: (locationArg) => dataRouteMatcher.match(locationArg),
351
370
  matches: payload.matches.map((match) => ({
352
371
  params: match.params,
353
372
  pathname: match.pathname,
@@ -363,22 +382,7 @@ function RSCStaticRouter({ getPayload, nonce }) {
363
382
  }
364
383
  }))
365
384
  };
366
- const router = createStaticRouter(payload.matches.reduceRight((previous, match) => {
367
- const route = {
368
- id: match.id,
369
- action: match.hasAction || !!match.clientAction,
370
- element: match.element,
371
- errorElement: match.errorElement,
372
- handle: match.handle,
373
- hydrateFallbackElement: match.hydrateFallbackElement,
374
- index: match.index,
375
- loader: match.hasLoader || !!match.clientLoader,
376
- path: match.path,
377
- shouldRevalidate: match.shouldRevalidate
378
- };
379
- if (previous.length > 0) route.children = previous;
380
- return [route];
381
- }, []), context);
385
+ const router = createStaticRouter(routes, context);
382
386
  const frameworkContext = {
383
387
  future: {},
384
388
  isSpaMode: false,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,11 +8,10 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { matchRoutesImpl } from "../router/utils.js";
12
11
  import invariant from "./invariant.js";
13
12
  //#region lib/server-runtime/routeMatching.ts
14
- function matchServerRoutes(manifest, dataRoutes, branches, pathname, basename) {
15
- let matches = matchRoutesImpl(dataRoutes, pathname, basename ?? "/", false, branches);
13
+ function matchServerRoutes(manifest, staticHandler, pathname) {
14
+ let matches = staticHandler.match(pathname);
16
15
  if (!matches) return null;
17
16
  return matches.map((match) => {
18
17
  let route = manifest[match.route.id];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -90,12 +90,12 @@ function derive(build, mode) {
90
90
  }
91
91
  let manifestUrl = getManifestPath(build.routeDiscovery.manifestPath, build.basename);
92
92
  if (build.routeDiscovery.mode === "lazy" && requestUrl.pathname === manifestUrl) try {
93
- return await handleManifestRequest(build, staticHandler.dataRoutes, staticHandler._internalRouteBranches, requestUrl);
93
+ return await handleManifestRequest(build, staticHandler, requestUrl);
94
94
  } catch (e) {
95
95
  handleError(e);
96
96
  return new Response("Unknown Server Error", { status: 500 });
97
97
  }
98
- let matches = matchServerRoutes(build.routes, staticHandler.dataRoutes, staticHandler._internalRouteBranches, normalizedPathname, build.basename);
98
+ let matches = matchServerRoutes(build.routes, staticHandler, normalizedPathname);
99
99
  if (matches && matches.length > 0) Object.assign(params, matches[0].params);
100
100
  if (requestHandlerInstrumentations?.length) loadContext.set(instrumentationResultMetaContext, {
101
101
  url: createDataFunctionUrl(request, normalizedPath),
@@ -173,7 +173,7 @@ const createRequestHandler = (build, mode) => {
173
173
  return _requestHandler(request, initialContext);
174
174
  };
175
175
  };
176
- async function handleManifestRequest(build, dataRoutes, branches, url) {
176
+ async function handleManifestRequest(build, staticHandler, url) {
177
177
  if (url.toString().length > 7680) return new Response(null, {
178
178
  statusText: "Bad Request",
179
179
  status: 400
@@ -188,7 +188,7 @@ async function handleManifestRequest(build, dataRoutes, branches, url) {
188
188
  let paths = new Set(pathParam.split(",").filter(Boolean));
189
189
  for (let path of paths) {
190
190
  if (!path.startsWith("/")) path = `/${path}`;
191
- let matches = matchServerRoutes(build.routes, dataRoutes, branches, path, build.basename);
191
+ let matches = matchServerRoutes(build.routes, staticHandler, path);
192
192
  if (matches) for (let match of matches) {
193
193
  let routeId = match.route.id;
194
194
  let route = build.assets.routes[routeId];
@@ -256,7 +256,7 @@ async function handleDocumentRequest(serverMode, build, staticHandler, request,
256
256
  };
257
257
  let entryContext = {
258
258
  manifest: build.assets,
259
- branches: staticHandler._internalRouteBranches,
259
+ branches: [],
260
260
  routeModules: createEntryRouteModules(build.routes),
261
261
  staticHandlerContext: context,
262
262
  criticalCss,
@@ -291,6 +291,7 @@ async function handleDocumentRequest(serverMode, build, staticHandler, request,
291
291
  };
292
292
  entryContext = {
293
293
  ...entryContext,
294
+ branches: [],
294
295
  staticHandlerContext: context,
295
296
  serverHandoffString: createServerHandoffString(baseServerHandoff),
296
297
  serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -0,0 +1,3 @@
1
+
2
+ import { unstable_preloadRoutePattern } from "./lib/router/matcher-route-pattern.preload.js";
3
+ export { unstable_preloadRoutePattern };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * react-router v8.4.0
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import { unstable_preloadRoutePattern } from "./lib/router/matcher-route-pattern.preload.js";
12
+ export { unstable_preloadRoutePattern };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.1
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -85,7 +85,7 @@ export default function Component() {
85
85
 
86
86
  ### 3. Submit the form
87
87
 
88
- If you submit the form now, the fetcher will call the action and revalidate the route data automatically.
88
+ If you submit the form now, the fetcher will call the action and revalidate the route data automatically. See [Revalidation Optimization](./optimize-revalidation) to skip that reload for a route or a single submission.
89
89
 
90
90
  ### 4. Render pending state
91
91
 
@@ -305,3 +305,8 @@ Fetchers can be submitted programmatically with `fetcher.submit`:
305
305
  ```
306
306
 
307
307
  Note the input event's form is passed as the first argument to `fetcher.submit`. The fetcher will use that form to submit the request, reading its attributes and serializing the data from its elements.
308
+
309
+ `fetcher.submit` accepts the same [`defaultShouldRevalidate`][fetcher-submit-options] option as [`<Form>`][form]. Pass `false` to skip loader revalidation for that submission. See [Revalidation Optimization](./optimize-revalidation) for how that interacts with route-level `shouldRevalidate`.
310
+
311
+ [fetcher-submit-options]: https://api.reactrouter.com/v8/interfaces/react-router.FetcherSubmitOptions.html#defaultshouldrevalidate
312
+ [form]: ../api/components/Form#defaultshouldrevalidate
@@ -358,7 +358,9 @@ In theory, we could have leveraged [`AsyncLocalStorage`][asynclocalstorage] dire
358
358
 
359
359
  That said, this API still works great with React Router middleware and can be used in place of, or alongside of the `context` API:
360
360
 
361
- <docs-info>[`AsyncLocalStorage`][asynclocalstorage] is _especially_ powerful when using [React Server Components](../how-to/react-server-components) because it allows you to provide information from `middleware` to your Server Components and Server Actions because they run in the same server execution context 🤯</docs-info>
361
+ <docs-info>[`AsyncLocalStorage`][asynclocalstorage] is _especially_ powerful when using [React Server Components](../how-to/react-server-components) because it allows you to provide information from `middleware` to your Server Components and route actions because they run in the same server execution context 🤯</docs-info>
362
+
363
+ <docs-warning>Do not rely on route middleware to provide access control for React Server Functions. Server Functions are not inherently associated with a route, and a client can call the same Server Function through a URL with different middleware. Server Functions must perform all of their own access control checks. Use a route `action` instead when you want middleware-driven access control.</docs-warning>
362
364
 
363
365
  ```tsx filename=app/user-context.ts
364
366
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -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`](fetcher-submit))
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`])
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
@@ -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