@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"@opentelemetry/api": "^1.9.0",
|
|
198
198
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
199
199
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
200
|
-
"@playwright/test": "^1.
|
|
200
|
+
"@playwright/test": "^1.62.0",
|
|
201
201
|
"@testing-library/dom": "^10.4.1",
|
|
202
202
|
"@testing-library/react": "^16.3.2",
|
|
203
203
|
"@types/node": "^24.10.1",
|
|
@@ -5,6 +5,10 @@ description: Build a typed client for consuming your own response-route JSON API
|
|
|
5
5
|
|
|
6
6
|
# Typed API Client
|
|
7
7
|
|
|
8
|
+
Use this skill when first-party TypeScript code (a client component, another
|
|
9
|
+
worker, a script) calls your own `path.json()` endpoints and you want typed
|
|
10
|
+
params and payloads without writing `fetch` + URL building by hand.
|
|
11
|
+
|
|
8
12
|
Response routes (`path.json()`) already ship typed responses — `RouteResponse<typeof patterns, "name">` resolves to the **bare payload**, inferred from your handler with no codegen. This skill wraps that inference in a small **typed client** so first-party TypeScript code calls your endpoints like functions instead of hand-writing `fetch` + URL building per call site.
|
|
9
13
|
|
|
10
14
|
This is a **recipe, not a framework feature** — copy the helper below into your app. It depends only on **type-only** imports from `@rangojs/router` (`RouteResponse`, `ExtractParams`, `ProblemDetails`), which are erased at build time, so it runs anywhere a `fetch` does — **browser, worker, or server**. Nothing new to install or version.
|
|
@@ -14,7 +18,9 @@ This is a **recipe, not a framework feature** — copy the helper below into you
|
|
|
14
18
|
## What you get
|
|
15
19
|
|
|
16
20
|
```ts
|
|
17
|
-
const api = createApiClient
|
|
21
|
+
const api = createApiClient<typeof apiShopPatterns, typeof routes>(routes, {
|
|
22
|
+
baseUrl,
|
|
23
|
+
});
|
|
18
24
|
|
|
19
25
|
await api.health.get(); // no params → callable bare
|
|
20
26
|
await api.product.get({ params: { productId } }); // params typed + required
|
|
@@ -31,8 +37,8 @@ await api.cart.post({ body: { productId, qty: 2 } }); // body sent as JSON
|
|
|
31
37
|
|
|
32
38
|
## The two inputs
|
|
33
39
|
|
|
34
|
-
1. **The `urls()` patterns
|
|
35
|
-
2. **The generated route map** — the name → pattern source. `rango generate` emits a per-module `<name>.gen.ts` exporting `routes`:
|
|
40
|
+
1. **The `urls()` patterns type** — the type source, passed as the first type argument. `typeof apiShopPatterns` carries the per-route response payloads and patterns. Only the type is needed, so import it with `import type` (see Notes).
|
|
41
|
+
2. **The generated route map** — the name → pattern source, passed as the value argument (and its `typeof` as the second type argument). `rango generate` emits a per-module `<name>.gen.ts` exporting `routes`:
|
|
36
42
|
|
|
37
43
|
```ts
|
|
38
44
|
// api-shop.gen.ts (generated — do not edit)
|
|
@@ -44,7 +50,9 @@ export const routes = {
|
|
|
44
50
|
} as const;
|
|
45
51
|
```
|
|
46
52
|
|
|
47
|
-
Routes that declare a **search schema** are generated as objects instead — `index: { path: "/", search: { q: "string" } }`. The helper accepts both the string and `{ path }` forms.
|
|
53
|
+
Routes that declare a **search schema** are generated as objects instead — `index: { path: "/", search: { q: "string" } }`. The helper accepts both the string and `{ path }` forms.
|
|
54
|
+
|
|
55
|
+
The per-module map's keys are the block's local route names (matching `RouteResponse`), but its patterns are **mount-relative**: `"/catalog"`, not `"/api/shop/catalog"`. When the block is mounted under a URL prefix, either put the prefix in `baseUrl` (`` `${origin}/api/shop` ``) or build a local-keyed map of absolute patterns from the global `NamedRoutes` in `router.named-routes.gen.ts` (e.g. `{ catalog: NamedRoutes["apiShop.catalog"], ... } as const`).
|
|
48
56
|
|
|
49
57
|
## The helper (copy into your app)
|
|
50
58
|
|
|
@@ -54,6 +62,7 @@ import type {
|
|
|
54
62
|
RouteResponse,
|
|
55
63
|
ExtractParams,
|
|
56
64
|
ProblemDetails,
|
|
65
|
+
UrlPatterns,
|
|
57
66
|
} from "@rangojs/router";
|
|
58
67
|
|
|
59
68
|
type SearchParams = Record<string, string | number | boolean>;
|
|
@@ -121,11 +130,12 @@ function fillPath(pattern: string, params?: Record<string, string>): string {
|
|
|
121
130
|
.replace(/\/{2,}/g, "/");
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
// Both type arguments are explicit: TPatterns has no value to infer from,
|
|
134
|
+
// and TypeScript does not mix explicit and inferred type arguments.
|
|
124
135
|
export function createApiClient<
|
|
125
|
-
TPatterns,
|
|
126
|
-
|
|
136
|
+
TPatterns extends UrlPatterns<any, any, Record<string, unknown>>,
|
|
137
|
+
TRouteMap extends Record<string, RouteMapEntry>,
|
|
127
138
|
>(
|
|
128
|
-
_patterns: TPatterns,
|
|
129
139
|
routeMap: TRouteMap,
|
|
130
140
|
opts: { baseUrl?: string; fetch?: typeof fetch } = {},
|
|
131
141
|
): ApiClient<TPatterns, TRouteMap> {
|
|
@@ -179,11 +189,11 @@ export function createApiClient<
|
|
|
179
189
|
## Using it
|
|
180
190
|
|
|
181
191
|
```ts
|
|
182
|
-
import { apiShopPatterns } from "./urls/api-shop";
|
|
192
|
+
import type { apiShopPatterns } from "./urls/api-shop";
|
|
183
193
|
import { routes } from "./urls/api-shop.gen";
|
|
184
194
|
import { createApiClient, ApiError } from "./lib/api-client";
|
|
185
195
|
|
|
186
|
-
const api = createApiClient
|
|
196
|
+
const api = createApiClient<typeof apiShopPatterns, typeof routes>(routes, {
|
|
187
197
|
baseUrl: import.meta.env.VITE_API_URL ?? "",
|
|
188
198
|
});
|
|
189
199
|
|
|
@@ -202,8 +212,10 @@ try {
|
|
|
202
212
|
## Notes
|
|
203
213
|
|
|
204
214
|
- **Client-safe by construction.** The helper imports only **types** from `@rangojs/router` (erased at build) and builds URLs itself by substituting `:params` into the pattern — it does **not** use `createReverse`, which is a server/RSC-only export that throws in the browser. So `createApiClient` works in client components, workers, and on the server alike.
|
|
215
|
+
- **Import the `urls()` module with `import type`.** `urls()` only runs on the server: in client code a value import throws when the module loads, and it would pull the route handlers into the client bundle. A type import is erased at build, so the same call works in client components, workers and server code. The generated `*.gen.ts` map has no imports and is safe to import anywhere.
|
|
216
|
+
- **Always pass both type arguments.** If you leave them out, `TPatterns` falls back to its constraint and every response is typed `never`, so the first property access on a result is a compile error rather than a silent `any`.
|
|
205
217
|
- **Params are route-typed; search and body are not.** Path params come from the route pattern (`ExtractParams`), so they are precise and required. `search` is generically typed (`Record<string, string | number | boolean>`), and `body` is `unknown` (serialized to JSON). Typed request **input** needs a declared schema layer, which is intentionally out of scope here — thread per-route schemas in yourself if you want typed search/body.
|
|
206
|
-
- **Verb-agnostic wire.** Rango response routes do not dispatch on HTTP method — `.get`/`.post`/etc. set the request method but hit the same handler. Use whichever verb reads best for the operation.
|
|
218
|
+
- **Verb-agnostic wire.** Rango response routes do not dispatch on HTTP method — `.get`/`.post`/etc. set the request method but hit the same handler (branch on `ctx.request.method` inside it if you need to). Use whichever verb reads best for the operation. Response-route `cache()` only serves and stores GET/HEAD.
|
|
207
219
|
- **Path building.** `fillPath` handles standard `:param`, optional `:param?`, and constrained `:param(a|b)` forms. For exotic patterns or strict trailing-slash policies, swap in your own builder (or the router's `reverse` on the server).
|
|
208
220
|
- **Want a return-based style instead of throwing?** Branch on `res.ok` yourself: the wire is the bare value on 2xx and `ProblemDetails` on non-2xx (see `/response-routes`). Wrapping the calls in a `{ ok, data } | { ok: false, error }` result type is a small variation on the same helper.
|
|
209
221
|
- **Third parties.** The typed client is TypeScript-only and needs your route types. External consumers in any language use the plain wire as-is (bare JSON + problem+json); no client required.
|
|
@@ -7,9 +7,14 @@ argument-hint: "[setup]"
|
|
|
7
7
|
# Breadcrumbs
|
|
8
8
|
|
|
9
9
|
Built-in handle for accumulating breadcrumb items across route segments.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`
|
|
10
|
+
Route, layout, and parallel-slot handlers and loader bodies push items via
|
|
11
|
+
`ctx.use(Breadcrumbs)`; client components read the collected trail with
|
|
12
|
+
`useHandle(Breadcrumbs)`. Items are collected in parent-to-child order with
|
|
13
|
+
automatic deduplication by `href`.
|
|
14
|
+
|
|
15
|
+
`Breadcrumbs` is exported from both `@rangojs/router` (server, for pushing) and
|
|
16
|
+
`@rangojs/router/client` (for `useHandle`). For page titles and meta tags, use
|
|
17
|
+
the `Meta` handle the same way.
|
|
13
18
|
|
|
14
19
|
## BreadcrumbItem Type
|
|
15
20
|
|
|
@@ -59,6 +64,20 @@ export const urlpatterns = urls(({ path, layout }) => [
|
|
|
59
64
|
|
|
60
65
|
On `/blog/my-post`, breadcrumbs accumulate: `Home > Blog > my-post`.
|
|
61
66
|
|
|
67
|
+
The push function also accepts a `Promise` of an item, or an async function
|
|
68
|
+
(called immediately) that returns one:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
72
|
+
breadcrumb(async () => {
|
|
73
|
+
const post = await getPost(ctx.params.slug);
|
|
74
|
+
return { label: post.title, href: `/blog/${post.slug}` };
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Either way the item is resolved before any consumer reads it — `useHandle`
|
|
79
|
+
never sees a pending crumb.
|
|
80
|
+
|
|
62
81
|
## Pushing from Loaders
|
|
63
82
|
|
|
64
83
|
A data-derived crumb belongs where the data lives — the loader body pushes
|
|
@@ -66,6 +85,8 @@ with the same API (handler parity), so routes without handlers (`useLoader`
|
|
|
66
85
|
consumption, `clientUrls()` groups) still build trails:
|
|
67
86
|
|
|
68
87
|
```typescript
|
|
88
|
+
import { Breadcrumbs, createLoader } from "@rangojs/router";
|
|
89
|
+
|
|
69
90
|
export const ProductLoader = createLoader(async (ctx) => {
|
|
70
91
|
const product = await getProduct(ctx.params.slug);
|
|
71
92
|
|
|
@@ -82,7 +103,16 @@ Delivery follows the loader race model: a push that beats the handler barrier
|
|
|
82
103
|
is in the SSR'd document; a push after a slow fetch streams and applies
|
|
83
104
|
client-side (`useHandle` re-renders when it lands). To guarantee document
|
|
84
105
|
delivery, register the loader as `loader(Def, { ssr: false })` —
|
|
85
|
-
see `/loader` → "Writing Handles from Loaders".
|
|
106
|
+
see `/loader` → "Writing Handles from Loaders". On a `ppr` route the race is
|
|
107
|
+
decided at capture: a loader without `ssr: false` never runs there, so its
|
|
108
|
+
crumb is not in the shell and applies client-side on every HIT; with
|
|
109
|
+
`ssr: false` the crumb bakes into the shell (`/ppr` → "On a shell HIT").
|
|
110
|
+
Under `cache()` loader crumbs are not stored with the cached segments; the
|
|
111
|
+
loader re-pushes them on every hit, so they appear once even without an `href`.
|
|
112
|
+
A loader with its own `cache()` keeps its crumbs on its own hits too: they are
|
|
113
|
+
replayed from the loader's cache entry. A crumb from a loader it awaits appears
|
|
114
|
+
once even when a sibling loader or the handler reads that loader too; that live
|
|
115
|
+
run's crumb replaces the cached one in the trail.
|
|
86
116
|
|
|
87
117
|
## Async Content
|
|
88
118
|
|
|
@@ -107,6 +137,13 @@ path("/product/:id", async (ctx) => {
|
|
|
107
137
|
Async content is a `Promise<ReactNode>`. Resolve it in your component
|
|
108
138
|
with React's `use()` hook wrapped in `<Suspense>`.
|
|
109
139
|
|
|
140
|
+
A crumb the HANDLER pushes is handler output. In a route `cache()` entry and
|
|
141
|
+
in a `ppr` shell it is stored resolved: the cache write, or the shell capture
|
|
142
|
+
(within `ppr.captureTimeout`), waits for a `content` promise and for a
|
|
143
|
+
`.defer()` slot (below), and every HIT replays the crumb as it was resolved
|
|
144
|
+
then, without running the handler. For per-request crumb content, push it
|
|
145
|
+
from a loader without `ssr: false` (see "Pushing from Loaders").
|
|
146
|
+
|
|
110
147
|
### Deferred content (decide now, resolve from a deep component)
|
|
111
148
|
|
|
112
149
|
When the handler should DECIDE to push a crumb early but the value is produced
|
|
@@ -219,9 +256,11 @@ const count = useHandle(Breadcrumbs, (data) => data.length);
|
|
|
219
256
|
|
|
220
257
|
## Deduplication
|
|
221
258
|
|
|
222
|
-
The built-in collect function deduplicates by `href
|
|
223
|
-
|
|
224
|
-
|
|
259
|
+
The built-in collect function deduplicates by `href`: each `href` appears once,
|
|
260
|
+
at the position of its **first** push, with the value of its **last** push. A
|
|
261
|
+
child that re-pushes a parent's `href` therefore updates that crumb's label in
|
|
262
|
+
place without reordering the trail. Items without a string `href` are kept
|
|
263
|
+
as-is and skip deduplication.
|
|
225
264
|
|
|
226
265
|
## Passing as Props
|
|
227
266
|
|
|
@@ -246,7 +285,9 @@ function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
|
|
|
246
285
|
return (
|
|
247
286
|
<nav>
|
|
248
287
|
{crumbs.map((c) => (
|
|
249
|
-
<a
|
|
288
|
+
<a key={c.href} href={c.href}>
|
|
289
|
+
{c.label}
|
|
290
|
+
</a>
|
|
250
291
|
))}
|
|
251
292
|
</nav>
|
|
252
293
|
);
|
|
@@ -256,20 +297,31 @@ function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
|
|
|
256
297
|
## Complete Example
|
|
257
298
|
|
|
258
299
|
```typescript
|
|
300
|
+
// document.tsx — passed to createRouter({ document: Document, ... })
|
|
301
|
+
"use client";
|
|
302
|
+
import type { ReactNode } from "react";
|
|
303
|
+
import { Html } from "@rangojs/router/client";
|
|
304
|
+
|
|
305
|
+
export function Document({ children }: { children: ReactNode }) {
|
|
306
|
+
return (
|
|
307
|
+
<html lang="en">
|
|
308
|
+
<head><Html.Meta /></head>
|
|
309
|
+
<body>{children}</body>
|
|
310
|
+
</html>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
259
314
|
// urls.tsx
|
|
260
315
|
import { urls, Breadcrumbs, Meta } from "@rangojs/router";
|
|
261
|
-
import { Outlet
|
|
262
|
-
import { BreadcrumbNav } from "./components/BreadcrumbNav";
|
|
316
|
+
import { Outlet } from "@rangojs/router/client";
|
|
317
|
+
import { BreadcrumbNav } from "./components/BreadcrumbNav"; // the client component above
|
|
263
318
|
|
|
264
319
|
function RootLayout() {
|
|
265
320
|
return (
|
|
266
|
-
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
<main><Outlet /></main>
|
|
271
|
-
</body>
|
|
272
|
-
</html>
|
|
321
|
+
<>
|
|
322
|
+
<BreadcrumbNav />
|
|
323
|
+
<main><Outlet /></main>
|
|
324
|
+
</>
|
|
273
325
|
);
|
|
274
326
|
}
|
|
275
327
|
|
|
@@ -342,9 +394,9 @@ second argument to `createHandle()`:
|
|
|
342
394
|
import { createHandle } from "@rangojs/router";
|
|
343
395
|
|
|
344
396
|
// With a collect function (reducer): collect is first arg, tag is second
|
|
345
|
-
export const
|
|
346
|
-
|
|
347
|
-
"
|
|
397
|
+
export const PackageCrumbs = createHandle<CrumbItem, CrumbItem[]>(
|
|
398
|
+
collectCrumbs,
|
|
399
|
+
"__my_package_crumbs__",
|
|
348
400
|
);
|
|
349
401
|
|
|
350
402
|
// Without a collect function: pass undefined, then the tag
|
|
@@ -354,5 +406,6 @@ export const Warnings = createHandle<string>(
|
|
|
354
406
|
);
|
|
355
407
|
```
|
|
356
408
|
|
|
357
|
-
The tag must be globally unique and stable across builds. Without it
|
|
358
|
-
`createHandle` throws in
|
|
409
|
+
The tag must be globally unique and stable across builds. Without it (and
|
|
410
|
+
without a plugin-injected id), `createHandle` throws — in dev and in production
|
|
411
|
+
builds. Only test runners fall back to a synthetic id.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bundle-analysis
|
|
3
|
-
description: Audit a Rango app's production bundle for server-side code leaking into the client, dev/prod React duplication, oversized chunks, and
|
|
3
|
+
description: Audit a Rango app's production bundle for server-side code leaking into the client, dev/prod React duplication, oversized chunks, and poor client-chunk grouping. Use when investigating bundle size growth, before a production deploy, or when the client/SSR/RSC output suddenly balloons.
|
|
4
4
|
argument-hint: "<app-dir>"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,11 +13,11 @@ Use this when you want **proof** that your Rango app is shipping the bundles you
|
|
|
13
13
|
Your app builds in three Vite environments — `client`, `ssr`, and `rsc` — and each ships its own bundle. The most common bundle bugs in a Rango app are:
|
|
14
14
|
|
|
15
15
|
1. **Server code leaking into the client.** A file that imports `node:fs`, calls a database, or contains action logic ends up in the client bundle because a client component pulled it in transitively. Symptom: your client bundle is much larger than expected, sometimes with imports that fail at runtime.
|
|
16
|
-
2. **Both dev and prod React in the SSR/RSC bundle.** When `process.env.NODE_ENV` isn't folded at build time, React's CJS files ship both `.development.js` _and_ `.production.js` variants — doubling React's footprint. The Cloudflare
|
|
16
|
+
2. **Both dev and prod React in the SSR/RSC bundle.** When `process.env.NODE_ENV` isn't folded at build time, React's CJS files ship both `.development.js` _and_ `.production.js` variants — doubling React's footprint. The Cloudflare Vite plugin and rango's `vercel` preset fold NODE_ENV automatically; on the default `node` preset, `vite build` does it for the client but not always for SSR/RSC, so add the `define` from Step 2.
|
|
17
17
|
3. **An oversized routes-manifest in your RSC worker.** The `virtual:rsc-router/routes-manifest/<routerId>` chunk holds your route trie and precomputed entries — large only in proportion to your route count. If it's surprisingly big, you may have unintentionally generated routes (e.g., parametrized fixtures) that bloated the trie.
|
|
18
|
-
4. **
|
|
18
|
+
4. **Poor client-chunk grouping.** `rango({ clientChunks })` decides how `"use client"` modules are grouped into browser chunks. The default groups them by route directory (`routes/<id>/**` → `app-<id>`), with flat folders like `src/components/` in one shared chunk; `clientChunks: false` ships one client chunk per router. A route that pulls in unrelated routes' client code, or one oversized shared chunk, points at the grouping.
|
|
19
19
|
|
|
20
|
-
Tree-shaking does _not_ catch
|
|
20
|
+
Tree-shaking does _not_ catch generated data inlined as string literals, or data-dependent conditionals like React's `NODE_ENV` branches. You need a visualizer.
|
|
21
21
|
|
|
22
22
|
## Step 1: Install the visualizer
|
|
23
23
|
|
|
@@ -64,9 +64,9 @@ export default defineConfig(({ command }) => ({
|
|
|
64
64
|
// your existing plugins...
|
|
65
65
|
...analyze(),
|
|
66
66
|
],
|
|
67
|
-
// For
|
|
68
|
-
// emit only the .production.js variants in SSR/RSC. Skip
|
|
69
|
-
//
|
|
67
|
+
// For node-preset apps, fold NODE_ENV explicitly so React's CJS files
|
|
68
|
+
// emit only the .production.js variants in SSR/RSC. Skip it on the
|
|
69
|
+
// cloudflare and vercel presets, which already do this.
|
|
70
70
|
define:
|
|
71
71
|
command === "build"
|
|
72
72
|
? { "process.env.NODE_ENV": JSON.stringify("production") }
|
|
@@ -104,12 +104,12 @@ The treemap shows nested boxes; box area = uncompressed size. Hover for gzip/bro
|
|
|
104
104
|
**Look for:**
|
|
105
105
|
|
|
106
106
|
- **Your server code.** Any of your own files that contain database queries, secret keys, server actions implementation (not the action _reference_), or `node:` imports. If they appear in the client treemap with non-zero bytes, they leaked. Common causes:
|
|
107
|
-
- A shared module that mixes client and server code
|
|
107
|
+
- A shared module that mixes client-safe helpers and server-only code, imported from a `"use client"` component.
|
|
108
108
|
- A barrel file (`index.ts`) that re-exports both client and server symbols. Tree-shaking should help, but `JSON.parse('{...}')` data and side-effecting top-level statements survive.
|
|
109
109
|
- Client component imports a server-only utility through an indirect path (e.g., shared types file that pulls server modules).
|
|
110
110
|
- **Multiple copies of the same package.** Look for two boxes with the same package name but different version paths. Usually means a transitive dep pinned a different version.
|
|
111
|
-
- **The
|
|
112
|
-
- **Per-route client
|
|
111
|
+
- **The router runtime chunk** (`router-<hash>.js`) is around **40–45 KB gzip**; the Rango repository's CI caps it at 44 KB on its reference app. If yours is much larger, check that client components import from `@rangojs/router/client`, not from a server path.
|
|
112
|
+
- **Per-route client chunks** (groups named like `app-<route>`, plus `app-fallback` for error/not-found boundaries). A route's chunk should hold that route's client components. One massive shared chunk usually means client components live in a flat folder (or `clientChunks: false`); move route-specific ones under a route directory or pass a `clientChunks` function (see `/router-setup`).
|
|
113
113
|
|
|
114
114
|
### Now check `ssr.html`
|
|
115
115
|
|
|
@@ -123,23 +123,23 @@ The treemap shows nested boxes; box area = uncompressed size. Hover for gzip/bro
|
|
|
123
123
|
|
|
124
124
|
**Look for:**
|
|
125
125
|
|
|
126
|
-
- **`virtual:rsc-router/routes-manifest`** should be **tiny
|
|
127
|
-
- **`virtual:rsc-router/routes-manifest/<
|
|
126
|
+
- **`virtual:rsc-router/routes-manifest`** (the eager module) should be **tiny**: well under 2 KB gzip even with tens of thousands of routes. If it is large, route data is being inlined eagerly; report it (see below).
|
|
127
|
+
- **`virtual:rsc-router/routes-manifest/<routerId>`** is the lazy per-router chunk. Its size is proportional to your route count. For a typical app: 5–50 KB gzip. For a stress-test app with thousands of routes: hundreds of KB. If yours is unexpectedly huge, check whether you're generating routes you don't need.
|
|
128
128
|
- **`<your-router>.named-routes.gen.ts`** — generated route map. Should match your route count.
|
|
129
129
|
- **Your action and loader implementations** — these run server-side. Expected to be here, not in client.
|
|
130
130
|
- **Worker-incompatible code** (Node-only imports like `node:fs` that Cloudflare doesn't support). The build will usually fail before the analyzer runs, but if you're seeing runtime errors at the edge, the RSC treemap shows what made it in.
|
|
131
131
|
|
|
132
132
|
## Step 5: Fix what you find
|
|
133
133
|
|
|
134
|
-
| Finding | Fix
|
|
135
|
-
| -------------------------------------------------------- |
|
|
136
|
-
| Your server code in `client.html` (non-zero bytes) | Audit the import chain. Add `"
|
|
137
|
-
| Your server code in `client.html` listed but 0 bytes | Tree-shaking already eliminated it. Cosmetic. Leave it.
|
|
138
|
-
| `react-dom-server.edge.development-*.js` in SSR or RSC | Add the `define` block from Step 2 to your vite config.
|
|
139
|
-
|
|
|
140
|
-
| Same package version present twice | Run `pnpm dedupe` (or `npm dedupe`). If the duplication persists, a transitive dep pins an incompatible version — open a PR upstream or pin the resolution.
|
|
141
|
-
| Client chunk > 500 KB gzip with a single dominant module | That module is your largest client component. Consider lazy-loading via dynamic `import()` or moving non-interactive parts to server components.
|
|
142
|
-
|
|
|
134
|
+
| Finding | Fix |
|
|
135
|
+
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| Your server code in `client.html` (non-zero bytes) | Audit the import chain from the `"use client"` file. Add `import "server-only"` to server-only modules so the client build fails on the import (`@vitejs/plugin-rsc` checks it; no package install needed). Move shared data out of barrel files. Do not add `"use server"` to a utility module: that turns every export into a client-callable server action. |
|
|
137
|
+
| Your server code in `client.html` listed but 0 bytes | Tree-shaking already eliminated it. Cosmetic. Leave it. |
|
|
138
|
+
| `react-dom-server.edge.development-*.js` in SSR or RSC | Add the `define` block from Step 2 to your vite config. |
|
|
139
|
+
| Eager routes-manifest is large in RSC | Update `@rangojs/router`; if it persists, report it as a regression. |
|
|
140
|
+
| Same package version present twice | Run `pnpm dedupe` (or `npm dedupe`). If the duplication persists, a transitive dep pins an incompatible version — open a PR upstream or pin the resolution. |
|
|
141
|
+
| Client chunk > 500 KB gzip with a single dominant module | That module is your largest client component. Consider lazy-loading via dynamic `import()` or moving non-interactive parts to server components. |
|
|
142
|
+
| One route downloads other routes' client code | Move route-specific client components under a route directory (`routes/<id>/`), or group them yourself with a `clientChunks` function. |
|
|
143
143
|
|
|
144
144
|
## When to re-run
|
|
145
145
|
|
|
@@ -156,4 +156,4 @@ If a finding looks like a `@rangojs/router` regression (the framework is shippin
|
|
|
156
156
|
- The `@rangojs/router` version (`pnpm why @rangojs/router`).
|
|
157
157
|
- Your `vite.config.ts`.
|
|
158
158
|
|
|
159
|
-
The
|
|
159
|
+
The Rango repository runs the same analysis in CI (client leak checks, a router-chunk size cap, and an eager-manifest ceiling).
|