@rangojs/router 0.16.0 → 0.17.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 +254 -89
- package/dist/testing/vitest.js +252 -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-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- 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/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/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- 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 +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- 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/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- 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/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 +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- 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/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -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 +39 -33
- 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 +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -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 +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- 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 +8 -5
- 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/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 +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- 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 +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- 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 +1 -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 +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- 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 +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- 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 +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -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 +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -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 +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
# Route Types
|
|
2
2
|
|
|
3
|
+
Route names come from the `name` option on `path()`. Names are typed in two
|
|
4
|
+
places: the `urls()` value itself (local names, patterns and response payloads
|
|
5
|
+
as phantom types) and the generated `router.named-routes.gen.ts` (the global
|
|
6
|
+
name map behind `Handler<"name">`, `ctx.reverse()` and `href()`). See
|
|
7
|
+
[`./generated-files-and-cli.md`](./generated-files-and-cli.md) for which surface
|
|
8
|
+
feeds which API.
|
|
9
|
+
|
|
3
10
|
## Route Definition with Type-Safe Names
|
|
4
11
|
|
|
5
12
|
```typescript
|
|
6
13
|
// urls.tsx
|
|
7
14
|
import { urls } from "@rangojs/router";
|
|
8
15
|
|
|
9
|
-
export const urlpatterns = urls(({ path
|
|
16
|
+
export const urlpatterns = urls(({ path }) => [
|
|
10
17
|
path("/", HomePage, { name: "home" }),
|
|
11
18
|
path("/products", ProductsPage, { name: "products" }),
|
|
12
19
|
path("/product/:slug", ProductPage, { name: "product" }),
|
|
@@ -17,7 +24,7 @@ export const urlpatterns = urls(({ path, layout }) => [
|
|
|
17
24
|
// Route names are inferred from the { name } option
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
## Type-Safe
|
|
27
|
+
## Type-Safe URL Generation
|
|
21
28
|
|
|
22
29
|
### Server: ctx.reverse with route names
|
|
23
30
|
|
|
@@ -36,8 +43,15 @@ export const ProductHandler: Handler<"shop.product"> = (ctx) => {
|
|
|
36
43
|
};
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
Without a local route map, `.name` calls accept any string (the include scope
|
|
47
|
+
is only known at runtime). Middleware and response-route handlers are the
|
|
48
|
+
exception: their `ctx.reverse` (`GlobalReverseFunction`) has no include scope,
|
|
49
|
+
so it takes global names only and a `.name` call there is a type error. Global names are checked against the generated
|
|
50
|
+
map once it exists. For type-safe local names, generate a route types file with
|
|
51
|
+
`npx rango generate urls/shop.tsx` and pass its `routes` type as the second
|
|
52
|
+
generic to `Handler` or `Prerender`. With a local map, local names also
|
|
53
|
+
require their params (mount params such as `:tenantId` may still be passed as
|
|
54
|
+
extras):
|
|
41
55
|
|
|
42
56
|
```typescript
|
|
43
57
|
import type { Handler } from "@rangojs/router";
|
|
@@ -61,6 +75,7 @@ import { href, useHref, Link } from "@rangojs/router/client";
|
|
|
61
75
|
// href() validates absolute paths via PatternToPath types
|
|
62
76
|
href("/about"); // Valid path
|
|
63
77
|
href("/blog/hello"); // Matches /blog/:slug
|
|
78
|
+
href("/blog/hello?page=2#top"); // Query and hash suffixes are allowed
|
|
64
79
|
|
|
65
80
|
// useHref() auto-prefixes with include() mount
|
|
66
81
|
function ShopNav() {
|
|
@@ -69,6 +84,10 @@ function ShopNav() {
|
|
|
69
84
|
}
|
|
70
85
|
```
|
|
71
86
|
|
|
87
|
+
`href()` is compile-time only: at runtime it returns the path unchanged. It
|
|
88
|
+
does not resolve route names. `useHref()` prepends the mount but its argument
|
|
89
|
+
is typed as any `/${string}`, not checked against routes.
|
|
90
|
+
|
|
72
91
|
`href()` and the `Rango.Path` type read from `RegisteredRoutes` when you augment
|
|
73
92
|
it, otherwise from the auto-generated `GeneratedRouteMap` — so `rango generate`
|
|
74
93
|
alone type-checks `href()` paths with no manual augmentation. The augmentation
|
|
@@ -76,6 +95,7 @@ below is only needed for **`Rango.PathResponse`** (response-payload inference),
|
|
|
76
95
|
`GeneratedRouteMap` cannot provide:
|
|
77
96
|
|
|
78
97
|
```typescript
|
|
98
|
+
// router.tsx, below `export const router = createRouter(...).routes(urlpatterns)`
|
|
79
99
|
// The alias is required: an interface heritage clause cannot take a `typeof`
|
|
80
100
|
// type query directly (TS1109).
|
|
81
101
|
type AppRoutes = typeof router.routeMap;
|
|
@@ -200,15 +220,31 @@ a rename is a type error in one place instead of silent drift:
|
|
|
200
220
|
```ts
|
|
201
221
|
import { addToCart, removeFromCart } from "./actions/cart";
|
|
202
222
|
import * as CartActions from "./actions/cart";
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
revalidate((ctx) => ctx.isAction(
|
|
223
|
+
import * as OrderActions from "./actions/order";
|
|
224
|
+
|
|
225
|
+
// On a loader: after an action, re-run only for cart actions; on navigation,
|
|
226
|
+
// keep the default.
|
|
227
|
+
revalidate((ctx) => (ctx.isAction() ? ctx.isAction(CartActions) : undefined));
|
|
228
|
+
|
|
229
|
+
// Matcher forms (each returns a boolean):
|
|
230
|
+
ctx.isAction(); // any action at all
|
|
231
|
+
ctx.isAction(addToCart); // one action
|
|
232
|
+
ctx.isAction(addToCart, removeFromCart); // several
|
|
233
|
+
ctx.isAction(CartActions); // any action in the module
|
|
234
|
+
ctx.isAction({ addToCart, removeFromCart }); // object form
|
|
235
|
+
ctx.isAction({ Cart: CartActions, Order: OrderActions }); // grouped namespaces
|
|
208
236
|
```
|
|
209
237
|
|
|
210
|
-
`ctx.isAction()` (only available on the revalidate predicate's
|
|
211
|
-
raw boolean
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
238
|
+
`ctx.isAction()` (only available on the revalidate predicate's argument)
|
|
239
|
+
returns a raw boolean and is `false` on plain navigation. Wrap it by what the
|
|
240
|
+
segment's default already does: `|| undefined` adds a re-render on a parent
|
|
241
|
+
layout or layout-level parallel (skipped after actions by default); on a loader
|
|
242
|
+
or route segment, which already re-run after every action, `|| undefined`
|
|
243
|
+
changes nothing, so narrow with the ternary above. Avoid a bare
|
|
244
|
+
`ctx.isAction(X)`: it is a hard `false` on navigation too. See `/loader` →
|
|
245
|
+
"`revalidate()` return shapes". It resolves
|
|
246
|
+
the reference the same way the router derives `actionId` (`$id ?? $$id`: the
|
|
247
|
+
file-path `$id` injected in a production RSC build, otherwise React's `$$id`),
|
|
248
|
+
so matching works in dev and production. `actionId` stays available for
|
|
249
|
+
advanced cases. In `clientUrls()` groups the predicate runs in the browser,
|
|
250
|
+
where `actionId` is the hashed form (see `/client-urls`).
|
|
@@ -6,14 +6,21 @@ argument-hint: [profile-name]
|
|
|
6
6
|
|
|
7
7
|
# "use cache" Directive
|
|
8
8
|
|
|
9
|
-
Function-level caching for async server functions and RSC components.
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Function-level caching for async server functions and RSC components. Put the
|
|
10
|
+
directive in a function (or at the top of a module) and its return value is
|
|
11
|
+
cached with a TTL and a stale-while-revalidate window, keyed by its arguments.
|
|
12
|
+
Use it when one data fetch or one component is expensive and is reused across
|
|
13
|
+
routes or requests; the code around the call keeps running.
|
|
14
|
+
|
|
15
|
+
It complements the route-level `cache()` DSL (a whole segment subtree) and
|
|
16
|
+
build-time `Static()`/`Prerender()`. `"use cache"` needs an app-level store
|
|
17
|
+
(`createRouter({ cache })`); without one the function runs uncached.
|
|
12
18
|
|
|
13
19
|
## Not this skill if…
|
|
14
20
|
|
|
15
21
|
- You want to cache a whole route or a rendered subtree of segments — that is
|
|
16
22
|
the segment-level `cache()` DSL: see `/caching`.
|
|
23
|
+
- You want a value fresh on every request — use a loader (see `/loader`).
|
|
17
24
|
- You are unsure which cache layer you need — start at `/cache-guide`.
|
|
18
25
|
|
|
19
26
|
## Basic Usage
|
|
@@ -56,6 +63,16 @@ export async function ProductCard({ id }: { id: string }) {
|
|
|
56
63
|
}
|
|
57
64
|
```
|
|
58
65
|
|
|
66
|
+
### Results that are not cached
|
|
67
|
+
|
|
68
|
+
A result Flight cannot encode cleanly is not cached: an async child component
|
|
69
|
+
that throws, a promise in the result or in a handle push that rejects, a
|
|
70
|
+
function or a class instance. Storing it would bake the failure into every hit
|
|
71
|
+
until expiry. Instead the write is skipped and reported to `onError` (phase
|
|
72
|
+
`"cache"`, category `cache-write`, or `stale-revalidation` for a background
|
|
73
|
+
refresh): a miss still returns the live result, the next call runs the
|
|
74
|
+
function again, and a stale entry keeps serving.
|
|
75
|
+
|
|
59
76
|
## Named Cache Profiles
|
|
60
77
|
|
|
61
78
|
Define profiles in createRouter. Profile names map to `"use cache: <name>"` in
|
|
@@ -85,11 +102,17 @@ createRouter({
|
|
|
85
102
|
mutation-related cached data; incidental TTL staleness on an ordinary action
|
|
86
103
|
stays SWR so the action is not turned into a synchronous cache-refresh barrier.
|
|
87
104
|
For strong read-your-own-writes after a mutation, prefer `updateTag()` (a hard
|
|
88
|
-
purge, so the action's own re-render is a fresh foreground miss).
|
|
105
|
+
purge, so the action's own re-render is a fresh foreground miss). Only
|
|
106
|
+
`"use cache"` honors this flag; `cache()` boundaries and cached loaders ignore
|
|
107
|
+
it.
|
|
108
|
+
- `default` is built in as `{ ttl: 900, swr: 1800 }`; defining `default`
|
|
109
|
+
overrides it.
|
|
110
|
+
- Profile names must match `[a-zA-Z0-9_-]+`, and `ttl`/`swr` must be finite and
|
|
111
|
+
non-negative; `createRouter()` throws otherwise.
|
|
89
112
|
- Unknown profile names throw at runtime, on the first invocation of the cached
|
|
90
113
|
function (the Vite transform does not validate names at build/boot). The error
|
|
91
|
-
|
|
92
|
-
|
|
114
|
+
names the missing profile and shows the `createRouter({ cacheProfiles })` entry
|
|
115
|
+
to add.
|
|
93
116
|
|
|
94
117
|
## Cache Key
|
|
95
118
|
|
|
@@ -97,8 +120,20 @@ cacheProfiles: { ... } })` entry to add.
|
|
|
97
120
|
use-cache:{functionId}:{serializedArgs}
|
|
98
121
|
```
|
|
99
122
|
|
|
100
|
-
- `functionId` -- stable ID from Vite transform (module path + export name
|
|
101
|
-
|
|
123
|
+
- `functionId` -- stable ID from Vite transform (module path + export name in
|
|
124
|
+
dev, a hash of both in production builds).
|
|
125
|
+
- `serializedArgs` -- key-generating arguments: a stable JSON encoding when every
|
|
126
|
+
argument is JSON-safe, otherwise serialized via RSC `encodeReply()`.
|
|
127
|
+
|
|
128
|
+
React elements (`header`, `children` slots) and client or server references
|
|
129
|
+
are left out of the key: the first call's rendered slot is part of the cached
|
|
130
|
+
output, and a hit returns it for any slot passed.
|
|
131
|
+
|
|
132
|
+
If any other argument cannot be serialized, top-level or nested (a function, a
|
|
133
|
+
class instance such as a database client, a symbol), the call runs uncached and
|
|
134
|
+
warns once per function in dev. Flight's encoder writes the same placeholder
|
|
135
|
+
for every such value, so keying on it would serve one caller's result to every
|
|
136
|
+
other caller. Pass the serializable values the function needs.
|
|
102
137
|
|
|
103
138
|
When there are no key-generating arguments, the key has no trailing colon -- it is
|
|
104
139
|
just `use-cache:{functionId}`.
|
|
@@ -117,16 +152,48 @@ params excluded). The same cached function called with `ctx` on different routes
|
|
|
117
152
|
param combinations, hosts, response types, or query variants therefore produces
|
|
118
153
|
distinct cache entries -- not one shared entry.
|
|
119
154
|
|
|
120
|
-
##
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
155
|
+
## Request-Scoped Arguments (ctx, Request, env)
|
|
156
|
+
|
|
157
|
+
Three request-scoped arguments are kept out of the serialized arguments:
|
|
158
|
+
|
|
159
|
+
- **`ctx`** is branded with `Symbol.for('rango:nocache')` at creation. Its route
|
|
160
|
+
fields are folded into the key, and its handle pushes are captured and
|
|
161
|
+
replayed (below). Every ctx is branded: handler, loader, middleware,
|
|
162
|
+
response-route and the request context. A loader ctx keys exactly like the
|
|
163
|
+
handler ctx of the same request. A middleware ctx keys by host, pathname,
|
|
164
|
+
its params and search, plus the route name once the route is matched (a
|
|
165
|
+
global middleware before `next()` has none yet). A ctx carrying a request
|
|
166
|
+
body (a fetchable loader called with `method: "POST"` and a `body` or form
|
|
167
|
+
data) runs the call uncached and warns in dev: the body is not in the key.
|
|
168
|
+
The method is not in the key either.
|
|
169
|
+
- **A `Request`** (such as `ctx.request`) keys by its URL: host, pathname and
|
|
170
|
+
the user-facing sorted search params, with the same internal-param exclusion
|
|
171
|
+
and `cache.searchParams` filter as the URL-keyed tiers. Headers, cookies and
|
|
172
|
+
the method are not in the key; read what you need outside and pass the values.
|
|
173
|
+
- **The request's `env`** (the object `ctx.env` returns) is left out of the key:
|
|
174
|
+
it is fixed per deployment. A binding passed on its own (`env.DB`) is a class
|
|
175
|
+
instance and runs uncached; pass `env` instead.
|
|
176
|
+
|
|
177
|
+
When a `ctx` is detected:
|
|
124
178
|
|
|
125
179
|
1. **Excluded from cache key** -- request-scoped, not meaningful for keying.
|
|
126
180
|
(The route-identifying fields read off `ctx` are still folded in -- see
|
|
127
181
|
"Route context is folded into the key" above.)
|
|
128
|
-
2. **Handle data captured on miss** -- side effects via `ctx.use(Handle)` are recorded
|
|
129
|
-
|
|
182
|
+
2. **Handle data captured on miss** -- side effects via `ctx.use(Handle)` are recorded:
|
|
183
|
+
the function's own pushes (and those of cached functions it calls), not the
|
|
184
|
+
handler's or loaders' pushes into the same request.
|
|
185
|
+
3. **Handle data replayed on hit** -- appended to the calling segment (for a
|
|
186
|
+
loader ctx, the segment that declares the loader), after what the handler
|
|
187
|
+
and loaders already pushed, as if the body had run (a layout and its page
|
|
188
|
+
calling the same function each get their copy). A
|
|
189
|
+
stale hit's background refresh records its pushes into the refreshed entry only.
|
|
190
|
+
4. **A loader the function reads shows its pushes once** -- pushes from a loader
|
|
191
|
+
read with `ctx.use(Loader)` inside the function are recorded under that
|
|
192
|
+
loader. Loaders stay live, so if the handler or a DSL loader also reads it,
|
|
193
|
+
that live run's pushes are the ones on the page: a live run before the hit
|
|
194
|
+
makes the replay skip them, and one after it replaces the replayed values in
|
|
195
|
+
place. A loader read only inside the function is replayed once, also when
|
|
196
|
+
a layout and its page both call the function.
|
|
130
197
|
|
|
131
198
|
```typescript
|
|
132
199
|
export async function getProductData(ctx) {
|
|
@@ -136,6 +203,17 @@ export async function getProductData(ctx) {
|
|
|
136
203
|
return await db.query("SELECT * FROM products");
|
|
137
204
|
}
|
|
138
205
|
// On hit: return value restored, breadcrumb replayed.
|
|
206
|
+
|
|
207
|
+
export async function getProduct(ctx) {
|
|
208
|
+
"use cache";
|
|
209
|
+
const category = await ctx.use(CategoryLoader); // pushes a category crumb
|
|
210
|
+
return db.product(ctx.params.id, category.id);
|
|
211
|
+
}
|
|
212
|
+
// Handler: `await getProduct(ctx); await ctx.use(CategoryLoader);`
|
|
213
|
+
// On hit: one category crumb, from the handler's live CategoryLoader run.
|
|
214
|
+
|
|
215
|
+
export const ProductLoader = createLoader(async (ctx) => getProduct(ctx));
|
|
216
|
+
// A loader ctx keys like the handler ctx: one entry per route, params and query.
|
|
139
217
|
```
|
|
140
218
|
|
|
141
219
|
## Request-Scoped Guards
|
|
@@ -153,10 +231,35 @@ const locale = cookies().get("locale")?.value ?? "en";
|
|
|
153
231
|
const data = await getCachedData(locale); // locale is now in the cache key
|
|
154
232
|
```
|
|
155
233
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
234
|
+
`ctx.get()` of a **non-cacheable variable** (`createVar({ cache: false })`, or
|
|
235
|
+
a value written with `ctx.set(key, value, { cache: false })`) throws the same
|
|
236
|
+
way, whether it goes through `getRequestContext().get()` or a `ctx` passed in
|
|
237
|
+
(handler, loader, middleware or response-route).
|
|
238
|
+
The key does not include the value, so the first caller's value would be
|
|
239
|
+
served to later callers. Read it before the call and pass it in:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const Tenant = createVar<string>({ cache: false });
|
|
243
|
+
|
|
244
|
+
async function getNav(tenant: string) {
|
|
245
|
+
"use cache";
|
|
246
|
+
return loadNav(tenant); // tenant is part of the key
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// in a handler or loader, outside the cached function
|
|
250
|
+
const nav = await getNav(ctx.get(Tenant)!);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Ordinary (cacheable) variables stay readable. Calling the cached function from
|
|
254
|
+
a loader does not exempt it: the loader re-runs on every request, but the
|
|
255
|
+
cached body does not.
|
|
256
|
+
|
|
257
|
+
The non-cacheable variable guard does not reach into LOADER bodies consumed
|
|
258
|
+
inside the cached function (`await ctx.use(loader)`): loaders always run
|
|
259
|
+
fresh, so their variable reads are exempt — but the CONSUMED VALUE is captured
|
|
260
|
+
into the shared cache entry like any other computed data. (`cookies()` and
|
|
261
|
+
`headers()` still throw there: that guard follows the cached body's whole
|
|
262
|
+
async chain, loaders included.) Same rule across `cache()` and the PPR shell:
|
|
160
263
|
handler/cached-scope consumption = baked copy, client-side `useLoader` = live
|
|
161
264
|
(the consumption-lane rule, `/rango` → Invariants).
|
|
162
265
|
|
|
@@ -165,15 +268,17 @@ handler/cached-scope consumption = baked copy, client-side `useLoader` = live
|
|
|
165
268
|
These ctx methods **throw** inside a `"use cache"` function because their effects
|
|
166
269
|
are lost on cache hit (the function body is skipped):
|
|
167
270
|
|
|
168
|
-
- `ctx.set()` for passing values to children
|
|
169
|
-
- `ctx.
|
|
271
|
+
- `ctx.set()` for passing values to children (handler and middleware ctx)
|
|
272
|
+
- `ctx.headers.set()` and the other mutating `Headers` methods (handler and
|
|
273
|
+
middleware ctx), and a middleware's `ctx.header()`
|
|
274
|
+
- cookie writes and the request-context writers `header()`, `setCookie()`,
|
|
275
|
+
`deleteCookie()`, `setStatus()`, `onResponse()`
|
|
170
276
|
- `ctx.setTheme()`
|
|
171
277
|
- `ctx.setLocationState()`
|
|
172
|
-
- `ctx.onResponse()`
|
|
173
278
|
|
|
174
|
-
`ctx.get()` is
|
|
175
|
-
|
|
176
|
-
route-level `cache()`
|
|
279
|
+
`ctx.get()` is a read, not a side effect: it stays allowed for ordinary
|
|
280
|
+
variables and throws only for a non-cacheable one (see "Read Guards" above).
|
|
281
|
+
The same non-cacheable read throws inside a route-level `cache()` boundary.
|
|
177
282
|
|
|
178
283
|
The error message recommends two alternatives:
|
|
179
284
|
|
|
@@ -257,9 +362,8 @@ On cache miss, the function executes and the result is serialized inline (blocki
|
|
|
257
362
|
The cache **store write** (`setItem`) is deferred to `waitUntil` and does NOT block
|
|
258
363
|
the response.
|
|
259
364
|
|
|
260
|
-
On stale hit, stale data is returned immediately. Background revalidation
|
|
261
|
-
|
|
262
|
-
- store) runs entirely inside `waitUntil`.
|
|
365
|
+
On stale hit, stale data is returned immediately. Background revalidation
|
|
366
|
+
(re-execute + store write) runs entirely inside `waitUntil`.
|
|
263
367
|
|
|
264
368
|
| Phase | Blocks response? |
|
|
265
369
|
| ------------------------------------ | ---------------- |
|
|
@@ -356,75 +460,89 @@ decrypt; without it they fail until the entry expires or revalidates.
|
|
|
356
460
|
|
|
357
461
|
## Vite Transform
|
|
358
462
|
|
|
359
|
-
The
|
|
360
|
-
`registerCachedFunction()
|
|
463
|
+
The `@rangojs/router:use-cache` Vite plugin (part of `rango()`) detects the directive and wraps each cached
|
|
464
|
+
function with `registerCachedFunction(fn, functionId, profileName)` from
|
|
465
|
+
`@rangojs/router/cache-runtime`. You never call it yourself. Simplified output:
|
|
361
466
|
|
|
362
467
|
```typescript
|
|
363
|
-
// Input
|
|
364
|
-
"use cache"
|
|
468
|
+
// Input (file-level directive)
|
|
469
|
+
"use cache";
|
|
365
470
|
export async function getProducts() { ... }
|
|
366
471
|
|
|
367
|
-
// Output
|
|
368
|
-
import { registerCachedFunction } from
|
|
472
|
+
// Output (simplified)
|
|
473
|
+
import { registerCachedFunction } from "@rangojs/router/cache-runtime";
|
|
369
474
|
export const getProducts = registerCachedFunction(
|
|
370
475
|
async function getProducts() { ... },
|
|
371
|
-
"src/data/products.ts#getProducts",
|
|
372
|
-
"default"
|
|
476
|
+
"src/data/products.ts#getProducts", // a hash of this in production builds
|
|
477
|
+
"default",
|
|
373
478
|
);
|
|
374
479
|
```
|
|
375
480
|
|
|
376
|
-
|
|
481
|
+
A function-level directive (`"use cache: short"` inside the body) is hoisted out
|
|
482
|
+
of the function and wrapped the same way, with the profile name from the
|
|
483
|
+
directive.
|
|
377
484
|
|
|
378
|
-
|
|
379
|
-
// Input
|
|
380
|
-
export async function getProducts() {
|
|
381
|
-
"use cache: short";
|
|
382
|
-
return await db.query("...");
|
|
383
|
-
}
|
|
485
|
+
File-level rules:
|
|
384
486
|
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return __rango_cached_getProducts();
|
|
395
|
-
}
|
|
396
|
-
```
|
|
487
|
+
- Only exports that are statically confirmed functions are wrapped
|
|
488
|
+
(`export async function foo() {}`, `export const foo = async () => {}`). Any
|
|
489
|
+
other export (a constant, or a factory call like `makeCached(fn)`) fails the
|
|
490
|
+
build — move it to another module.
|
|
491
|
+
- The default export of a file named `layout.tsx` or `template.tsx` (any
|
|
492
|
+
`.ts/.tsx/.js/.jsx` extension) is not wrapped: it receives `children`, which
|
|
493
|
+
cannot be part of a cache key.
|
|
494
|
+
- Functions carrying their own `"use server"` directive are server actions, not
|
|
495
|
+
cached functions, and are left alone.
|
|
397
496
|
|
|
398
497
|
## Backing Store
|
|
399
498
|
|
|
400
|
-
|
|
401
|
-
One store, one
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
`
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
499
|
+
`"use cache"` reads and writes the app-level store from `createRouter({ cache })`
|
|
500
|
+
— the same store `cache()` boundaries and cached loaders use. One store, one
|
|
501
|
+
configuration. (`Static()`/`Prerender()` output is not in this store; it is
|
|
502
|
+
built into the server bundle, see `/prerender`.)
|
|
503
|
+
|
|
504
|
+
Entries are tagged by the profile's `tags` plus any runtime `cacheTag(...tags)`
|
|
505
|
+
calls in the function body. `cacheTag` has two forms: inside a `"use cache"`
|
|
506
|
+
function it tags that entry; called during a request render outside
|
|
507
|
+
`"use cache"` it tags the request's `ppr` shell / document-cache entry instead
|
|
508
|
+
(see `/caching` → "Tag-Based Invalidation"). All built-in stores
|
|
509
|
+
(`MemorySegmentCacheStore`, `CFCacheStore`, `VercelCacheStore`) index by tag.
|
|
510
|
+
Invalidate on demand with `updateTag(...tags)` (awaitable,
|
|
511
|
+
read-your-own-writes; for server actions) or `revalidateTag(...tags)`
|
|
512
|
+
(background, non-blocking; for route handlers/webhooks). Both hard-purge; the
|
|
513
|
+
difference is awaitability, not stale-serving. For `CFCacheStore`, cross-colo
|
|
514
|
+
invalidation needs a `kv` namespace (markers live in that same namespace) or
|
|
515
|
+
`tagPurge`. The separate `revalidate()` export is the client-update axis (which
|
|
516
|
+
segments re-render on a navigation or action), not a cache bust.
|
|
517
|
+
|
|
518
|
+
A `"use cache"` entry's tags do not propagate to an enclosing `cache()` segment
|
|
519
|
+
entry: invalidating them re-runs the function on the next miss of the outer
|
|
520
|
+
boundary, not before (see `/cache-guide` → "Nesting rule").
|
|
415
521
|
|
|
416
522
|
## Interaction with Other Caching
|
|
417
523
|
|
|
418
|
-
| Mechanism
|
|
419
|
-
|
|
|
420
|
-
| `"use cache"`
|
|
421
|
-
| `cache()`
|
|
422
|
-
| `cache(
|
|
423
|
-
| `
|
|
424
|
-
| `
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
524
|
+
| Mechanism | Granularity | When | Use case |
|
|
525
|
+
| ---------------- | ------------------ | ---------- | ----------------------------------------------------- |
|
|
526
|
+
| `"use cache"` | Function/component | Runtime | Cache individual data fetches or components |
|
|
527
|
+
| loader `cache()` | One loader's data | Runtime | Cache a loader result while the page stays live |
|
|
528
|
+
| `cache()` DSL | Route segment tree | Runtime | Cache entire route subtrees with children |
|
|
529
|
+
| `ppr` | HTML shell | Runtime | Serve a cached HTML shell, resume live holes (`/ppr`) |
|
|
530
|
+
| `Static()` | One segment | Build-time | Render once at build, no params |
|
|
531
|
+
| `Prerender()` | Route segment tree | Build-time | Pre-render known params, optional live fallback |
|
|
532
|
+
|
|
533
|
+
Inside a `ppr` shell, a `"use cache"` value that renders as shell material is
|
|
534
|
+
pinned at capture time for the life of that shell (see `/ppr` → Pitfalls).
|
|
535
|
+
|
|
536
|
+
## Dev Mode and tests
|
|
537
|
+
|
|
538
|
+
The transform runs in dev exactly as in production, but caching only happens
|
|
539
|
+
when the router has a store: there is no implicit dev store. Configure one (a
|
|
540
|
+
`MemorySegmentCacheStore` is the usual dev/test choice) to see cache hits
|
|
541
|
+
locally.
|
|
542
|
+
|
|
543
|
+
With no item-capable store configured, a `"use cache"` function simply runs on
|
|
544
|
+
every call. The request-scoped guards (`cookies()`, `ctx.set()`, …) are not
|
|
545
|
+
active on that uncached path, so a guard violation only shows up once a store is
|
|
546
|
+
configured — test with a store. Under Vitest, `runLoader`, `renderHandler`, and
|
|
547
|
+
the other `@rangojs/router/testing` helpers take `{ cacheStore, cacheProfiles }`
|
|
548
|
+
and warn when a cached function ran without one (see `/testing`).
|
package/skills/vercel/SKILL.md
CHANGED
|
@@ -6,7 +6,11 @@ argument-hint:
|
|
|
6
6
|
|
|
7
7
|
# Vercel deployment
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This skill covers deploying a Rango app to Vercel Functions: the `vercel`
|
|
10
|
+
preset, function settings, the Runtime Cache store, host routers, tracing, and
|
|
11
|
+
local validation. For router options in general see `/router-setup`.
|
|
12
|
+
|
|
13
|
+
The `vercel` preset builds like the `node` preset (Vercel runs Node Functions, not Workers): rango owns the RSC entry (it imports the named `router` export of your `createRouter()` module), folds `process.env.NODE_ENV` for the SSR/RSC build, and after `vite build` assembles a `.vercel/output` directory (Build Output API v3) from `dist/`: a single streaming Node Function plus the static client assets.
|
|
10
14
|
|
|
11
15
|
## Deployment boundary
|
|
12
16
|
|
|
@@ -30,27 +34,27 @@ checklist before adding shared-cache headers.
|
|
|
30
34
|
## Setup
|
|
31
35
|
|
|
32
36
|
```bash
|
|
33
|
-
|
|
37
|
+
pnpm add @vercel/functions
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
```typescript
|
|
37
41
|
// vite.config.ts
|
|
38
42
|
import { defineConfig } from "vite";
|
|
39
|
-
import react from "@vitejs/plugin-react";
|
|
40
43
|
import { rango } from "@rangojs/router/vite";
|
|
41
44
|
|
|
42
45
|
export default defineConfig({
|
|
43
|
-
plugins: [
|
|
46
|
+
plugins: [rango({ preset: "vercel" })], // add react() first if you use @vitejs/plugin-react
|
|
44
47
|
});
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
`@vercel/functions` is required: it backs the generated function launcher (`waitUntil`) and `VercelCacheStore`. The build fails with a clear error if it is missing.
|
|
48
51
|
|
|
49
|
-
`vite build` produces `.vercel/output`; deploy with the Vercel CLI (`vercel deploy --prebuilt`) or via Git integration.
|
|
52
|
+
`vite build` produces `.vercel/output`; deploy with the Vercel CLI (`vercel deploy --prebuilt`) or via Git integration. The generated routing serves files first (hashed assets under the assets directory get `Cache-Control: public, max-age=31536000, immutable`) and sends everything else to the function.
|
|
50
53
|
|
|
51
54
|
## Function configuration
|
|
52
55
|
|
|
53
|
-
Per-function knobs go under `vercel` and are written into `.vc-config.json
|
|
56
|
+
Per-function knobs go under `vercel` and are written into `.vc-config.json`
|
|
57
|
+
(`functionName` also sets the `config.json` route):
|
|
54
58
|
|
|
55
59
|
```typescript
|
|
56
60
|
rango({
|
|
@@ -60,11 +64,15 @@ rango({
|
|
|
60
64
|
maxDuration: 30, // seconds, default
|
|
61
65
|
memory: 1024, // MB (platform default when omitted)
|
|
62
66
|
regions: ["fra1"], // pin regions (platform default when omitted)
|
|
63
|
-
functionName: "index", // the <name>.func dir + config.json route
|
|
67
|
+
functionName: "index", // the <name>.func dir + config.json route (default)
|
|
64
68
|
},
|
|
65
69
|
});
|
|
66
70
|
```
|
|
67
71
|
|
|
72
|
+
`runtime` must be a `nodejs*` runtime. The preset emits a Node serverless
|
|
73
|
+
function, so the build fails on any other value; the Edge runtime is not
|
|
74
|
+
supported.
|
|
75
|
+
|
|
68
76
|
## Runtime Cache
|
|
69
77
|
|
|
70
78
|
`VercelCacheStore` wraps the Vercel Runtime Cache for segment, item, response,
|
|
@@ -72,6 +80,7 @@ and PPR shell families. Locally (no `process.env.VERCEL`) fall back to an
|
|
|
72
80
|
in-memory store so dev/preview work without the platform:
|
|
73
81
|
|
|
74
82
|
```typescript
|
|
83
|
+
import { createRouter } from "@rangojs/router";
|
|
75
84
|
import {
|
|
76
85
|
MemorySegmentCacheStore,
|
|
77
86
|
VercelCacheStore,
|
|
@@ -97,7 +106,7 @@ function resolveCache() {
|
|
|
97
106
|
export const router = createRouter({ cache: resolveCache }).routes(/* ... */);
|
|
98
107
|
```
|
|
99
108
|
|
|
100
|
-
The cache factory receives `(env, ctx)
|
|
109
|
+
The cache factory receives `(env, ctx)`: on Vercel `env` is `process.env` and `ctx` is `{ waitUntil }`; off-platform `ctx` is `undefined`. Cache semantics and tag invalidation: `/caching`, `/cache-guide`.
|
|
101
110
|
|
|
102
111
|
## Host routers (multi-app)
|
|
103
112
|
|
|
@@ -121,7 +130,7 @@ hostRouter.host(["."]).lazy(() => import("./apps/site/handler.js"));
|
|
|
121
130
|
export default hostRouter; // the instance
|
|
122
131
|
```
|
|
123
132
|
|
|
124
|
-
`{ env, ctx }` is threaded unchanged from the function to each matched sub-app's handler and its `cache(env, ctx)` factory. See
|
|
133
|
+
`{ env, ctx }` is threaded unchanged from the function to each matched sub-app's handler and its `cache(env, ctx)` factory. See `/host-router` for sub-app structure and routing patterns.
|
|
125
134
|
|
|
126
135
|
## Tracing (custom spans)
|
|
127
136
|
|
|
@@ -138,12 +147,13 @@ registerOTel({ serviceName: "my-app" });
|
|
|
138
147
|
export const tracing = createVercelTracing();
|
|
139
148
|
|
|
140
149
|
// router.tsx — importing `tracing` runs instrumentation.ts (and registerOTel)
|
|
150
|
+
import { createRouter } from "@rangojs/router";
|
|
141
151
|
import { tracing } from "./instrumentation.js";
|
|
142
152
|
export const router = createRouter({ tracing }).routes(/* ... */);
|
|
143
153
|
```
|
|
144
154
|
|
|
145
|
-
`createVercelTracing(opts?)` takes `{ enabled, spans, tracerName, tracer }`
|
|
155
|
+
`createVercelTracing(opts?)` takes `{ enabled, spans, tracerName, tracer }` (`tracerName` defaults to `"rango"`; `tracer` overrides the global tracer) and emits the same phases as `createCloudflareTracing`: `rango.request`, `rango.middleware`, `rango.action`, `rango.loader`, `rango.handler`, `rango.render`, `rango.ssr`, `rango.response`, and `rango.background`. Caveats: Node-runtime only (Vercel custom spans are unsupported on Edge); `registerOTel()` must run before the first request; `@vercel/otel` is what unlocks Vercel Session Tracing + Trace Drains. The deploy bundles `@vercel/otel` and its `@opentelemetry/*` peers into the function (no `node_modules` at runtime), so they must be installed. The Rango repository's `examples/vercel-basic` (not shipped in this package) has a worked setup; `/observability` covers the cross-platform tracing model.
|
|
146
156
|
|
|
147
157
|
## Local validation without deploying
|
|
148
158
|
|
|
149
|
-
`vite preview`
|
|
159
|
+
`vite preview` does not run the assembled function. To exercise it locally, serve `.vercel/output` with filesystem-then-function routing (static files first, everything else to `functions/<name>.func/index.mjs`). The Rango repository's `examples/vercel-basic` (not shipped in this package) has two scripts for this: `scripts/preview.mjs` (`pnpm preview:vercel`) serves it in place, and `scripts/smoke.mjs` copies it to a temp dir outside the repo for a faithful deploy test (isolated filesystem, ESM, self-contained bundle). Both share `scripts/serve-vercel-output.mjs`.
|