react-router 7.5.1 → 7.5.2

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 (36) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/development/{chunk-LSOULM7L.mjs → chunk-BAXFHI7N.mjs} +104 -35
  3. package/dist/development/dom-export.d.mts +2 -2
  4. package/dist/development/dom-export.d.ts +2 -2
  5. package/dist/development/dom-export.js +89 -61
  6. package/dist/development/dom-export.mjs +17 -41
  7. package/dist/{production/fog-of-war-CyHis97d.d.mts → development/fog-of-war-BLArG-qZ.d.ts} +2 -2
  8. package/dist/development/{fog-of-war-D4x86-Xc.d.ts → fog-of-war-D2zsXvum.d.mts} +2 -2
  9. package/dist/development/index.d.mts +19 -16
  10. package/dist/development/index.d.ts +19 -16
  11. package/dist/development/index.js +105 -35
  12. package/dist/development/index.mjs +6 -4
  13. package/dist/development/lib/types/route-module.d.mts +1 -1
  14. package/dist/development/lib/types/route-module.d.ts +1 -1
  15. package/dist/development/lib/types/route-module.js +1 -1
  16. package/dist/development/lib/types/route-module.mjs +1 -1
  17. package/dist/development/{route-data-OcOrqK13.d.ts → route-data-C12CLHiN.d.mts} +1 -1
  18. package/dist/{production/route-data-OcOrqK13.d.mts → development/route-data-C12CLHiN.d.ts} +1 -1
  19. package/dist/production/{chunk-SAWFLE7G.mjs → chunk-XAHXRTXS.mjs} +104 -35
  20. package/dist/production/dom-export.d.mts +2 -2
  21. package/dist/production/dom-export.d.ts +2 -2
  22. package/dist/production/dom-export.js +89 -61
  23. package/dist/production/dom-export.mjs +17 -41
  24. package/dist/{development/fog-of-war-CyHis97d.d.mts → production/fog-of-war-BLArG-qZ.d.ts} +2 -2
  25. package/dist/production/{fog-of-war-D4x86-Xc.d.ts → fog-of-war-D2zsXvum.d.mts} +2 -2
  26. package/dist/production/index.d.mts +19 -16
  27. package/dist/production/index.d.ts +19 -16
  28. package/dist/production/index.js +105 -35
  29. package/dist/production/index.mjs +6 -4
  30. package/dist/production/lib/types/route-module.d.mts +1 -1
  31. package/dist/production/lib/types/route-module.d.ts +1 -1
  32. package/dist/production/lib/types/route-module.js +1 -1
  33. package/dist/production/lib/types/route-module.mjs +1 -1
  34. package/dist/production/{route-data-OcOrqK13.d.ts → route-data-C12CLHiN.d.mts} +1 -1
  35. package/dist/{development/route-data-OcOrqK13.d.mts → production/route-data-C12CLHiN.d.ts} +1 -1
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # `react-router`
2
2
 
