@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +60 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +22 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +3 -6
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- 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 +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -11
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- 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 +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +20 -58
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -47
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -22,9 +22,9 @@ import { createHostRouter } from "@rangojs/router/host";
|
|
|
22
22
|
|
|
23
23
|
const router = createHostRouter();
|
|
24
24
|
|
|
25
|
-
router.host(["."]).
|
|
26
|
-
router.host(["admin.*"]).
|
|
27
|
-
router.host(["api.*"]).
|
|
25
|
+
router.host(["."]).lazy(() => import("./apps/main"));
|
|
26
|
+
router.host(["admin.*"]).lazy(() => import("./apps/admin"));
|
|
27
|
+
router.host(["api.*"]).lazy(() => import("./apps/api"));
|
|
28
28
|
|
|
29
29
|
export default {
|
|
30
30
|
fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
@@ -33,7 +33,71 @@ export default {
|
|
|
33
33
|
};
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
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", host: "./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
|
+
## Inline handlers (`.map`) vs lazy mounts (`.lazy`)
|
|
77
|
+
|
|
78
|
+
A host pattern maps to one of two things, and you pick the method by intent:
|
|
79
|
+
|
|
80
|
+
| Method | Argument | Use for |
|
|
81
|
+
| ------- | ------------------------------ | ------------------------------------------------------------ |
|
|
82
|
+
| `.map` | `(request, input) => Response` | An inline request handler that produces a response directly. |
|
|
83
|
+
| `.lazy` | `() => import("./sub-app")` | A lazily-imported handler or nested host router (a sub-app). |
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Lazy mount: the module's default export is a handler or a HostRouter.
|
|
87
|
+
router.host(["admin.*"]).lazy(() => import("./apps/admin"));
|
|
88
|
+
|
|
89
|
+
// Inline handler: returns a Response itself (sync or async).
|
|
90
|
+
router.host(["health.*"]).map(() => new Response("ok"));
|
|
91
|
+
router
|
|
92
|
+
.host(["echo.*"])
|
|
93
|
+
.map((request) => new Response(new URL(request.url).pathname));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Why two methods instead of one overloaded `.map()`:
|
|
97
|
+
|
|
98
|
+
- **Build-time discovery** invokes only `.lazy()` mounts (to trigger each sub-app's `createRouter()` registration). Inline `.map()` handlers are never invoked during discovery, so they can't crash it or pollute its errors.
|
|
99
|
+
- `.map(() => import("./sub-app"))` is a **type error** — a lazy import resolves to a module, not a `Response`. Use `.lazy()` for imports. (If the types are bypassed, e.g. from JS, a `.map()` handler that resolves to a module throws a clear `HostRouterError` at request time instead of returning the module.)
|
|
100
|
+
- A lazy loader may declare an ignored parameter (`.lazy((_request?) => import("./x"))`); `.lazy()` accepts it because intent is explicit, not inferred from the signature.
|
|
37
101
|
|
|
38
102
|
## Pattern Syntax
|
|
39
103
|
|
|
@@ -65,8 +129,8 @@ const hosts = defineHosts({
|
|
|
65
129
|
app: [".", "www.*"],
|
|
66
130
|
});
|
|
67
131
|
|
|
68
|
-
router.host(hosts.admin).
|
|
69
|
-
router.host(hosts.app).
|
|
132
|
+
router.host(hosts.admin).lazy(() => import("./apps/admin"));
|
|
133
|
+
router.host(hosts.app).lazy(() => import("./apps/main"));
|
|
70
134
|
```
|
|
71
135
|
|
|
72
136
|
Returns a frozen object — keys are autocompleted by TypeScript.
|
|
@@ -88,7 +152,7 @@ router.use(async (request, input, next) => {
|
|
|
88
152
|
router
|
|
89
153
|
.host(["admin.*"])
|
|
90
154
|
.use(requireAuth)
|
|
91
|
-
.
|
|
155
|
+
.lazy(() => import("./apps/admin"));
|
|
92
156
|
```
|
|
93
157
|
|
|
94
158
|
Middleware signature: `(request: Request, input: RouterRequestInput, next: () => Promise<Response>) => Promise<Response>`
|
|
@@ -165,12 +229,26 @@ Logs pattern matching, route registration, and cookie override decisions to cons
|
|
|
165
229
|
## Testing
|
|
166
230
|
|
|
167
231
|
```typescript
|
|
168
|
-
import {
|
|
232
|
+
import {
|
|
233
|
+
createTestRequest,
|
|
234
|
+
testPattern,
|
|
235
|
+
matchesHost,
|
|
236
|
+
} from "@rangojs/router/host/testing";
|
|
169
237
|
|
|
170
|
-
// Test pattern matching
|
|
238
|
+
// Test pattern matching (host-only)
|
|
171
239
|
testPattern("admin.*", "admin.example.com"); // true
|
|
172
240
|
testPattern([".", "www.*"], "example.com"); // true
|
|
173
241
|
|
|
242
|
+
// Path-based patterns need the third pathname arg (defaults to "/", so a
|
|
243
|
+
// host-only pattern still works with two args):
|
|
244
|
+
testPattern("**.workers.dev/admin", "foo.workers.dev", "/admin"); // true
|
|
245
|
+
|
|
246
|
+
// Or match a pattern against a real Request (hostname + pathname from the URL):
|
|
247
|
+
matchesHost(
|
|
248
|
+
"**.workers.dev/admin",
|
|
249
|
+
new Request("https://foo.workers.dev/admin"),
|
|
250
|
+
); // true
|
|
251
|
+
|
|
174
252
|
// Create requests for integration tests
|
|
175
253
|
const request = createTestRequest({
|
|
176
254
|
host: "admin.example.com",
|
|
@@ -179,40 +257,62 @@ const request = createTestRequest({
|
|
|
179
257
|
});
|
|
180
258
|
|
|
181
259
|
// Test which route would match (without executing)
|
|
182
|
-
router.test("admin.example.com"); // { pattern, handler } | null
|
|
260
|
+
router.test("admin.example.com"); // { pattern, handler, kind } | null
|
|
183
261
|
```
|
|
184
262
|
|
|
185
263
|
## Error Types
|
|
186
264
|
|
|
187
265
|
All errors extend `HostRouterError`:
|
|
188
266
|
|
|
189
|
-
| Error | When
|
|
190
|
-
| ----------------------------- |
|
|
191
|
-
| `InvalidPatternError` | Pattern is empty, non-string, or has spaces
|
|
192
|
-
| `HostOverrideNotAllowedError` | Cookie override from disallowed host
|
|
193
|
-
| `InvalidHostnameError` | Cookie value isn't a valid hostname
|
|
194
|
-
| `HostValidationError` | Custom `validate` function threw
|
|
195
|
-
| `NoRouteMatchError` | No host pattern matched the request
|
|
196
|
-
| `InvalidHandlerError` | Handler is not a function
|
|
267
|
+
| Error | When |
|
|
268
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
269
|
+
| `InvalidPatternError` | Pattern is empty, non-string, or has spaces |
|
|
270
|
+
| `HostOverrideNotAllowedError` | Cookie override from disallowed host |
|
|
271
|
+
| `InvalidHostnameError` | Cookie value isn't a valid hostname |
|
|
272
|
+
| `HostValidationError` | Custom `validate` function threw |
|
|
273
|
+
| `NoRouteMatchError` | No host pattern matched the request |
|
|
274
|
+
| `InvalidHandlerError` | Handler is not a function, or a lazy mount resolved to a module without a usable `default` export |
|
|
275
|
+
| `HostRouterError` | A `.map()` inline handler resolved to a module namespace (a misused lazy import — use `.lazy()`) |
|
|
197
276
|
|
|
198
277
|
See the fallback section above for a `NoRouteMatchError` catch example.
|
|
199
278
|
|
|
200
279
|
## Nesting Host Routers
|
|
201
280
|
|
|
202
|
-
A lazy
|
|
281
|
+
A lazy mount can resolve to another `HostRouter`:
|
|
203
282
|
|
|
204
283
|
```typescript
|
|
205
284
|
// apps/regional.ts
|
|
206
285
|
import { createHostRouter } from "@rangojs/router/host";
|
|
207
286
|
|
|
208
287
|
const regional = createHostRouter();
|
|
209
|
-
regional.host(["us.*"]).
|
|
210
|
-
regional.host(["eu.*"]).
|
|
288
|
+
regional.host(["us.*"]).lazy(() => import("./regions/us"));
|
|
289
|
+
regional.host(["eu.*"]).lazy(() => import("./regions/eu"));
|
|
211
290
|
|
|
212
291
|
export default regional;
|
|
213
292
|
```
|
|
214
293
|
|
|
215
294
|
```typescript
|
|
216
295
|
// host-router.ts
|
|
217
|
-
router.host(["**.regional.example.com"]).
|
|
296
|
+
router.host(["**.regional.example.com"]).lazy(() => import("./apps/regional"));
|
|
218
297
|
```
|
|
298
|
+
|
|
299
|
+
## Cross-app navigation is a full document load
|
|
300
|
+
|
|
301
|
+
A client-side navigation that crosses an app boundary (e.g. a `<Link>` or
|
|
302
|
+
intercepted `<a>` from the app at `/` into an app mounted at `/shop`) is a **hard
|
|
303
|
+
document navigation**, not a soft in-tree swap. When the server sees a partial
|
|
304
|
+
(SPA) request whose router id doesn't match the matched app, it returns
|
|
305
|
+
`X-RSC-Reload` and the client does a real document navigation to the target.
|
|
306
|
+
|
|
307
|
+
Why a reload rather than a soft swap: a soft swap can't faithfully re-establish
|
|
308
|
+
the target app's **document-level** state. Stylesheets shared across apps are
|
|
309
|
+
dropped by React 19's by-`href` resource dedup; and theme, warmup, and
|
|
310
|
+
prefetch-TTL are document-lifetime (captured once at load — see
|
|
311
|
+
`browser/app-shell.ts`), so the target app's config would never take effect. A
|
|
312
|
+
full document load re-establishes the target app's entire document — CSS, theme,
|
|
313
|
+
meta, everything — by construction. So you do **not** need to coordinate
|
|
314
|
+
stylesheet `href`s, `precedence`, theme config, etc. across independently-authored
|
|
315
|
+
apps; each app owns its own document.
|
|
316
|
+
|
|
317
|
+
**Within-app** navigation is unchanged — a normal soft SPA update (the document
|
|
318
|
+
stays mounted). Only crossing an app boundary triggers the reload.
|
|
@@ -8,9 +8,6 @@ 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
|
-
Canonical semantics reference:
|
|
12
|
-
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
13
|
-
|
|
14
11
|
## Basic Intercept
|
|
15
12
|
|
|
16
13
|
```typescript
|
|
@@ -110,8 +107,10 @@ Use named revalidation contracts on both the outer producer and the intercept
|
|
|
110
107
|
consumer when they share `ctx.set()` data:
|
|
111
108
|
|
|
112
109
|
```typescript
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
import * as ProductActions from "./actions/product";
|
|
111
|
+
|
|
112
|
+
export const revalidateProductShell = (ctx) =>
|
|
113
|
+
ctx.isAction(ProductActions) || undefined;
|
|
115
114
|
|
|
116
115
|
layout(ProductLayout, () => [
|
|
117
116
|
revalidate(revalidateProductShell), // producer reruns
|
|
@@ -197,6 +196,31 @@ function ModalWrapper({ children }) {
|
|
|
197
196
|
}
|
|
198
197
|
```
|
|
199
198
|
|
|
199
|
+
## Interaction with View Transitions
|
|
200
|
+
|
|
201
|
+
A layout that owns the `@modal` slot can also configure `transition()` for page
|
|
202
|
+
fades — opening a modal does **not** fire the layout's view transition. Rango
|
|
203
|
+
narrows the layout's `<ViewTransition>` wrap to the layout's default outlet
|
|
204
|
+
content, so `<ParallelOutlet />` (the slot where the modal mounts) is a sibling
|
|
205
|
+
of the wrap, not inside its subtree. Form actions submitted from inside an open
|
|
206
|
+
modal also commit without firing the underlying layout's transition, and the
|
|
207
|
+
modal subtree identity is preserved across revalidation (no remount,
|
|
208
|
+
`useActionState` survives). Closing the modal restores the page without a
|
|
209
|
+
stray transition.
|
|
210
|
+
|
|
211
|
+
For a modal-only morph (e.g. when intercepted URLs change while the modal
|
|
212
|
+
stays open), use an element-level React `<ViewTransition>` inside the modal
|
|
213
|
+
component — `transition()` accepted on `intercept()` via the DSL is not
|
|
214
|
+
applied to slot rendering today.
|
|
215
|
+
|
|
216
|
+
Caveat: route-level `transition()` wraps the route component itself, so a
|
|
217
|
+
`<ParallelOutlet />` rendered directly inside that route component would still
|
|
218
|
+
be inside the route's VT subtree. Mount the slot in a layout instead when you
|
|
219
|
+
combine intercept modals with route-level transitions.
|
|
220
|
+
|
|
221
|
+
See [skills/view-transitions](../view-transitions/SKILL.md) for the full
|
|
222
|
+
contract and direction-aware examples.
|
|
223
|
+
|
|
200
224
|
## Interaction with Prerender
|
|
201
225
|
|
|
202
226
|
When the target route of an intercept uses `Prerender`, the intercept handler is
|
package/skills/layout/SKILL.md
CHANGED
|
@@ -8,9 +8,6 @@ argument-hint: [component]
|
|
|
8
8
|
|
|
9
9
|
Layouts wrap child routes and persist during navigation within their scope.
|
|
10
10
|
|
|
11
|
-
Canonical semantics reference:
|
|
12
|
-
[docs/execution-model.md](../../docs/internal/execution-model.md)
|
|
13
|
-
|
|
14
11
|
## Basic Layout
|
|
15
12
|
|
|
16
13
|
```typescript
|
|
@@ -118,6 +115,8 @@ function ShopLayout() {
|
|
|
118
115
|
}
|
|
119
116
|
```
|
|
120
117
|
|
|
118
|
+
A layout's `transition()` config wraps the content that flows through `<Outlet />` — not the layout chrome itself, and not sibling `<ParallelOutlet />` slots. Stacking transitions across nested layouts collapses around the deepest default outlet content. See [skills/view-transitions](../view-transitions/SKILL.md) for the full wrap rules and intercept-modal interaction.
|
|
119
|
+
|
|
121
120
|
## Named Outlets
|
|
122
121
|
|
|
123
122
|
For parallel routes, use named outlets:
|
|
@@ -203,8 +202,10 @@ layout(<ShopLayout />, () => [
|
|
|
203
202
|
])
|
|
204
203
|
|
|
205
204
|
// Or revalidate based on conditions
|
|
205
|
+
import * as CartActions from "./actions/cart";
|
|
206
|
+
|
|
206
207
|
layout(<CartLayout />, () => [
|
|
207
|
-
revalidate((
|
|
208
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
208
209
|
|
|
209
210
|
path("/cart", CartPage, { name: "cart" }),
|
|
210
211
|
])
|
|
@@ -222,8 +223,9 @@ them on both producer and consumer segments:
|
|
|
222
223
|
|
|
223
224
|
```typescript
|
|
224
225
|
// revalidation-contracts.ts
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
import { addToCart } from "./actions/cart";
|
|
227
|
+
|
|
228
|
+
export const revalidateCartData = (ctx) => ctx.isAction(addToCart) || undefined;
|
|
227
229
|
```
|
|
228
230
|
|
|
229
231
|
```typescript
|
|
@@ -243,9 +245,10 @@ You can also package them as importable handoff helpers:
|
|
|
243
245
|
```typescript
|
|
244
246
|
// revalidation-contracts.ts
|
|
245
247
|
import { revalidate } from "@rangojs/router";
|
|
248
|
+
import * as AuthActions from "./actions/auth";
|
|
246
249
|
|
|
247
|
-
export const revalidateAuthData = (
|
|
248
|
-
|
|
250
|
+
export const revalidateAuthData = (ctx) =>
|
|
251
|
+
ctx.isAction(AuthActions) || undefined;
|
|
249
252
|
export const revalidateAuth = () => [revalidate(revalidateAuthData)];
|
|
250
253
|
```
|
|
251
254
|
|
|
@@ -263,6 +266,7 @@ layout(<ShellLayout />, () => [
|
|
|
263
266
|
```typescript
|
|
264
267
|
import { urls } from "@rangojs/router";
|
|
265
268
|
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
269
|
+
import * as CartActions from "./actions/cart";
|
|
266
270
|
|
|
267
271
|
function ShopLayout() {
|
|
268
272
|
return (
|
|
@@ -292,7 +296,7 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
|
|
|
292
296
|
}, () => [
|
|
293
297
|
// Layout loaders
|
|
294
298
|
loader(CartLoader, () => [
|
|
295
|
-
revalidate((
|
|
299
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
296
300
|
]),
|
|
297
301
|
|
|
298
302
|
// Parallel routes
|
package/skills/links/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: links
|
|
3
|
-
description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, and scopedReverse
|
|
4
|
-
argument-hint: [ctx.reverse|href|useHref|useMount|scopedReverse]
|
|
3
|
+
description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse
|
|
4
|
+
argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Links & URL Generation
|
|
@@ -10,7 +10,12 @@ argument-hint: [ctx.reverse|href|useHref|useMount|scopedReverse]
|
|
|
10
10
|
|
|
11
11
|
**Default server API: `ctx.reverse()`.** Generate URLs from the handler context — it's typed, auto-fills mount params, and resolves local (`.name`) and absolute (`name.sub`) names.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**On the client, two patterns:**
|
|
14
|
+
|
|
15
|
+
1. **Receive URLs as props / loader data / action return.** The default. The server has the full route manifest and handler context — generate URLs there and hand strings to client components.
|
|
16
|
+
2. **`useReverse(routes)`.** Import a generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("name", params?)` (the leading dot is optional). Mount-aware via `useMount()`, auto-fills params from `useParams()`, fully typed from the imported map. Use this when a client component needs to generate URLs into a known module without round-tripping through the server.
|
|
17
|
+
|
|
18
|
+
`ctx.reverse()` itself is **server-only** — it depends on the full route manifest and handler context. Client components never import or call it.
|
|
14
19
|
|
|
15
20
|
## Server: ctx.reverse()
|
|
16
21
|
|
|
@@ -127,9 +132,7 @@ path("/product/:slug", (ctx) => {
|
|
|
127
132
|
|
|
128
133
|
## Client components: receive URLs as props
|
|
129
134
|
|
|
130
|
-
`reverse()` is not available inside `"use client"` modules — there is no handler context
|
|
131
|
-
|
|
132
|
-
Three patterns, in order of preference:
|
|
135
|
+
`ctx.reverse()` is not available inside `"use client"` modules — there is no handler context in the browser bundle. For in-module names, prefer `useReverse(routes)` (see below) and import the relevant `urls/*.gen.js`. For cross-module URLs or one-off names, generate the URL on the server and hand it to the client component using one of these three patterns:
|
|
133
136
|
|
|
134
137
|
1. Pass as a prop from a server component:
|
|
135
138
|
|
|
@@ -210,7 +213,17 @@ function GlobalNav() {
|
|
|
210
213
|
}
|
|
211
214
|
```
|
|
212
215
|
|
|
213
|
-
`href()` provides compile-time validation via `
|
|
216
|
+
`href()` provides compile-time validation via the `Rango.Path` type. Paths are validated against registered route patterns using `PatternToPath`.
|
|
217
|
+
|
|
218
|
+
When wrapping `href()`, type the wrapper's path parameter as `Rango.Path` so it
|
|
219
|
+
keeps the same generated-route validation. `Rango.Path` is ambient — no import,
|
|
220
|
+
just like `Rango.Env` / `Rango.Vars`:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { href } from "@rangojs/router/client";
|
|
224
|
+
|
|
225
|
+
export const appHref = (path: Rango.Path): string => href(path);
|
|
226
|
+
```
|
|
214
227
|
|
|
215
228
|
`href()` is a raw path helper — it is **not** basename-aware. It returns the path as-is (or with the include mount prefix via `useHref()`). For basename-aware navigation, use `Link`, `useRouter().push()`, or `reverse()`, which auto-prefix root-relative paths with the router's basename.
|
|
216
229
|
|
|
@@ -256,18 +269,161 @@ function MountInfo() {
|
|
|
256
269
|
|
|
257
270
|
`useMount()` reads from `MountContext`, which is automatically set by `include()` in the segment tree.
|
|
258
271
|
|
|
259
|
-
##
|
|
272
|
+
## Client: useReverse(routes)
|
|
273
|
+
|
|
274
|
+
Hook that returns a typed local reverse function for a `routes` map imported from a generated `.gen.ts` next to a `urls()` module. The route map is the **exposure boundary** — `useReverse` only knows about names in that map, never the full app manifest.
|
|
275
|
+
|
|
276
|
+
> **Which map?** `useReverse` accepts any routes map. Prefer the per-module `routes` (e.g. `urls/blog.gen.ts`): it gives **mount-aware** local `.name` reverse (auto-prefixes the `include()` mount) and only that module's names enter the client bundle. You _can_ instead pass `router.named-routes.gen.ts` (`NamedRoutes`) for **global** names (`blog.post`; the leading dot is optional) — it is a plain importable map and works on the client (it is **not** server-only) — but its paths are **absolute** while `useReverse` mount-prefixes, so it is correct only at the root mount (under a non-root mount it double-prefixes), and importing it pulls every route name and pattern in the app into the client bundle (a small names-to-paths map — not components or loaders), versus the per-module map which exposes only one module's names. So the per-module map is preferred for in-module links; the named-routes map is the escape hatch for global names.
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
"use client";
|
|
280
|
+
import { Link, useReverse } from "@rangojs/router/client";
|
|
281
|
+
import { routes as blogRoutes } from "../urls/blog.gen.js";
|
|
282
|
+
|
|
283
|
+
export function BlogNav() {
|
|
284
|
+
const reverse = useReverse(blogRoutes);
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<nav>
|
|
288
|
+
<Link to={reverse("index")}>Blog</Link>
|
|
289
|
+
<Link to={reverse("post", { postId: "hello" })}>Post</Link>
|
|
290
|
+
</nav>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### How it resolves
|
|
296
|
+
|
|
297
|
+
1. Strips an optional leading `.` and looks up the name in the imported `routes` map.
|
|
298
|
+
2. Joins the local pattern with the surrounding `useMount()` value — the include's URL pattern.
|
|
299
|
+
3. Substitutes params: explicit params from the call, then auto-filled from `useParams()` for anything still unresolved (mount params like `:tenantId` flow in this way).
|
|
300
|
+
4. Appends a query string if a search object is passed and the route has a `search` schema.
|
|
301
|
+
|
|
302
|
+
### Mount-relativity
|
|
303
|
+
|
|
304
|
+
Patterns in the generated `routes` map are **mount-relative** — they're the patterns as defined inside the `urls()` module, _not_ the full app paths. Mount-joining happens at runtime via `useMount()`, so the same component works under any include:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// urls/blog.tsx
|
|
308
|
+
export const blogPatterns = urls(({ path }) => [
|
|
309
|
+
path("/", BlogIndex, { name: "index" }),
|
|
310
|
+
path("/:postId", BlogPost, { name: "post" }),
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
// Generated urls/blog.gen.ts
|
|
314
|
+
// export const routes = { index: "/", post: "/:postId" } as const;
|
|
315
|
+
|
|
316
|
+
// urls.tsx — same module mounted twice
|
|
317
|
+
include("/news", blogPatterns, { name: "news" }), // <BlogNav> renders /news, /news/hello
|
|
318
|
+
include("/journal", blogPatterns, { name: "diary" }), // <BlogNav> renders /journal, /journal/hello
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The `/` pattern under a non-root mount collapses cleanly: under `/news`, `reverse(".index")` returns `/news` (no trailing slash), matching `ctx.reverse(".index")` on the server.
|
|
322
|
+
|
|
323
|
+
### Auto-filled params (mount params)
|
|
324
|
+
|
|
325
|
+
When the include itself carries `:params`, those are auto-filled from `useParams()` so the caller doesn't have to thread them through:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// urls.tsx
|
|
329
|
+
include("/tenant/:tenantId", clientReversePatterns, { name: "tenant" });
|
|
330
|
+
```
|
|
260
331
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
332
|
+
```tsx
|
|
333
|
+
// At /tenant/acme/posts/p1, useParams() = { tenantId: "acme", postId: "p1" }
|
|
334
|
+
const reverse = useReverse(clientReverseRoutes);
|
|
335
|
+
|
|
336
|
+
reverse(".index"); // "/tenant/acme"
|
|
337
|
+
reverse(".post", { postId: "p2" }); // "/tenant/acme/posts/p2" (tenantId auto-filled)
|
|
338
|
+
reverse(".post", { tenantId: "other", postId: "p2" }); // "/tenant/other/posts/p2" (explicit override)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Auto-fill follows soft navigation — when the matched route changes, `useReverse` re-renders with the new params.
|
|
342
|
+
|
|
343
|
+
### Search schemas
|
|
344
|
+
|
|
345
|
+
Routes declared with a `search` schema accept a typed search object as the third argument:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// urls/blog.tsx
|
|
349
|
+
path("/search", SearchPage, {
|
|
350
|
+
name: "search",
|
|
351
|
+
search: { q: "string", page: "number?" },
|
|
352
|
+
}),
|
|
353
|
+
|
|
354
|
+
// Generated as: search: { path: "/search", search: { q: "string", page: "number?" } }
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
const reverse = useReverse(blogRoutes);
|
|
359
|
+
reverse(".search", {}, { q: "hello world", page: 2 });
|
|
360
|
+
// "/news/search?q=hello%20world&page=2"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Errors
|
|
364
|
+
|
|
365
|
+
- Unknown name: throws `Unknown route: ".not-a-route"`.
|
|
366
|
+
- Missing required param: throws `Missing param "postId" for route ".detail"`.
|
|
367
|
+
|
|
368
|
+
Both happen synchronously during `reverse()` — wrap calls in try/catch (or an ErrorBoundary if the throw happens during render) when you need to surface them as UI.
|
|
369
|
+
|
|
370
|
+
### The leading dot is optional
|
|
371
|
+
|
|
372
|
+
`reverse("post")` and `reverse(".post")` resolve **identically** — the leading dot is cosmetic. The map you import IS the scope, so there is no separate global namespace to disambiguate and the dot carries no meaning; it exists only as a readability convention and for parity with `ctx.reverse(".name")` on the server. To link into a different module, import that module's `routes`:
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
import { routes as blogRoutes } from "../urls/blog.gen.js";
|
|
376
|
+
import { routes as shopRoutes } from "../urls/shop.gen.js";
|
|
377
|
+
|
|
378
|
+
function CrossNav() {
|
|
379
|
+
const blog = useReverse(blogRoutes);
|
|
380
|
+
const shop = useReverse(shopRoutes);
|
|
381
|
+
return (
|
|
382
|
+
<nav>
|
|
383
|
+
<Link to={blog("index")}>Blog</Link>
|
|
384
|
+
<Link to={shop("cart")}>Cart</Link>
|
|
385
|
+
</nav>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Codegen
|
|
391
|
+
|
|
392
|
+
Each `urls()` module gets a sibling `.gen.ts` with the local route names and patterns, produced by `rango generate`:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
pnpm exec rango generate src/urls/blog.tsx
|
|
396
|
+
# or generate everything under a directory:
|
|
397
|
+
pnpm exec rango generate src/urls --static
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Don't edit the file by hand — re-run codegen when patterns change.
|
|
401
|
+
|
|
402
|
+
**Today the Vite plugin only regenerates the router-level `*.named-routes.gen.ts`.** Per-module `urls/*.gen.ts` files are emitted only by the CLI (or `writePerModuleRouteTypesForFile` programmatically). Commit the generated files and re-run `rango generate` whenever a `urls()` module's `path()`/`include()` shape changes. A common workflow is to wire it into a `predev` script:
|
|
403
|
+
|
|
404
|
+
```jsonc
|
|
405
|
+
// package.json
|
|
406
|
+
{
|
|
407
|
+
"scripts": {
|
|
408
|
+
"predev": "rango generate src",
|
|
409
|
+
"dev": "vite",
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## When to use what
|
|
269
415
|
|
|
270
|
-
|
|
416
|
+
| Context | API | Resolves | Use for |
|
|
417
|
+
| ---------------- | -------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
|
|
418
|
+
| Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | **Default** server-side URL generation |
|
|
419
|
+
| Server handler | `scopedReverse<T>(ctx.reverse)` | Same, with type safety | Type-safe server URLs |
|
|
420
|
+
| Client component | `useReverse(routes)` | Local names from an imported `routes` map | Typed in-module URL generation without round-tripping the server |
|
|
421
|
+
| Client component | (URL passed as prop / loader data / action return) | Named routes | Cross-module URLs or one-off names you don't want to import |
|
|
422
|
+
| Client component | `href("/path")` | Absolute paths (static strings) | Static navigation where no named-route lookup is needed |
|
|
423
|
+
| Client component | `useHref()` | Mount-prefixed paths | Local navigation inside `include()` |
|
|
424
|
+
| Client component | `useMount()` | Raw mount path | Custom mount-aware logic |
|
|
425
|
+
|
|
426
|
+
> `ctx.reverse()` is server-only. On the client, either generate URLs on the server and pass them in, or import the `routes` map and use `useReverse(routes)` for in-module names.
|
|
271
427
|
|
|
272
428
|
## Complete example: mounted module
|
|
273
429
|
|