@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,228 @@
|
|
|
1
|
+
# State and Cache Control Hooks
|
|
2
|
+
|
|
3
|
+
## State Hooks
|
|
4
|
+
|
|
5
|
+
### useLocationState()
|
|
6
|
+
|
|
7
|
+
Read type-safe state from history:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
"use client";
|
|
11
|
+
import { useLocationState, createLocationState } from "@rangojs/router/client";
|
|
12
|
+
|
|
13
|
+
// Define typed state (all export patterns supported)
|
|
14
|
+
// Keys are auto-injected by the Vite plugin -- no manual key needed.
|
|
15
|
+
export const ProductState = createLocationState<{
|
|
16
|
+
name: string;
|
|
17
|
+
price: number;
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
// Also valid: const ProductState = createLocationState<...>();
|
|
21
|
+
// export { ProductState };
|
|
22
|
+
// Also valid: export { ProductState as MyState };
|
|
23
|
+
|
|
24
|
+
function ProductHeader() {
|
|
25
|
+
const state = useLocationState(ProductState);
|
|
26
|
+
// { name: string; price: number } | undefined
|
|
27
|
+
|
|
28
|
+
if (state) {
|
|
29
|
+
return (
|
|
30
|
+
<h1>
|
|
31
|
+
{state.name} - ${state.price}
|
|
32
|
+
</h1>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return <h1>Loading...</h1>;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Pass state through Link:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Link } from "@rangojs/router/client";
|
|
43
|
+
import { ProductState } from "./state";
|
|
44
|
+
|
|
45
|
+
<Link to="/product/123" state={[ProductState({ name: "Widget", price: 99 })]}>
|
|
46
|
+
View Product
|
|
47
|
+
</Link>;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Pass typed state just in time (getter evaluated at click time, not render time):
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
"use client"; // JIT state requires a client component (getter can't cross RSC boundary)
|
|
54
|
+
|
|
55
|
+
import { Link } from "@rangojs/router/client";
|
|
56
|
+
import { ProductState } from "./state";
|
|
57
|
+
|
|
58
|
+
// The getter is stored lazily and only called when the user clicks the link.
|
|
59
|
+
// This is useful for capturing values that change after render (e.g., scroll
|
|
60
|
+
// position, form state, ref values).
|
|
61
|
+
<Link
|
|
62
|
+
to="/product/123"
|
|
63
|
+
state={[ProductState(() => ({ name: product.name, price: product.price }))]}
|
|
64
|
+
>
|
|
65
|
+
View Product
|
|
66
|
+
</Link>;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Plain state can also be evaluated just in time (also requires a client component):
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<Link to="/product/123" state={() => ({ from: window.location.pathname })}>
|
|
73
|
+
View Product
|
|
74
|
+
</Link>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Flash State (read-once)
|
|
78
|
+
|
|
79
|
+
Create a location state with `{ flash: true }` for read-once state that
|
|
80
|
+
auto-clears after first render. Ideal for flash messages (success/error
|
|
81
|
+
notifications after redirect):
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// location-states.ts
|
|
85
|
+
import { createLocationState } from "@rangojs/router";
|
|
86
|
+
|
|
87
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
88
|
+
flash: true,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Read flash state with `useLocationState` (same hook as persistent state):
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
"use client";
|
|
96
|
+
import { useLocationState } from "@rangojs/router/client";
|
|
97
|
+
import { FlashMessage } from "../location-states";
|
|
98
|
+
|
|
99
|
+
function FlashBanner() {
|
|
100
|
+
const flash = useLocationState(FlashMessage);
|
|
101
|
+
// { text: string } | undefined
|
|
102
|
+
|
|
103
|
+
if (!flash) return null;
|
|
104
|
+
return <div className="flash">{flash.text}</div>;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Flash behavior is determined by the definition (`{ flash: true }`), not by which
|
|
109
|
+
hook reads it. `useLocationState` reads the value synchronously during render,
|
|
110
|
+
then clears it from `history.state` via `replaceState` in a `useEffect`.
|
|
111
|
+
Multiple components reading the same flash definition all see the value.
|
|
112
|
+
Pressing back/forward will not re-show the flash since it was cleared.
|
|
113
|
+
|
|
114
|
+
Set flash state from the server via `redirect()` with state:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// In a route handler
|
|
118
|
+
import { redirect, createLocationState } from "@rangojs/router";
|
|
119
|
+
|
|
120
|
+
export const FlashMessage = createLocationState<{ text: string }>({
|
|
121
|
+
flash: true,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Handler
|
|
125
|
+
(ctx) => {
|
|
126
|
+
return redirect("/dashboard", {
|
|
127
|
+
state: [FlashMessage({ text: "Item saved!" })],
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Or via `ctx.setLocationState()` on any response:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
(ctx) => {
|
|
136
|
+
ctx.setLocationState(FlashMessage({ text: "Welcome back!" }));
|
|
137
|
+
return <Dashboard />;
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### .read() (non-hook access)
|
|
142
|
+
|
|
143
|
+
Read current location state outside React components (client-side only):
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { FlashMessage, ProductState } from "../location-states";
|
|
147
|
+
|
|
148
|
+
// Returns TState | undefined. Returns undefined during SSR.
|
|
149
|
+
const flash = FlashMessage.read();
|
|
150
|
+
const product = ProductState.read();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
> **Hydration:** `.read()` returns `undefined` on the server but may return
|
|
154
|
+
> a real value on the first client render (history state survives reload).
|
|
155
|
+
> Do not call `.read()` directly during the initial render of a component;
|
|
156
|
+
> call it from an event handler or inside a `useEffect` post-mount. For
|
|
157
|
+
> reactive hydration-safe access, use `useLocationState()` instead.
|
|
158
|
+
|
|
159
|
+
### .write() / .delete() (static, non-reactive)
|
|
160
|
+
|
|
161
|
+
Static counterparts to `.read()`. Both mutate the current history entry's
|
|
162
|
+
`history.state` via `replaceState`, preserving any other keys (router
|
|
163
|
+
bookkeeping, other location state slots). Both are client-only; they throw
|
|
164
|
+
when called on the server.
|
|
165
|
+
|
|
166
|
+
Neither dispatches an event, so components reading via `useLocationState`
|
|
167
|
+
will NOT re-render until the next navigation/popstate. Pair with `.read()`
|
|
168
|
+
(or a fresh mount via back/forward/reload) instead.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
"use client";
|
|
172
|
+
import { ProductState } from "./state";
|
|
173
|
+
|
|
174
|
+
// Persisted across hard refresh and back/forward of this entry.
|
|
175
|
+
ProductState.write({ name: "Widget", price: 9.99 });
|
|
176
|
+
|
|
177
|
+
// Read later (or on next mount).
|
|
178
|
+
const current = ProductState.read();
|
|
179
|
+
|
|
180
|
+
// Manually clear the slot. Idempotent if it isn't set.
|
|
181
|
+
ProductState.delete();
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Method | Updates `history.state` | Fires `useLocationState` rerender | SSR behavior |
|
|
185
|
+
| ----------- | ----------------------- | --------------------------------- | ------------------- |
|
|
186
|
+
| `.read()` | no | n/a (returns snapshot) | returns `undefined` |
|
|
187
|
+
| `.write()` | yes (replace this slot) | no | throws |
|
|
188
|
+
| `.delete()` | yes (remove this slot) | no | throws |
|
|
189
|
+
|
|
190
|
+
## Cache Control
|
|
191
|
+
|
|
192
|
+
### invalidateClientCache()
|
|
193
|
+
|
|
194
|
+
Force the client's caches to miss after a mutation the router can't see (a REST
|
|
195
|
+
call, a WebSocket push, a login). It is a plain function, not a hook, so it works
|
|
196
|
+
from module-level callbacks too. Imported from the root entry `@rangojs/router`,
|
|
197
|
+
it is selected by export conditions: in a client component it marks the caches
|
|
198
|
+
stale immediately; from a handler/server component it writes a rotated
|
|
199
|
+
`Set-Cookie` for the responding client.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
"use client";
|
|
203
|
+
import { invalidateClientCache } from "@rangojs/router";
|
|
204
|
+
|
|
205
|
+
function SaveButton() {
|
|
206
|
+
const handleSave = async () => {
|
|
207
|
+
await fetch("/api/data", {
|
|
208
|
+
method: "POST",
|
|
209
|
+
body: JSON.stringify(data),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Invalidate the client's caches after the mutation
|
|
213
|
+
invalidateClientCache();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return <button onClick={handleSave}>Save</button>;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
A module-level subscription works the same way (no component needed):
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { invalidateClientCache } from "@rangojs/router";
|
|
224
|
+
|
|
225
|
+
socket.on("catalog-updated", () => invalidateClientCache());
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Use cases**: REST API mutations, WebSocket updates, non-RSC data changes.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# URL Hooks
|
|
2
|
+
|
|
3
|
+
### useParams()
|
|
4
|
+
|
|
5
|
+
Access route params from the current URL:
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
"use client";
|
|
9
|
+
import { useParams } from "@rangojs/router/client";
|
|
10
|
+
|
|
11
|
+
// Route: /product/:productId
|
|
12
|
+
function ProductPage() {
|
|
13
|
+
const params = useParams();
|
|
14
|
+
// { productId: "123" }
|
|
15
|
+
|
|
16
|
+
return <h1>Product {params.productId}</h1>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Annotate the expected shape via a generic
|
|
20
|
+
function ProductPageTyped() {
|
|
21
|
+
const { productId } = useParams<{ productId: string }>();
|
|
22
|
+
return <h1>Product {productId}</h1>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// With selector for performance (re-renders only when selected value changes)
|
|
26
|
+
function ProductId() {
|
|
27
|
+
const productId = useParams((p) => p.productId);
|
|
28
|
+
return <span>ID: {productId}</span>;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Returns merged params from all matched route segments as a `Readonly<T>` map. Updates on navigation commit (not during pending navigation).
|
|
33
|
+
|
|
34
|
+
### usePathname()
|
|
35
|
+
|
|
36
|
+
Access the current URL pathname:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
"use client";
|
|
40
|
+
import { usePathname } from "@rangojs/router/client";
|
|
41
|
+
|
|
42
|
+
function CurrentPage() {
|
|
43
|
+
const pathname = usePathname();
|
|
44
|
+
// "/product/123" (no search params)
|
|
45
|
+
|
|
46
|
+
return <span>Current path: {pathname}</span>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Returns the pathname string without search params or hash. Updates on navigation commit.
|
|
51
|
+
|
|
52
|
+
### useSearchParams()
|
|
53
|
+
|
|
54
|
+
Access the current URL search params:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
"use client";
|
|
58
|
+
import { useSearchParams } from "@rangojs/router/client";
|
|
59
|
+
|
|
60
|
+
function SearchResults() {
|
|
61
|
+
const searchParams = useSearchParams();
|
|
62
|
+
const query = searchParams.get("q"); // "react"
|
|
63
|
+
const page = searchParams.get("page"); // "2"
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div>
|
|
67
|
+
Searching for: {query}, page {page}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Returns a `ReadonlyURLSearchParams` (URLSearchParams without mutation methods). During SSR, returns empty params and syncs from the browser URL on mount.
|
|
74
|
+
|
|
75
|
+
### useHref()
|
|
76
|
+
|
|
77
|
+
Mount-aware href for client components inside `include()` scopes:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
"use client";
|
|
81
|
+
import { useHref, href, Link } from "@rangojs/router/client";
|
|
82
|
+
|
|
83
|
+
// Inside include("/shop", shopPatterns)
|
|
84
|
+
function ShopNav() {
|
|
85
|
+
const href = useHref();
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<>
|
|
89
|
+
{/* Local paths - auto-prefixed with /shop */}
|
|
90
|
+
<Link to={href("/cart")}>Cart</Link>
|
|
91
|
+
<Link to={href("/product/widget")}>Widget</Link>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use `useHref()` for local navigation. Use the bare `href()` function for absolute paths.
|
|
98
|
+
|
|
99
|
+
### useMount()
|
|
100
|
+
|
|
101
|
+
Returns the current `include()` mount path:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
"use client";
|
|
105
|
+
import { useMount } from "@rangojs/router/client";
|
|
106
|
+
|
|
107
|
+
function MountInfo() {
|
|
108
|
+
const mount = useMount(); // "/shop" inside include("/shop", ...)
|
|
109
|
+
return <span>Mounted at: {mount}</span>;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### useReverse(routes)
|
|
114
|
+
|
|
115
|
+
Mount-aware local reverse for client components. Import the generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("name", params?)` — the leading dot is optional. Auto-fills params from `useParams()`; explicit params override.
|
|
116
|
+
|
|
117
|
+
> Per-module `*.gen.ts` files are **CLI opt-in and not Vite-watched** — run `rango generate <urls-file>` (or wire it into `predev`) and re-run it whenever the module's routes change. See `/links` for the full generated-file setup and exposure-boundary rules.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
"use client";
|
|
121
|
+
import { Link, useReverse } from "@rangojs/router/client";
|
|
122
|
+
import { routes as blogRoutes } from "../urls/blog.gen.js";
|
|
123
|
+
|
|
124
|
+
function BlogNav() {
|
|
125
|
+
const reverse = useReverse(blogRoutes);
|
|
126
|
+
return (
|
|
127
|
+
<nav>
|
|
128
|
+
<Link to={reverse("index")}>Blog</Link>
|
|
129
|
+
<Link to={reverse("post", { postId: "hello" })}>Post</Link>
|
|
130
|
+
</nav>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
See `/links` for the full URL generation guide. `ctx.reverse()` is server-only; on the client, prefer `useReverse(routes)` for in-module names and pass URLs as props for cross-module ones.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: host-router
|
|
3
|
-
description: Multi-app host routing with domain/subdomain patterns
|
|
3
|
+
description: Multi-app host routing with domain/subdomain patterns. Use when running multiple apps behind one domain or across subdomains, or routing requests to different apps based on hostname.
|
|
4
4
|
argument-hint:
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -33,6 +33,48 @@ export default {
|
|
|
33
33
|
};
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## Deploying: Cloudflare vs node/vercel
|
|
37
|
+
|
|
38
|
+
How a host router is _served_ depends on the preset, because the preset decides who owns the server entry.
|
|
39
|
+
|
|
40
|
+
| Preset | Who owns the entry | What the host module exports |
|
|
41
|
+
| ----------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `cloudflare` | You (your `worker.rsc.tsx`) | `export default { fetch(request, env, ctx) { return router.match(request, { env, ctx }); } }` |
|
|
43
|
+
| `node` / `vercel` | rango (generated RSC entry) | `export default router;` (the `HostRouter` instance itself), or a named `export const hostRouter`/`router`. |
|
|
44
|
+
|
|
45
|
+
On `node`/`vercel`, rango generates the served RSC entry, so it needs the `HostRouter` **instance** to call `hostRouter.match()` for you. Export the instance, not a `{ fetch }` object:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// src/worker.rsc.tsx (node / vercel)
|
|
49
|
+
import { createHostRouter } from "@rangojs/router/host";
|
|
50
|
+
|
|
51
|
+
export const hostRouter = createHostRouter();
|
|
52
|
+
hostRouter.host(["admin.*"]).lazy(() => import("./apps/admin/handler.js"));
|
|
53
|
+
hostRouter.host(["."]).lazy(() => import("./apps/site/handler.js"));
|
|
54
|
+
|
|
55
|
+
// Export the instance — the generated entry serves it via hostRouter.match().
|
|
56
|
+
export default hostRouter;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Each sub-app exports a handler exactly as on Cloudflare (no change):
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// src/apps/admin/handler.ts
|
|
63
|
+
import { router } from "./router.js";
|
|
64
|
+
export default (request: Request, input: any) => router.fetch(request, input);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Selecting the host entry — a host app has several `createRouter()` sub-apps, so single-router auto-discovery can't pick one. Either let rango auto-detect the lone `createHostRouter()` file, or point at it explicitly:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// vite.config.ts
|
|
71
|
+
rango({ preset: "vercel", hostRouter: "./src/worker.rsc.tsx" });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
On Vercel this is a single function running `hostRouter.match()` for every request (mirrors the Cloudflare single-worker model); `{ env, ctx }` (`process.env` + `{ waitUntil }`) is threaded unchanged to each matched sub-app's handler and `cache(env, ctx)` factory. See the `vercel` skill.
|
|
75
|
+
|
|
76
|
+
Unmatched hosts on node/vercel: because rango owns the generated entry (you have no worker `try/catch`), it catches `NoRouteMatchError` and returns **404** by default — so you do **not** need a catch-all host route. If you want different behavior (a branded 404, a redirect, a default app), register a catch-all mount as the **last** route, e.g. `host(["**"]).lazy(() => import("./apps/site/handler.js"))` — it matches any host, so the built-in 404 only fires when nothing matched at all. (Note `fallback()` is for cookie-override errors, not general unmatched hosts.)
|
|
77
|
+
|
|
36
78
|
## Inline handlers (`.map`) vs lazy mounts (`.lazy`)
|
|
37
79
|
|
|
38
80
|
A host pattern maps to one of two things, and you pick the method by intent:
|
|
@@ -66,7 +108,7 @@ Why two methods instead of one overloaded `.map()`:
|
|
|
66
108
|
| `.` or `*` | Any apex domain (`example.com`) |
|
|
67
109
|
| `**` | Any domain (apex + all subdomains) |
|
|
68
110
|
| `*.` | Any single-level subdomain (`www.example.com`) |
|
|
69
|
-
|
|
|
111
|
+
| `**.` | Any multi-level subdomain (`a.b.example.com`) |
|
|
70
112
|
| `example.com` | Exact domain |
|
|
71
113
|
| `*.com` | Any apex `.com` domain |
|
|
72
114
|
| `*.example.com` | Single subdomain of `example.com` |
|
|
@@ -134,17 +176,17 @@ router.fallback().map((request) => {
|
|
|
134
176
|
});
|
|
135
177
|
```
|
|
136
178
|
|
|
137
|
-
For unmatched hosts without `hostOverride`, catch `NoRouteMatchError` in your worker fetch
|
|
179
|
+
For unmatched hosts without `hostOverride`, catch `NoRouteMatchError` in your worker fetch. Use the `isNoRouteMatchError()` guard rather than a bare `instanceof`: a workspace with a duplicated `@rangojs/router` copy can throw the error with a different class identity, and `instanceof` would then turn the 404 into an opaque 500.
|
|
138
180
|
|
|
139
181
|
```typescript
|
|
140
|
-
import {
|
|
182
|
+
import { isNoRouteMatchError } from "@rangojs/router/host";
|
|
141
183
|
|
|
142
184
|
export default {
|
|
143
185
|
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
144
186
|
try {
|
|
145
187
|
return await router.match(request, { env, ctx });
|
|
146
188
|
} catch (err) {
|
|
147
|
-
if (err
|
|
189
|
+
if (isNoRouteMatchError(err)) {
|
|
148
190
|
return new Response("Not Found", { status: 404 });
|
|
149
191
|
}
|
|
150
192
|
throw err;
|
|
@@ -189,12 +231,26 @@ Logs pattern matching, route registration, and cookie override decisions to cons
|
|
|
189
231
|
## Testing
|
|
190
232
|
|
|
191
233
|
```typescript
|
|
192
|
-
import {
|
|
234
|
+
import {
|
|
235
|
+
createTestRequest,
|
|
236
|
+
testPattern,
|
|
237
|
+
matchesHost,
|
|
238
|
+
} from "@rangojs/router/host/testing";
|
|
193
239
|
|
|
194
|
-
// Test pattern matching
|
|
240
|
+
// Test pattern matching (host-only)
|
|
195
241
|
testPattern("admin.*", "admin.example.com"); // true
|
|
196
242
|
testPattern([".", "www.*"], "example.com"); // true
|
|
197
243
|
|
|
244
|
+
// Path-based patterns need the third pathname arg (defaults to "/", so a
|
|
245
|
+
// host-only pattern still works with two args):
|
|
246
|
+
testPattern("**.workers.dev/admin", "foo.workers.dev", "/admin"); // true
|
|
247
|
+
|
|
248
|
+
// Or match a pattern against a real Request (hostname + pathname from the URL):
|
|
249
|
+
matchesHost(
|
|
250
|
+
"**.workers.dev/admin",
|
|
251
|
+
new Request("https://foo.workers.dev/admin"),
|
|
252
|
+
); // true
|
|
253
|
+
|
|
198
254
|
// Create requests for integration tests
|
|
199
255
|
const request = createTestRequest({
|
|
200
256
|
host: "admin.example.com",
|
|
@@ -241,3 +297,24 @@ export default regional;
|
|
|
241
297
|
// host-router.ts
|
|
242
298
|
router.host(["**.regional.example.com"]).lazy(() => import("./apps/regional"));
|
|
243
299
|
```
|
|
300
|
+
|
|
301
|
+
## Cross-app navigation is a full document load
|
|
302
|
+
|
|
303
|
+
A client-side navigation that crosses an app boundary (e.g. a `<Link>` or
|
|
304
|
+
intercepted `<a>` from the app at `/` into an app mounted at `/shop`) is a **hard
|
|
305
|
+
document navigation**, not a soft in-tree swap. When the server sees a partial
|
|
306
|
+
(SPA) request whose router id doesn't match the matched app, it returns
|
|
307
|
+
`X-RSC-Reload` and the client does a real document navigation to the target.
|
|
308
|
+
|
|
309
|
+
Why a reload rather than a soft swap: a soft swap can't faithfully re-establish
|
|
310
|
+
the target app's **document-level** state. Stylesheets shared across apps are
|
|
311
|
+
dropped by React 19's by-`href` resource dedup; and theme, warmup, and
|
|
312
|
+
prefetch-TTL are document-lifetime (captured once at load — see
|
|
313
|
+
`browser/app-shell.ts`), so the target app's config would never take effect. A
|
|
314
|
+
full document load re-establishes the target app's entire document — CSS, theme,
|
|
315
|
+
meta, everything — by construction. So you do **not** need to coordinate
|
|
316
|
+
stylesheet `href`s, `precedence`, theme config, etc. across independently-authored
|
|
317
|
+
apps; each app owns its own document.
|
|
318
|
+
|
|
319
|
+
**Within-app** navigation is unchanged — a normal soft SPA update (the document
|
|
320
|
+
stays mounted). Only crossing an app boundary triggers the reload.
|
package/skills/i18n/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: i18n
|
|
3
|
-
description: Locale-aware routing with `include("/:locale?", ...)`, locale resolution chains, and react-intl integration
|
|
3
|
+
description: Locale-aware routing with `include("/:locale?", ...)`, locale resolution chains, and react-intl integration. Use when building a multi-language app, routes need a locale segment, or wiring up react-intl translations.
|
|
4
4
|
argument-hint: "[topic]"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: intercept
|
|
3
|
-
description: Define intercept routes for modals, slide-overs, and soft navigation patterns in @rangojs/router
|
|
3
|
+
description: Define intercept routes for modals, slide-overs, and soft navigation patterns in @rangojs/router. Use when opening a route as a modal/overlay on top of the current page while keeping the URL shareable, or asking "how do I show this page in a modal".
|
|
4
4
|
argument-hint: [@slot-name] [route-to-intercept]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,6 +8,13 @@ argument-hint: [@slot-name] [route-to-intercept]
|
|
|
8
8
|
|
|
9
9
|
Intercept routes render a different component during soft navigation (client-side) while preserving the background route. Hard navigation (direct URL) shows the full page.
|
|
10
10
|
|
|
11
|
+
## Not this skill if…
|
|
12
|
+
|
|
13
|
+
- You want a slot that ALWAYS renders alongside the page (sidebar, multi-column
|
|
14
|
+
layout) — that is a permanent `parallel()` slot: see `/parallel`.
|
|
15
|
+
- You want the same component regardless of soft vs hard navigation —
|
|
16
|
+
intercepts only swap on soft navigation; see `/parallel`.
|
|
17
|
+
|
|
11
18
|
## Basic Intercept
|
|
12
19
|
|
|
13
20
|
```typescript
|
|
@@ -23,7 +30,7 @@ function ShopLayout() {
|
|
|
23
30
|
);
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
export const urlpatterns = urls(({ path, layout, intercept, loader }) => [
|
|
33
|
+
export const urlpatterns = urls(({ path, layout, intercept, loader, loading }) => [
|
|
27
34
|
layout(<ShopLayout />, () => [
|
|
28
35
|
// Intercept product detail - shows modal during soft navigation
|
|
29
36
|
intercept(
|
|
@@ -107,8 +114,10 @@ Use named revalidation contracts on both the outer producer and the intercept
|
|
|
107
114
|
consumer when they share `ctx.set()` data:
|
|
108
115
|
|
|
109
116
|
```typescript
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
import * as ProductActions from "./actions/product";
|
|
118
|
+
|
|
119
|
+
export const revalidateProductShell = (ctx) =>
|
|
120
|
+
ctx.isAction(ProductActions) || undefined;
|
|
112
121
|
|
|
113
122
|
layout(ProductLayout, () => [
|
|
114
123
|
revalidate(revalidateProductShell), // producer reruns
|
|
@@ -140,18 +149,41 @@ layout(ProductLayout, () => [
|
|
|
140
149
|
]);
|
|
141
150
|
```
|
|
142
151
|
|
|
143
|
-
## Conditional Intercept with when
|
|
152
|
+
## Conditional Intercept with the `when` config
|
|
144
153
|
|
|
145
|
-
Only intercept based on navigation context
|
|
154
|
+
Only intercept based on navigation context. `when` is the 4th argument
|
|
155
|
+
(an `InterceptConfig` object); the other use-items go in the 5th-argument
|
|
156
|
+
callback.
|
|
146
157
|
|
|
147
158
|
```typescript
|
|
148
159
|
intercept(
|
|
149
160
|
"@modal",
|
|
150
161
|
"product",
|
|
151
162
|
<ProductModal />,
|
|
163
|
+
// Only intercept when coming from a different section
|
|
164
|
+
{ when: ({ from }) => !from.pathname.startsWith("/shop/product/") },
|
|
165
|
+
() => [
|
|
166
|
+
loader(ProductLoader),
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`when` is a match-time selector receiving `{ from, to, params, segments, ... }`.
|
|
172
|
+
Pass an array of predicates for AND logic (all must return true). Omit `when`
|
|
173
|
+
entirely and the intercept always activates.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
intercept(
|
|
177
|
+
"@modal",
|
|
178
|
+
"product",
|
|
179
|
+
<ProductModal />,
|
|
180
|
+
{
|
|
181
|
+
when: [
|
|
182
|
+
({ from }) => from.pathname.startsWith("/shop"),
|
|
183
|
+
({ params }) => params.slug !== "featured",
|
|
184
|
+
],
|
|
185
|
+
},
|
|
152
186
|
() => [
|
|
153
|
-
// Only intercept when coming from a different section
|
|
154
|
-
when(({ from }) => !from.pathname.startsWith("/shop/product/")),
|
|
155
187
|
loader(ProductLoader),
|
|
156
188
|
]
|
|
157
189
|
)
|
|
@@ -240,10 +272,13 @@ layout(ShopLayout, () => [
|
|
|
240
272
|
]),
|
|
241
273
|
|
|
242
274
|
// This intercept is also pre-rendered at build time
|
|
243
|
-
intercept(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
275
|
+
intercept(
|
|
276
|
+
"@modal",
|
|
277
|
+
".detail",
|
|
278
|
+
<ProductModal />,
|
|
279
|
+
{ when: ({ from }) => from.pathname.startsWith("/shop") },
|
|
280
|
+
() => [loader(ProductLoader)],
|
|
281
|
+
),
|
|
247
282
|
])
|
|
248
283
|
```
|
|
249
284
|
|
|
@@ -251,8 +286,8 @@ Build-time behavior:
|
|
|
251
286
|
|
|
252
287
|
- The intercept handler (`<ProductModal />`) is resolved with BuildContext
|
|
253
288
|
- Result is stored under the key `"detail/paramHash/i"` (intercept variant)
|
|
254
|
-
- `when
|
|
255
|
-
- `when
|
|
289
|
+
- `when` config conditions are skipped at build time (all intercepts pre-rendered unconditionally)
|
|
290
|
+
- `when` is still evaluated at runtime by the intercept-resolution middleware
|
|
256
291
|
|
|
257
292
|
Runtime behavior:
|
|
258
293
|
|
|
@@ -303,7 +338,6 @@ export const shopPatterns = urls(({
|
|
|
303
338
|
intercept,
|
|
304
339
|
loader,
|
|
305
340
|
loading,
|
|
306
|
-
when,
|
|
307
341
|
}) => [
|
|
308
342
|
layout(<ShopLayout />, () => [
|
|
309
343
|
parallel({
|
|
@@ -315,8 +349,8 @@ export const shopPatterns = urls(({
|
|
|
315
349
|
"@modal",
|
|
316
350
|
"product", // Route name (without prefix)
|
|
317
351
|
<ProductModalContent />,
|
|
352
|
+
{ when: ({ from }) => !from.pathname.startsWith("/shop/product/") },
|
|
318
353
|
() => [
|
|
319
|
-
when(({ from }) => !from.pathname.startsWith("/shop/product/")),
|
|
320
354
|
layout(<ModalWrapper />),
|
|
321
355
|
loading(<ProductModalSkeleton />),
|
|
322
356
|
loader(ProductLoader, () => [cache()]),
|
|
@@ -336,7 +370,7 @@ export const shopPatterns = urls(({
|
|
|
336
370
|
|
|
337
371
|
## Handler-attached `.use`
|
|
338
372
|
|
|
339
|
-
Intercept handlers can carry their own middleware, loaders, loading state, error/notFound boundaries, and even nested `layout`/`route
|
|
373
|
+
Intercept handlers can carry their own middleware, loaders, loading state, error/notFound boundaries, and even nested `layout`/`route` defaults via `.use` — useful for self-contained modal components that travel with their own data and chrome. (Conditional activation is set via the `when` config on the mount-site `intercept()` call, not inside `.use`.)
|
|
340
374
|
|
|
341
375
|
```typescript
|
|
342
376
|
const QuickViewModal: Handler = async (ctx) => {
|