3
+ ## 7.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Update Single Fetch to also handle the 204 redirects used in `?_data` requests in Remix v2 ([#13364](https://github.com/remix-run/react-router/pull/13364))
8
+
9
+ - This allows applications to return a redirect on `.data` requests from outside the scope of React Router (i.e., an `express`/`hono` middleware)
10
+ - ⚠️ Please note that doing so relies on implementation details that are subject to change without a SemVer major release
11
+ - This is primarily done to ease upgrading to Single Fetch for existing Remix v2 applications, but the recommended way to handle this is redirecting from a route middleware
12
+
13
+ - Adjust approach for Prerendering/SPA Mode via headers ([#13453](https://github.com/remix-run/react-router/pull/13453))
14
+
3
15
  ## 7.5.1
4
16
 
5
17
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v7.5.1
2
+ * react-router v7.5.2
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -6312,6 +6312,7 @@ async function createRequestInit(request) {
6312
6312
 
6313
6313
  // lib/dom/ssr/single-fetch.tsx
6314
6314
  var SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");
6315
+ var SINGLE_FETCH_REDIRECT_STATUS = 202;
6315
6316
  var NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([100, 101, 204, 205]);
6316
6317
  function StreamTransfer({
6317
6318
  context,
@@ -6379,10 +6380,19 @@ function StreamTransfer({
6379
6380
  )));
6380
6381
  }
6381
6382
  }
6382
- function getSingleFetchDataStrategy(getRouter, getRouteInfo, ssr, basename) {
6383
+ function getTurboStreamSingleFetchDataStrategy(getRouter, manifest, routeModules, ssr, basename) {
6383
6384
  let dataStrategy = getSingleFetchDataStrategyImpl(
6384
6385
  getRouter,
6385
- getRouteInfo,
6386
+ (match) => {
6387
+ let manifestRoute = manifest.routes[match.route.id];
6388
+ invariant2(manifestRoute, "Route not found in manifest");
6389
+ let routeModule = routeModules[match.route.id];
6390
+ return {
6391
+ hasLoader: manifestRoute.hasLoader,
6392
+ hasClientLoader: manifestRoute.hasClientLoader,
6393
+ hasShouldRevalidate: Boolean(routeModule?.shouldRevalidate)
6394
+ };
6395
+ },
6386
6396
  fetchAndDecodeViaTurboStream,
6387
6397
  ssr,
6388
6398
  basename
@@ -6397,7 +6407,7 @@ function getSingleFetchDataStrategyImpl(getRouter, getRouteInfo, fetchAndDecode,
6397
6407
  return singleFetchActionStrategy(args, fetchAndDecode, basename);
6398
6408
  }
6399
6409
  let foundRevalidatingServerLoader = matches.some((m) => {
6400
- let { hasLoader, hasClientLoader } = getRouteInfo(m.route.id);
6410
+ let { hasLoader, hasClientLoader } = getRouteInfo(m);
6401
6411
  return m.unstable_shouldCallHandler() && hasLoader && !hasClientLoader;
6402
6412
  });
6403
6413
  if (!ssr && !foundRevalidatingServerLoader) {
@@ -6449,7 +6459,7 @@ async function nonSsrStrategy(args, getRouteInfo, fetchAndDecode, basename) {
6449
6459
  matchesToLoad.map(
6450
6460
  (m) => m.resolve(async (handler) => {
6451
6461
  try {
6452
- let { hasClientLoader } = getRouteInfo(m.route.id);
6462
+ let { hasClientLoader } = getRouteInfo(m);
6453
6463
  let routeId = m.route.id;
6454
6464
  let result = hasClientLoader ? await handler(async () => {
6455
6465
  let { data: data2 } = await fetchAndDecode(args, basename, [routeId]);
@@ -6475,7 +6485,7 @@ async function singleFetchLoaderNavigationStrategy(args, router, getRouteInfo, f
6475
6485
  async (m, i) => m.resolve(async (handler) => {
6476
6486
  routeDfds[i].resolve();
6477
6487
  let routeId = m.route.id;
6478
- let { hasLoader, hasClientLoader, hasShouldRevalidate } = getRouteInfo(routeId);
6488
+ let { hasLoader, hasClientLoader, hasShouldRevalidate } = getRouteInfo(m);
6479
6489
  let defaultShouldRevalidate = !m.unstable_shouldRevalidateArgs || m.unstable_shouldRevalidateArgs.actionStatus == null || m.unstable_shouldRevalidateArgs.actionStatus < 400;
6480
6490
  let shouldCall = m.unstable_shouldCallHandler(defaultShouldRevalidate);
6481
6491
  if (!shouldCall) {
@@ -6516,7 +6526,7 @@ async function singleFetchLoaderNavigationStrategy(args, router, getRouteInfo, f
6516
6526
  );
6517
6527
  await Promise.all(routeDfds.map((d) => d.promise));
6518
6528
  if ((!router.state.initialized || routesParams.size === 0) && !window.__reactRouterHdrActive) {
6519
- singleFetchDfd.resolve({});
6529
+ singleFetchDfd.resolve({ routes: {} });
6520
6530
  } else {
6521
6531
  let targetRoutes = ssr && foundOptOutRoute && routesParams.size > 0 ? [...routesParams.keys()] : void 0;
6522
6532
  try {
@@ -6584,6 +6594,20 @@ async function fetchAndDecodeViaTurboStream(args, basename, targetRoutes) {
6584
6594
  if (res.status === 404 && !res.headers.has("X-Remix-Response")) {
6585
6595
  throw new ErrorResponseImpl(404, "Not Found", true);
6586
6596
  }
6597
+ if (res.status === 204 && res.headers.has("X-Remix-Redirect")) {
6598
+ return {
6599
+ status: SINGLE_FETCH_REDIRECT_STATUS,
6600
+ data: {
6601
+ redirect: {
6602
+ redirect: res.headers.get("X-Remix-Redirect"),
6603
+ status: Number(res.headers.get("X-Remix-Status") || "302"),
6604
+ revalidate: res.headers.get("X-Remix-Revalidate") === "true",
6605
+ reload: res.headers.get("X-Remix-Reload-Document") === "true",
6606
+ replace: res.headers.get("X-Remix-Replace") === "true"
6607
+ }
6608
+ }
6609
+ };
6610
+ }
6587
6611
  if (NO_BODY_STATUS_CODES.has(res.status)) {
6588
6612
  let routes = {};
6589
6613
  if (targetRoutes && request.method !== "GET") {
@@ -7034,8 +7058,9 @@ function createClientRoutes(manifest, routeModulesCache, initialState, ssr, isSp
7034
7058
  }
7035
7059
  };
7036
7060
  dataRoute.loader.hydrate = shouldHydrateRouteLoader(
7037
- route,
7038
- routeModule,
7061
+ route.id,
7062
+ routeModule.clientLoader,
7063
+ route.hasLoader,
7039
7064
  isSpaMode
7040
7065
  );
7041
7066
  dataRoute.action = ({ request, params, context }, singleFetch) => {
@@ -7233,8 +7258,8 @@ function getRouteModuleComponent(routeModule) {
7233
7258
  return routeModule.default;
7234
7259
  }
7235
7260
  }
7236
- function shouldHydrateRouteLoader(route, routeModule, isSpaMode) {
7237
- return isSpaMode && route.id !== "root" || routeModule.clientLoader != null && (routeModule.clientLoader.hydrate === true || route.hasLoader !== true);
7261
+ function shouldHydrateRouteLoader(routeId, clientLoader, hasLoader, isSpaMode) {
7262
+ return isSpaMode && routeId !== "root" || clientLoader != null && (clientLoader.hydrate === true || hasLoader !== true);
7238
7263
  }
7239
7264
 
7240
7265
  // lib/dom/ssr/fog-of-war.ts
@@ -7916,7 +7941,7 @@ function mergeRefs(...refs) {
7916
7941
  var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
7917
7942
  try {
7918
7943
  if (isBrowser) {
7919
- window.__reactRouterVersion = "7.5.1";
7944
+ window.__reactRouterVersion = "7.5.2";
7920
7945
  }
7921
7946
  } catch (e) {
7922
7947
  }
@@ -9019,7 +9044,12 @@ function ServerRouter({
9019
9044
  let routeId = match.route.id;
9020
9045
  let route = routeModules[routeId];
9021
9046
  let manifestRoute = context.manifest.routes[routeId];
9022
- if (route && manifestRoute && shouldHydrateRouteLoader(manifestRoute, route, context.isSpaMode) && (route.HydrateFallback || !manifestRoute.hasLoader)) {
9047
+ if (route && manifestRoute && shouldHydrateRouteLoader(
9048
+ routeId,
9049
+ route.clientLoader,
9050
+ manifestRoute.hasLoader,
9051
+ context.isSpaMode
9052
+ ) && (route.HydrateFallback || !manifestRoute.hasLoader)) {
9023
9053
  delete context.staticHandlerContext.loaderData[routeId];
9024
9054
  }
9025
9055
  }
@@ -9488,6 +9518,26 @@ function invariant3(value, message) {
9488
9518
  }
9489
9519
  }
9490
9520
 
9521
+ // lib/server-runtime/dev.ts
9522
+ var globalDevServerHooksKey = "__reactRouterDevServerHooks";
9523
+ function setDevServerHooks(devServerHooks) {
9524
+ globalThis[globalDevServerHooksKey] = devServerHooks;
9525
+ }
9526
+ function getDevServerHooks() {
9527
+ return globalThis[globalDevServerHooksKey];
9528
+ }
9529
+ function getBuildTimeHeader(request, headerName) {
9530
+ if (typeof process !== "undefined") {
9531
+ try {
9532
+ if (process.env?.IS_RR_BUILD_REQUEST === "yes") {
9533
+ return request.headers.get(headerName);
9534
+ }
9535
+ } catch (e) {
9536
+ }
9537
+ }
9538
+ return null;
9539
+ }
9540
+
9491
9541
  // lib/server-runtime/routes.ts
9492
9542
  function groupRoutesByParentId2(manifest) {
9493
9543
  let routes = {};
@@ -9519,10 +9569,11 @@ function createStaticHandlerDataRoutes(manifest, future, parentId = "", routesBy
9519
9569
  // Need to use RR's version in the param typed here to permit the optional
9520
9570
  // context even though we know it'll always be provided in remix
9521
9571
  loader: route.module.loader ? async (args) => {
9522
- if (args.request.headers.has("X-React-Router-Prerender-Data")) {
9523
- const preRenderedData = args.request.headers.get(
9524
- "X-React-Router-Prerender-Data"
9525
- );
9572
+ let preRenderedData = getBuildTimeHeader(
9573
+ args.request,
9574
+ "X-React-Router-Prerender-Data"
9575
+ );
9576
+ if (preRenderedData != null) {
9526
9577
  let encoded = preRenderedData ? decodeURI(preRenderedData) : preRenderedData;
9527
9578
  invariant3(encoded, "Missing prerendered data for route");
9528
9579
  let uint8array = new TextEncoder().encode(encoded);
@@ -9597,15 +9648,6 @@ function createServerHandoffString(serverHandoff) {
9597
9648
  return escapeHtml2(JSON.stringify(serverHandoff));
9598
9649
  }
9599
9650
 
9600
- // lib/server-runtime/dev.ts
9601
- var globalDevServerHooksKey = "__reactRouterDevServerHooks";
9602
- function setDevServerHooks(devServerHooks) {
9603
- globalThis[globalDevServerHooksKey] = devServerHooks;
9604
- }
9605
- function getDevServerHooks() {
9606
- return globalThis[globalDevServerHooksKey];
9607
- }
9608
-
9609
9651
  // lib/server-runtime/single-fetch.ts
9610
9652
  import { encode } from "turbo-stream";
9611
9653
 
@@ -9676,7 +9718,6 @@ function prependCookies(parentHeaders, childHeaders) {
9676
9718
  }
9677
9719
 
9678
9720
  // lib/server-runtime/single-fetch.ts
9679
- var SINGLE_FETCH_REDIRECT_STATUS = 202;
9680
9721
  var SERVER_NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([
9681
9722
  ...NO_BODY_STATUS_CODES,
9682
9723
  304
@@ -10015,9 +10056,10 @@ Error: ${e instanceof Error ? e.toString() : e}`
10015
10056
  if (stripBasename(normalizedPath, normalizedBasename) !== "/" && normalizedPath.endsWith("/")) {
10016
10057
  normalizedPath = normalizedPath.slice(0, -1);
10017
10058
  }
10059
+ let isSpaMode = getBuildTimeHeader(request, "X-React-Router-SPA-Mode") === "yes";
10018
10060
  if (!_build.ssr) {
10019
10061
  if (_build.prerender.length === 0) {
10020
- request.headers.set("X-React-Router-SPA-Mode", "yes");
10062
+ isSpaMode = true;
10021
10063
  } else if (!_build.prerender.includes(normalizedPath) && !_build.prerender.includes(normalizedPath + "/")) {
10022
10064
  if (url.pathname.endsWith(".data")) {
10023
10065
  errorHandler(
@@ -10037,7 +10079,7 @@ Error: ${e instanceof Error ? e.toString() : e}`
10037
10079
  statusText: "Not Found"
10038
10080
  });
10039
10081
  } else {
10040
- request.headers.set("X-React-Router-SPA-Mode", "yes");
10082
+ isSpaMode = true;
10041
10083
  }
10042
10084
  }
10043
10085
  }
@@ -10106,7 +10148,7 @@ Error: ${e instanceof Error ? e.toString() : e}`
10106
10148
  );
10107
10149
  }
10108
10150
  }
10109
- } else if (!request.headers.has("X-React-Router-SPA-Mode") && matches && matches[matches.length - 1].route.module.default == null && matches[matches.length - 1].route.module.ErrorBoundary == null) {
10151
+ } else if (!isSpaMode && matches && matches[matches.length - 1].route.module.default == null && matches[matches.length - 1].route.module.ErrorBoundary == null) {
10110
10152
  response = await handleResourceRequest(
10111
10153
  serverMode,
10112
10154
  _build,
@@ -10131,6 +10173,7 @@ Error: ${e instanceof Error ? e.toString() : e}`
10131
10173
  request,
10132
10174
  loadContext,
10133
10175
  handleError,
10176
+ isSpaMode,
10134
10177
  criticalCss
10135
10178
  );
10136
10179
  }
@@ -10206,8 +10249,7 @@ async function handleSingleFetchRequest(serverMode, build, staticHandler, reques
10206
10249
  );
10207
10250
  return response;
10208
10251
  }
10209
- async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) {
10210
- let isSpaMode = request.headers.has("X-React-Router-SPA-Mode");
10252
+ async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
10211
10253
  try {
10212
10254
  let response = await staticHandler.query(request, {
10213
10255
  requestContext: loadContext,
@@ -10480,7 +10522,7 @@ function createSessionStorage({
10480
10522
  function warnOnceAboutSigningSessionCookie(cookie) {
10481
10523
  warnOnce(
10482
10524
  cookie.isSigned,
10483
- `The "${cookie.name}" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://remix.run/utils/cookies#signing-cookies for more information.`
10525
+ `The "${cookie.name}" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://reactrouter.com/explanation/sessions-and-cookies#signing-cookies for more information.`
10484
10526
  );
10485
10527
  }
10486
10528
 
@@ -10597,6 +10639,32 @@ function deserializeErrors2(errors) {
10597
10639
  return serialized;
10598
10640
  }
10599
10641
 
10642
+ // lib/dom/ssr/hydration.tsx
10643
+ function getHydrationData(state, routes, getRouteInfo, location, basename, isSpaMode) {
10644
+ let hydrationData = {
10645
+ ...state,
10646
+ loaderData: { ...state.loaderData }
10647
+ };
10648
+ let initialMatches = matchRoutes(routes, location, basename);
10649
+ if (initialMatches) {
10650
+ for (let match of initialMatches) {
10651
+ let routeId = match.route.id;
10652
+ let routeInfo = getRouteInfo(routeId);
10653
+ if (shouldHydrateRouteLoader(
10654
+ routeId,
10655
+ routeInfo.clientLoader,
10656
+ routeInfo.hasLoader,
10657
+ isSpaMode
10658
+ ) && (routeInfo.hasHydrateFallback || !routeInfo.hasLoader)) {
10659
+ delete hydrationData.loaderData[routeId];
10660
+ } else if (!routeInfo.hasLoader) {
10661
+ hydrationData.loaderData[routeId] = null;
10662
+ }
10663
+ }
10664
+ }
10665
+ return hydrationData;
10666
+ }
10667
+
10600
10668
  export {
10601
10669
  Action,
10602
10670
  createBrowserHistory,
@@ -10663,7 +10731,7 @@ export {
10663
10731
  renderMatches,
10664
10732
  createSearchParams,
10665
10733
  SingleFetchRedirectSymbol,
10666
- getSingleFetchDataStrategy,
10734
+ getTurboStreamSingleFetchDataStrategy,
10667
10735
  decodeViaTurboStream,
10668
10736
  RemixErrorBoundary,
10669
10737
  createClientRoutesWithHMRRevalidationOptOut,
@@ -10712,5 +10780,6 @@ export {
10712
10780
  createCookieSessionStorage,
10713
10781
  createMemorySessionStorage,
10714
10782
  href,
10715
- deserializeErrors2 as deserializeErrors
10783
+ deserializeErrors2 as deserializeErrors,
10784
+ getHydrationData
10716
10785
  };
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { R as RouterProviderProps$1 } from './fog-of-war-CyHis97d.mjs';
3
- import { R as RouterInit } from './route-data-OcOrqK13.mjs';
2
+ import { R as RouterProviderProps$1 } from './fog-of-war-D2zsXvum.mjs';
3
+ import { R as RouterInit } from './route-data-C12CLHiN.mjs';
4
4
 
5
5
  type RouterProviderProps = Omit<RouterProviderProps$1, "flushSync">;
6
6
  declare function RouterProvider(props: Omit<RouterProviderProps, "flushSync">): React.JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { R as RouterProviderProps$1 } from './fog-of-war-D4x86-Xc.js';
3
- import { R as RouterInit } from './route-data-OcOrqK13.js';
2
+ import { R as RouterProviderProps$1 } from './fog-of-war-BLArG-qZ.js';
3
+ import { R as RouterInit } from './route-data-C12CLHiN.js';
4
4
 
5
5
  type RouterProviderProps = Omit<RouterProviderProps$1, "flushSync">;
6
6
  declare function RouterProvider(props: Omit<RouterProviderProps, "flushSync">): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v7.5.1
2
+ * react-router v7.5.2
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -189,7 +189,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options =
189
189
  listener({ action, location: history.location, delta: 1 });
190
190
  }
191
191
  }
192
- function replace2(to, state) {
192
+ function replace(to, state) {
193
193
  action = "REPLACE" /* Replace */;
194
194
  let location = createLocation(history.location, to, state);
195
195
  if (validateLocation) validateLocation(location, to);
@@ -242,7 +242,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options =
242
242
  };
243
243
  },
244
244
  push,
245
- replace: replace2,
245
+ replace,
246
246
  go(n) {
247
247
  return globalHistory.go(n);
248
248
  }
@@ -1420,20 +1420,20 @@ function createRouter(init) {
1420
1420
  }
1421
1421
  }
1422
1422
  if (isRedirectResult(result)) {
1423
- let replace2;
1423
+ let replace;
1424
1424
  if (opts && opts.replace != null) {
1425
- replace2 = opts.replace;
1425
+ replace = opts.replace;
1426
1426
  } else {
1427
1427
  let location2 = normalizeRedirectLocation(
1428
1428
  result.response.headers.get("Location"),
1429
1429
  new URL(request.url),
1430
1430
  basename
1431
1431
  );
1432
- replace2 = location2 === state.location.pathname + state.location.search;
1432
+ replace = location2 === state.location.pathname + state.location.search;
1433
1433
  }
1434
1434
  await startRedirectNavigation(request, result, true, {
1435
1435
  submission,
1436
- replace: replace2
1436
+ replace
1437
1437
  });
1438
1438
  return { shortCircuited: true };
1439
1439
  }
@@ -1452,7 +1452,7 @@ function createRouter(init) {
1452
1452
  pendingActionResult: [actionMatch.route.id, result]
1453
1453
  };
1454
1454
  }
1455
- async function handleLoaders(request, location, matches, scopedContext, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace2, initialHydration, flushSync2, pendingActionResult) {
1455
+ async function handleLoaders(request, location, matches, scopedContext, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync2, pendingActionResult) {
1456
1456
  let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
1457
1457
  let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
1458
1458
  let shouldUpdateNavigationState = !isUninterruptedRevalidation && !initialHydration;
@@ -1585,7 +1585,7 @@ function createRouter(init) {
1585
1585
  let redirect2 = findRedirect(loaderResults);
1586
1586
  if (redirect2) {
1587
1587
  await startRedirectNavigation(request, redirect2.result, true, {
1588
- replace: replace2
1588
+ replace
1589
1589
  });
1590
1590
  return { shortCircuited: true };
1591
1591
  }
@@ -1593,7 +1593,7 @@ function createRouter(init) {
1593
1593
  if (redirect2) {
1594
1594
  fetchRedirectIds.add(redirect2.key);
1595
1595
  await startRedirectNavigation(request, redirect2.result, true, {
1596
- replace: replace2
1596
+ replace
1597
1597
  });
1598
1598
  return { shortCircuited: true };
1599
1599
  }
@@ -2032,7 +2032,7 @@ function createRouter(init) {
2032
2032
  submission,
2033
2033
  fetcherSubmission,
2034
2034
  preventScrollReset,
2035
- replace: replace2
2035
+ replace
2036
2036
  } = {}) {
2037
2037
  if (redirect2.response.headers.has("X-Remix-Revalidate")) {
2038
2038
  isRevalidationRequired = true;
@@ -2058,7 +2058,7 @@ function createRouter(init) {
2058
2058
  stripBasename(url.pathname, basename) == null;
2059
2059
  }
2060
2060
  if (isDocumentReload) {
2061
- if (replace2) {
2061
+ if (replace) {
2062
2062
  routerWindow.location.replace(location);
2063
2063
  } else {
2064
2064
  routerWindow.location.assign(location);
@@ -2067,7 +2067,7 @@ function createRouter(init) {
2067
2067
  }
2068
2068
  }
2069
2069
  pendingNavigationController = null;
2070
- let redirectNavigationType = replace2 === true || redirect2.response.headers.has("X-Remix-Replace") ? "REPLACE" /* Replace */ : "PUSH" /* Push */;
2070
+ let redirectNavigationType = replace === true || redirect2.response.headers.has("X-Remix-Replace") ? "REPLACE" /* Replace */ : "PUSH" /* Push */;
2071
2071
  let { formMethod, formAction, formEncType } = state.navigation;
2072
2072
  if (!submission && !fetcherSubmission && formMethod && formAction && formEncType) {
2073
2073
  submission = getSubmissionFromNavigation(state.navigation);
@@ -4835,11 +4835,21 @@ async function createRequestInit(request) {
4835
4835
 
4836
4836
  // lib/dom/ssr/single-fetch.tsx
4837
4837
  var SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");
4838
+ var SINGLE_FETCH_REDIRECT_STATUS = 202;
4838
4839
  var NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([100, 101, 204, 205]);
4839
- function getSingleFetchDataStrategy(getRouter, getRouteInfo, ssr, basename) {
4840
+ function getTurboStreamSingleFetchDataStrategy(getRouter, manifest, routeModules, ssr, basename) {
4840
4841
  let dataStrategy = getSingleFetchDataStrategyImpl(
4841
4842
  getRouter,
4842
- getRouteInfo,
4843
+ (match) => {
4844
+ let manifestRoute = manifest.routes[match.route.id];
4845
+ invariant2(manifestRoute, "Route not found in manifest");
4846
+ let routeModule = routeModules[match.route.id];
4847
+ return {
4848
+ hasLoader: manifestRoute.hasLoader,
4849
+ hasClientLoader: manifestRoute.hasClientLoader,
4850
+ hasShouldRevalidate: Boolean(routeModule?.shouldRevalidate)
4851
+ };
4852
+ },
4843
4853
  fetchAndDecodeViaTurboStream,
4844
4854
  ssr,
4845
4855
  basename
@@ -4854,7 +4864,7 @@ function getSingleFetchDataStrategyImpl(getRouter, getRouteInfo, fetchAndDecode,
4854
4864
  return singleFetchActionStrategy(args, fetchAndDecode, basename);
4855
4865
  }
4856
4866
  let foundRevalidatingServerLoader = matches.some((m) => {
4857
- let { hasLoader, hasClientLoader } = getRouteInfo(m.route.id);
4867
+ let { hasLoader, hasClientLoader } = getRouteInfo(m);
4858
4868
  return m.unstable_shouldCallHandler() && hasLoader && !hasClientLoader;
4859
4869
  });
4860
4870
  if (!ssr && !foundRevalidatingServerLoader) {
@@ -4906,7 +4916,7 @@ async function nonSsrStrategy(args, getRouteInfo, fetchAndDecode, basename) {
4906
4916
  matchesToLoad.map(
4907
4917
  (m) => m.resolve(async (handler) => {
4908
4918
  try {
4909
- let { hasClientLoader } = getRouteInfo(m.route.id);
4919
+ let { hasClientLoader } = getRouteInfo(m);
4910
4920
  let routeId = m.route.id;
4911
4921
  let result = hasClientLoader ? await handler(async () => {
4912
4922
  let { data: data2 } = await fetchAndDecode(args, basename, [routeId]);
@@ -4932,7 +4942,7 @@ async function singleFetchLoaderNavigationStrategy(args, router2, getRouteInfo,
4932
4942
  async (m, i) => m.resolve(async (handler) => {
4933
4943
  routeDfds[i].resolve();
4934
4944
  let routeId = m.route.id;
4935
- let { hasLoader, hasClientLoader, hasShouldRevalidate } = getRouteInfo(routeId);
4945
+ let { hasLoader, hasClientLoader, hasShouldRevalidate } = getRouteInfo(m);
4936
4946
  let defaultShouldRevalidate = !m.unstable_shouldRevalidateArgs || m.unstable_shouldRevalidateArgs.actionStatus == null || m.unstable_shouldRevalidateArgs.actionStatus < 400;
4937
4947
  let shouldCall = m.unstable_shouldCallHandler(defaultShouldRevalidate);
4938
4948
  if (!shouldCall) {
@@ -4973,7 +4983,7 @@ async function singleFetchLoaderNavigationStrategy(args, router2, getRouteInfo,
4973
4983
  );
4974
4984
  await Promise.all(routeDfds.map((d) => d.promise));
4975
4985
  if ((!router2.state.initialized || routesParams.size === 0) && !window.__reactRouterHdrActive) {
4976
- singleFetchDfd.resolve({});
4986
+ singleFetchDfd.resolve({ routes: {} });
4977
4987
  } else {
4978
4988
  let targetRoutes = ssr && foundOptOutRoute && routesParams.size > 0 ? [...routesParams.keys()] : void 0;
4979
4989
  try {
@@ -5041,6 +5051,20 @@ async function fetchAndDecodeViaTurboStream(args, basename, targetRoutes) {
5041
5051
  if (res.status === 404 && !res.headers.has("X-Remix-Response")) {
5042
5052
  throw new ErrorResponseImpl(404, "Not Found", true);
5043
5053
  }
5054
+ if (res.status === 204 && res.headers.has("X-Remix-Redirect")) {
5055
+ return {
5056
+ status: SINGLE_FETCH_REDIRECT_STATUS,
5057
+ data: {
5058
+ redirect: {
5059
+ redirect: res.headers.get("X-Remix-Redirect"),
5060
+ status: Number(res.headers.get("X-Remix-Status") || "302"),
5061
+ revalidate: res.headers.get("X-Remix-Revalidate") === "true",
5062
+ reload: res.headers.get("X-Remix-Reload-Document") === "true",
5063
+ replace: res.headers.get("X-Remix-Replace") === "true"
5064
+ }
5065
+ }
5066
+ };
5067
+ }
5044
5068
  if (NO_BODY_STATUS_CODES.has(res.status)) {
5045
5069
  let routes = {};
5046
5070
  if (targetRoutes && request.method !== "GET") {
@@ -5116,7 +5140,7 @@ function unwrapSingleFetchResult(result, routeId) {
5116
5140
  redirect: location,
5117
5141
  revalidate,
5118
5142
  reload,
5119
- replace: replace2,
5143
+ replace,
5120
5144
  status
5121
5145
  } = result.redirect;
5122
5146
  throw redirect(location, {
@@ -5125,7 +5149,7 @@ function unwrapSingleFetchResult(result, routeId) {
5125
5149
  // Three R's of redirecting (lol Veep)
5126
5150
  ...revalidate ? { "X-Remix-Revalidate": "yes" } : null,
5127
5151
  ...reload ? { "X-Remix-Reload-Document": "yes" } : null,
5128
- ...replace2 ? { "X-Remix-Replace": "yes" } : null
5152
+ ...replace ? { "X-Remix-Replace": "yes" } : null
5129
5153
  }
5130
5154
  });
5131
5155
  }
@@ -5451,8 +5475,9 @@ function createClientRoutes(manifest, routeModulesCache, initialState, ssr, isSp
5451
5475
  }
5452
5476
  };
5453
5477
  dataRoute.loader.hydrate = shouldHydrateRouteLoader(
5454
- route,
5455
- routeModule,
5478
+ route.id,
5479
+ routeModule.clientLoader,
5480
+ route.hasLoader,
5456
5481
  isSpaMode
5457
5482
  );
5458
5483
  dataRoute.action = ({ request, params, context }, singleFetch) => {
@@ -5650,8 +5675,8 @@ function getRouteModuleComponent(routeModule) {
5650
5675
  return routeModule.default;
5651
5676
  }
5652
5677
  }
5653
- function shouldHydrateRouteLoader(route, routeModule, isSpaMode) {
5654
- return isSpaMode && route.id !== "root" || routeModule.clientLoader != null && (routeModule.clientLoader.hydrate === true || route.hasLoader !== true);
5678
+ function shouldHydrateRouteLoader(routeId, clientLoader, hasLoader, isSpaMode) {
5679
+ return isSpaMode && routeId !== "root" || clientLoader != null && (clientLoader.hydrate === true || hasLoader !== true);
5655
5680
  }
5656
5681
 
5657
5682
  // lib/dom/ssr/fog-of-war.ts
@@ -6062,6 +6087,32 @@ function deserializeErrors(errors) {
6062
6087
  return serialized;
6063
6088
  }
6064
6089
 
6090
+ // lib/dom/ssr/hydration.tsx
6091
+ function getHydrationData(state, routes, getRouteInfo, location, basename, isSpaMode) {
6092
+ let hydrationData = {
6093
+ ...state,
6094
+ loaderData: { ...state.loaderData }
6095
+ };
6096
+ let initialMatches = matchRoutes(routes, location, basename);
6097
+ if (initialMatches) {
6098
+ for (let match of initialMatches) {
6099
+ let routeId = match.route.id;
6100
+ let routeInfo = getRouteInfo(routeId);
6101
+ if (shouldHydrateRouteLoader(
6102
+ routeId,
6103
+ routeInfo.clientLoader,
6104
+ routeInfo.hasLoader,
6105
+ isSpaMode
6106
+ ) && (routeInfo.hasHydrateFallback || !routeInfo.hasLoader)) {
6107
+ delete hydrationData.loaderData[routeId];
6108
+ } else if (!routeInfo.hasLoader) {
6109
+ hydrationData.loaderData[routeId] = null;
6110
+ }
6111
+ }
6112
+ }
6113
+ return hydrationData;
6114
+ }
6115
+
6065
6116
  // lib/dom-export/dom-router-provider.tsx
6066
6117
  function RouterProvider2(props) {
6067
6118
  return /* @__PURE__ */ React10.createElement(RouterProvider, { flushSync: ReactDOM.flushSync, ...props });
@@ -6130,8 +6181,8 @@ function createHydratedRouter({
6130
6181
  ssrInfo.context.isSpaMode
6131
6182
  );
6132
6183
  let hydrationData = void 0;
6133
- let loaderData = ssrInfo.context.state.loaderData;
6134
6184
  if (ssrInfo.context.isSpaMode) {
6185
+ let { loaderData } = ssrInfo.context.state;
6135
6186
  if (ssrInfo.manifest.routes.root?.hasLoader && loaderData && "root" in loaderData) {
6136
6187
  hydrationData = {
6137
6188
  loaderData: {
@@ -6140,31 +6191,18 @@ function createHydratedRouter({
6140
6191
  };
6141
6192
  }
6142
6193
  } else {
6143
- hydrationData = {
6144
- ...ssrInfo.context.state,
6145
- loaderData: { ...loaderData }
6146
- };
6147
- let initialMatches = matchRoutes(
6194
+ hydrationData = getHydrationData(
6195
+ ssrInfo.context.state,
6148
6196
  routes,
6197
+ (routeId) => ({
6198
+ clientLoader: ssrInfo.routeModules[routeId]?.clientLoader,
6199
+ hasLoader: ssrInfo.manifest.routes[routeId]?.hasLoader === true,
6200
+ hasHydrateFallback: ssrInfo.routeModules[routeId]?.HydrateFallback != null
6201
+ }),
6149
6202
  window.location,
6150
- window.__reactRouterContext?.basename
6203
+ window.__reactRouterContext?.basename,
6204
+ ssrInfo.context.isSpaMode
6151
6205
  );
6152
- if (initialMatches) {
6153
- for (let match of initialMatches) {
6154
- let routeId = match.route.id;
6155
- let route = ssrInfo.routeModules[routeId];
6156
- let manifestRoute = ssrInfo.manifest.routes[routeId];
6157
- if (route && manifestRoute && shouldHydrateRouteLoader(
6158
- manifestRoute,
6159
- route,
6160
- ssrInfo.context.isSpaMode
6161
- ) && (route.HydrateFallback || !manifestRoute.hasLoader)) {
6162
- delete hydrationData.loaderData[routeId];
6163
- } else if (manifestRoute && !manifestRoute.hasLoader) {
6164
- hydrationData.loaderData[routeId] = null;
6165
- }
6166
- }
6167
- }
6168
6206
  if (hydrationData && hydrationData.errors) {
6169
6207
  hydrationData.errors = deserializeErrors(hydrationData.errors);
6170
6208
  }
@@ -6180,20 +6218,10 @@ function createHydratedRouter({
6180
6218
  future: {
6181
6219
  unstable_middleware: ssrInfo.context.future.unstable_middleware
6182
6220
  },
6183
- dataStrategy: getSingleFetchDataStrategy(
6221
+ dataStrategy: getTurboStreamSingleFetchDataStrategy(
6184
6222
  () => router2,
6185
- (routeId) => {
6186
- let manifestRoute = ssrInfo.manifest.routes[routeId];
6187
- invariant(manifestRoute, "Route not found in manifest/routeModules");
6188
- let routeModule = ssrInfo.routeModules[routeId];
6189
- return {
6190
- hasLoader: manifestRoute.hasLoader,
6191
- hasClientLoader: manifestRoute.hasClientLoader,
6192
- // In some cases the module may not be loaded yet and we don't care
6193
- // if it's got shouldRevalidate or not
6194
- hasShouldRevalidate: routeModule ? routeModule.shouldRevalidate != null : void 0
6195
- };
6196
- },
6223
+ ssrInfo.manifest,
6224
+ ssrInfo.routeModules,
6197
6225
  ssrInfo.context.ssr,
6198
6226
  ssrInfo.context.basename
6199
6227
  ),