@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ReactNode } from "react";
|
|
9
9
|
import { invariant } from "../../errors";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
getParallelEntries,
|
|
12
|
+
getParallelSlotEntries,
|
|
13
|
+
type EntryData,
|
|
14
|
+
} from "../../server/context";
|
|
11
15
|
import type {
|
|
12
16
|
HandlerContext,
|
|
13
17
|
InternalHandlerContext,
|
|
@@ -15,59 +19,24 @@ import type {
|
|
|
15
19
|
} from "../../types";
|
|
16
20
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
17
21
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
22
|
+
import { _getRequestContext } from "../../server/request-context.js";
|
|
23
|
+
import { appendMetric } from "../metrics.js";
|
|
18
24
|
import {
|
|
19
25
|
handleHandlerResult,
|
|
20
26
|
tryStaticHandler,
|
|
21
27
|
tryStaticSlot,
|
|
22
28
|
resolveLayoutComponent,
|
|
23
29
|
resolveWithErrorBoundary,
|
|
30
|
+
warnOnStreamedResponse,
|
|
24
31
|
} from "./helpers.js";
|
|
32
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
25
33
|
import { getRouterContext } from "../router-context.js";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
35
|
-
* React catches the actual error via its error boundary; this only emits
|
|
36
|
-
* the handler.error telemetry event.
|
|
37
|
-
*/
|
|
38
|
-
function observeStreamedHandler(
|
|
39
|
-
promise: Promise<ReactNode>,
|
|
40
|
-
segmentId: string,
|
|
41
|
-
segmentType: string,
|
|
42
|
-
pathname?: string,
|
|
43
|
-
routeKey?: string,
|
|
44
|
-
params?: Record<string, string>,
|
|
45
|
-
): void {
|
|
46
|
-
let routerCtx;
|
|
47
|
-
try {
|
|
48
|
-
routerCtx = getRouterContext();
|
|
49
|
-
} catch {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (!routerCtx?.telemetry) return;
|
|
53
|
-
const sink = resolveSink(routerCtx.telemetry);
|
|
54
|
-
const reqId = routerCtx.requestId;
|
|
55
|
-
promise.catch((err: unknown) => {
|
|
56
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
57
|
-
safeEmit(sink, {
|
|
58
|
-
type: "handler.error",
|
|
59
|
-
timestamp: performance.now(),
|
|
60
|
-
requestId: reqId,
|
|
61
|
-
segmentId,
|
|
62
|
-
segmentType,
|
|
63
|
-
error: errorObj,
|
|
64
|
-
handledByBoundary: true,
|
|
65
|
-
pathname,
|
|
66
|
-
routeKey,
|
|
67
|
-
params,
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
|
34
|
+
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
35
|
+
import {
|
|
36
|
+
track,
|
|
37
|
+
RangoContext,
|
|
38
|
+
runInsideLoaderScope,
|
|
39
|
+
} from "../../server/context.js";
|
|
71
40
|
|
|
72
41
|
// ---------------------------------------------------------------------------
|
|
73
42
|
// Fresh path (full match, no revalidation)
|
|
@@ -90,9 +59,11 @@ export async function resolveLoaders<TEnv>(
|
|
|
90
59
|
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
91
60
|
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
92
61
|
const loadingDisabled = hasLoading && entry.loading === false;
|
|
62
|
+
const ms = _getRequestContext()?._metricsStore;
|
|
93
63
|
|
|
94
64
|
if (!loadingDisabled) {
|
|
95
|
-
|
|
65
|
+
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
66
|
+
const segments = loaderEntries.map((loaderEntry, i) => {
|
|
96
67
|
const { loader } = loaderEntry;
|
|
97
68
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
98
69
|
return {
|
|
@@ -104,7 +75,9 @@ export async function resolveLoaders<TEnv>(
|
|
|
104
75
|
params: ctx.params,
|
|
105
76
|
loaderId: loader.$$id,
|
|
106
77
|
loaderData: deps.wrapLoaderPromise(
|
|
107
|
-
|
|
78
|
+
runInsideLoaderScope(() =>
|
|
79
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
80
|
+
),
|
|
108
81
|
entry,
|
|
109
82
|
segmentId,
|
|
110
83
|
ctx.pathname,
|
|
@@ -112,32 +85,61 @@ export async function resolveLoaders<TEnv>(
|
|
|
112
85
|
belongsToRoute,
|
|
113
86
|
};
|
|
114
87
|
});
|
|
88
|
+
|
|
89
|
+
return segments;
|
|
115
90
|
}
|
|
116
91
|
|
|
117
92
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
118
93
|
// settled promises so handlers don't stream loading placeholders.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
94
|
+
//
|
|
95
|
+
// Wrap each loader promise with wrapLoaderPromise BEFORE awaiting. The wrapped
|
|
96
|
+
// promise resolves to a LoaderDataResult and never rejects, routing a failed
|
|
97
|
+
// loader to its own per-loader error boundary. Awaiting the RAW promises here
|
|
98
|
+
// instead would (1) propagate a rejection to the segment-level boundary,
|
|
99
|
+
// collapsing the whole entry and discarding successful sibling data, and
|
|
100
|
+
// (2) leave the other in-flight raw promises without a .catch, producing
|
|
101
|
+
// unhandled rejections. Mirrors the loading path and intercept-resolution.
|
|
102
|
+
const pendingLoaderData = loaderEntries.map((loaderEntry, i) => {
|
|
103
|
+
const { loader } = loaderEntry;
|
|
104
|
+
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
105
|
+
const start = performance.now();
|
|
106
|
+
const wrapped = deps.wrapLoaderPromise(
|
|
107
|
+
runInsideLoaderScope(() =>
|
|
108
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
109
|
+
),
|
|
110
|
+
entry,
|
|
111
|
+
segmentId,
|
|
112
|
+
ctx.pathname,
|
|
113
|
+
);
|
|
114
|
+
return { wrapped, start, segmentId, loaderId: loader.$$id };
|
|
115
|
+
});
|
|
116
|
+
await Promise.all(pendingLoaderData.map((p) => p.wrapped));
|
|
123
117
|
|
|
124
118
|
return loaderEntries.map((loaderEntry, i) => {
|
|
125
119
|
const { loader } = loaderEntry;
|
|
126
|
-
const
|
|
120
|
+
const pending = pendingLoaderData[i]!;
|
|
121
|
+
if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
|
|
122
|
+
// All loaders ran in parallel via Promise.all — each span covers
|
|
123
|
+
// from its own kickoff to the batch settlement, giving a ceiling
|
|
124
|
+
// on that loader's contribution to the overall wait.
|
|
125
|
+
const batchEnd = performance.now();
|
|
126
|
+
appendMetric(
|
|
127
|
+
ms,
|
|
128
|
+
`loader:${loader.$$id}`,
|
|
129
|
+
pending.start,
|
|
130
|
+
batchEnd - pending.start,
|
|
131
|
+
2,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
127
134
|
return {
|
|
128
|
-
id: segmentId,
|
|
135
|
+
id: pending.segmentId,
|
|
129
136
|
namespace: entry.id,
|
|
130
137
|
type: "loader" as const,
|
|
131
138
|
index: i,
|
|
132
139
|
component: null,
|
|
133
140
|
params: ctx.params,
|
|
134
141
|
loaderId: loader.$$id,
|
|
135
|
-
loaderData:
|
|
136
|
-
pendingLoaderData[i]!,
|
|
137
|
-
entry,
|
|
138
|
-
segmentId,
|
|
139
|
-
ctx.pathname,
|
|
140
|
-
),
|
|
142
|
+
loaderData: pending.wrapped,
|
|
141
143
|
belongsToRoute,
|
|
142
144
|
};
|
|
143
145
|
});
|
|
@@ -190,14 +192,20 @@ export async function resolveSegment<TEnv>(
|
|
|
190
192
|
index: 0,
|
|
191
193
|
component,
|
|
192
194
|
loading: entry.loading === false ? null : entry.loading,
|
|
193
|
-
transition:
|
|
195
|
+
transition: applyViewTransitionDefault(
|
|
196
|
+
entry.transition,
|
|
197
|
+
deps.viewTransitionDefault,
|
|
198
|
+
),
|
|
194
199
|
params,
|
|
195
200
|
belongsToRoute: false,
|
|
196
201
|
layoutName: entry.id,
|
|
197
202
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
198
203
|
});
|
|
199
204
|
|
|
200
|
-
|
|
205
|
+
const resolvedParallelEntries = new Set<string>();
|
|
206
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
207
|
+
entry.parallel,
|
|
208
|
+
)) {
|
|
201
209
|
const parallelSegments = await resolveParallelEntry(
|
|
202
210
|
parallelEntry,
|
|
203
211
|
params,
|
|
@@ -207,8 +215,11 @@ export async function resolveSegment<TEnv>(
|
|
|
207
215
|
deps,
|
|
208
216
|
options,
|
|
209
217
|
routeKey,
|
|
218
|
+
[slot],
|
|
219
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
210
220
|
);
|
|
211
221
|
segments.push(...parallelSegments);
|
|
222
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
212
223
|
}
|
|
213
224
|
|
|
214
225
|
for (const orphan of entry.layout) {
|
|
@@ -244,10 +255,16 @@ export async function resolveSegment<TEnv>(
|
|
|
244
255
|
entry.shortCode,
|
|
245
256
|
);
|
|
246
257
|
if (component === undefined) {
|
|
258
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
259
|
+
// the build handler. At build time (context.build === true), always
|
|
260
|
+
// use the build handler from entry.handler.
|
|
261
|
+
const handler =
|
|
262
|
+
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
247
263
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
248
264
|
if (entry.loading) {
|
|
249
|
-
const result = handleHandlerResult(
|
|
265
|
+
const result = handleHandlerResult(handler(context));
|
|
250
266
|
if (result instanceof Promise) {
|
|
267
|
+
warnOnStreamedResponse(result, entry.id);
|
|
251
268
|
result.finally(doneRouteHandler).catch(() => {});
|
|
252
269
|
const tracked = deps.trackHandler(result, {
|
|
253
270
|
segmentId: entry.shortCode,
|
|
@@ -267,7 +284,7 @@ export async function resolveSegment<TEnv>(
|
|
|
267
284
|
component = result;
|
|
268
285
|
}
|
|
269
286
|
} else {
|
|
270
|
-
component = handleHandlerResult(await
|
|
287
|
+
component = handleHandlerResult(await handler(context));
|
|
271
288
|
doneRouteHandler();
|
|
272
289
|
}
|
|
273
290
|
}
|
|
@@ -282,11 +299,15 @@ export async function resolveSegment<TEnv>(
|
|
|
282
299
|
deps,
|
|
283
300
|
options,
|
|
284
301
|
routeKey,
|
|
302
|
+
entry,
|
|
285
303
|
);
|
|
286
304
|
segments.push(...orphanSegments);
|
|
287
305
|
}
|
|
288
306
|
|
|
289
|
-
|
|
307
|
+
const resolvedParallelEntries = new Set<string>();
|
|
308
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
309
|
+
entry.parallel,
|
|
310
|
+
)) {
|
|
290
311
|
const parallelSegments = await resolveParallelEntry(
|
|
291
312
|
parallelEntry,
|
|
292
313
|
params,
|
|
@@ -296,8 +317,11 @@ export async function resolveSegment<TEnv>(
|
|
|
296
317
|
deps,
|
|
297
318
|
options,
|
|
298
319
|
routeKey,
|
|
320
|
+
[slot],
|
|
321
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
299
322
|
);
|
|
300
323
|
segments.push(...parallelSegments);
|
|
324
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
301
325
|
}
|
|
302
326
|
|
|
303
327
|
segments.push({
|
|
@@ -305,9 +329,12 @@ export async function resolveSegment<TEnv>(
|
|
|
305
329
|
namespace: entry.id,
|
|
306
330
|
type: "route",
|
|
307
331
|
index: 0,
|
|
308
|
-
component,
|
|
332
|
+
component: component ?? null,
|
|
309
333
|
loading: entry.loading === false ? null : entry.loading,
|
|
310
|
-
transition:
|
|
334
|
+
transition: applyViewTransitionDefault(
|
|
335
|
+
entry.transition,
|
|
336
|
+
deps.viewTransitionDefault,
|
|
337
|
+
),
|
|
311
338
|
params,
|
|
312
339
|
belongsToRoute: true,
|
|
313
340
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
@@ -331,6 +358,9 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
331
358
|
deps: SegmentResolutionDeps<TEnv>,
|
|
332
359
|
options?: ResolveSegmentOptions,
|
|
333
360
|
routeKey?: string,
|
|
361
|
+
/** Parent route entry — its loaders are inherited by the layout so
|
|
362
|
+
* parallel slots inside this layout can access them via useLoader(). */
|
|
363
|
+
parentRouteEntry?: EntryData,
|
|
334
364
|
): Promise<ResolvedSegment[]> {
|
|
335
365
|
invariant(
|
|
336
366
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -346,6 +376,30 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
346
376
|
deps,
|
|
347
377
|
);
|
|
348
378
|
segments.push(...loaderSegments);
|
|
379
|
+
|
|
380
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
381
|
+
// can access them via useLoader(). Without this, the route's loaders
|
|
382
|
+
// are only in the route's OutletProvider (rendered as <Outlet /> content),
|
|
383
|
+
// which is a child — not a parent — of the layout's context.
|
|
384
|
+
if (
|
|
385
|
+
parentRouteEntry &&
|
|
386
|
+
parentRouteEntry.loader &&
|
|
387
|
+
parentRouteEntry.loader.length > 0 &&
|
|
388
|
+
Object.keys(orphan.parallel).length > 0
|
|
389
|
+
) {
|
|
390
|
+
const inheritedLoaders = await resolveLoaders(
|
|
391
|
+
parentRouteEntry,
|
|
392
|
+
context,
|
|
393
|
+
belongsToRoute,
|
|
394
|
+
deps,
|
|
395
|
+
orphan.shortCode,
|
|
396
|
+
);
|
|
397
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
398
|
+
for (const s of inheritedLoaders) {
|
|
399
|
+
s._inherited = true;
|
|
400
|
+
}
|
|
401
|
+
segments.push(...inheritedLoaders);
|
|
402
|
+
}
|
|
349
403
|
}
|
|
350
404
|
|
|
351
405
|
// Handler-first: orphan layout handler executes before its parallels
|
|
@@ -364,11 +418,17 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
364
418
|
belongsToRoute,
|
|
365
419
|
layoutName: orphan.id,
|
|
366
420
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
367
|
-
transition:
|
|
421
|
+
transition: applyViewTransitionDefault(
|
|
422
|
+
orphan.transition,
|
|
423
|
+
deps.viewTransitionDefault,
|
|
424
|
+
),
|
|
368
425
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
369
426
|
});
|
|
370
427
|
|
|
371
|
-
|
|
428
|
+
const resolvedParallelEntries = new Set<string>();
|
|
429
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
430
|
+
orphan.parallel,
|
|
431
|
+
)) {
|
|
372
432
|
const parallelSegments = await resolveParallelEntry(
|
|
373
433
|
parallelEntry,
|
|
374
434
|
params,
|
|
@@ -378,8 +438,11 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
378
438
|
deps,
|
|
379
439
|
options,
|
|
380
440
|
routeKey,
|
|
441
|
+
[slot],
|
|
442
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
381
443
|
);
|
|
382
444
|
segments.push(...parallelSegments);
|
|
445
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
383
446
|
}
|
|
384
447
|
|
|
385
448
|
return segments;
|
|
@@ -397,6 +460,8 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
397
460
|
deps: SegmentResolutionDeps<TEnv>,
|
|
398
461
|
options?: ResolveSegmentOptions,
|
|
399
462
|
routeKey?: string,
|
|
463
|
+
slotNames?: `@${string}`[],
|
|
464
|
+
includeLoaders: boolean = true,
|
|
400
465
|
): Promise<ResolvedSegment[]> {
|
|
401
466
|
invariant(
|
|
402
467
|
parallelEntry.type === "parallel",
|
|
@@ -411,7 +476,12 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
411
476
|
| ReactNode
|
|
412
477
|
>;
|
|
413
478
|
|
|
414
|
-
|
|
479
|
+
const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
|
|
480
|
+
|
|
481
|
+
for (const slot of slotsToResolve) {
|
|
482
|
+
// Try static lookup first — in production, handler bodies are evicted
|
|
483
|
+
// and replaced with stubs that have no .handler property (undefined).
|
|
484
|
+
// The static store holds the pre-rendered component for these slots.
|
|
415
485
|
let component: ReactNode | undefined = await tryStaticSlot(
|
|
416
486
|
parallelEntry,
|
|
417
487
|
slot,
|
|
@@ -419,6 +489,18 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
419
489
|
);
|
|
420
490
|
|
|
421
491
|
if (component === undefined) {
|
|
492
|
+
const handler = slots[slot];
|
|
493
|
+
if (handler === undefined) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
// Pin `_currentSegmentId` to the slot's own id so handle pushes from
|
|
497
|
+
// inside the slot handler get their own bucket in the HandleStore.
|
|
498
|
+
// Parent-keying would collapse them into the parent layout's bucket;
|
|
499
|
+
// the partial-update merge then replaces the parent's bucket on a
|
|
500
|
+
// slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
|
|
501
|
+
// filterSegmentOrder() retains slot ids so the client preserves them.
|
|
502
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
503
|
+
`${parentShortCode}.${slot}`;
|
|
422
504
|
const doneParallelHandler = track(
|
|
423
505
|
`handler:${parallelEntry.id}.${slot}`,
|
|
424
506
|
2,
|
|
@@ -461,7 +543,10 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
461
543
|
index: 0,
|
|
462
544
|
component,
|
|
463
545
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
464
|
-
transition:
|
|
546
|
+
transition: applyViewTransitionDefault(
|
|
547
|
+
parallelEntry.transition,
|
|
548
|
+
deps.viewTransitionDefault,
|
|
549
|
+
),
|
|
465
550
|
params,
|
|
466
551
|
slot,
|
|
467
552
|
belongsToRoute,
|
|
@@ -472,7 +557,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
472
557
|
});
|
|
473
558
|
}
|
|
474
559
|
|
|
475
|
-
if (!
|
|
560
|
+
if (!options?.skipLoaders && includeLoaders) {
|
|
476
561
|
const loaderSegments = await resolveLoaders(
|
|
477
562
|
parallelEntry,
|
|
478
563
|
context,
|
|
@@ -480,6 +565,15 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
480
565
|
deps,
|
|
481
566
|
parentShortCode,
|
|
482
567
|
);
|
|
568
|
+
// Tag parallel-owned loaders so renderSegments can stream them
|
|
569
|
+
// using the parallel's loading() instead of awaiting on the layout
|
|
570
|
+
const parallelLoading =
|
|
571
|
+
parallelEntry.loading === false ? undefined : parallelEntry.loading;
|
|
572
|
+
if (parallelLoading) {
|
|
573
|
+
for (const seg of loaderSegments) {
|
|
574
|
+
seg.parallelLoading = parallelLoading;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
483
577
|
segments.push(...loaderSegments);
|
|
484
578
|
}
|
|
485
579
|
|
|
@@ -515,6 +609,13 @@ export async function resolveAllSegments<TEnv>(
|
|
|
515
609
|
} catch {}
|
|
516
610
|
|
|
517
611
|
for (const entry of entries) {
|
|
612
|
+
// Set ALS flag when entering a cache() boundary so that ctx.get()
|
|
613
|
+
// can guard non-cacheable variable reads. Also guards response-level
|
|
614
|
+
// side effects (headers.set). Persists for all descendant entries.
|
|
615
|
+
if (entry.type === "cache") {
|
|
616
|
+
const store = RangoContext.getStore();
|
|
617
|
+
if (store) store.insideCacheScope = true;
|
|
618
|
+
}
|
|
518
619
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
519
620
|
const resolvedSegments = await resolveWithErrorBoundary(
|
|
520
621
|
entry,
|
|
@@ -559,11 +660,77 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
559
660
|
deps: SegmentResolutionDeps<TEnv>,
|
|
560
661
|
): Promise<ResolvedSegment[]> {
|
|
561
662
|
const loaderSegments: ResolvedSegment[] = [];
|
|
663
|
+
const seenIds = new Set<string>();
|
|
664
|
+
|
|
665
|
+
async function collectEntryLoaders(
|
|
666
|
+
entry: EntryData,
|
|
667
|
+
belongsToRoute: boolean,
|
|
668
|
+
shortCodeOverride?: string,
|
|
669
|
+
): Promise<void> {
|
|
670
|
+
// Skip if all loaders from this entry have already been resolved
|
|
671
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
672
|
+
const entryLoaders = entry.loader ?? [];
|
|
673
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
674
|
+
const allAlreadySeen =
|
|
675
|
+
entryLoaders.length > 0 &&
|
|
676
|
+
entryLoaders.every((le, i) =>
|
|
677
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
678
|
+
);
|
|
679
|
+
if (!allAlreadySeen) {
|
|
680
|
+
const segments = await resolveLoaders(
|
|
681
|
+
entry,
|
|
682
|
+
context,
|
|
683
|
+
belongsToRoute,
|
|
684
|
+
deps,
|
|
685
|
+
shortCodeOverride,
|
|
686
|
+
);
|
|
687
|
+
for (const seg of segments) {
|
|
688
|
+
if (!seenIds.has(seg.id)) {
|
|
689
|
+
seenIds.add(seg.id);
|
|
690
|
+
loaderSegments.push(seg);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const seenParallelEntryIds = new Set<string>();
|
|
696
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
697
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
698
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
699
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
703
|
+
for (const layoutEntry of entry.layout) {
|
|
704
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
705
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
706
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
707
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
708
|
+
if (
|
|
709
|
+
entry.type === "route" &&
|
|
710
|
+
entry.loader &&
|
|
711
|
+
entry.loader.length > 0 &&
|
|
712
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
713
|
+
) {
|
|
714
|
+
const inherited = await resolveLoaders(
|
|
715
|
+
entry,
|
|
716
|
+
context,
|
|
717
|
+
childBelongsToRoute,
|
|
718
|
+
deps,
|
|
719
|
+
layoutEntry.shortCode,
|
|
720
|
+
);
|
|
721
|
+
for (const seg of inherited) {
|
|
722
|
+
if (!seenIds.has(seg.id)) {
|
|
723
|
+
seenIds.add(seg.id);
|
|
724
|
+
seg._inherited = true;
|
|
725
|
+
loaderSegments.push(seg);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
562
731
|
|
|
563
732
|
for (const entry of entries) {
|
|
564
|
-
|
|
565
|
-
const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
|
|
566
|
-
loaderSegments.push(...segments);
|
|
733
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
567
734
|
}
|
|
568
735
|
|
|
569
736
|
return loaderSegments;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Error boundary segment creation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type
|
|
11
|
+
import { createElement, type ReactNode } from "react";
|
|
12
12
|
import { DataNotFoundError } from "../../errors";
|
|
13
13
|
import {
|
|
14
14
|
createErrorInfo,
|
|
@@ -52,6 +52,40 @@ export function handleHandlerResult(
|
|
|
52
52
|
return result;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Dev-only: warn when a handler on a route that declares loading() resolves or
|
|
57
|
+
* rejects with a Response (e.g. redirect()).
|
|
58
|
+
*
|
|
59
|
+
* On a non-loading route a returned/thrown Response short-circuits to an HTTP
|
|
60
|
+
* redirect. But when the route declares loading(), the handler result is
|
|
61
|
+
* streamed (not awaited at the resolution boundary), so the Response surfaces
|
|
62
|
+
* only during RSC serialization and is rendered into the stream instead of
|
|
63
|
+
* becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
|
|
64
|
+
* a loader, or a synchronous handler return instead. Compiled out in production.
|
|
65
|
+
*/
|
|
66
|
+
export function warnOnStreamedResponse(
|
|
67
|
+
result: Promise<unknown>,
|
|
68
|
+
entryId: string,
|
|
69
|
+
): void {
|
|
70
|
+
if (process.env.NODE_ENV === "production") return;
|
|
71
|
+
// A Response can surface either as a rejection (handleHandlerResult rethrows a
|
|
72
|
+
// resolved Response) or as a resolved value (the raw parallel-slot handler is
|
|
73
|
+
// not run through handleHandlerResult). Check both so every streamed path is
|
|
74
|
+
// covered. Each handler is an independent observer; it does not consume the
|
|
75
|
+
// rejection for the trackHandler/observeStreamedHandler chains.
|
|
76
|
+
const check = (value: unknown) => {
|
|
77
|
+
if (value instanceof Response) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[rango] Handler for "${entryId}" returned a Response (e.g. ` +
|
|
80
|
+
`redirect()), but it declares loading(): the Response is rendered ` +
|
|
81
|
+
`into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
|
|
82
|
+
`from middleware, a loader, or a synchronous handler return.`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
result.then(check, check);
|
|
87
|
+
}
|
|
88
|
+
|
|
55
89
|
// ---------------------------------------------------------------------------
|
|
56
90
|
// Static handler interception
|
|
57
91
|
// ---------------------------------------------------------------------------
|
|
@@ -180,34 +214,39 @@ export function catchSegmentError<TEnv>(
|
|
|
180
214
|
|
|
181
215
|
if (error instanceof DataNotFoundError) {
|
|
182
216
|
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
217
|
+
// Fall back to router's notFound component, then a plain default
|
|
218
|
+
const notFoundOption = deps.notFoundComponent;
|
|
219
|
+
const defaultFallback =
|
|
220
|
+
typeof notFoundOption === "function"
|
|
221
|
+
? notFoundOption({ pathname: pathname ?? "" })
|
|
222
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
223
|
+
const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
|
|
183
224
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
225
|
+
const notFoundInfo = createNotFoundInfo(
|
|
226
|
+
error,
|
|
227
|
+
entry.shortCode,
|
|
228
|
+
entry.type,
|
|
229
|
+
pathname,
|
|
230
|
+
);
|
|
191
231
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
232
|
+
reportError(true, {
|
|
233
|
+
notFound: true,
|
|
234
|
+
message: notFoundInfo.message,
|
|
235
|
+
});
|
|
196
236
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
237
|
+
debugLog("segment", "notFound boundary handled error", {
|
|
238
|
+
segmentId: entry.shortCode,
|
|
239
|
+
message: notFoundInfo.message,
|
|
240
|
+
});
|
|
201
241
|
|
|
202
|
-
|
|
242
|
+
setResponseStatus(404);
|
|
203
243
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
244
|
+
return createNotFoundSegment(
|
|
245
|
+
notFoundInfo,
|
|
246
|
+
effectiveNotFoundFallback,
|
|
247
|
+
entry,
|
|
248
|
+
params,
|
|
249
|
+
);
|
|
211
250
|
}
|
|
212
251
|
|
|
213
252
|
const fallback = deps.findNearestErrorBoundary(entry);
|