@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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 +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- 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 +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Params and Search Types
|
|
2
|
+
|
|
3
|
+
## Typed Search Params
|
|
4
|
+
|
|
5
|
+
Add a `search` schema to `path()` options for type-safe query parameters:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Route definition with search schema
|
|
9
|
+
path("/search", SearchPage, {
|
|
10
|
+
name: "search",
|
|
11
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Handler with typed search params
|
|
16
|
+
|
|
17
|
+
`Handler<"name">` automatically resolves route params and search params from the
|
|
18
|
+
global `GeneratedRouteMap` (the gen file). No explicit route map import needed:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// pages/search.tsx
|
|
22
|
+
import type { Handler } from "@rangojs/router";
|
|
23
|
+
|
|
24
|
+
export const SearchPage: Handler<"search"> = (ctx) => {
|
|
25
|
+
// ctx.search is typed: { q: string; page?: number; sort?: string }
|
|
26
|
+
const { q, page, sort } = ctx.search;
|
|
27
|
+
return <SearchResults q={q} page={page} sort={sort} />;
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This avoids circular references because `Handler` defaults to `GeneratedRouteMap`
|
|
32
|
+
(from `router.named-routes.gen.ts`) instead of `RegisteredRoutes` (which depends on `router.tsx`).
|
|
33
|
+
|
|
34
|
+
You can also pass an explicit route map for per-module isolation (opt-in,
|
|
35
|
+
after running `npx rango generate`). With a local map, the route name is
|
|
36
|
+
**dot-prefixed** so params and search resolve from `routes`, not the global map:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import type { Handler } from "@rangojs/router";
|
|
40
|
+
import type { routes } from "./urls.gen.js";
|
|
41
|
+
|
|
42
|
+
export const SearchPage: Handler<".search", routes> = (ctx) => { ... };
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Note the difference: `Handler<"search">` (no dot) resolves against the global
|
|
46
|
+
`GeneratedRouteMap`; `Handler<".search", routes>` resolves against the local
|
|
47
|
+
`routes` map. Mixing them — `Handler<"search", routes>` — silently ignores
|
|
48
|
+
`routes` for param/search inference and only uses it for local `ctx.reverse(".x")`.
|
|
49
|
+
|
|
50
|
+
Supported types: `"string"`, `"number"`, `"boolean"`, with `?` suffix for optional.
|
|
51
|
+
Values are automatically coerced from query string (e.g., `"2"` becomes `2` for numbers).
|
|
52
|
+
Routes without a `search` schema keep the standard `URLSearchParams` behavior.
|
|
53
|
+
|
|
54
|
+
### RouteSearchParams and RouteParams utility types
|
|
55
|
+
|
|
56
|
+
Extract typed params by route name for use in component props, return types, or anywhere:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import type { RouteSearchParams, RouteParams } from "@rangojs/router";
|
|
60
|
+
|
|
61
|
+
// RouteSearchParams<"name"> resolves the search schema to a typed object
|
|
62
|
+
type SP = RouteSearchParams<"search">;
|
|
63
|
+
// { q: string | undefined; page?: number; sort?: string }
|
|
64
|
+
|
|
65
|
+
// RouteParams<"name"> resolves URL params from the route pattern
|
|
66
|
+
type P = RouteParams<"blogPost">;
|
|
67
|
+
// { slug: string }
|
|
68
|
+
|
|
69
|
+
// Optional URL params (`:slug?`) resolve to `string | undefined`
|
|
70
|
+
// because absent segments are omitted from `ctx.params` at runtime.
|
|
71
|
+
type C = RouteParams<"checkout">;
|
|
72
|
+
// { step?: string }
|
|
73
|
+
// → ctx.params.step is `string | undefined`; use `?? "default"` to coalesce.
|
|
74
|
+
|
|
75
|
+
// Use in component props
|
|
76
|
+
interface SearchResultsProps {
|
|
77
|
+
params: RouteSearchParams<"search">;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Both default to the global route map (`RegisteredRoutes` or `GeneratedRouteMap`).
|
|
82
|
+
Pass an explicit route map as the second type argument when needed:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import type { routes } from "./urls.gen.js";
|
|
86
|
+
|
|
87
|
+
type SP = RouteSearchParams<"search", routes>;
|
|
88
|
+
type P = RouteParams<"blogPost", routes>;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Generated route types
|
|
92
|
+
|
|
93
|
+
In the generated `router.named-routes.gen.ts`, routes with search schemas
|
|
94
|
+
use `{ path, search }` objects:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// router.named-routes.gen.ts (auto-generated)
|
|
98
|
+
export const NamedRoutes = {
|
|
99
|
+
"search.index": {
|
|
100
|
+
path: "/search",
|
|
101
|
+
search: { q: "string", page: "number?", sort: "string?" },
|
|
102
|
+
},
|
|
103
|
+
"home.index": "/", // No search schema -> plain string
|
|
104
|
+
} as const;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
You never open a `.gen.ts` by hand. Treat the generated types as call-site
|
|
108
|
+
honesty checks, not modules to read:
|
|
109
|
+
|
|
110
|
+
- **Do not import `router.named-routes.gen.ts` directly**, and don't reach for
|
|
111
|
+
`Rango.GeneratedRouteMap`. It is the whole-app manifest, auto-wired
|
|
112
|
+
globally — `Handler<"name">` and `ctx.reverse("name")` already see it.
|
|
113
|
+
- **Per-module `*.gen.ts` imports are fine** — they are the opt-in local-route
|
|
114
|
+
pattern for `useReverse(routes)` and explicit local handler typing
|
|
115
|
+
(`Handler<".name", routes>`). See `/links`.
|
|
116
|
+
|
|
117
|
+
If a type error points at a generated map instead of your call site, that's a
|
|
118
|
+
smell — fix the call site (or regenerate), never edit the generated file.
|
|
119
|
+
|
|
120
|
+
## Loader Type Safety
|
|
121
|
+
|
|
122
|
+
Loaders have typed return values:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// loaders/product.ts
|
|
126
|
+
export const ProductLoader = createLoader(async (ctx) => {
|
|
127
|
+
return {
|
|
128
|
+
id: ctx.params.slug,
|
|
129
|
+
name: "Widget",
|
|
130
|
+
price: 99,
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// In server component - type is inferred
|
|
135
|
+
import { useLoader } from "@rangojs/router/client";
|
|
136
|
+
|
|
137
|
+
async function ProductPage() {
|
|
138
|
+
const product = await useLoader(ProductLoader);
|
|
139
|
+
// product: { id: string; name: string; price: number }
|
|
140
|
+
return <h1>{product.name}</h1>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// In client component - same type
|
|
144
|
+
"use client";
|
|
145
|
+
import { useLoader } from "@rangojs/router/client";
|
|
146
|
+
|
|
147
|
+
function ProductPrice() {
|
|
148
|
+
const { data } = useLoader(ProductLoader);
|
|
149
|
+
// data: { id: string; name: string; price: number }
|
|
150
|
+
const product = data;
|
|
151
|
+
return <span>${product.price}</span>;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Route Types
|
|
2
|
+
|
|
3
|
+
## Route Definition with Type-Safe Names
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// urls.tsx
|
|
7
|
+
import { urls } from "@rangojs/router";
|
|
8
|
+
|
|
9
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
10
|
+
path("/", HomePage, { name: "home" }),
|
|
11
|
+
path("/products", ProductsPage, { name: "products" }),
|
|
12
|
+
path("/product/:slug", ProductPage, { name: "product" }),
|
|
13
|
+
path("/cart", CartPage, { name: "cart" }),
|
|
14
|
+
path("/checkout/:step?", CheckoutPage, { name: "checkout" }),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
// Route names are inferred from the { name } option
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Type-Safe href()
|
|
21
|
+
|
|
22
|
+
### Server: ctx.reverse with route names
|
|
23
|
+
|
|
24
|
+
In route handlers, `ctx.reverse()` uses two namespaces:
|
|
25
|
+
|
|
26
|
+
- **`.name`** — local route, resolved within the current `include()` scope
|
|
27
|
+
- **`name`** — global route, from the named-routes definition
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import type { Handler } from "@rangojs/router";
|
|
31
|
+
|
|
32
|
+
export const ProductHandler: Handler<"shop.product"> = (ctx) => {
|
|
33
|
+
ctx.reverse(".cart"); // Local: /shop/cart
|
|
34
|
+
ctx.reverse(".product", { slug: "widget" }); // Local: /shop/product/widget
|
|
35
|
+
ctx.reverse("blog.post", { slug: "1" }); // Global: /blog/1
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For type-safe local names, generate a route types file with `npx rango generate urls/shop.tsx`
|
|
40
|
+
and pass it as the second generic to `Handler` or `Prerender`:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import type { Handler } from "@rangojs/router";
|
|
44
|
+
import type { routes } from "./shop.gen.js";
|
|
45
|
+
|
|
46
|
+
export const ProductHandler: Handler<"shop.product", routes> = (ctx) => {
|
|
47
|
+
ctx.reverse(".cart"); // Type-safe local name
|
|
48
|
+
ctx.reverse(".product", { slug: "widget" }); // Type-safe local with params
|
|
49
|
+
ctx.reverse("blog.post", { slug: "hi" }); // Type-safe global name
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Client: href + useHref
|
|
54
|
+
|
|
55
|
+
On the client, `href()` validates paths against registered route patterns at compile time:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
"use client";
|
|
59
|
+
import { href, useHref, Link } from "@rangojs/router/client";
|
|
60
|
+
|
|
61
|
+
// href() validates absolute paths via PatternToPath types
|
|
62
|
+
href("/about"); // Valid path
|
|
63
|
+
href("/blog/hello"); // Matches /blog/:slug
|
|
64
|
+
|
|
65
|
+
// useHref() auto-prefixes with include() mount
|
|
66
|
+
function ShopNav() {
|
|
67
|
+
const href = useHref();
|
|
68
|
+
return <Link to={href("/cart")}>Cart</Link>; // "/shop/cart"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
|
|
73
|
+
it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
|
|
74
|
+
alone type-checks `href()` paths with no manual augmentation. The augmentation
|
|
75
|
+
below is only needed for **`Rango.PathResponse`** (response-payload inference), which
|
|
76
|
+
`GeneratedRouteMap` cannot provide:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
declare global {
|
|
80
|
+
namespace Rango {
|
|
81
|
+
interface RegisteredRoutes extends typeof router.routeMap {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For wrapper helpers, type the path parameter as `Rango.Path`. It is ambient (no
|
|
87
|
+
import) and shares `href()`'s compile-time path checking, so a wrapper stays in
|
|
88
|
+
sync with your routes automatically:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { href } from "@rangojs/router/client";
|
|
92
|
+
|
|
93
|
+
export const appHref = (path: Rango.Path): string => href(path);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For response-route payloads, `Rango.PathResponse<T>` is the ambient lookup. It
|
|
97
|
+
accepts a route _pattern_ **or** a concrete path, so it also serves as the return
|
|
98
|
+
type of a typed `fetch` wrapper. It only resolves once `RegisteredRoutes` carries
|
|
99
|
+
response metadata:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { href } from "@rangojs/router/client";
|
|
103
|
+
|
|
104
|
+
type Product = Rango.PathResponse<"/api/products/:id">; // by pattern
|
|
105
|
+
type Same = Rango.PathResponse<"/api/products/42">; // by concrete path
|
|
106
|
+
|
|
107
|
+
// Response inferred from the concrete path passed in:
|
|
108
|
+
async function get<T extends Rango.Path>(
|
|
109
|
+
path: T,
|
|
110
|
+
): Promise<Rango.PathResponse<T>> {
|
|
111
|
+
return fetch(href(path)).then((r) => r.json());
|
|
112
|
+
}
|
|
113
|
+
const product = await get("/api/products/42"); // Product (bare value)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Pattern keys (`/:id`) match exactly; a concrete path under a _nested_ dynamic
|
|
117
|
+
route can match several patterns and union their responses.
|
|
118
|
+
|
|
119
|
+
`Rango.PathResponse` describes the JSON **wire** shape, not the handler's raw
|
|
120
|
+
return. A `path.json()` handler returning `{ createdAt: Date }` resolves here to
|
|
121
|
+
`{ createdAt: string }` (bare value), matching what `r.json()` yields. This
|
|
122
|
+
is applied via the ambient `Rango.JsonSerialize<T>` transform (`Date -> string`,
|
|
123
|
+
honors `toJSON()`, drops functions/`undefined`, `bigint -> never`). A separate
|
|
124
|
+
`Rango.FlightSerialize<T>` models the higher-fidelity RSC Flight boundary
|
|
125
|
+
(loaders / RSC props, where `Date` is preserved) — do **not** use it for
|
|
126
|
+
`path.json()`.
|
|
127
|
+
|
|
128
|
+
### Overriding serialization globally
|
|
129
|
+
|
|
130
|
+
For your own types, the zero-config way to control the JSON wire shape is a
|
|
131
|
+
`toJSON()` method — `Rango.JsonSerialize` honors it, and it matches the runtime
|
|
132
|
+
exactly (`JSON.stringify` calls `toJSON()`):
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
class Money {
|
|
136
|
+
constructor(private cents: number) {}
|
|
137
|
+
toJSON(): number {
|
|
138
|
+
return this.cents;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Rango.JsonSerialize<Money> is number; Rango.PathResponse reflects it.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
To override a transform for types you **don't** own (or for the Flight boundary,
|
|
145
|
+
which has no `toJSON()`), augment its override slot. Because `Rango.JsonSerialize`
|
|
146
|
+
/ `Rango.FlightSerialize` are type _aliases_ (TS can't merge those), you provide a
|
|
147
|
+
single member that is your **complete** transform, delegating to the built-in for
|
|
148
|
+
the cases you don't change:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
declare global {
|
|
152
|
+
namespace Rango {
|
|
153
|
+
interface JsonSerializeOverride<T> {
|
|
154
|
+
app: T extends Decimal ? string : Rango.JsonSerializeBuiltin<T>;
|
|
155
|
+
}
|
|
156
|
+
interface FlightSerializeOverride<T> {
|
|
157
|
+
app: T extends Money ? number : Rango.FlightSerializeBuiltin<T>;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Rango.JsonSerialize<Decimal> -> string; Rango.FlightSerialize<Money> -> number;
|
|
162
|
+
// everything else stays on the built-in, recursively (nested fields too).
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Rules: provide **exactly one** member (the slot is read as
|
|
166
|
+
`Override<T>[keyof Override<T>]`, so multiple members union and conflict).
|
|
167
|
+
Overrides win over `toJSON()` and apply at every nesting level. Caveat for JSON:
|
|
168
|
+
the `path.json()` runtime is plain `JSON.stringify`, which only honors `toJSON()`,
|
|
169
|
+
so a `JsonSerializeOverride` that disagrees with what the runtime emits will lie —
|
|
170
|
+
prefer `toJSON()` for your own types and use the slot only for types you can't
|
|
171
|
+
modify.
|
|
172
|
+
|
|
173
|
+
See `/links` for full URL generation guide.
|
|
174
|
+
|
|
175
|
+
## Stable identity: `path#export`
|
|
176
|
+
|
|
177
|
+
Loaders, handles, cached functions (`functionId`), and server actions
|
|
178
|
+
(`actionId`) all share one identity scheme: `{modulePath}#{exportName}`,
|
|
179
|
+
injected at build by the `exposeInternalIds` and `exposeActionId` Vite plugins.
|
|
180
|
+
This is also the identity React server actions carry across the Flight boundary,
|
|
181
|
+
which is why a `revalidate()` predicate sees an action as a `path#export` string:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
revalidate(
|
|
185
|
+
({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined,
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`actionId` is the only stable reference React exposes across the Flight boundary,
|
|
190
|
+
so it stays as the floor and escape hatch. The hand-written-string surface
|
|
191
|
+
(`actionId?.includes("cart.ts#")`) is brittle: a renamed action or moved file
|
|
192
|
+
silently stops matching with no compile error. Prefer **`ctx.isAction()`** in a
|
|
193
|
+
revalidate predicate — it resolves the action's id from an imported reference, so
|
|
194
|
+
a rename is a type error in one place instead of silent drift:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { addToCart, removeFromCart } from "./actions/cart";
|
|
198
|
+
import * as CartActions from "./actions/cart";
|
|
199
|
+
|
|
200
|
+
revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
|
|
201
|
+
revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
|
|
202
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any action in the module
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`ctx.isAction()` (only available on the revalidate predicate's context) returns a
|
|
206
|
+
raw boolean — combine with `|| undefined` for the "revalidate on match, else
|
|
207
|
+
defer" intent. It resolves the reference the same way the router derives
|
|
208
|
+
`actionId` (`$id` in production, `$$id` in dev), so matching
|
|
209
|
+
works in both modes. `actionId` stays available for advanced cases.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: use-cache
|
|
3
|
-
description: Function-level caching with "use cache" directive for RSC data functions and components
|
|
3
|
+
description: Function-level caching with the "use cache" directive for RSC data functions and components in @rangojs/router. Use when a single function or component should memoize its own output, not a whole route/segment subtree or HTTP response.
|
|
4
4
|
argument-hint: [profile-name]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,6 +10,12 @@ Function-level caching for async server functions and RSC components. Caches
|
|
|
10
10
|
return values with TTL + stale-while-revalidate. Complementary to the route-level
|
|
11
11
|
`cache()` DSL and build-time `Static()`/`Prerender()`.
|
|
12
12
|
|
|
13
|
+
## Not this skill if…
|
|
14
|
+
|
|
15
|
+
- You want to cache a whole route or a rendered subtree of segments — that is
|
|
16
|
+
the segment-level `cache()` DSL: see `/caching`.
|
|
17
|
+
- You are unsure which cache layer you need — start at `/cache-guide`.
|
|
18
|
+
|
|
13
19
|
## Basic Usage
|
|
14
20
|
|
|
15
21
|
### File-level (all exports cached with default profile)
|
|
@@ -52,8 +58,9 @@ export async function ProductCard({ id }: { id: string }) {
|
|
|
52
58
|
|
|
53
59
|
## Named Cache Profiles
|
|
54
60
|
|
|
55
|
-
Define profiles in createRouter. Profile names map to `"use cache: <name>"`
|
|
56
|
-
`cache(
|
|
61
|
+
Define profiles in createRouter. Profile names map to `"use cache: <name>"` in
|
|
62
|
+
the directive. The DSL `cache()` does not accept a string profile name; use an
|
|
63
|
+
options object (`cache({ ttl: 60 })`) or the `"use cache: <name>"` directive.
|
|
57
64
|
|
|
58
65
|
```typescript
|
|
59
66
|
createRouter({
|
|
@@ -62,12 +69,23 @@ createRouter({
|
|
|
62
69
|
short: { ttl: 60, swr: 120 },
|
|
63
70
|
long: { ttl: 3600, swr: 7200 },
|
|
64
71
|
products: { ttl: 300, swr: 600, tags: ["products"] },
|
|
72
|
+
// Opt-in: a stale entry re-executes in the foreground during a server
|
|
73
|
+
// action's revalidation render (fresh action response), instead of SWR.
|
|
74
|
+
cms: { ttl: 300, swr: 600, foregroundOnAction: true },
|
|
65
75
|
},
|
|
66
76
|
});
|
|
67
77
|
```
|
|
68
78
|
|
|
69
79
|
- `"use cache"` (no name) resolves to `default`.
|
|
70
80
|
- `"use cache: short"` resolves to the `short` profile.
|
|
81
|
+
- `foregroundOnAction: true` (default false): a stale entry serves stale +
|
|
82
|
+
revalidates in the background on a plain navigation (SWR), but re-executes in
|
|
83
|
+
the FOREGROUND during a server action's revalidation render so the action
|
|
84
|
+
response reflects a fresh value (only the store write is deferred). Use it for
|
|
85
|
+
mutation-related cached data; incidental TTL staleness on an ordinary action
|
|
86
|
+
stays SWR so the action is not turned into a synchronous cache-refresh barrier.
|
|
87
|
+
For strong read-your-own-writes after a mutation, prefer `updateTag()` (a hard
|
|
88
|
+
purge, so the action's own re-render is a fresh foreground miss).
|
|
71
89
|
- Unknown profile names throw at runtime, on the first invocation of the cached
|
|
72
90
|
function (the Vite transform does not validate names at build/boot). The error
|
|
73
91
|
is actionable -- it names the missing profile and shows the `createRouter({
|
|
@@ -135,6 +153,13 @@ const locale = cookies().get("locale")?.value ?? "en";
|
|
|
135
153
|
const data = await getCachedData(locale); // locale is now in the cache key
|
|
136
154
|
```
|
|
137
155
|
|
|
156
|
+
The guard does not reach into LOADER bodies consumed inside the cached
|
|
157
|
+
function (`await ctx.use(loader)`): loaders always run fresh, so their reads
|
|
158
|
+
are exempt — but the CONSUMED VALUE is captured into the shared cache entry
|
|
159
|
+
like any other computed data. Same rule across `cache()` and the PPR shell:
|
|
160
|
+
handler/cached-scope consumption = baked copy, client-side `useLoader` = live
|
|
161
|
+
(the consumption-lane rule, `/rango` → Invariants).
|
|
162
|
+
|
|
138
163
|
### Side-Effect Guards
|
|
139
164
|
|
|
140
165
|
These ctx methods **throw** inside a `"use cache"` function because their effects
|
|
@@ -328,23 +353,28 @@ export async function getProducts() {
|
|
|
328
353
|
Writes to the same `SegmentCacheStore` as `cache()` DSL, `Static()`, and `Prerender()`.
|
|
329
354
|
One store, one configuration.
|
|
330
355
|
|
|
331
|
-
Cache entries (and `cacheProfiles`)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
`
|
|
337
|
-
|
|
356
|
+
Cache entries (and `cacheProfiles`) can be tagged via `cache({ tags })` or runtime
|
|
357
|
+
`cacheTag(...tags)`. `cacheTag` has two forms: inside a `"use cache"` function it
|
|
358
|
+
tags that entry; called during a request render outside `"use cache"` it tags the
|
|
359
|
+
request's document/shell artifact (rides `_requestTags`) instead of throwing. The
|
|
360
|
+
built-in
|
|
361
|
+
`MemorySegmentCacheStore` and `CFCacheStore` index by tag. Invalidate on demand
|
|
362
|
+
with `updateTag(...tags)` (awaitable, read-your-own-writes; for server actions) or
|
|
363
|
+
`revalidateTag(...tags)` (background, non-blocking; for route handlers/webhooks).
|
|
364
|
+
Both hard-purge; the difference is awaitability, not stale-serving. For
|
|
365
|
+
`CFCacheStore`, distributed invalidation needs a `kv` namespace (markers live in
|
|
366
|
+
that same namespace). The separate `revalidate()` export is the client-update axis
|
|
367
|
+
(which segments re-render on a navigation or action), not a cache bust.
|
|
338
368
|
|
|
339
369
|
## Interaction with Other Caching
|
|
340
370
|
|
|
341
|
-
| Mechanism
|
|
342
|
-
|
|
|
343
|
-
| `"use cache"`
|
|
344
|
-
| `cache()` DSL
|
|
345
|
-
| `cache(
|
|
346
|
-
| `Static()`
|
|
347
|
-
| `Prerender()`
|
|
371
|
+
| Mechanism | Granularity | When | Use case |
|
|
372
|
+
| -------------------- | ------------------ | ---------- | ----------------------------------------------- |
|
|
373
|
+
| `"use cache"` | Function/component | Runtime | Cache individual data fetches or components |
|
|
374
|
+
| `cache()` DSL | Route segment | Runtime | Cache entire route subtrees with children |
|
|
375
|
+
| `cache({ ttl })` DSL | Route segment | Runtime | Cache a route subtree with explicit options |
|
|
376
|
+
| `Static()` | Route segment | Build-time | Render once, never re-render |
|
|
377
|
+
| `Prerender()` | Route segment | Build-time | Pre-render known params, optional live fallback |
|
|
348
378
|
|
|
349
379
|
## Dev Mode
|
|
350
380
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vercel
|
|
3
|
+
description: Deploy a Rango app to Vercel Functions (Build Output API v3). Use when deploying a Rango app to Vercel, or asking how the router works with Vercel Functions and the Build Output API.
|
|
4
|
+
argument-hint:
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Vercel deployment
|
|
8
|
+
|
|
9
|
+
The `vercel` preset builds like the `node` preset (Vercel runs Node Functions, not Workers): rango owns the RSC entry, folds `process.env.NODE_ENV` for the SSR/RSC build, and after `vite build` assembles a `.vercel/output` directory (Build Output API v3) from `dist/` — a single streaming Node Function plus the static client assets.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @vercel/functions
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// vite.config.ts
|
|
19
|
+
import { defineConfig } from "vite";
|
|
20
|
+
import react from "@vitejs/plugin-react";
|
|
21
|
+
import { rango } from "@rangojs/router/vite";
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
plugins: [react(), rango({ preset: "vercel" })],
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`@vercel/functions` is required: it backs the generated function launcher (`waitUntil`) and `VercelCacheStore`. The build fails with a clear error if it is missing.
|
|
29
|
+
|
|
30
|
+
`vite build` produces `.vercel/output`; deploy with the Vercel CLI (`vercel deploy --prebuilt`) or via Git integration.
|
|
31
|
+
|
|
32
|
+
## Function configuration
|
|
33
|
+
|
|
34
|
+
Per-function knobs go under `vercel` and are written into `.vc-config.json`:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
rango({
|
|
38
|
+
preset: "vercel",
|
|
39
|
+
vercel: {
|
|
40
|
+
runtime: "nodejs22.x", // default
|
|
41
|
+
maxDuration: 30, // seconds, default
|
|
42
|
+
memory: 1024, // MB (platform default when omitted)
|
|
43
|
+
regions: ["fra1"], // pin regions (platform default when omitted)
|
|
44
|
+
functionName: "index", // the <name>.func dir + config.json route
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Runtime Cache
|
|
50
|
+
|
|
51
|
+
`VercelCacheStore` wraps the Vercel Runtime Cache. Locally (no `process.env.VERCEL`) fall back to an in-memory store so dev/preview work without the platform:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import {
|
|
55
|
+
MemorySegmentCacheStore,
|
|
56
|
+
VercelCacheStore,
|
|
57
|
+
} from "@rangojs/router/cache";
|
|
58
|
+
import { getCache, waitUntil } from "@vercel/functions";
|
|
59
|
+
|
|
60
|
+
const defaults = { ttl: 60, swr: 300 };
|
|
61
|
+
const memoryStore = new MemorySegmentCacheStore({ defaults });
|
|
62
|
+
|
|
63
|
+
function resolveCache() {
|
|
64
|
+
if (process.env.VERCEL) {
|
|
65
|
+
return {
|
|
66
|
+
store: new VercelCacheStore({
|
|
67
|
+
cache: getCache({ namespace: process.env.VERCEL_DEPLOYMENT_ID }),
|
|
68
|
+
waitUntil,
|
|
69
|
+
defaults,
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { store: memoryStore };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const router = createRouter({ cache: resolveCache }).routes(/* ... */);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The cache factory receives `(env, ctx)`; on Vercel `env` is `process.env` and `ctx` is `{ waitUntil }`.
|
|
80
|
+
|
|
81
|
+
## Host routers (multi-app)
|
|
82
|
+
|
|
83
|
+
A multi-app host router deploys as a **single function** running `hostRouter.match()` for every request (mirrors the Cloudflare single-worker model). Two requirements:
|
|
84
|
+
|
|
85
|
+
1. The host module exports the `HostRouter` **instance** (default export, or a named `hostRouter`/`router` export) — not a Cloudflare-style `{ fetch }` object, because rango owns the entry and calls `match()` for you.
|
|
86
|
+
2. Point at the host entry (a host app has several `createRouter()` sub-apps, so auto-discovery can't pick one). rango auto-detects a lone `createHostRouter()` file; set `hostRouter` to be explicit:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
rango({ preset: "vercel", hostRouter: "./src/worker.rsc.tsx" });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// src/worker.rsc.tsx
|
|
94
|
+
import { createHostRouter } from "@rangojs/router/host";
|
|
95
|
+
|
|
96
|
+
export const hostRouter = createHostRouter();
|
|
97
|
+
hostRouter.host(["admin.*"]).lazy(() => import("./apps/admin/handler.js"));
|
|
98
|
+
hostRouter.host(["."]).lazy(() => import("./apps/site/handler.js"));
|
|
99
|
+
|
|
100
|
+
export default hostRouter; // the instance
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`{ env, ctx }` is threaded unchanged from the function to each matched sub-app's handler and its `cache(env, ctx)` factory. See the `host-router` skill for sub-app structure and routing patterns.
|
|
104
|
+
|
|
105
|
+
## Tracing (custom spans)
|
|
106
|
+
|
|
107
|
+
Vercel exposes tracing through OpenTelemetry. `createVercelTracing()` (from `@rangojs/router/vercel`) emits the router's `rango.*` phase spans onto the global OTel tracer that `@vercel/otel`'s `registerOTel()` installs:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// instrumentation.ts — install the provider, then export the tracing config so
|
|
111
|
+
// importing this module is what runs registerOTel(). A Rango/Vite app does NOT
|
|
112
|
+
// auto-load `instrumentation.ts` like Next.js does, so a standalone
|
|
113
|
+
// registerOTel() that nothing imports is a silent no-op.
|
|
114
|
+
import { registerOTel } from "@vercel/otel";
|
|
115
|
+
import { createVercelTracing } from "@rangojs/router/vercel";
|
|
116
|
+
registerOTel({ serviceName: "my-app" });
|
|
117
|
+
export const tracing = createVercelTracing();
|
|
118
|
+
|
|
119
|
+
// router.tsx — importing `tracing` runs instrumentation.ts (and registerOTel)
|
|
120
|
+
import { tracing } from "./instrumentation.js";
|
|
121
|
+
export const router = createRouter({ tracing }).routes(/* ... */);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`createVercelTracing(opts?)` takes `{ enabled, spans, tracerName, tracer }` — same phase set as `createCloudflareTracing` (`rango.request/middleware/action/loader/render/ssr`). Caveats: Node-runtime only (Vercel custom spans are unsupported on Edge); `registerOTel()` must run before the first request; `@vercel/otel` is what unlocks Vercel Session Tracing + Trace Drains. The deploy bundles `@vercel/otel` and its `@opentelemetry/*` peers into the function (no `node_modules` at runtime), so they must be installed. See `examples/vercel-basic` for a worked hybrid setup and the `observability` skill for the cross-platform tracing model.
|
|
125
|
+
|
|
126
|
+
## Local validation without deploying
|
|
127
|
+
|
|
128
|
+
`vite preview` serves the static client assets only. To preview the RSC **function**, serve the assembled `.vercel/output` behind filesystem-then-function routing — `examples/vercel-basic/scripts/preview.mjs` does this (and `pnpm preview:vercel` runs it). For a faithful deploy test (isolated filesystem, ESM, self-contained bundle), `examples/vercel-basic/scripts/smoke.mjs` serves it from a temp dir outside the repo. Both share `scripts/serve-vercel-output.mjs`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: view-transitions
|
|
3
|
-
description: Configure React View Transitions on layouts, routes, and parallel slots in @rangojs/router
|
|
3
|
+
description: Configure React View Transitions on layouts, routes, and parallel slots in @rangojs/router. Use when navigation should animate smoothly between pages, or wiring up React View Transitions on a route or layout.
|
|
4
4
|
argument-hint: [layout|route|parallel|intercept]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -209,6 +209,10 @@ interface TransitionConfig {
|
|
|
209
209
|
default?: string | Record<string, string>; // fallback for any phase
|
|
210
210
|
name?: string; // explicit view-transition-name
|
|
211
211
|
viewTransition?: "auto" | false; // boundary opt-out (see below)
|
|
212
|
+
// Conditional gate, evaluated server-side AFTER the route handler. Return
|
|
213
|
+
// false to drop this transition for the request, so the navigation streams its
|
|
214
|
+
// loading() fallback instead of holding. See the gate section below.
|
|
215
|
+
when?: (ctx: TransitionWhenContext) => boolean;
|
|
212
216
|
}
|
|
213
217
|
```
|
|
214
218
|
|
|
@@ -217,6 +221,45 @@ interface TransitionConfig {
|
|
|
217
221
|
- `name` lets you participate in cross-page morphs by name (advanced; you usually don't need this on a layout/route-level wrap).
|
|
218
222
|
- `viewTransition` toggles whether rango places its own `<ViewTransition>` boundary. `"auto"` (default) wraps as described above; `false` opts out — see the next section.
|
|
219
223
|
|
|
224
|
+
## Conditional transitions (`when`)
|
|
225
|
+
|
|
226
|
+
`transition({ when })` gates the hold per request. The predicate runs **server-side, AFTER the route handler** and outside any cache scope; return `false` to drop this segment's transition for the request (the navigation streams its `loading()` fallback instead of holding).
|
|
227
|
+
|
|
228
|
+
Its context mirrors the `revalidate()` predicate args — the same navigation/action metadata — plus `get`/`env` for post-handler reads:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import type { TransitionWhenContext } from "@rangojs/router";
|
|
232
|
+
|
|
233
|
+
// Hold only when the handler marked this request (handler sets, gate reads):
|
|
234
|
+
transition({ when: (ctx) => ctx.get(KeepScroll) === true });
|
|
235
|
+
|
|
236
|
+
// Hold only when arriving from a specific page (the navigation SOURCE):
|
|
237
|
+
transition({
|
|
238
|
+
when: ({ currentUrl }) => currentUrl?.pathname.startsWith("/list") === true,
|
|
239
|
+
});
|
|
240
|
+
transition({ when: ({ fromRouteName }) => fromRouteName === "products.list" });
|
|
241
|
+
|
|
242
|
+
// Hold only after a specific action revalidated the route:
|
|
243
|
+
transition({
|
|
244
|
+
when: ({ actionId }) => actionId === "src/actions/cart.ts#addToCart",
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
| field | meaning | populated |
|
|
249
|
+
| ------------------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
250
|
+
| `currentUrl` / `currentParams` / `fromRouteName` | navigation **source** | soft nav + action-success; `undefined` on initial full load and action/PE error paths |
|
|
251
|
+
| `nextUrl` / `nextParams` | navigation **target** | always |
|
|
252
|
+
| `toRouteName` (and `fromRouteName`) | route **name** | when the route is named (undefined for unnamed/auto-generated) |
|
|
253
|
+
| `actionId` / `actionUrl` / `actionResult` / `formData` | the server action that triggered this render | action-triggered renders only |
|
|
254
|
+
| `method` | `"GET"` (nav) / `"POST"` (action) | always |
|
|
255
|
+
| `get` / `env` | read handler/middleware vars + app env | always |
|
|
256
|
+
|
|
257
|
+
A predicate that throws is reported to `router.onError` (phase `"rendering"`) and treated as no-hold (conservative).
|
|
258
|
+
|
|
259
|
+
**Same-route content-holds need the transition present on the FIRST render.** The same-route hold works by giving the route a param-agnostic key so a param change reconciles instead of remounting — but that key is established when the route first mounts. A source gate that returns `false` on the initial full load (where `currentUrl`/`currentParams`/`fromRouteName` are undefined) drops the transition before the route mounts, so the route mounts _outside_ a transition scope and **every** later same-route param nav remounts (flashing the skeleton) regardless of what the gate decides on those navs. Write source gates so they hold when there is no source — e.g. `({ currentParams }) => currentParams?.tab !== "raw"` (true on the initial load) rather than `=== "details"` (false on the initial load) — when the same-route content-hold must engage. This only affects same-route param navigations; action-only or cross-route gating is unaffected (no shared param key is in play).
|
|
260
|
+
|
|
261
|
+
**Prefetch / cache caveat.** The gate runs during resolution, so a **prefetched** navigation decides at prefetch time — `currentUrl`/`currentParams`/`fromRouteName` reflect the page the prefetch fired from, not necessarily the click-time source — and a `cache()`/prerender hit replays the stored transition without re-running the predicate. A source-sensitive gate can therefore be frozen to prefetch/store-time state. This covers ~99% of navigations; if yours must reflect the exact click-time source, source-scope the prefetch (`<Link prefetchKey=":source">`) and don't `cache()` that segment.
|
|
262
|
+
|
|
220
263
|
## Opting out of the router boundary (place your own `<ViewTransition>`)
|
|
221
264
|
|
|
222
265
|
By default a `transition()` segment gets a rango-placed `<ViewTransition>` boundary — a cross-fade of the whole outlet/route. If you'd rather animate specific elements yourself (place `<ViewTransition name="...">` in your components), set `viewTransition: false`. The router then contributes **no boundary of its own** but still:
|