@rangojs/router 0.0.0-experimental.31 → 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 +121 -205
- 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 +192 -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 +64 -25
- 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 +348 -128
- 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
package/skills/loader/SKILL.md
CHANGED
|
@@ -6,7 +6,10 @@ argument-hint: [loader]
|
|
|
6
6
|
|
|
7
7
|
# Data Loaders with loader()
|
|
8
8
|
|
|
9
|
-
Loaders fetch data on the server and stream it to the client.
|
|
9
|
+
Loaders fetch data on the server and stream it to the client. For mutations
|
|
10
|
+
(writes triggered by forms or buttons), use server actions instead — see
|
|
11
|
+
`/server-actions`. Loaders re-resolve after an action runs, so the typical
|
|
12
|
+
flow is _action mutates → loader re-reads → UI updates_.
|
|
10
13
|
|
|
11
14
|
## Creating a Loader
|
|
12
15
|
|
|
@@ -65,19 +68,10 @@ export const urlpatterns = urls(({ path, loader }) => [
|
|
|
65
68
|
|
|
66
69
|
## Consuming Loader Data
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
import { ProductLoader } from "./loaders/product";
|
|
73
|
-
|
|
74
|
-
async function ProductPage() {
|
|
75
|
-
const { product } = await useLoader(ProductLoader);
|
|
76
|
-
return <h1>{product.name}</h1>;
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### In Client Components
|
|
71
|
+
Register loaders with `loader()` in the DSL and consume them in client
|
|
72
|
+
components with `useLoader()`. This is the recommended pattern — it keeps
|
|
73
|
+
data fetching on the server and consumption on the client, with a clean
|
|
74
|
+
separation that works correctly with `cache()`.
|
|
81
75
|
|
|
82
76
|
```typescript
|
|
83
77
|
"use client";
|
|
@@ -90,9 +84,101 @@ function ProductDetails() {
|
|
|
90
84
|
}
|
|
91
85
|
```
|
|
92
86
|
|
|
87
|
+
```typescript
|
|
88
|
+
// Route definition — loader() registration required
|
|
89
|
+
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
90
|
+
loader(ProductLoader),
|
|
91
|
+
]);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Client refresh `key` vs. server `cache({ key })` vs. `revalidate()`.** Three
|
|
95
|
+
> different "what refreshes" knobs that are easy to confuse:
|
|
96
|
+
>
|
|
97
|
+
> - `useLoader(Loader, { key })` / `useFetchLoader(Loader, { key })` — a
|
|
98
|
+
> **client** refresh identity. It groups which mounted reads of one loader
|
|
99
|
+
> refresh together when one calls `load()`. It never touches the server
|
|
100
|
+
> request. For refreshing **different** loaders together, tag them with
|
|
101
|
+
> `{ refreshGroup }` (one name or several) and call `useRefreshLoaders()(name)`
|
|
102
|
+
> (plain GET only). See the hooks skill ("Scoping refetch with a `key`" and
|
|
103
|
+
> "Refreshing multiple loaders together").
|
|
104
|
+
> - `cache({ key })` — a **server** cache identity (storage hit/miss/ttl/swr).
|
|
105
|
+
> - `revalidate()` — which **server** segments/loaders recompute during
|
|
106
|
+
> navigation and action refreshes.
|
|
107
|
+
|
|
108
|
+
DSL loaders are the **live data layer** — they resolve fresh on every
|
|
109
|
+
request, even when the route is inside a `cache()` boundary. The router
|
|
110
|
+
excludes them from the segment cache at storage time and re-resolves them
|
|
111
|
+
on retrieval. This means `cache()` gives you cached UI + fresh data by
|
|
112
|
+
default.
|
|
113
|
+
|
|
114
|
+
### Cache safety
|
|
115
|
+
|
|
116
|
+
DSL loaders can safely read `createVar({ cache: false })` variables
|
|
117
|
+
because they are always resolved fresh. The read guard is bypassed for
|
|
118
|
+
loader functions — they never produce stale data.
|
|
119
|
+
|
|
120
|
+
### ctx.use(Loader) — escape hatch
|
|
121
|
+
|
|
122
|
+
For cases where you need loader data in the server handler itself (e.g.,
|
|
123
|
+
to set ctx variables or make routing decisions), use `ctx.use(Loader)`:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
path("/product/:slug", async (ctx) => {
|
|
127
|
+
const { product } = await ctx.use(ProductLoader);
|
|
128
|
+
ctx.set(Product, product); // make available to children
|
|
129
|
+
return <ProductPage />;
|
|
130
|
+
}, { name: "product" }, () => [
|
|
131
|
+
loader(ProductLoader), // still register for client consumption
|
|
132
|
+
])
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
When you register with `loader()` in the DSL, `ctx.use()` returns the
|
|
136
|
+
same memoized result — loaders never run twice per request.
|
|
137
|
+
|
|
138
|
+
**Limitations of ctx.use(Loader):**
|
|
139
|
+
|
|
140
|
+
- The handler output depends on the loader data. If the route is inside
|
|
141
|
+
`cache()`, the handler is cached with the loader result baked in —
|
|
142
|
+
defeating the live data guarantee.
|
|
143
|
+
- Non-cacheable variable reads (`createVar({ cache: false })`) inside the
|
|
144
|
+
handler still throw, even if the data came from a loader.
|
|
145
|
+
- Prefer DSL `loader()` + client `useLoader()` for data that depends on
|
|
146
|
+
non-cacheable context variables.
|
|
147
|
+
|
|
148
|
+
**Never use `useLoader()` in server components** — it is a client-only API.
|
|
149
|
+
|
|
150
|
+
### Summary
|
|
151
|
+
|
|
152
|
+
| Pattern | API | Cache-safe | Recommended |
|
|
153
|
+
| ---------------------- | ------------------- | ---------- | ----------- |
|
|
154
|
+
| DSL + client component | `useLoader(Loader)` | Yes | Yes |
|
|
155
|
+
| Handler escape hatch | `ctx.use(Loader)` | No | When needed |
|
|
156
|
+
|
|
93
157
|
## Loader Context
|
|
94
158
|
|
|
95
|
-
Loaders receive the same context as route handlers
|
|
159
|
+
Loaders receive the same context shape as route handlers.
|
|
160
|
+
|
|
161
|
+
### Full field surface
|
|
162
|
+
|
|
163
|
+
| Field | Type | Notes |
|
|
164
|
+
| -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
|
|
166
|
+
| `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
|
|
167
|
+
| `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
|
|
168
|
+
| `url` | `URL` | Parsed request URL. |
|
|
169
|
+
| `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
|
|
170
|
+
| `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
|
|
171
|
+
| `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
|
|
172
|
+
| `env` | `TEnv` | Plain bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). |
|
|
173
|
+
| `get` | `(key \| ContextVar) => value` | Reads variables/context-vars set by middleware. |
|
|
174
|
+
| `use` | `(loader \| handle) => T` | Access another loader's data (Promise) or a handle's collected data (after `await ctx.rendered()`). |
|
|
175
|
+
| `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for all non-loader segments (including `loading()` streaming handlers) to settle before reading handle data. |
|
|
176
|
+
| `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
|
|
177
|
+
| `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
|
|
178
|
+
| `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
|
|
179
|
+
| `reverse` | `ScopedReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers). |
|
|
180
|
+
|
|
181
|
+
### Example
|
|
96
182
|
|
|
97
183
|
```typescript
|
|
98
184
|
export const ProductLoader = createLoader(async (ctx) => {
|
|
@@ -113,13 +199,24 @@ export const ProductLoader = createLoader(async (ctx) => {
|
|
|
113
199
|
// Request headers
|
|
114
200
|
const auth = ctx.request.headers.get("Authorization");
|
|
115
201
|
|
|
116
|
-
// Variables set by middleware (from
|
|
202
|
+
// Variables set by middleware (from Rango.Vars augmentation)
|
|
117
203
|
const user = ctx.get("user");
|
|
118
204
|
|
|
119
|
-
|
|
205
|
+
// Type-checked URLs for payloads. `.name` resolves within the current
|
|
206
|
+
// include() scope; a bare `name` resolves globally. See /route and
|
|
207
|
+
// /typesafety for scope rules and route-name autocomplete.
|
|
208
|
+
const detailUrl = ctx.reverse(".detail", { slug });
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
product: await fetchProduct(slug),
|
|
212
|
+
links: { self: detailUrl },
|
|
213
|
+
};
|
|
120
214
|
});
|
|
121
215
|
```
|
|
122
216
|
|
|
217
|
+
See `/route` for the full handler-context contract (shared with loaders) and
|
|
218
|
+
`/typesafety` for route-name typing that powers `ctx.reverse` autocomplete.
|
|
219
|
+
|
|
123
220
|
### params vs routeParams
|
|
124
221
|
|
|
125
222
|
- `ctx.params` — merged route params + explicit loader params. For fetchable
|
|
@@ -152,6 +249,8 @@ export const OrderLoader = createLoader(async (ctx) => {
|
|
|
152
249
|
Add caching or revalidation to specific loaders:
|
|
153
250
|
|
|
154
251
|
```typescript
|
|
252
|
+
import * as CartActions from "./actions/cart";
|
|
253
|
+
|
|
155
254
|
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
156
255
|
// Cached loader
|
|
157
256
|
loader(ProductLoader, () => [cache({ ttl: 300 })]),
|
|
@@ -161,13 +260,104 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
161
260
|
revalidate(() => false), // Never revalidate
|
|
162
261
|
]),
|
|
163
262
|
|
|
164
|
-
// Loader that revalidates after cart actions
|
|
263
|
+
// Loader that revalidates after cart actions (defer otherwise — keeps the
|
|
264
|
+
// permissive loader defaults for navigation and other actions intact)
|
|
165
265
|
loader(CartLoader, () => [
|
|
166
|
-
revalidate((
|
|
266
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
167
267
|
]),
|
|
168
268
|
]);
|
|
169
269
|
```
|
|
170
270
|
|
|
271
|
+
### `revalidate()` return shapes
|
|
272
|
+
|
|
273
|
+
> **Scope: `revalidate()` is a partial-render concern, not a cache concern.**
|
|
274
|
+
> It decides whether a segment (here, a loader) re-runs and streams to the
|
|
275
|
+
> client on a navigation or action — never whether a cached value is stale. The
|
|
276
|
+
> cache decides hit/miss/ttl/swr independently and never reads `revalidate()`.
|
|
277
|
+
> Caching a loader is a separate, opt-in step (`loader(Fn, () => [cache({...})])`).
|
|
278
|
+
> See `/cache-guide` → "Two axes" and `/rango` → "The shape of rango".
|
|
279
|
+
|
|
280
|
+
A `revalidate(fn)` callback can return one of four shapes. The chain
|
|
281
|
+
processes revalidators in order; each call's return controls how the
|
|
282
|
+
chain continues:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// 1) Hard decision — short-circuits the chain, used as the final answer.
|
|
286
|
+
revalidate(() => true);
|
|
287
|
+
revalidate(({ actionId }) => actionId?.includes("Cart") ?? false);
|
|
288
|
+
|
|
289
|
+
// 2) Soft decision — updates the running suggestion for downstream
|
|
290
|
+
// revalidators on the same segment, chain continues.
|
|
291
|
+
revalidate(({ defaultShouldRevalidate }) => ({
|
|
292
|
+
defaultShouldRevalidate: !defaultShouldRevalidate,
|
|
293
|
+
}));
|
|
294
|
+
|
|
295
|
+
// 3) Defer (no opinion) — leaves the running suggestion unchanged and
|
|
296
|
+
// continues to the next revalidator. Implicit return / null /
|
|
297
|
+
// undefined are all equivalent and consumer-friendly.
|
|
298
|
+
revalidate(({ actionId }) => {
|
|
299
|
+
if (actionId?.includes("Cart")) return true; // hard for this branch only
|
|
300
|
+
// implicit return — let downstream revalidators or the segment default decide
|
|
301
|
+
});
|
|
302
|
+
revalidate(() => undefined); // explicit defer
|
|
303
|
+
revalidate(() => null); // explicit defer
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
If every revalidator on a segment defers, the segment-type default
|
|
307
|
+
(e.g. params-changed for routes, `false` for parallels) is used.
|
|
308
|
+
|
|
309
|
+
#### `|| undefined` (defer) vs `?? false` (hard) — pick deliberately
|
|
310
|
+
|
|
311
|
+
A boolean return — including `false` — is a **hard** decision: it short-circuits
|
|
312
|
+
the chain and overrides the segment default. `undefined` **defers** to the
|
|
313
|
+
running suggestion / segment default. They are not interchangeable:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Defer: "revalidate on match, otherwise let the default/downstream decide."
|
|
317
|
+
revalidate(({ actionId }) => actionId?.includes("Cart") || undefined);
|
|
318
|
+
|
|
319
|
+
// Hard: "revalidate ONLY on match, suppress everything else."
|
|
320
|
+
revalidate(({ actionId }) => actionId?.includes("Cart") ?? false);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
This matters most for loaders, whose defaults are permissive: a loader defaults
|
|
324
|
+
to revalidating on **any** action (`POST`) and on **param/search changes**
|
|
325
|
+
during navigation. So `?? false` on a loader silently suppresses both — the
|
|
326
|
+
loader will not refetch when you navigate to a different `:id`. Use
|
|
327
|
+
`|| undefined` when you want to _add_ a revalidation signal on top of the
|
|
328
|
+
sensible defaults, and reserve `?? false` for the rare case where you genuinely
|
|
329
|
+
want the loader to refetch on nothing but your matched action.
|
|
330
|
+
|
|
331
|
+
When **composing multiple revalidators** on one segment (see below), defer is
|
|
332
|
+
mandatory: the first hard `?? false` ends the chain and the later contracts
|
|
333
|
+
never run.
|
|
334
|
+
|
|
335
|
+
#### Matching actions: `ctx.isAction()`
|
|
336
|
+
|
|
337
|
+
To revalidate after specific server actions, match them by **reference** with
|
|
338
|
+
`ctx.isAction()` rather than hand-written `actionId` substrings. A rename or
|
|
339
|
+
moved file then becomes a type error instead of silently failing to match:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { addToCart, removeFromCart } from "../actions/cart";
|
|
343
|
+
import * as CartActions from "../actions/cart";
|
|
344
|
+
|
|
345
|
+
loader(CartLoader, () => [
|
|
346
|
+
revalidate((ctx) => ctx.isAction(addToCart) || undefined), // one action
|
|
347
|
+
]);
|
|
348
|
+
revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
|
|
349
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`isAction()` is a method on the revalidate predicate's **context argument** —
|
|
353
|
+
there is no standalone `isAction` import; you always reach it through the callback
|
|
354
|
+
parameter (`revalidate((ctx) => ctx.isAction(...))`). It returns a raw boolean, so
|
|
355
|
+
pair it with `|| undefined` for the usual "revalidate on match, else defer"
|
|
356
|
+
intent. It returns `false` on plain navigation and on non-matches, and resolves
|
|
357
|
+
the reference the same way the router derives `actionId` (`$id` in production,
|
|
358
|
+
`$$id` in dev), so it matches in both modes. The raw `actionId` string stays
|
|
359
|
+
available on the same context as an escape hatch.
|
|
360
|
+
|
|
171
361
|
### Revalidation Contracts for Loader Dependencies
|
|
172
362
|
|
|
173
363
|
If a loader reads `ctx.get()` data produced by an outer handler/layout, share
|
|
@@ -175,8 +365,12 @@ the same named revalidation contract across producer and consumer segments.
|
|
|
175
365
|
|
|
176
366
|
```typescript
|
|
177
367
|
// revalidation-contracts.ts
|
|
178
|
-
|
|
179
|
-
|
|
368
|
+
import * as AccountActions from "./actions/account";
|
|
369
|
+
|
|
370
|
+
// Match by reference with ctx.isAction() (rename-safe), and defer (|| undefined)
|
|
371
|
+
// so these contracts compose — a hard `false` would short-circuit the rest.
|
|
372
|
+
export const revalidateAccountScope = (ctx) =>
|
|
373
|
+
ctx.isAction(AccountActions) || undefined;
|
|
180
374
|
|
|
181
375
|
layout(AccountLayout, () => [
|
|
182
376
|
revalidate(revalidateAccountScope), // producer reruns
|
|
@@ -219,6 +413,64 @@ follows the same rule: at build time, loaders are skipped entirely (there is no
|
|
|
219
413
|
real request context), and at runtime the worker resolves them fresh against
|
|
220
414
|
the live database.
|
|
221
415
|
|
|
416
|
+
### Parallel and streaming — latency overlaps first paint
|
|
417
|
+
|
|
418
|
+
Loaders do not block the page. As the render pass begins — the pass that route
|
|
419
|
+
middleware wraps, so loaders run right after middleware, not in a later
|
|
420
|
+
phase — every matched loader is kicked off **concurrently** (their promises start in the
|
|
421
|
+
same tick), and each result is **streamed** to the client as its own RSC Flight
|
|
422
|
+
chunk rather than awaited up front. Pair a loader with `loading()` (or a
|
|
423
|
+
client `<Suspense>`) and the shell paints immediately while the data streams in.
|
|
424
|
+
|
|
425
|
+
This is why **"cached UI still pays full data latency" is the wrong intuition**:
|
|
426
|
+
on a `cache()` hit the UI segments stream instantly from cache while the live
|
|
427
|
+
loaders resolve fresh **in parallel** — data latency _overlaps_ first paint
|
|
428
|
+
instead of being added on top of it. (Without a `loading()` / `<Suspense>`
|
|
429
|
+
boundary a parallel loader blocks its parent, so add one to keep the overlap.)
|
|
430
|
+
|
|
431
|
+
If you come from a framework where the loader is a blocking step that runs
|
|
432
|
+
before the response is built, this is the shift to internalize: here the
|
|
433
|
+
response starts streaming first and loader data fills in.
|
|
434
|
+
|
|
435
|
+
### See it: `debugPerformance`
|
|
436
|
+
|
|
437
|
+
Turn on the per-request performance timeline early — it is the fastest way to
|
|
438
|
+
confirm loaders overlap rather than serialize, and to find the real bottleneck
|
|
439
|
+
locally instead of guessing:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const router = createRouter({ document: Document, debugPerformance: true });
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Or enable it per-request from middleware (e.g. only when `?debug` is present) by
|
|
446
|
+
calling `ctx.debugPerformance()` **before** `await next()`. Each HTML request
|
|
447
|
+
then prints a shared-axis waterfall (and emits a `Server-Timing` header):
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
[RSC Perf] GET /product/widget (24.53ms)
|
|
451
|
+
start dur span timeline
|
|
452
|
+
0.08ms 3.20ms route-matching |#####...................................|
|
|
453
|
+
3.40ms 8.70ms ssr-render-html |.....##############.....................|
|
|
454
|
+
3.42ms 11.90ms loader:…#ProductLoader |.....###################................|
|
|
455
|
+
3.45ms 11.40ms loader:…#ReviewsLoader |.....##################.................|
|
|
456
|
+
0.00ms 24.53ms handler:total |########################################|
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
How to read it:
|
|
460
|
+
|
|
461
|
+
- **Humans:** scan the `#` bars on the shared axis. Bars that start at the same
|
|
462
|
+
offset and run side by side are executing **in parallel** — loaders should
|
|
463
|
+
overlap `ssr-render-html` / `render:total`, not sit alone to the right of
|
|
464
|
+
everything. A lone `loader:*` bar past the render bar is serialized latency to
|
|
465
|
+
chase. `handler:total` is the whole request; `render:total` is the render pass.
|
|
466
|
+
- **LLMs / programmatic:** read each row as `{ start, dur, label }`. A loader
|
|
467
|
+
overlaps paint when its `[start, start+dur]` interval intersects
|
|
468
|
+
`render:total` / `ssr-render-html`. Flag a regression when a `loader:*`
|
|
469
|
+
interval is **disjoint from and starts after** `render:total`, or when its
|
|
470
|
+
`dur` approaches `handler:total` — that loader is on the critical path instead
|
|
471
|
+
of overlapping it. Two `loader:*` rows with near-equal `start` confirm
|
|
472
|
+
parallel execution.
|
|
473
|
+
|
|
222
474
|
### Opting a Loader into Caching
|
|
223
475
|
|
|
224
476
|
To cache a specific loader's data, attach a `cache()` child:
|
|
@@ -492,6 +744,13 @@ export const FileUploadLoader = createLoader(async (ctx) => {
|
|
|
492
744
|
|
|
493
745
|
Client usage — see `/hooks useFetchLoader` for the full client-side pattern.
|
|
494
746
|
|
|
747
|
+
> **Refetch sharing**: when the loader is registered on the route via
|
|
748
|
+
> `loader()`, a plain `load()` call (no `params`, no `body`) broadcasts
|
|
749
|
+
> the new value to every component reading the same loader id —
|
|
750
|
+
> `useLoader` reads in layouts, pages, and parallel slots all converge.
|
|
751
|
+
> Calls with `params` or a non-GET method stay local to the call site.
|
|
752
|
+
> See `/hooks` → "Shared refetch behavior" for the full contract.
|
|
753
|
+
|
|
495
754
|
## Complete Example
|
|
496
755
|
|
|
497
756
|
```typescript
|
|
@@ -507,7 +766,7 @@ export const ProductLoader = createLoader(async (ctx) => {
|
|
|
507
766
|
.first();
|
|
508
767
|
|
|
509
768
|
if (!product) {
|
|
510
|
-
|
|
769
|
+
notFound("Product not found");
|
|
511
770
|
}
|
|
512
771
|
|
|
513
772
|
return { product };
|
|
@@ -523,12 +782,13 @@ export const CartLoader = createLoader(async (ctx) => {
|
|
|
523
782
|
return { cart };
|
|
524
783
|
});
|
|
525
784
|
|
|
526
|
-
// urls.tsx
|
|
785
|
+
// urls.tsx — register loaders in the DSL
|
|
786
|
+
import * as CartActions from "./actions/cart";
|
|
787
|
+
|
|
527
788
|
export const urlpatterns = urls(({ path, layout, loader, loading, cache, revalidate }) => [
|
|
528
789
|
layout(<ShopLayout />, () => [
|
|
529
|
-
// Shared cart loader for all shop routes
|
|
530
790
|
loader(CartLoader, () => [
|
|
531
|
-
revalidate((
|
|
791
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
532
792
|
]),
|
|
533
793
|
|
|
534
794
|
path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
|
|
@@ -538,18 +798,22 @@ export const urlpatterns = urls(({ path, layout, loader, loading, cache, revalid
|
|
|
538
798
|
]),
|
|
539
799
|
]);
|
|
540
800
|
|
|
541
|
-
//
|
|
801
|
+
// components/ProductDetails.tsx — consume in client component
|
|
802
|
+
"use client";
|
|
542
803
|
import { useLoader } from "@rangojs/router/client";
|
|
543
804
|
import { ProductLoader, CartLoader } from "./loaders/shop";
|
|
544
805
|
|
|
545
|
-
|
|
546
|
-
const { product } =
|
|
547
|
-
const { cart } =
|
|
806
|
+
function ProductDetails() {
|
|
807
|
+
const { data: { product } } = useLoader(ProductLoader);
|
|
808
|
+
const { data: { cart } } = useLoader(CartLoader);
|
|
548
809
|
|
|
549
810
|
return (
|
|
550
811
|
<div>
|
|
551
812
|
<h1>{product.name}</h1>
|
|
552
|
-
<AddToCartButton
|
|
813
|
+
<AddToCartButton
|
|
814
|
+
productId={product.id}
|
|
815
|
+
inCart={cart?.items.includes(product.id)}
|
|
816
|
+
/>
|
|
553
817
|
</div>
|
|
554
818
|
);
|
|
555
819
|
}
|
|
@@ -10,9 +10,6 @@ Middleware runs before/after route handlers using the onion model.
|
|
|
10
10
|
|
|
11
11
|
## Execution Model
|
|
12
12
|
|
|
13
|
-
Canonical semantics reference:
|
|
14
|
-
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
15
|
-
|
|
16
13
|
There are two levels of middleware with different execution scopes:
|
|
17
14
|
|
|
18
15
|
### Global middleware (`router.use()`)
|
|
@@ -26,21 +23,32 @@ const router = createRouter<AppEnv>({})
|
|
|
26
23
|
.routes(urlpatterns);
|
|
27
24
|
```
|
|
28
25
|
|
|
26
|
+
When the router has a `basename`, pattern-scoped `.use()` patterns are automatically prefixed. For example, with `basename: "/app"`, `.use("/admin/*", mw)` matches `/app/admin/*`.
|
|
27
|
+
|
|
29
28
|
### Route middleware (`middleware()` in `urls()`)
|
|
30
29
|
|
|
31
30
|
Registered inside `urls()` callback. Wraps **rendering only** -- it does NOT wrap server action execution. Actions run before route middleware, so when route middleware executes during post-action revalidation, it can observe state that the action set (cookies, context variables, headers).
|
|
32
31
|
|
|
32
|
+
> **Implication for auth:** route middleware cannot guard server actions. Use `router.use("/admin/*", requireAuth)` (global, scoped) for action protection, or check inside the action body. See `/server-actions` for action-side auth patterns.
|
|
33
|
+
|
|
33
34
|
```
|
|
34
35
|
Request flow (with action):
|
|
35
|
-
global mw -> action executes -> route mw ->
|
|
36
|
+
global mw -> action executes -> route mw -> render pass
|
|
36
37
|
|
|
37
38
|
Request flow (no action):
|
|
38
|
-
global mw -> route mw ->
|
|
39
|
+
global mw -> route mw -> render pass
|
|
39
40
|
|
|
40
41
|
Progressive enhancement (no-JS form POST):
|
|
41
42
|
global mw -> action executes -> route mw -> full page re-render
|
|
42
43
|
```
|
|
43
44
|
|
|
45
|
+
The **render pass** resolves handler, layouts, parallels, and loaders together —
|
|
46
|
+
it is not a handler-then-loaders sequence. Handler-first ordering is guaranteed
|
|
47
|
+
only between a route handler and its child/orphan layouts and parallels (so
|
|
48
|
+
`ctx.set` is visible); loaders run **concurrently** and stream their results, so
|
|
49
|
+
their latency overlaps rendering rather than blocking it. See `/loader` →
|
|
50
|
+
"Parallel and streaming".
|
|
51
|
+
|
|
44
52
|
The contract is: **route middleware wraps rendering regardless of transport** (JS-enabled RSC stream or no-JS HTML). During PE re-render, route middleware observes action-set state (cookies, context variables) the same way it does during JS-enabled post-action revalidation.
|
|
45
53
|
|
|
46
54
|
Revalidation is still partial. Route middleware wraps the render pass that
|
|
@@ -59,8 +67,10 @@ For shared segment data, use named revalidation contracts on both the producer
|
|
|
59
67
|
and consumer segments, even when middleware is present in the chain.
|
|
60
68
|
|
|
61
69
|
```typescript
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
import * as CartActions from "./actions/cart";
|
|
71
|
+
|
|
72
|
+
export const revalidateCartData = (ctx) =>
|
|
73
|
+
ctx.isAction(CartActions) || undefined;
|
|
64
74
|
|
|
65
75
|
layout(CartLayout, () => [
|
|
66
76
|
middleware(cartRenderMiddleware),
|
|
@@ -135,17 +145,46 @@ export const urlpatterns = urls(({ path, layout, middleware }) => [
|
|
|
135
145
|
## Middleware with Multiple Handlers
|
|
136
146
|
|
|
137
147
|
```typescript
|
|
138
|
-
//
|
|
148
|
+
// Group multiple middleware in an array
|
|
139
149
|
export const shopMiddleware = [loggerMiddleware, mockAuthMiddleware];
|
|
140
150
|
|
|
141
|
-
// In routes
|
|
151
|
+
// In routes — pass the array directly
|
|
142
152
|
layout(<ShopLayout />, () => [
|
|
143
|
-
middleware(
|
|
153
|
+
middleware(shopMiddleware),
|
|
144
154
|
|
|
145
155
|
path("/shop", ShopIndex, { name: "shop" }),
|
|
146
156
|
])
|
|
147
157
|
```
|
|
148
158
|
|
|
159
|
+
## Wrapping Middleware (Scoped to Children)
|
|
160
|
+
|
|
161
|
+
Use the wrapping form to scope middleware to a subset of routes without
|
|
162
|
+
introducing a visible layout:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
urls(({ path, middleware }) => [
|
|
166
|
+
// authMw only applies to /admin and /admin/settings
|
|
167
|
+
middleware(authMw, () => [
|
|
168
|
+
path("/admin", AdminPage, { name: "admin" }),
|
|
169
|
+
path("/admin/settings", SettingsPage, { name: "settings" }),
|
|
170
|
+
]),
|
|
171
|
+
|
|
172
|
+
// Public route — no authMw
|
|
173
|
+
path("/", HomePage, { name: "home" }),
|
|
174
|
+
]);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Multiple middleware with wrapping:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
middleware([authMw, loggingMw], () => [
|
|
181
|
+
path("/admin", AdminPage, { name: "admin" }),
|
|
182
|
+
]);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This creates a transparent layout (`<Outlet />`) that carries the middleware.
|
|
186
|
+
The middleware does not affect sibling routes outside the callback.
|
|
187
|
+
|
|
149
188
|
## Middleware Context
|
|
150
189
|
|
|
151
190
|
```typescript
|
|
@@ -159,7 +198,7 @@ export const myMiddleware: Middleware = async (ctx, next) => {
|
|
|
159
198
|
ctx.env.DB; // D1Database
|
|
160
199
|
ctx.env.KV; // KVNamespace
|
|
161
200
|
|
|
162
|
-
// Set variables for downstream handlers (typed via
|
|
201
|
+
// Set variables for downstream handlers (typed via Rango.Vars)
|
|
163
202
|
ctx.set("user", { id: "123", name: "John" });
|
|
164
203
|
|
|
165
204
|
// Continue to next middleware/handler
|
|
@@ -200,8 +239,8 @@ const Dashboard: Handler<"dashboard"> = (ctx) => {
|
|
|
200
239
|
```
|
|
201
240
|
|
|
202
241
|
This works alongside `ctx.get("key")` / `ctx.set("key", value)` (global typing
|
|
203
|
-
via
|
|
204
|
-
data; use
|
|
242
|
+
via Rango.Vars augmentation). Use `createVar` for route-local or feature-scoped
|
|
243
|
+
data; use Rango.Vars for app-wide middleware state.
|
|
205
244
|
|
|
206
245
|
## Redirect with State in Middleware
|
|
207
246
|
|