@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
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/README.md +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -12
- package/skills/migrate-nextjs/SKILL.md +562 -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 +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +23 -64
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- 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 +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- 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-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +12 -5
- package/src/index.ts +61 -11
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -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 +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -51
- 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 +188 -93
- 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 +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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 +214 -0
- package/src/vite/plugins/expose-action-id.ts +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import type { NonceProvider } from "../rsc/types.js";
|
|
9
9
|
import type { ExecutionContext } from "../server/request-context.js";
|
|
10
10
|
import type { UrlPatterns } from "../urls.js";
|
|
11
|
+
import type { UrlBuilder } from "../urls/pattern-types.js";
|
|
11
12
|
import type { NamedRouteEntry } from "./content-negotiation.js";
|
|
12
13
|
import type { TelemetrySink } from "./telemetry.js";
|
|
13
14
|
import type { RouterTimeouts, OnTimeoutCallback } from "./timeout.js";
|
|
@@ -72,7 +73,7 @@ export interface RootLayoutProps {
|
|
|
72
73
|
/**
|
|
73
74
|
* Router configuration options
|
|
74
75
|
*/
|
|
75
|
-
export interface
|
|
76
|
+
export interface RangoOptions<TEnv = any> {
|
|
76
77
|
/**
|
|
77
78
|
* Unique identifier for this router instance.
|
|
78
79
|
* Used to namespace static output files and route maps.
|
|
@@ -95,6 +96,28 @@ export interface RSCRouterOptions<TEnv = any> {
|
|
|
95
96
|
*/
|
|
96
97
|
$$sourceFile?: string;
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* URL prefix applied to all routes registered with this router.
|
|
101
|
+
*
|
|
102
|
+
* Useful when the app is served under a sub-path (e.g. `/admin` or `/v2`).
|
|
103
|
+
* All `path()` patterns are automatically prefixed and `reverse()` returns
|
|
104
|
+
* full paths including the basename. Route names are NOT prefixed.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const router = createRouter({
|
|
109
|
+
* basename: "/admin",
|
|
110
|
+
* }).routes(({ path }) => [
|
|
111
|
+
* path("/", Dashboard, { name: "home" }), // matches /admin
|
|
112
|
+
* path("/users", Users, { name: "users" }), // matches /admin/users
|
|
113
|
+
* ]);
|
|
114
|
+
*
|
|
115
|
+
* router.reverse("home"); // "/admin"
|
|
116
|
+
* router.reverse("users"); // "/admin/users"
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
basename?: string;
|
|
120
|
+
|
|
98
121
|
/**
|
|
99
122
|
* Enable performance metrics collection
|
|
100
123
|
* When enabled, metrics are output to console and available via Server-Timing header
|
|
@@ -109,6 +132,21 @@ export interface RSCRouterOptions<TEnv = any> {
|
|
|
109
132
|
*/
|
|
110
133
|
allowDebugManifest?: boolean;
|
|
111
134
|
|
|
135
|
+
/**
|
|
136
|
+
* DEVELOPMENT/TEST ONLY. Emit an `X-Rango-Cache` response header describing
|
|
137
|
+
* the cache status of the matched route, for use by testing primitives such
|
|
138
|
+
* as `assertCacheStatus`.
|
|
139
|
+
*
|
|
140
|
+
* Defaults to `false`. When neither this option nor the
|
|
141
|
+
* `RANGO_TEST_SIGNALS=1` environment flag is set, NO header is emitted and
|
|
142
|
+
* router output is byte-identical to the default.
|
|
143
|
+
*
|
|
144
|
+
* The header encodes per-segment (v1: coarse route-level) status keyed by the
|
|
145
|
+
* route NAME, e.g. `X-Rango-Cache: product.detail=hit`. Do NOT enable in
|
|
146
|
+
* production — it exposes internal cache decisions.
|
|
147
|
+
*/
|
|
148
|
+
debugCacheSignal?: boolean;
|
|
149
|
+
|
|
112
150
|
/**
|
|
113
151
|
* Document component that wraps the entire application.
|
|
114
152
|
*
|
|
@@ -335,27 +373,54 @@ export interface RSCRouterOptions<TEnv = any> {
|
|
|
335
373
|
theme?: import("../theme/types.js").ThemeConfig | true;
|
|
336
374
|
|
|
337
375
|
/**
|
|
338
|
-
*
|
|
376
|
+
* Default for whether the router wraps `transition()` segments in its own
|
|
377
|
+
* React `<ViewTransition>` boundary (experimental React only).
|
|
339
378
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
379
|
+
* - "auto" (default): every route/layout that opts in via `transition()`
|
|
380
|
+
* gets a router-owned cross-fade.
|
|
381
|
+
* - false: the router never places its own boundary. Routes that use
|
|
382
|
+
* `transition()` still drive navigation through startTransition (so loaders
|
|
383
|
+
* hold instead of flashing a skeleton) and still let consumer-placed
|
|
384
|
+
* `<ViewTransition>` elements animate — the router just contributes no
|
|
385
|
+
* cross-fade of its own. This is the "router triggers, you place the
|
|
386
|
+
* transitions" model.
|
|
387
|
+
*
|
|
388
|
+
* A per-segment `transition({ viewTransition })` overrides this default.
|
|
342
389
|
*
|
|
343
390
|
* @example
|
|
344
391
|
* ```typescript
|
|
345
|
-
*
|
|
392
|
+
* // App-wide: drive + hold, but never auto-wrap. Place <ViewTransition>
|
|
393
|
+
* // yourself in components where you want a morph.
|
|
394
|
+
* const router = createRouter<AppEnv>({ viewTransition: false });
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
viewTransition?: "auto" | false;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* URL patterns to register with the router.
|
|
346
401
|
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* path("/about", AboutPage, { name: "about" }),
|
|
350
|
-
* ]);
|
|
402
|
+
* Accepts either a `UrlPatterns` object from `urls()` or a builder function
|
|
403
|
+
* directly (urls() is called implicitly).
|
|
351
404
|
*
|
|
352
|
-
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* // With urls()
|
|
408
|
+
* createRouter<AppEnv>({
|
|
353
409
|
* document: Document,
|
|
354
410
|
* urls: urlpatterns,
|
|
355
411
|
* });
|
|
412
|
+
*
|
|
413
|
+
* // With builder function
|
|
414
|
+
* createRouter<AppEnv>({
|
|
415
|
+
* document: Document,
|
|
416
|
+
* urls: ({ path }) => [
|
|
417
|
+
* path("/", HomePage, { name: "home" }),
|
|
418
|
+
* path("/about", AboutPage, { name: "about" }),
|
|
419
|
+
* ],
|
|
420
|
+
* });
|
|
356
421
|
* ```
|
|
357
422
|
*/
|
|
358
|
-
urls?: UrlPatterns<TEnv, any>;
|
|
423
|
+
urls?: UrlPatterns<TEnv, any> | UrlBuilder<TEnv>;
|
|
359
424
|
|
|
360
425
|
/**
|
|
361
426
|
* Injected by the Vite transform at compile time.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RangoInternal } from "./router-interfaces.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Brand marker for identifying router instances at build time.
|
|
@@ -12,10 +12,7 @@ export const RSC_ROUTER_BRAND = "__rsc_router__" as const;
|
|
|
12
12
|
* Used by the Vite plugin at build time to discover routers and extract
|
|
13
13
|
* manifests, prefix trees, and pre-render candidates.
|
|
14
14
|
*/
|
|
15
|
-
export const RouterRegistry: Map<
|
|
16
|
-
string,
|
|
17
|
-
RSCRouterInternal<any, any>
|
|
18
|
-
> = new Map();
|
|
15
|
+
export const RouterRegistry: Map<string, RangoInternal<any, any>> = new Map();
|
|
19
16
|
|
|
20
17
|
export let routerAutoId = 0;
|
|
21
18
|
|
|
@@ -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,6 +19,8 @@ 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,
|
|
@@ -22,9 +28,14 @@ import {
|
|
|
22
28
|
resolveLayoutComponent,
|
|
23
29
|
resolveWithErrorBoundary,
|
|
24
30
|
} from "./helpers.js";
|
|
31
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
25
32
|
import { getRouterContext } from "../router-context.js";
|
|
26
33
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
27
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
track,
|
|
36
|
+
RangoContext,
|
|
37
|
+
runInsideLoaderScope,
|
|
38
|
+
} from "../../server/context.js";
|
|
28
39
|
|
|
29
40
|
// ---------------------------------------------------------------------------
|
|
30
41
|
// Streamed handler telemetry
|
|
@@ -90,9 +101,11 @@ export async function resolveLoaders<TEnv>(
|
|
|
90
101
|
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
91
102
|
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
92
103
|
const loadingDisabled = hasLoading && entry.loading === false;
|
|
104
|
+
const ms = _getRequestContext()?._metricsStore;
|
|
93
105
|
|
|
94
106
|
if (!loadingDisabled) {
|
|
95
|
-
|
|
107
|
+
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
108
|
+
const segments = loaderEntries.map((loaderEntry, i) => {
|
|
96
109
|
const { loader } = loaderEntry;
|
|
97
110
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
98
111
|
return {
|
|
@@ -104,7 +117,9 @@ export async function resolveLoaders<TEnv>(
|
|
|
104
117
|
params: ctx.params,
|
|
105
118
|
loaderId: loader.$$id,
|
|
106
119
|
loaderData: deps.wrapLoaderPromise(
|
|
107
|
-
|
|
120
|
+
runInsideLoaderScope(() =>
|
|
121
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
122
|
+
),
|
|
108
123
|
entry,
|
|
109
124
|
segmentId,
|
|
110
125
|
ctx.pathname,
|
|
@@ -112,18 +127,38 @@ export async function resolveLoaders<TEnv>(
|
|
|
112
127
|
belongsToRoute,
|
|
113
128
|
};
|
|
114
129
|
});
|
|
130
|
+
|
|
131
|
+
return segments;
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
118
135
|
// settled promises so handlers don't stream loading placeholders.
|
|
119
|
-
const pendingLoaderData = loaderEntries.map((loaderEntry) =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
const pendingLoaderData = loaderEntries.map((loaderEntry) => {
|
|
137
|
+
const start = performance.now();
|
|
138
|
+
const promise = runInsideLoaderScope(() =>
|
|
139
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
140
|
+
);
|
|
141
|
+
return { promise, start, loaderId: loaderEntry.loader.$$id };
|
|
142
|
+
});
|
|
143
|
+
await Promise.all(pendingLoaderData.map((p) => p.promise));
|
|
123
144
|
|
|
124
145
|
return loaderEntries.map((loaderEntry, i) => {
|
|
125
146
|
const { loader } = loaderEntry;
|
|
126
147
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
148
|
+
const pending = pendingLoaderData[i]!;
|
|
149
|
+
if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
|
|
150
|
+
// All loaders ran in parallel via Promise.all — each span covers
|
|
151
|
+
// from its own kickoff to the batch settlement, giving a ceiling
|
|
152
|
+
// on that loader's contribution to the overall wait.
|
|
153
|
+
const batchEnd = performance.now();
|
|
154
|
+
appendMetric(
|
|
155
|
+
ms,
|
|
156
|
+
`loader:${loader.$$id}`,
|
|
157
|
+
pending.start,
|
|
158
|
+
batchEnd - pending.start,
|
|
159
|
+
2,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
127
162
|
return {
|
|
128
163
|
id: segmentId,
|
|
129
164
|
namespace: entry.id,
|
|
@@ -133,7 +168,7 @@ export async function resolveLoaders<TEnv>(
|
|
|
133
168
|
params: ctx.params,
|
|
134
169
|
loaderId: loader.$$id,
|
|
135
170
|
loaderData: deps.wrapLoaderPromise(
|
|
136
|
-
|
|
171
|
+
pending.promise,
|
|
137
172
|
entry,
|
|
138
173
|
segmentId,
|
|
139
174
|
ctx.pathname,
|
|
@@ -190,14 +225,20 @@ export async function resolveSegment<TEnv>(
|
|
|
190
225
|
index: 0,
|
|
191
226
|
component,
|
|
192
227
|
loading: entry.loading === false ? null : entry.loading,
|
|
193
|
-
transition:
|
|
228
|
+
transition: applyViewTransitionDefault(
|
|
229
|
+
entry.transition,
|
|
230
|
+
deps.viewTransitionDefault,
|
|
231
|
+
),
|
|
194
232
|
params,
|
|
195
233
|
belongsToRoute: false,
|
|
196
234
|
layoutName: entry.id,
|
|
197
235
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
198
236
|
});
|
|
199
237
|
|
|
200
|
-
|
|
238
|
+
const resolvedParallelEntries = new Set<string>();
|
|
239
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
240
|
+
entry.parallel,
|
|
241
|
+
)) {
|
|
201
242
|
const parallelSegments = await resolveParallelEntry(
|
|
202
243
|
parallelEntry,
|
|
203
244
|
params,
|
|
@@ -207,8 +248,11 @@ export async function resolveSegment<TEnv>(
|
|
|
207
248
|
deps,
|
|
208
249
|
options,
|
|
209
250
|
routeKey,
|
|
251
|
+
[slot],
|
|
252
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
210
253
|
);
|
|
211
254
|
segments.push(...parallelSegments);
|
|
255
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
212
256
|
}
|
|
213
257
|
|
|
214
258
|
for (const orphan of entry.layout) {
|
|
@@ -244,9 +288,14 @@ export async function resolveSegment<TEnv>(
|
|
|
244
288
|
entry.shortCode,
|
|
245
289
|
);
|
|
246
290
|
if (component === undefined) {
|
|
291
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
292
|
+
// the build handler. At build time (context.build === true), always
|
|
293
|
+
// use the build handler from entry.handler.
|
|
294
|
+
const handler =
|
|
295
|
+
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
247
296
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
248
297
|
if (entry.loading) {
|
|
249
|
-
const result = handleHandlerResult(
|
|
298
|
+
const result = handleHandlerResult(handler(context));
|
|
250
299
|
if (result instanceof Promise) {
|
|
251
300
|
result.finally(doneRouteHandler).catch(() => {});
|
|
252
301
|
const tracked = deps.trackHandler(result, {
|
|
@@ -267,7 +316,7 @@ export async function resolveSegment<TEnv>(
|
|
|
267
316
|
component = result;
|
|
268
317
|
}
|
|
269
318
|
} else {
|
|
270
|
-
component = handleHandlerResult(await
|
|
319
|
+
component = handleHandlerResult(await handler(context));
|
|
271
320
|
doneRouteHandler();
|
|
272
321
|
}
|
|
273
322
|
}
|
|
@@ -282,11 +331,15 @@ export async function resolveSegment<TEnv>(
|
|
|
282
331
|
deps,
|
|
283
332
|
options,
|
|
284
333
|
routeKey,
|
|
334
|
+
entry,
|
|
285
335
|
);
|
|
286
336
|
segments.push(...orphanSegments);
|
|
287
337
|
}
|
|
288
338
|
|
|
289
|
-
|
|
339
|
+
const resolvedParallelEntries = new Set<string>();
|
|
340
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
341
|
+
entry.parallel,
|
|
342
|
+
)) {
|
|
290
343
|
const parallelSegments = await resolveParallelEntry(
|
|
291
344
|
parallelEntry,
|
|
292
345
|
params,
|
|
@@ -296,8 +349,11 @@ export async function resolveSegment<TEnv>(
|
|
|
296
349
|
deps,
|
|
297
350
|
options,
|
|
298
351
|
routeKey,
|
|
352
|
+
[slot],
|
|
353
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
299
354
|
);
|
|
300
355
|
segments.push(...parallelSegments);
|
|
356
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
301
357
|
}
|
|
302
358
|
|
|
303
359
|
segments.push({
|
|
@@ -305,9 +361,12 @@ export async function resolveSegment<TEnv>(
|
|
|
305
361
|
namespace: entry.id,
|
|
306
362
|
type: "route",
|
|
307
363
|
index: 0,
|
|
308
|
-
component,
|
|
364
|
+
component: component ?? null,
|
|
309
365
|
loading: entry.loading === false ? null : entry.loading,
|
|
310
|
-
transition:
|
|
366
|
+
transition: applyViewTransitionDefault(
|
|
367
|
+
entry.transition,
|
|
368
|
+
deps.viewTransitionDefault,
|
|
369
|
+
),
|
|
311
370
|
params,
|
|
312
371
|
belongsToRoute: true,
|
|
313
372
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
@@ -331,6 +390,9 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
331
390
|
deps: SegmentResolutionDeps<TEnv>,
|
|
332
391
|
options?: ResolveSegmentOptions,
|
|
333
392
|
routeKey?: string,
|
|
393
|
+
/** Parent route entry — its loaders are inherited by the layout so
|
|
394
|
+
* parallel slots inside this layout can access them via useLoader(). */
|
|
395
|
+
parentRouteEntry?: EntryData,
|
|
334
396
|
): Promise<ResolvedSegment[]> {
|
|
335
397
|
invariant(
|
|
336
398
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -346,6 +408,30 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
346
408
|
deps,
|
|
347
409
|
);
|
|
348
410
|
segments.push(...loaderSegments);
|
|
411
|
+
|
|
412
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
413
|
+
// can access them via useLoader(). Without this, the route's loaders
|
|
414
|
+
// are only in the route's OutletProvider (rendered as <Outlet /> content),
|
|
415
|
+
// which is a child — not a parent — of the layout's context.
|
|
416
|
+
if (
|
|
417
|
+
parentRouteEntry &&
|
|
418
|
+
parentRouteEntry.loader &&
|
|
419
|
+
parentRouteEntry.loader.length > 0 &&
|
|
420
|
+
Object.keys(orphan.parallel).length > 0
|
|
421
|
+
) {
|
|
422
|
+
const inheritedLoaders = await resolveLoaders(
|
|
423
|
+
parentRouteEntry,
|
|
424
|
+
context,
|
|
425
|
+
belongsToRoute,
|
|
426
|
+
deps,
|
|
427
|
+
orphan.shortCode,
|
|
428
|
+
);
|
|
429
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
430
|
+
for (const s of inheritedLoaders) {
|
|
431
|
+
s._inherited = true;
|
|
432
|
+
}
|
|
433
|
+
segments.push(...inheritedLoaders);
|
|
434
|
+
}
|
|
349
435
|
}
|
|
350
436
|
|
|
351
437
|
// Handler-first: orphan layout handler executes before its parallels
|
|
@@ -364,11 +450,17 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
364
450
|
belongsToRoute,
|
|
365
451
|
layoutName: orphan.id,
|
|
366
452
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
367
|
-
transition:
|
|
453
|
+
transition: applyViewTransitionDefault(
|
|
454
|
+
orphan.transition,
|
|
455
|
+
deps.viewTransitionDefault,
|
|
456
|
+
),
|
|
368
457
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
369
458
|
});
|
|
370
459
|
|
|
371
|
-
|
|
460
|
+
const resolvedParallelEntries = new Set<string>();
|
|
461
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
462
|
+
orphan.parallel,
|
|
463
|
+
)) {
|
|
372
464
|
const parallelSegments = await resolveParallelEntry(
|
|
373
465
|
parallelEntry,
|
|
374
466
|
params,
|
|
@@ -378,8 +470,11 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
378
470
|
deps,
|
|
379
471
|
options,
|
|
380
472
|
routeKey,
|
|
473
|
+
[slot],
|
|
474
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
381
475
|
);
|
|
382
476
|
segments.push(...parallelSegments);
|
|
477
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
383
478
|
}
|
|
384
479
|
|
|
385
480
|
return segments;
|
|
@@ -397,6 +492,8 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
397
492
|
deps: SegmentResolutionDeps<TEnv>,
|
|
398
493
|
options?: ResolveSegmentOptions,
|
|
399
494
|
routeKey?: string,
|
|
495
|
+
slotNames?: `@${string}`[],
|
|
496
|
+
includeLoaders: boolean = true,
|
|
400
497
|
): Promise<ResolvedSegment[]> {
|
|
401
498
|
invariant(
|
|
402
499
|
parallelEntry.type === "parallel",
|
|
@@ -411,7 +508,12 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
411
508
|
| ReactNode
|
|
412
509
|
>;
|
|
413
510
|
|
|
414
|
-
|
|
511
|
+
const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
|
|
512
|
+
|
|
513
|
+
for (const slot of slotsToResolve) {
|
|
514
|
+
// Try static lookup first — in production, handler bodies are evicted
|
|
515
|
+
// and replaced with stubs that have no .handler property (undefined).
|
|
516
|
+
// The static store holds the pre-rendered component for these slots.
|
|
415
517
|
let component: ReactNode | undefined = await tryStaticSlot(
|
|
416
518
|
parallelEntry,
|
|
417
519
|
slot,
|
|
@@ -419,6 +521,18 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
419
521
|
);
|
|
420
522
|
|
|
421
523
|
if (component === undefined) {
|
|
524
|
+
const handler = slots[slot];
|
|
525
|
+
if (handler === undefined) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
// Pin `_currentSegmentId` to the slot's own id so handle pushes from
|
|
529
|
+
// inside the slot handler get their own bucket in the HandleStore.
|
|
530
|
+
// Parent-keying would collapse them into the parent layout's bucket;
|
|
531
|
+
// the partial-update merge then replaces the parent's bucket on a
|
|
532
|
+
// slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
|
|
533
|
+
// filterSegmentOrder() retains slot ids so the client preserves them.
|
|
534
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
535
|
+
`${parentShortCode}.${slot}`;
|
|
422
536
|
const doneParallelHandler = track(
|
|
423
537
|
`handler:${parallelEntry.id}.${slot}`,
|
|
424
538
|
2,
|
|
@@ -461,7 +575,10 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
461
575
|
index: 0,
|
|
462
576
|
component,
|
|
463
577
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
464
|
-
transition:
|
|
578
|
+
transition: applyViewTransitionDefault(
|
|
579
|
+
parallelEntry.transition,
|
|
580
|
+
deps.viewTransitionDefault,
|
|
581
|
+
),
|
|
465
582
|
params,
|
|
466
583
|
slot,
|
|
467
584
|
belongsToRoute,
|
|
@@ -472,7 +589,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
472
589
|
});
|
|
473
590
|
}
|
|
474
591
|
|
|
475
|
-
if (!
|
|
592
|
+
if (!options?.skipLoaders && includeLoaders) {
|
|
476
593
|
const loaderSegments = await resolveLoaders(
|
|
477
594
|
parallelEntry,
|
|
478
595
|
context,
|
|
@@ -480,6 +597,15 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
480
597
|
deps,
|
|
481
598
|
parentShortCode,
|
|
482
599
|
);
|
|
600
|
+
// Tag parallel-owned loaders so renderSegments can stream them
|
|
601
|
+
// using the parallel's loading() instead of awaiting on the layout
|
|
602
|
+
const parallelLoading =
|
|
603
|
+
parallelEntry.loading === false ? undefined : parallelEntry.loading;
|
|
604
|
+
if (parallelLoading) {
|
|
605
|
+
for (const seg of loaderSegments) {
|
|
606
|
+
seg.parallelLoading = parallelLoading;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
483
609
|
segments.push(...loaderSegments);
|
|
484
610
|
}
|
|
485
611
|
|
|
@@ -515,6 +641,13 @@ export async function resolveAllSegments<TEnv>(
|
|
|
515
641
|
} catch {}
|
|
516
642
|
|
|
517
643
|
for (const entry of entries) {
|
|
644
|
+
// Set ALS flag when entering a cache() boundary so that ctx.get()
|
|
645
|
+
// can guard non-cacheable variable reads. Also guards response-level
|
|
646
|
+
// side effects (headers.set). Persists for all descendant entries.
|
|
647
|
+
if (entry.type === "cache") {
|
|
648
|
+
const store = RangoContext.getStore();
|
|
649
|
+
if (store) store.insideCacheScope = true;
|
|
650
|
+
}
|
|
518
651
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
519
652
|
const resolvedSegments = await resolveWithErrorBoundary(
|
|
520
653
|
entry,
|
|
@@ -559,11 +692,77 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
559
692
|
deps: SegmentResolutionDeps<TEnv>,
|
|
560
693
|
): Promise<ResolvedSegment[]> {
|
|
561
694
|
const loaderSegments: ResolvedSegment[] = [];
|
|
695
|
+
const seenIds = new Set<string>();
|
|
696
|
+
|
|
697
|
+
async function collectEntryLoaders(
|
|
698
|
+
entry: EntryData,
|
|
699
|
+
belongsToRoute: boolean,
|
|
700
|
+
shortCodeOverride?: string,
|
|
701
|
+
): Promise<void> {
|
|
702
|
+
// Skip if all loaders from this entry have already been resolved
|
|
703
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
704
|
+
const entryLoaders = entry.loader ?? [];
|
|
705
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
706
|
+
const allAlreadySeen =
|
|
707
|
+
entryLoaders.length > 0 &&
|
|
708
|
+
entryLoaders.every((le, i) =>
|
|
709
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
710
|
+
);
|
|
711
|
+
if (!allAlreadySeen) {
|
|
712
|
+
const segments = await resolveLoaders(
|
|
713
|
+
entry,
|
|
714
|
+
context,
|
|
715
|
+
belongsToRoute,
|
|
716
|
+
deps,
|
|
717
|
+
shortCodeOverride,
|
|
718
|
+
);
|
|
719
|
+
for (const seg of segments) {
|
|
720
|
+
if (!seenIds.has(seg.id)) {
|
|
721
|
+
seenIds.add(seg.id);
|
|
722
|
+
loaderSegments.push(seg);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const seenParallelEntryIds = new Set<string>();
|
|
728
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
729
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
730
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
731
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
735
|
+
for (const layoutEntry of entry.layout) {
|
|
736
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
737
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
738
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
739
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
740
|
+
if (
|
|
741
|
+
entry.type === "route" &&
|
|
742
|
+
entry.loader &&
|
|
743
|
+
entry.loader.length > 0 &&
|
|
744
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
745
|
+
) {
|
|
746
|
+
const inherited = await resolveLoaders(
|
|
747
|
+
entry,
|
|
748
|
+
context,
|
|
749
|
+
childBelongsToRoute,
|
|
750
|
+
deps,
|
|
751
|
+
layoutEntry.shortCode,
|
|
752
|
+
);
|
|
753
|
+
for (const seg of inherited) {
|
|
754
|
+
if (!seenIds.has(seg.id)) {
|
|
755
|
+
seenIds.add(seg.id);
|
|
756
|
+
seg._inherited = true;
|
|
757
|
+
loaderSegments.push(seg);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
562
763
|
|
|
563
764
|
for (const entry of entries) {
|
|
564
|
-
|
|
565
|
-
const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
|
|
566
|
-
loaderSegments.push(...segments);
|
|
765
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
567
766
|
}
|
|
568
767
|
|
|
569
768
|
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,
|
|
@@ -180,34 +180,39 @@ export function catchSegmentError<TEnv>(
|
|
|
180
180
|
|
|
181
181
|
if (error instanceof DataNotFoundError) {
|
|
182
182
|
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
183
|
+
// Fall back to router's notFound component, then a plain default
|
|
184
|
+
const notFoundOption = deps.notFoundComponent;
|
|
185
|
+
const defaultFallback =
|
|
186
|
+
typeof notFoundOption === "function"
|
|
187
|
+
? notFoundOption({ pathname: pathname ?? "" })
|
|
188
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
189
|
+
const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
|
|
183
190
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
+
const notFoundInfo = createNotFoundInfo(
|
|
192
|
+
error,
|
|
193
|
+
entry.shortCode,
|
|
194
|
+
entry.type,
|
|
195
|
+
pathname,
|
|
196
|
+
);
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
reportError(true, {
|
|
199
|
+
notFound: true,
|
|
200
|
+
message: notFoundInfo.message,
|
|
201
|
+
});
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
debugLog("segment", "notFound boundary handled error", {
|
|
204
|
+
segmentId: entry.shortCode,
|
|
205
|
+
message: notFoundInfo.message,
|
|
206
|
+
});
|
|
201
207
|
|
|
202
|
-
|
|
208
|
+
setResponseStatus(404);
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
210
|
+
return createNotFoundSegment(
|
|
211
|
+
notFoundInfo,
|
|
212
|
+
effectiveNotFoundFallback,
|
|
213
|
+
entry,
|
|
214
|
+
params,
|
|
215
|
+
);
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
const fallback = deps.findNearestErrorBoundary(entry);
|
|
@@ -147,6 +147,7 @@ export function resolveLoaderData<TEnv>(
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const loaderId = loaderEntry.loader.$$id;
|
|
150
|
+
|
|
150
151
|
const ttl = resolveTtl(options.ttl, store.defaults, DEFAULT_ROUTE_TTL);
|
|
151
152
|
const swrWindow = resolveSwrWindow(options.swr, store.defaults);
|
|
152
153
|
const swr = swrWindow || undefined;
|