@rangojs/router 0.16.0 → 0.18.0
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 +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +75 -57
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +26 -6
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/serve-shell-request.md +188 -0
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +6 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
package/skills/layout/SKILL.md
CHANGED
|
@@ -6,7 +6,15 @@ argument-hint: [component]
|
|
|
6
6
|
|
|
7
7
|
# Layouts with layout()
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`layout(handler, use?)` wraps its child routes in shared UI (nav, sidebar,
|
|
10
|
+
shell). The child content renders where the layout places `<Outlet />`, and the
|
|
11
|
+
layout stays mounted while you navigate between the routes it wraps. This skill
|
|
12
|
+
covers layout forms, outlets, orphan layouts (a layout inside a `path()`), and
|
|
13
|
+
layout revalidation.
|
|
14
|
+
|
|
15
|
+
Related skills: `/parallel` (named slots rendered by a layout), `/intercept`
|
|
16
|
+
(modal slots), `/route` (the routes a layout wraps), `/router-setup` (the
|
|
17
|
+
`document` shell that renders `<html>`).
|
|
10
18
|
|
|
11
19
|
## Basic Layout
|
|
12
20
|
|
|
@@ -50,7 +58,12 @@ layout(ShopLayout, () => [path("/shop", ShopIndex, { name: "shop" })]);
|
|
|
50
58
|
|
|
51
59
|
### Handler with Context
|
|
52
60
|
|
|
61
|
+
A function layout receives the same handler context as a route (`ctx.params`,
|
|
62
|
+
`ctx.get()`, `ctx.set()`, `ctx.use()`):
|
|
63
|
+
|
|
53
64
|
```typescript
|
|
65
|
+
import { Breadcrumbs } from "@rangojs/router";
|
|
66
|
+
|
|
54
67
|
layout((ctx) => {
|
|
55
68
|
const push = ctx.use(Breadcrumbs);
|
|
56
69
|
push({ label: "Shop", href: "/shop" });
|
|
@@ -97,7 +110,8 @@ layout(<ShopLayout />, () => [
|
|
|
97
110
|
|
|
98
111
|
## The Outlet Component
|
|
99
112
|
|
|
100
|
-
`<Outlet />` renders child content. Import from `@rangojs/router/client
|
|
113
|
+
`<Outlet />` renders child content. Import from `@rangojs/router/client`
|
|
114
|
+
(it works in server and client components):
|
|
101
115
|
|
|
102
116
|
```typescript
|
|
103
117
|
import { Outlet } from "@rangojs/router/client";
|
|
@@ -119,7 +133,9 @@ A layout's `transition()` config wraps the content that flows through `<Outlet /
|
|
|
119
133
|
|
|
120
134
|
## Named Outlets
|
|
121
135
|
|
|
122
|
-
For parallel routes, use named outlets
|
|
136
|
+
For parallel routes, use named outlets (`<Outlet name="@sidebar" />` is
|
|
137
|
+
equivalent to `<ParallelOutlet name="@sidebar" />`). Slot content is defined
|
|
138
|
+
with `parallel()` — see `/parallel`.
|
|
123
139
|
|
|
124
140
|
```typescript
|
|
125
141
|
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
@@ -143,40 +159,40 @@ function DashboardLayout() {
|
|
|
143
159
|
|
|
144
160
|
## Orphan Layout (inside route)
|
|
145
161
|
|
|
146
|
-
A layout
|
|
147
|
-
|
|
148
|
-
executes before its
|
|
149
|
-
|
|
150
|
-
This is the recommended way to pass handler data downward
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
skip). If an orphan layout depends on data established above its own
|
|
162
|
-
route entry, that outer segment must share a revalidation contract, or
|
|
163
|
-
the orphan must guard/reload the data independently. See `/rango` →
|
|
162
|
+
A layout declared inside a `path()` (an "orphan" layout) wraps that route's
|
|
163
|
+
content and can read what the route handler stored with `ctx.set()`. The
|
|
164
|
+
handler always executes before its orphan layouts and their parallels.
|
|
165
|
+
|
|
166
|
+
This is the recommended way to pass handler data downward. It needs no
|
|
167
|
+
revalidation setup: orphan layouts and their parallels belong to the route
|
|
168
|
+
entry, so on an action the route segment, its loaders, and these children all
|
|
169
|
+
re-run together, handler first. They can only desync if you narrow one side
|
|
170
|
+
with a predicate that returns a hard `false` — then put the same contract on
|
|
171
|
+
both (see "Revalidation Contracts").
|
|
172
|
+
|
|
173
|
+
Data from an **outer** handler or layout entry is the opposite case: outer
|
|
174
|
+
entries are skipped on actions by default. If an orphan layout depends on data
|
|
175
|
+
set above its own route entry, give that outer segment a shared revalidation
|
|
176
|
+
contract, or have the orphan load/guard the data itself. See `/rango` →
|
|
164
177
|
"Passing data down the tree" for the full safest-first ladder.
|
|
165
178
|
|
|
166
179
|
```typescript
|
|
180
|
+
import { createVar } from "@rangojs/router";
|
|
167
181
|
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
168
182
|
|
|
183
|
+
const CurrentProduct = createVar<Product>();
|
|
184
|
+
|
|
169
185
|
urls(({ path, layout, parallel }) => [
|
|
170
|
-
path("/product/:slug", (ctx) => {
|
|
186
|
+
path("/product/:slug", async (ctx) => {
|
|
171
187
|
const product = await fetchProduct(ctx.params.slug);
|
|
172
|
-
ctx.set(
|
|
188
|
+
ctx.set(CurrentProduct, product);
|
|
173
189
|
return <ProductPage product={product} />;
|
|
174
190
|
}, { name: "product" }, () => [
|
|
175
191
|
layout((ctx) => {
|
|
176
|
-
const product = ctx.get(
|
|
192
|
+
const product = ctx.get(CurrentProduct); // Product | undefined
|
|
177
193
|
return (
|
|
178
194
|
<div>
|
|
179
|
-
<
|
|
195
|
+
<ProductHeader name={product?.name} />
|
|
180
196
|
<Outlet />
|
|
181
197
|
<ParallelOutlet name="@related" />
|
|
182
198
|
</div>
|
|
@@ -184,7 +200,7 @@ urls(({ path, layout, parallel }) => [
|
|
|
184
200
|
}, () => [
|
|
185
201
|
parallel({
|
|
186
202
|
"@related": (ctx) => {
|
|
187
|
-
const product = ctx.get(
|
|
203
|
+
const product = ctx.get(CurrentProduct);
|
|
188
204
|
return <RelatedProducts category={product?.category} />;
|
|
189
205
|
},
|
|
190
206
|
}),
|
|
@@ -193,36 +209,65 @@ urls(({ path, layout, parallel }) => [
|
|
|
193
209
|
])
|
|
194
210
|
```
|
|
195
211
|
|
|
196
|
-
Orphan layouts can call `ctx.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
212
|
+
Orphan layouts can also call `ctx.set()` for their own children, but the usual
|
|
213
|
+
split is: middleware and route handlers write, orphan layouts and parallels
|
|
214
|
+
read.
|
|
215
|
+
|
|
216
|
+
Keep orphan layouts flat. A layout with no routes cannot contain another
|
|
217
|
+
`layout()`, directly or inside a `middleware(fn, () => [...])` wrapper;
|
|
218
|
+
`router.routes()` throws. Sibling layouts nest in declaration order, and a
|
|
219
|
+
sibling `middleware(fn)` runs for the whole route either way:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
path("/account", AccountPage, { name: "account" }, () => [
|
|
223
|
+
middleware(requireAuth),
|
|
224
|
+
layout(<AccountShell />), // wraps AccountNav
|
|
225
|
+
layout(<AccountNav />), // wraps AccountPage
|
|
226
|
+
]);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A `cache()` inside an orphan layout caches the whole path, like a `cache()`
|
|
230
|
+
among the path's children (`/caching`).
|
|
200
231
|
|
|
201
232
|
## Layout Revalidation
|
|
202
233
|
|
|
203
|
-
|
|
204
|
-
parent-chain segments are skipped unless a `revalidate()` opts them in.
|
|
205
|
-
(Orphan layouts inside a `path()` are the opposite: they ride along with
|
|
206
|
-
the route entry by default.) Control with `revalidate()`:
|
|
234
|
+
A standalone `layout()` renders when it is first mounted and is then kept as-is:
|
|
207
235
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
- **Navigation** between routes it already wraps does not re-render it — even
|
|
237
|
+
when the URL params change.
|
|
238
|
+
- **Actions** skip it too (the revalidation trace calls this
|
|
239
|
+
`action:parent-chain-skip`).
|
|
212
240
|
|
|
213
|
-
|
|
214
|
-
|
|
241
|
+
Orphan layouts inside a `path()` are the opposite: they belong to the route
|
|
242
|
+
entry and re-render with it (on param/search changes and on every action).
|
|
243
|
+
|
|
244
|
+
Opt a standalone layout in with `revalidate()`. Return `true` to re-render,
|
|
245
|
+
or `undefined` to defer to the default:
|
|
215
246
|
|
|
216
|
-
|
|
247
|
+
```typescript
|
|
217
248
|
import * as CartActions from "./actions/cart";
|
|
218
249
|
|
|
250
|
+
// Re-render after any cart action
|
|
219
251
|
layout(<CartLayout />, () => [
|
|
220
252
|
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
221
253
|
|
|
222
254
|
path("/cart", CartPage, { name: "cart" }),
|
|
223
255
|
])
|
|
256
|
+
|
|
257
|
+
// Re-render when a param the layout displays changes
|
|
258
|
+
layout(LocaleLayout, () => [
|
|
259
|
+
revalidate(({ currentParams, nextParams }) =>
|
|
260
|
+
currentParams.locale !== nextParams.locale || undefined,
|
|
261
|
+
),
|
|
262
|
+
|
|
263
|
+
path("/:locale/shop", ShopIndex, { name: "shop" }),
|
|
264
|
+
])
|
|
224
265
|
```
|
|
225
266
|
|
|
267
|
+
`revalidate(() => false)` on a standalone layout only restates the default.
|
|
268
|
+
The return shapes (hard boolean, soft `{ defaultShouldRevalidate }`, or defer)
|
|
269
|
+
are covered in `/loader` → "`revalidate()` return shapes".
|
|
270
|
+
|
|
226
271
|
If child segments read data that was established by this layout or by a
|
|
227
272
|
route handler above them, revalidate the outer segment too. Partial
|
|
228
273
|
revalidation does not re-run non-revalidated ancestors just to rebuild
|
|
@@ -240,9 +285,11 @@ consumer segments:
|
|
|
240
285
|
|
|
241
286
|
```typescript
|
|
242
287
|
// revalidation-contracts.ts
|
|
288
|
+
import type { Revalidate } from "@rangojs/router";
|
|
243
289
|
import { addToCart } from "./actions/cart";
|
|
244
290
|
|
|
245
|
-
export const revalidateCartData = (ctx) =>
|
|
291
|
+
export const revalidateCartData: Revalidate = (ctx) =>
|
|
292
|
+
ctx.isAction(addToCart) || undefined;
|
|
246
293
|
```
|
|
247
294
|
|
|
248
295
|
```typescript
|
|
@@ -254,6 +301,10 @@ layout(<CartLayout />, () => [
|
|
|
254
301
|
]);
|
|
255
302
|
```
|
|
256
303
|
|
|
304
|
+
On the layout the contract adds the re-render the default skips after an
|
|
305
|
+
action. The route segment already re-runs after every action, so there it
|
|
306
|
+
changes nothing by default; the shared name documents the dependency.
|
|
307
|
+
|
|
257
308
|
If a segment depends on multiple upstream domains, compose multiple
|
|
258
309
|
contracts (`revalidateAuthData`, `revalidateCartData`, and so on).
|
|
259
310
|
|
|
@@ -261,10 +312,10 @@ You can also package them as importable handoff helpers:
|
|
|
261
312
|
|
|
262
313
|
```typescript
|
|
263
314
|
// revalidation-contracts.ts
|
|
264
|
-
import { revalidate } from "@rangojs/router";
|
|
315
|
+
import { revalidate, type Revalidate } from "@rangojs/router";
|
|
265
316
|
import * as AuthActions from "./actions/auth";
|
|
266
317
|
|
|
267
|
-
export const revalidateAuthData = (ctx) =>
|
|
318
|
+
export const revalidateAuthData: Revalidate = (ctx) =>
|
|
268
319
|
ctx.isAction(AuthActions) || undefined;
|
|
269
320
|
export const revalidateAuth = () => [revalidate(revalidateAuthData)];
|
|
270
321
|
```
|
|
@@ -281,8 +332,8 @@ layout(<ShellLayout />, () => [
|
|
|
281
332
|
## Complete Example
|
|
282
333
|
|
|
283
334
|
```typescript
|
|
284
|
-
import { urls } from "@rangojs/router";
|
|
285
|
-
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
335
|
+
import { urls, Breadcrumbs } from "@rangojs/router";
|
|
336
|
+
import { Link, Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
286
337
|
import * as CartActions from "./actions/cart";
|
|
287
338
|
|
|
288
339
|
function ShopLayout() {
|
|
@@ -290,8 +341,8 @@ function ShopLayout() {
|
|
|
290
341
|
<div className="shop">
|
|
291
342
|
<ParallelOutlet name="@promoBanner" />
|
|
292
343
|
<nav>
|
|
293
|
-
<
|
|
294
|
-
<
|
|
344
|
+
<Link to="/shop">Home</Link>
|
|
345
|
+
<Link to="/shop/cart">Cart</Link>
|
|
295
346
|
</nav>
|
|
296
347
|
<div className="content">
|
|
297
348
|
<aside>
|
|
@@ -311,9 +362,12 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
|
|
|
311
362
|
push({ label: "Shop", href: "/shop" });
|
|
312
363
|
return <ShopLayout />;
|
|
313
364
|
}, () => [
|
|
314
|
-
// Layout loaders
|
|
365
|
+
// Layout loaders: after an action, re-run only for cart actions;
|
|
366
|
+
// on navigation, undefined keeps the default
|
|
315
367
|
loader(CartLoader, () => [
|
|
316
|
-
revalidate((ctx) =>
|
|
368
|
+
revalidate((ctx) =>
|
|
369
|
+
ctx.isAction() ? ctx.isAction(CartActions) : undefined,
|
|
370
|
+
),
|
|
317
371
|
]),
|
|
318
372
|
|
|
319
373
|
// Parallel routes
|
|
@@ -335,6 +389,8 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
|
|
|
335
389
|
Layout handlers can carry their own middleware, default parallels, and includes via `.use` so a layout becomes a self-contained unit reusable across mount sites.
|
|
336
390
|
|
|
337
391
|
```typescript
|
|
392
|
+
import { middleware, parallel, type Handler } from "@rangojs/router";
|
|
393
|
+
|
|
338
394
|
const AdminLayout: Handler = (ctx) => {
|
|
339
395
|
const user = ctx.get(CurrentUser);
|
|
340
396
|
return <Admin user={user} />;
|
package/skills/links/SKILL.md
CHANGED
|
@@ -6,9 +6,12 @@ argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
|
|
|
6
6
|
|
|
7
7
|
# Links & URL Generation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Use this skill to generate URLs to your own routes by name (server) or by
|
|
10
|
+
typed path (client), and to keep links correct when a module is mounted under
|
|
11
|
+
a different `include()` prefix. For route typing setup see `/typesafety`; for
|
|
12
|
+
links to JSON/text endpoints see `/response-routes`.
|
|
10
13
|
|
|
11
|
-
**Default server API: `ctx.reverse()`.** Generate URLs from the handler context — it's typed, auto-fills
|
|
14
|
+
**Default server API: `ctx.reverse()`.** Generate URLs from the handler context — it's typed, auto-fills params from the current request, and resolves local (`.name`) and global (`name`) names. In middleware and response routes it takes global names only (see below).
|
|
12
15
|
|
|
13
16
|
**On the client, two patterns:**
|
|
14
17
|
|
|
@@ -19,10 +22,19 @@ argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
|
|
|
19
22
|
|
|
20
23
|
## Server: ctx.reverse()
|
|
21
24
|
|
|
22
|
-
Available
|
|
25
|
+
Available on the handler context (route handlers, layouts, parallels), on
|
|
26
|
+
middleware and loader contexts, and on `getRequestContext()` inside server
|
|
27
|
+
actions. Resolves named routes using the full route map. This is the default
|
|
28
|
+
way to generate URLs on the server. Patterns in the map already include the
|
|
29
|
+
router `basename`, so results are basename-prefixed.
|
|
30
|
+
|
|
31
|
+
Middleware and response routes (`path.json()`, `path.text()`, …) get a
|
|
32
|
+
global-only `ctx.reverse` (`GlobalReverseFunction`): no `include()` scope and no
|
|
33
|
+
param auto-fill, so pass the fully qualified name and every param. A dot-local
|
|
34
|
+
`.name` is a type error there. See `/middleware` and `/response-routes`.
|
|
23
35
|
|
|
24
36
|
```typescript
|
|
25
|
-
import { urls
|
|
37
|
+
import { urls } from "@rangojs/router";
|
|
26
38
|
|
|
27
39
|
export const shopPatterns = urls(({ path, layout }) => [
|
|
28
40
|
layout(<ShopLayout />, () => [
|
|
@@ -63,7 +75,7 @@ path("/product/:slug", (ctx) => {
|
|
|
63
75
|
|
|
64
76
|
### Local names (dot-prefixed)
|
|
65
77
|
|
|
66
|
-
Prefix a name with `.` to resolve it within the current `include()` scope. The route is looked up using the include's mount namespace.
|
|
78
|
+
Prefix a name with `.` to resolve it within the current `include()` scope. The route is looked up using the include's mount namespace. If the name is not found there, lookup walks up the enclosing include scopes (`shop.admin.x`, then `shop.x`). It falls back to a bare root-level name only for root-scoped routes (top-level routes and routes under `{ name: "" }` mounts); inside a named mount, a dot-local name never leaks into unrelated global names.
|
|
67
79
|
|
|
68
80
|
```typescript
|
|
69
81
|
// urls/magazine.tsx — mounted at include("/magazine", magazinePatterns, { name: "magazine" })
|
|
@@ -97,7 +109,7 @@ export const tenantPatterns = urls(({ path }) => [
|
|
|
97
109
|
]);
|
|
98
110
|
```
|
|
99
111
|
|
|
100
|
-
Auto-fill uses `{ ...ctx.params, ...hrefParams }` — current request params are defaults, explicit params win. Params not needed by the target route are silently ignored.
|
|
112
|
+
Auto-fill uses `{ ...ctx.params, ...hrefParams }` — current request params are defaults, explicit params win. Params not needed by the target route are silently ignored. Because every current param is a default, drop an optional one explicitly with `undefined` or `""` — under `include("/:locale?", ...)` on `/en/...`, a bare `ctx.reverse("menu.index")` keeps `/en`, while `ctx.reverse("menu.index", { locale: undefined })` returns `/` (see `/i18n`). A missing required param throws `Missing param "slug" for route "…"`; an unknown name throws `Unknown route: "…"`.
|
|
101
113
|
|
|
102
114
|
### Global names (unprefixed)
|
|
103
115
|
|
|
@@ -112,7 +124,7 @@ Unprefixed names resolve against the full named-routes map (the generated `route
|
|
|
112
124
|
|
|
113
125
|
### reverse with search params
|
|
114
126
|
|
|
115
|
-
When a route has a `search` schema, pass a typed search object as the third argument:
|
|
127
|
+
When a route has a `search` schema, pass a typed search object as the third argument. `undefined`/`null` values are skipped; keys keep insertion order:
|
|
116
128
|
|
|
117
129
|
```typescript
|
|
118
130
|
path("/search", (ctx) => {
|
|
@@ -124,7 +136,7 @@ path("/search", (ctx) => {
|
|
|
124
136
|
|
|
125
137
|
### scopedReverse() - type-safe ctx.reverse
|
|
126
138
|
|
|
127
|
-
Wraps `ctx.reverse` with local route
|
|
139
|
+
Wraps `ctx.reverse` with the module's local route types for autocomplete and validation. Runtime behavior is identical to `ctx.reverse` — `scopedReverse` is a type-only cast, and it also works on loader contexts. Don't use it on middleware or response-route contexts: their `ctx.reverse` has no `include()` scope, so the local names it types would throw `Unknown route`. Always call local names with the dot: the returned function is typed only against the module's local map, so an unprefixed local name (`reverse("cart")`) type-checks but is resolved as a global name at runtime — it throws `Unknown route` unless a global route of that exact name happens to exist.
|
|
128
140
|
|
|
129
141
|
```typescript
|
|
130
142
|
import { scopedReverse } from "@rangojs/router";
|
|
@@ -134,12 +146,15 @@ path("/product/:slug", (ctx) => {
|
|
|
134
146
|
|
|
135
147
|
reverse(".cart"); // Local name (dot-prefixed) — resolves in include scope
|
|
136
148
|
reverse(".product", { slug: "widget" }); // Local name with params
|
|
137
|
-
|
|
149
|
+
|
|
150
|
+
ctx.reverse("blog.post", { slug: "hi" }); // Global names: call ctx.reverse directly
|
|
138
151
|
|
|
139
152
|
return <ProductPage slug={ctx.params.slug} />;
|
|
140
153
|
}, { name: "product" })
|
|
141
154
|
```
|
|
142
155
|
|
|
156
|
+
To type local and global names on one function, type the handler with the module's generated map instead: `Handler<"shop.product", routes>` (see `/typesafety`).
|
|
157
|
+
|
|
143
158
|
`reverse()` does not accept raw path strings (`"/about"`). For static paths in client components, use `href("/about")`; on the server, look up the route by name.
|
|
144
159
|
|
|
145
160
|
## Client components: receive URLs as props
|
|
@@ -150,9 +165,12 @@ path("/product/:slug", (ctx) => {
|
|
|
150
165
|
|
|
151
166
|
```tsx
|
|
152
167
|
// server
|
|
153
|
-
|
|
168
|
+
import type { Handler } from "@rangojs/router";
|
|
169
|
+
import { ShareButton } from "./share-button";
|
|
170
|
+
|
|
171
|
+
export const BlogPostPage: Handler<{ slug: string }> = (ctx) => {
|
|
154
172
|
return <ShareButton url={ctx.reverse(".post", { slug: ctx.params.slug })} />;
|
|
155
|
-
}
|
|
173
|
+
};
|
|
156
174
|
```
|
|
157
175
|
|
|
158
176
|
```tsx
|
|
@@ -169,6 +187,8 @@ export function ShareButton({ url }: { url: string }) {
|
|
|
169
187
|
|
|
170
188
|
```tsx
|
|
171
189
|
// server — loaders/nav.ts
|
|
190
|
+
import { createLoader } from "@rangojs/router";
|
|
191
|
+
|
|
172
192
|
export const NavLoader = createLoader((ctx) => ({
|
|
173
193
|
home: ctx.reverse("home"),
|
|
174
194
|
blog: ctx.reverse("blog.index"),
|
|
@@ -182,6 +202,8 @@ const urlpatterns = urls(({ path, loader }) => [
|
|
|
182
202
|
|
|
183
203
|
```tsx
|
|
184
204
|
"use client";
|
|
205
|
+
import { Link, useLoader } from "@rangojs/router/client";
|
|
206
|
+
import { NavLoader } from "../loaders/nav";
|
|
185
207
|
|
|
186
208
|
function Nav() {
|
|
187
209
|
const { data } = useLoader(NavLoader);
|
|
@@ -195,6 +217,7 @@ function Nav() {
|
|
|
195
217
|
|
|
196
218
|
```tsx
|
|
197
219
|
"use server";
|
|
220
|
+
import { getRequestContext } from "@rangojs/router";
|
|
198
221
|
|
|
199
222
|
export async function getProductUrl(slug: string) {
|
|
200
223
|
const ctx = getRequestContext();
|
|
@@ -237,7 +260,9 @@ import { href } from "@rangojs/router/client";
|
|
|
237
260
|
export const appHref = (path: Rango.Path): string => href(path);
|
|
238
261
|
```
|
|
239
262
|
|
|
240
|
-
`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()`).
|
|
263
|
+
`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()`). `<Link to={href("/about")}>` still lands on the basename-prefixed URL because `Link` and `useRouter().push()` prefix root-relative paths with the router's basename; a plain `<a href>`, `fetch()`, or `window.location` gets the raw path. On the server, `ctx.reverse()` output is already basename-prefixed.
|
|
264
|
+
|
|
265
|
+
`href.json()`, `href.text()`, `href.html()`, `href.xml()`, `href.md()`, `href.image()`, `href.stream()` and `href.any()` return `{ to, "data-external": "" }` props for linking to response routes with a hard navigation — see `/response-routes`.
|
|
241
266
|
|
|
242
267
|
## Client: useHref()
|
|
243
268
|
|
|
@@ -263,6 +288,8 @@ function ShopNav() {
|
|
|
263
288
|
|
|
264
289
|
Use `useHref()` for local navigation within a mounted module. Use the bare `href()` function for absolute paths outside the current mount.
|
|
265
290
|
|
|
291
|
+
`useHref()` concatenates the mount **pattern** as-is and does not substitute params, and its argument is not checked against your routes. Under a parameterized mount (`include("/tenant/:tenantId", ...)`) it would produce `/tenant/:tenantId/cart` — use `useReverse(routes)` there, which substitutes mount params from `useParams()`.
|
|
292
|
+
|
|
266
293
|
## Client: useMount()
|
|
267
294
|
|
|
268
295
|
Returns the current `include()` mount path. Useful for building custom logic based on mount location.
|
|
@@ -279,7 +306,7 @@ function MountInfo() {
|
|
|
279
306
|
}
|
|
280
307
|
```
|
|
281
308
|
|
|
282
|
-
`useMount()` reads from `MountContext`, which is automatically set by `include()` in the segment tree.
|
|
309
|
+
`useMount()` reads from `MountContext`, which is automatically set by `include()` in the segment tree. Nested includes return the nearest mount's full URL prefix (e.g. `/shop/admin`), and a parameterized include returns its pattern (`/tenant/:tenantId`), not the matched URL. `href(path, mount)` is the non-hook equivalent of `useHref()`.
|
|
283
310
|
|
|
284
311
|
## Client: useReverse(routes)
|
|
285
312
|
|
|
@@ -309,7 +336,9 @@ export function BlogNav() {
|
|
|
309
336
|
1. Strips an optional leading `.` and looks up the name in the imported `routes` map.
|
|
310
337
|
2. Joins the local pattern with the surrounding `useMount()` value — the include's URL pattern.
|
|
311
338
|
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).
|
|
312
|
-
4. Appends a query string
|
|
339
|
+
4. Appends a query string when a search object is passed (typed from the route's `search` schema; `undefined`/`null` values are skipped).
|
|
340
|
+
|
|
341
|
+
The returned function is memoized on the routes map, mount, and current params, so it is safe to pass to memoized children.
|
|
313
342
|
|
|
314
343
|
### Mount-relativity
|
|
315
344
|
|
|
@@ -337,13 +366,16 @@ The `/` pattern under a non-root mount collapses cleanly: under `/news`, `revers
|
|
|
337
366
|
When the include itself carries `:params`, those are auto-filled from `useParams()` so the caller doesn't have to thread them through:
|
|
338
367
|
|
|
339
368
|
```typescript
|
|
369
|
+
// urls/tenant.tsx: index "/" and post "/posts/:postId"
|
|
340
370
|
// urls.tsx
|
|
341
|
-
include("/tenant/:tenantId",
|
|
371
|
+
include("/tenant/:tenantId", tenantPatterns, { name: "tenant" });
|
|
342
372
|
```
|
|
343
373
|
|
|
344
374
|
```tsx
|
|
375
|
+
import { routes as tenantRoutes } from "../urls/tenant.gen.js";
|
|
376
|
+
|
|
345
377
|
// At /tenant/acme/posts/p1, useParams() = { tenantId: "acme", postId: "p1" }
|
|
346
|
-
const reverse = useReverse(
|
|
378
|
+
const reverse = useReverse(tenantRoutes);
|
|
347
379
|
|
|
348
380
|
reverse(".index"); // "/tenant/acme"
|
|
349
381
|
reverse(".post", { postId: "p2" }); // "/tenant/acme/posts/p2" (tenantId auto-filled)
|
|
@@ -411,7 +443,7 @@ pnpm exec rango generate src/urls --static
|
|
|
411
443
|
|
|
412
444
|
Don't edit the file by hand — re-run codegen when patterns change.
|
|
413
445
|
|
|
414
|
-
**
|
|
446
|
+
**The Vite plugin only regenerates the router-level `*.named-routes.gen.ts`.** Per-module `urls/*.gen.ts` files are emitted only by the `rango generate` CLI. Commit the generated files and re-run `rango generate` whenever a `urls()` module's `path()`/`include()` shape changes. `clientUrls()` modules get a `.gen.ts` the same way. A common workflow is to wire it into a `predev` script:
|
|
415
447
|
|
|
416
448
|
```jsonc
|
|
417
449
|
// package.json
|
|
@@ -452,7 +484,7 @@ queued or in flight; removing it re-arms both Link types.
|
|
|
452
484
|
| Context | API | Resolves | Use for |
|
|
453
485
|
| ---------------- | -------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
|
|
454
486
|
| Server handler | `ctx.reverse("name")` | Named routes (local + absolute) | **Default** server-side URL generation |
|
|
455
|
-
| Server handler | `scopedReverse<T>(ctx.reverse)` |
|
|
487
|
+
| Server handler | `scopedReverse<T>(ctx.reverse)` | Local `.name` routes, typed from `T` | Type-safe in-module server URLs |
|
|
456
488
|
| Client component | `useReverse(routes)` | Local names from an imported `routes` map | Typed in-module URL generation without round-tripping the server |
|
|
457
489
|
| 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 |
|
|
458
490
|
| Client component | `href("/path")` | Absolute paths (static strings) | Static navigation where no named-route lookup is needed |
|
|
@@ -470,7 +502,7 @@ import { urls, scopedReverse } from "@rangojs/router";
|
|
|
470
502
|
export const shopPatterns = urls(({ path, layout }) => [
|
|
471
503
|
layout((ctx) => {
|
|
472
504
|
const reverse = scopedReverse<typeof shopPatterns>(ctx.reverse);
|
|
473
|
-
return <ShopLayout cartUrl={reverse("cart")} />;
|
|
505
|
+
return <ShopLayout cartUrl={reverse(".cart")} />;
|
|
474
506
|
}, () => [
|
|
475
507
|
path("/", ShopIndex, { name: "index" }),
|
|
476
508
|
path("/cart", CartPage, { name: "cart" }),
|