@rangojs/router 0.0.0-experimental.002d056c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
package/package.json
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rangojs/router",
|
|
3
|
+
"version": "0.0.0-experimental.002d056c",
|
|
4
|
+
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"react-server-components",
|
|
8
|
+
"router",
|
|
9
|
+
"rsc",
|
|
10
|
+
"vite"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/ivogt/vite-rsc#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ivogt/vite-rsc/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Ivo Todorov",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/ivogt/vite-rsc.git",
|
|
21
|
+
"directory": "packages/rangojs-router"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"rango": "./dist/bin/rango.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"!src/**/__tests__",
|
|
29
|
+
"!src/**/__mocks__",
|
|
30
|
+
"!src/**/*.test.ts",
|
|
31
|
+
"!src/**/*.test.tsx",
|
|
32
|
+
"dist",
|
|
33
|
+
"skills",
|
|
34
|
+
"AGENTS.md",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"type": "module",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./src/index.rsc.ts",
|
|
41
|
+
"react-server": "./src/index.rsc.ts",
|
|
42
|
+
"default": "./src/index.ts"
|
|
43
|
+
},
|
|
44
|
+
"./server": {
|
|
45
|
+
"types": "./src/server.ts",
|
|
46
|
+
"import": "./src/server.ts"
|
|
47
|
+
},
|
|
48
|
+
"./client": {
|
|
49
|
+
"types": "./src/client.tsx",
|
|
50
|
+
"react-server": "./src/client.rsc.tsx",
|
|
51
|
+
"default": "./src/client.tsx"
|
|
52
|
+
},
|
|
53
|
+
"./browser": {
|
|
54
|
+
"types": "./src/browser/index.ts",
|
|
55
|
+
"default": "./src/browser/index.ts"
|
|
56
|
+
},
|
|
57
|
+
"./ssr": {
|
|
58
|
+
"types": "./src/ssr/index.tsx",
|
|
59
|
+
"default": "./src/ssr/index.tsx"
|
|
60
|
+
},
|
|
61
|
+
"./rsc": {
|
|
62
|
+
"types": "./src/rsc/index.ts",
|
|
63
|
+
"react-server": "./src/rsc/index.ts",
|
|
64
|
+
"default": "./src/rsc/index.ts"
|
|
65
|
+
},
|
|
66
|
+
"./vite": {
|
|
67
|
+
"types": "./src/vite/index.ts",
|
|
68
|
+
"import": "./dist/vite/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./types": {
|
|
71
|
+
"types": "./src/vite/plugins/version.d.ts"
|
|
72
|
+
},
|
|
73
|
+
"./__internal": {
|
|
74
|
+
"types": "./src/__internal.ts",
|
|
75
|
+
"default": "./src/__internal.ts"
|
|
76
|
+
},
|
|
77
|
+
"./internal/deps/browser": {
|
|
78
|
+
"types": "./src/deps/browser.ts",
|
|
79
|
+
"default": "./src/deps/browser.ts"
|
|
80
|
+
},
|
|
81
|
+
"./internal/deps/ssr": {
|
|
82
|
+
"types": "./src/deps/ssr.ts",
|
|
83
|
+
"default": "./src/deps/ssr.ts"
|
|
84
|
+
},
|
|
85
|
+
"./internal/deps/rsc": {
|
|
86
|
+
"types": "./src/deps/rsc.ts",
|
|
87
|
+
"react-server": "./src/deps/rsc.ts",
|
|
88
|
+
"default": "./src/deps/rsc.ts"
|
|
89
|
+
},
|
|
90
|
+
"./internal/deps/html-stream-client": {
|
|
91
|
+
"types": "./src/deps/html-stream-client.ts",
|
|
92
|
+
"default": "./src/deps/html-stream-client.ts"
|
|
93
|
+
},
|
|
94
|
+
"./internal/deps/html-stream-server": {
|
|
95
|
+
"types": "./src/deps/html-stream-server.ts",
|
|
96
|
+
"default": "./src/deps/html-stream-server.ts"
|
|
97
|
+
},
|
|
98
|
+
"./internal/rsc-handler": {
|
|
99
|
+
"types": "./src/rsc/handler.ts",
|
|
100
|
+
"react-server": "./src/rsc/handler.ts",
|
|
101
|
+
"default": "./src/rsc/handler.ts"
|
|
102
|
+
},
|
|
103
|
+
"./cache": {
|
|
104
|
+
"types": "./src/cache/index.ts",
|
|
105
|
+
"react-server": "./src/cache/index.ts",
|
|
106
|
+
"default": "./src/cache/index.ts"
|
|
107
|
+
},
|
|
108
|
+
"./cache-runtime": {
|
|
109
|
+
"types": "./src/cache/cache-runtime.ts",
|
|
110
|
+
"react-server": "./src/cache/cache-runtime.ts",
|
|
111
|
+
"default": "./src/cache/cache-runtime.ts"
|
|
112
|
+
},
|
|
113
|
+
"./theme": {
|
|
114
|
+
"types": "./src/theme/index.ts",
|
|
115
|
+
"default": "./src/theme/index.ts"
|
|
116
|
+
},
|
|
117
|
+
"./build": {
|
|
118
|
+
"types": "./src/build/index.ts",
|
|
119
|
+
"import": "./src/build/index.ts"
|
|
120
|
+
},
|
|
121
|
+
"./host": {
|
|
122
|
+
"types": "./src/host/index.ts",
|
|
123
|
+
"react-server": "./src/host/index.ts",
|
|
124
|
+
"default": "./src/host/index.ts"
|
|
125
|
+
},
|
|
126
|
+
"./host/testing": {
|
|
127
|
+
"types": "./src/host/testing.ts",
|
|
128
|
+
"default": "./src/host/testing.ts"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"publishConfig": {
|
|
132
|
+
"access": "public",
|
|
133
|
+
"tag": "experimental"
|
|
134
|
+
},
|
|
135
|
+
"scripts": {
|
|
136
|
+
"build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
|
|
137
|
+
"prepublishOnly": "pnpm build",
|
|
138
|
+
"typecheck": "tsc --noEmit",
|
|
139
|
+
"test": "playwright test",
|
|
140
|
+
"test:ui": "playwright test --ui",
|
|
141
|
+
"test:unit": "vitest run",
|
|
142
|
+
"test:unit:watch": "vitest"
|
|
143
|
+
},
|
|
144
|
+
"dependencies": {
|
|
145
|
+
"@vitejs/plugin-rsc": "^0.5.14",
|
|
146
|
+
"magic-string": "^0.30.17",
|
|
147
|
+
"picomatch": "^4.0.3",
|
|
148
|
+
"rsc-html-stream": "^0.0.7"
|
|
149
|
+
},
|
|
150
|
+
"devDependencies": {
|
|
151
|
+
"@playwright/test": "^1.49.1",
|
|
152
|
+
"@types/node": "^24.10.1",
|
|
153
|
+
"@types/react": "catalog:",
|
|
154
|
+
"@types/react-dom": "catalog:",
|
|
155
|
+
"esbuild": "^0.27.0",
|
|
156
|
+
"jiti": "^2.6.1",
|
|
157
|
+
"react": "catalog:",
|
|
158
|
+
"react-dom": "catalog:",
|
|
159
|
+
"tinyexec": "^0.3.2",
|
|
160
|
+
"typescript": "^5.3.0",
|
|
161
|
+
"vitest": "^4.0.0"
|
|
162
|
+
},
|
|
163
|
+
"peerDependencies": {
|
|
164
|
+
"@cloudflare/vite-plugin": "^1.25.0",
|
|
165
|
+
"@vitejs/plugin-rsc": "^0.5.14",
|
|
166
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
167
|
+
"vite": "^7.3.0"
|
|
168
|
+
},
|
|
169
|
+
"peerDependenciesMeta": {
|
|
170
|
+
"@cloudflare/vite-plugin": {
|
|
171
|
+
"optional": true
|
|
172
|
+
},
|
|
173
|
+
"vite": {
|
|
174
|
+
"optional": true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: breadcrumbs
|
|
3
|
+
description: Built-in Breadcrumbs handle for accumulating breadcrumb navigation across route segments
|
|
4
|
+
argument-hint: [setup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Breadcrumbs
|
|
8
|
+
|
|
9
|
+
Built-in handle for accumulating breadcrumb items across route segments.
|
|
10
|
+
Each layout/route pushes items via `ctx.use(Breadcrumbs)`, and they are
|
|
11
|
+
collected in parent-to-child order with automatic deduplication by `href`.
|
|
12
|
+
|
|
13
|
+
## BreadcrumbItem Type
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
interface BreadcrumbItem {
|
|
17
|
+
label: string; // Display text
|
|
18
|
+
href: string; // URL the breadcrumb links to
|
|
19
|
+
content?: ReactNode | Promise<ReactNode>; // Optional extra content (sync or async)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Pushing Breadcrumbs (Server)
|
|
24
|
+
|
|
25
|
+
Import `Breadcrumbs` from `@rangojs/router` in RSC/server context:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { urls, Breadcrumbs } from "@rangojs/router";
|
|
29
|
+
import { Outlet } from "@rangojs/router/client";
|
|
30
|
+
|
|
31
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
32
|
+
// Root layout pushes "Home"
|
|
33
|
+
layout((ctx) => {
|
|
34
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
35
|
+
breadcrumb({ label: "Home", href: "/" });
|
|
36
|
+
return <RootLayout />;
|
|
37
|
+
}, () => [
|
|
38
|
+
path("/", HomePage, { name: "home" }),
|
|
39
|
+
|
|
40
|
+
// Nested layout pushes "Blog"
|
|
41
|
+
layout((ctx) => {
|
|
42
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
43
|
+
breadcrumb({ label: "Blog", href: "/blog" });
|
|
44
|
+
return <BlogLayout />;
|
|
45
|
+
}, () => [
|
|
46
|
+
path("/blog", BlogIndex, { name: "blog.index" }),
|
|
47
|
+
|
|
48
|
+
// Route handler pushes post title
|
|
49
|
+
path("/blog/:slug", (ctx) => {
|
|
50
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
51
|
+
breadcrumb({ label: ctx.params.slug, href: `/blog/${ctx.params.slug}` });
|
|
52
|
+
return <BlogPost slug={ctx.params.slug} />;
|
|
53
|
+
}, { name: "blog.post" }),
|
|
54
|
+
]),
|
|
55
|
+
]),
|
|
56
|
+
]);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
On `/blog/my-post`, breadcrumbs accumulate: `Home > Blog > my-post`.
|
|
60
|
+
|
|
61
|
+
## Async Content
|
|
62
|
+
|
|
63
|
+
The `content` field supports `Promise<ReactNode>` for streaming:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
path("/product/:id", async (ctx) => {
|
|
67
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
68
|
+
const productPromise = fetchProduct(ctx.params.id);
|
|
69
|
+
|
|
70
|
+
breadcrumb({
|
|
71
|
+
label: "Product",
|
|
72
|
+
href: `/product/${ctx.params.id}`,
|
|
73
|
+
content: productPromise.then((p) => <span>({p.category})</span>),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const product = await productPromise;
|
|
77
|
+
return <ProductPage product={product} />;
|
|
78
|
+
}, { name: "product" })
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Async content is a `Promise<ReactNode>`. Resolve it in your component
|
|
82
|
+
with React's `use()` hook wrapped in `<Suspense>`.
|
|
83
|
+
|
|
84
|
+
## Consuming Breadcrumbs (Client)
|
|
85
|
+
|
|
86
|
+
Use `useHandle(Breadcrumbs)` in a client component to read the accumulated items:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
"use client";
|
|
90
|
+
import { useHandle, Breadcrumbs, Link } from "@rangojs/router/client";
|
|
91
|
+
|
|
92
|
+
function BreadcrumbNav() {
|
|
93
|
+
const breadcrumbs = useHandle(Breadcrumbs);
|
|
94
|
+
|
|
95
|
+
if (!breadcrumbs.length) return null;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<nav aria-label="Breadcrumb">
|
|
99
|
+
<ol>
|
|
100
|
+
{breadcrumbs.map((crumb, i) => (
|
|
101
|
+
<li key={crumb.href}>
|
|
102
|
+
{i === breadcrumbs.length - 1 ? (
|
|
103
|
+
<span aria-current="page">{crumb.label}</span>
|
|
104
|
+
) : (
|
|
105
|
+
<Link to={crumb.href}>{crumb.label}</Link>
|
|
106
|
+
)}
|
|
107
|
+
</li>
|
|
108
|
+
))}
|
|
109
|
+
</ol>
|
|
110
|
+
</nav>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### With Selector
|
|
116
|
+
|
|
117
|
+
Re-render only when the selected value changes:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// Only the last breadcrumb
|
|
121
|
+
const current = useHandle(Breadcrumbs, (data) => data.at(-1));
|
|
122
|
+
|
|
123
|
+
// Breadcrumb count
|
|
124
|
+
const count = useHandle(Breadcrumbs, (data) => data.length);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Deduplication
|
|
128
|
+
|
|
129
|
+
The built-in collect function deduplicates by `href`. If multiple segments
|
|
130
|
+
push the same `href`, the last one wins. This prevents duplicates when
|
|
131
|
+
navigating between sibling routes that share a common breadcrumb.
|
|
132
|
+
|
|
133
|
+
## Passing as Props
|
|
134
|
+
|
|
135
|
+
Breadcrumbs handle can be passed from server to client components:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// Server component
|
|
139
|
+
path("/dashboard", (ctx) => {
|
|
140
|
+
const breadcrumb = ctx.use(Breadcrumbs);
|
|
141
|
+
breadcrumb({ label: "Dashboard", href: "/dashboard" });
|
|
142
|
+
return <DashboardNav handle={Breadcrumbs} />;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Client component
|
|
146
|
+
("use client");
|
|
147
|
+
import { useHandle, type Breadcrumbs } from "@rangojs/router/client";
|
|
148
|
+
|
|
149
|
+
function DashboardNav({ handle }: { handle: typeof Breadcrumbs }) {
|
|
150
|
+
const crumbs = useHandle(handle);
|
|
151
|
+
return (
|
|
152
|
+
<nav>
|
|
153
|
+
{crumbs.map((c) => (
|
|
154
|
+
<a href={c.href}>{c.label}</a>
|
|
155
|
+
))}
|
|
156
|
+
</nav>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Complete Example
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// urls.tsx
|
|
165
|
+
import { urls, Breadcrumbs, Meta } from "@rangojs/router";
|
|
166
|
+
import { Outlet, MetaTags } from "@rangojs/router/client";
|
|
167
|
+
import { BreadcrumbNav } from "./components/BreadcrumbNav";
|
|
168
|
+
|
|
169
|
+
function RootLayout() {
|
|
170
|
+
return (
|
|
171
|
+
<html lang="en">
|
|
172
|
+
<head><MetaTags /></head>
|
|
173
|
+
<body>
|
|
174
|
+
<BreadcrumbNav />
|
|
175
|
+
<main><Outlet /></main>
|
|
176
|
+
</body>
|
|
177
|
+
</html>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
182
|
+
layout((ctx) => {
|
|
183
|
+
ctx.use(Breadcrumbs)({ label: "Home", href: "/" });
|
|
184
|
+
ctx.use(Meta)({ title: "My App" });
|
|
185
|
+
return <RootLayout />;
|
|
186
|
+
}, () => [
|
|
187
|
+
path("/", () => <h1>Welcome</h1>, { name: "home" }),
|
|
188
|
+
|
|
189
|
+
layout((ctx) => {
|
|
190
|
+
ctx.use(Breadcrumbs)({ label: "Shop", href: "/shop" });
|
|
191
|
+
return <Outlet />;
|
|
192
|
+
}, () => [
|
|
193
|
+
path("/shop", () => <h1>Shop</h1>, { name: "shop" }),
|
|
194
|
+
path("/shop/:slug", (ctx) => {
|
|
195
|
+
ctx.use(Breadcrumbs)({
|
|
196
|
+
label: ctx.params.slug,
|
|
197
|
+
href: `/shop/${ctx.params.slug}`,
|
|
198
|
+
});
|
|
199
|
+
return <h1>Product: {ctx.params.slug}</h1>;
|
|
200
|
+
}, { name: "shop.product" }),
|
|
201
|
+
]),
|
|
202
|
+
]),
|
|
203
|
+
]);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Navigating to `/shop/widget` produces: `Home / Shop / widget`
|
|
207
|
+
|
|
208
|
+
## Custom Handles
|
|
209
|
+
|
|
210
|
+
Create your own handle with `createHandle()`:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { createHandle } from "@rangojs/router";
|
|
214
|
+
|
|
215
|
+
// Default: flatten into array
|
|
216
|
+
export const PageTitle = createHandle<string, string>(
|
|
217
|
+
(segments) => segments.flat().at(-1) ?? "Default Title",
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// No collect function: default flattens into T[]
|
|
221
|
+
export const Warnings = createHandle<string>();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The Vite `exposeInternalIds` plugin auto-injects a stable `$$id` based on
|
|
225
|
+
file path and export name. No manual naming required for project-local code.
|
|
226
|
+
|
|
227
|
+
### Handles in 3rd-party packages
|
|
228
|
+
|
|
229
|
+
The `exposeInternalIds` plugin skips `node_modules/`, so handles defined in
|
|
230
|
+
published packages won't get auto-injected IDs. Pass a manual tag as the
|
|
231
|
+
second argument to `createHandle()`:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { createHandle } from "@rangojs/router";
|
|
235
|
+
|
|
236
|
+
// With a collect function (reducer): collect is first arg, tag is second
|
|
237
|
+
export const Breadcrumbs = createHandle<BreadcrumbItem, BreadcrumbItem[]>(
|
|
238
|
+
collectBreadcrumbs,
|
|
239
|
+
"__my_package_breadcrumbs__",
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Without a collect function: pass undefined, then the tag
|
|
243
|
+
export const Warnings = createHandle<string>(
|
|
244
|
+
undefined,
|
|
245
|
+
"__my_package_warnings__",
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The tag must be globally unique and stable across builds. Without it,
|
|
250
|
+
`createHandle` throws in development mode.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cache-guide
|
|
3
|
+
description: When to use cache() DSL vs "use cache" directive — key differences and decision guide
|
|
4
|
+
argument-hint:
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# cache() vs "use cache" — When to Use Which
|
|
8
|
+
|
|
9
|
+
Both mechanisms share the same backing store, cache profiles, and tag-based
|
|
10
|
+
invalidation. They differ in scope, cache key, execution model, and runtime control.
|
|
11
|
+
|
|
12
|
+
## Key Differences
|
|
13
|
+
|
|
14
|
+
| | `cache()` DSL | `"use cache"` directive |
|
|
15
|
+
| -------------------- | ----------------------------------------------------- | -------------------------------------------------- |
|
|
16
|
+
| **Scope** | Route segment tree (handler + children + parallels) | Single function return value |
|
|
17
|
+
| **Defined at** | Route definition site (`urls.ts`) | Inside function body or at file top |
|
|
18
|
+
| **Cache key** | Request type + pathname + params (+ optional custom) | Function identity + serialized non-tainted args |
|
|
19
|
+
| **Execution on hit** | All-or-nothing: entire handler skipped | Partial: function body skipped, calling code runs |
|
|
20
|
+
| **Runtime control** | `condition` to disable, custom `key` function | None — if the directive is present, it caches |
|
|
21
|
+
| **Side effects** | No guards needed — handler doesn't run on hit | `ctx.header()`, `ctx.set()`, etc. throw at runtime |
|
|
22
|
+
| **Handle data** | Captured and replayed | Captured and replayed |
|
|
23
|
+
| **Loaders** | Always fresh — excluded from cache, opt-in per loader | Can be used inside loaders |
|
|
24
|
+
| **Nesting** | Nest `cache()` boundaries with different TTLs | Compose by calling cached functions from uncached |
|
|
25
|
+
|
|
26
|
+
### cache() Cache Key
|
|
27
|
+
|
|
28
|
+
The key is `{requestType}:{pathname}:{params}` where requestType is one of
|
|
29
|
+
`doc:`, `partial:`, or `intercept:`. This means the same URL cached separately
|
|
30
|
+
for full document loads, client navigations, and intercept navigations.
|
|
31
|
+
|
|
32
|
+
Custom `key` functions can segment the cache further (e.g., by user role or locale).
|
|
33
|
+
`condition` can disable caching entirely at runtime (e.g., skip for authenticated users).
|
|
34
|
+
|
|
35
|
+
### "use cache" Cache Key
|
|
36
|
+
|
|
37
|
+
The key is `use-cache:{functionId}:{serializedArgs}` where functionId is a stable
|
|
38
|
+
ID from the Vite transform (module path + export name) and args are serialized via
|
|
39
|
+
RSC `encodeReply()`. Tainted arguments (ctx, env, req) are excluded.
|
|
40
|
+
|
|
41
|
+
## Execution Model
|
|
42
|
+
|
|
43
|
+
This is the most important distinction.
|
|
44
|
+
|
|
45
|
+
### cache() — all-or-nothing
|
|
46
|
+
|
|
47
|
+
On cache hit, the cache-lookup middleware short-circuits the entire pipeline.
|
|
48
|
+
No handler code runs. On miss, all handlers execute normally and segments are
|
|
49
|
+
stored.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
HIT → cached segments served, loaders resolved fresh, no handler runs
|
|
53
|
+
MISS → all handlers run, segments cached, response built normally
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Headers, cookies, and ctx.set() calls inside handlers naturally don't execute on
|
|
57
|
+
hit. There is no partial execution, so no runtime guards are needed.
|
|
58
|
+
|
|
59
|
+
### "use cache" — partial execution
|
|
60
|
+
|
|
61
|
+
Only the wrapped function body is skipped on hit. The code that calls the
|
|
62
|
+
cached function still runs. This means ctx side effects inside the cached body
|
|
63
|
+
would silently disappear on hit.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
HIT → function body skipped, calling code runs, handle data replayed
|
|
67
|
+
MISS → function body runs, return value + handle data cached
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Runtime guards throw if you call cookies(), headers(), ctx.header(), ctx.set(),
|
|
71
|
+
ctx.onResponse(), ctx.setTheme(), or ctx.setLocationState() inside a "use cache"
|
|
72
|
+
function. cookies() and headers() are blocked because per-request data is not in the
|
|
73
|
+
cache key. Side-effect methods are blocked because their effects are lost on hit.
|
|
74
|
+
Use ctx.use(Handle) instead for data — handle data is captured and replayed.
|
|
75
|
+
|
|
76
|
+
## When to Use cache()
|
|
77
|
+
|
|
78
|
+
Use the route-level `cache()` DSL when:
|
|
79
|
+
|
|
80
|
+
- **Caching entire routes or sections** — wrap a set of paths with one TTL.
|
|
81
|
+
- **You need runtime control** — disable caching for authenticated users with
|
|
82
|
+
`condition`, or segment cache keys by user/locale with `key`.
|
|
83
|
+
- **UI rendering is expensive** — the cached segments include the rendered
|
|
84
|
+
component tree, skipping RSC rendering on hit.
|
|
85
|
+
- **You want one cache entry per URL** — keyed on pathname + params, not on
|
|
86
|
+
function arguments.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
export const urlpatterns = urls(({ path, cache }) => [
|
|
90
|
+
cache({ ttl: 300, condition: (ctx) => !ctx.get("user") }, () => [
|
|
91
|
+
path("/blog", BlogIndex, { name: "blog" }),
|
|
92
|
+
path("/blog/:slug", BlogPost, { name: "blogPost" }),
|
|
93
|
+
]),
|
|
94
|
+
]);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## When to Use "use cache"
|
|
98
|
+
|
|
99
|
+
Use the `"use cache"` directive when:
|
|
100
|
+
|
|
101
|
+
- **Caching a specific data fetch** — one database query used across multiple
|
|
102
|
+
routes or components.
|
|
103
|
+
- **Different call sites need different cache entries** — the cache key includes
|
|
104
|
+
all non-tainted arguments, so `getProduct("a")` and `getProduct("b")` cache
|
|
105
|
+
separately.
|
|
106
|
+
- **Fine-grained caching within a handler** — cache the expensive part, keep
|
|
107
|
+
ctx side effects outside.
|
|
108
|
+
- **Caching an RSC component** — a component that fetches its own data can cache
|
|
109
|
+
its entire render.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
async function getProductData(slug: string) {
|
|
113
|
+
"use cache: short";
|
|
114
|
+
return await db.query("SELECT * FROM products WHERE slug = ?", [slug]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handler calls cached function, sets headers outside
|
|
118
|
+
async function ProductPage(ctx) {
|
|
119
|
+
const data = await getProductData(ctx.params.slug);
|
|
120
|
+
ctx.header("X-Product", data.id);
|
|
121
|
+
return <Product data={data} />;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Combining Both
|
|
126
|
+
|
|
127
|
+
They compose naturally. Use `cache()` for the route boundary and `"use cache"`
|
|
128
|
+
for shared data functions:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// urls.tsx — route-level cache for the rendered segment tree
|
|
132
|
+
cache({ ttl: 60 }, () => [
|
|
133
|
+
path("/product/:slug", ProductPage, { name: "product" }),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
// data.ts — function-level cache for the database query
|
|
137
|
+
export async function getProductData(slug: string) {
|
|
138
|
+
"use cache: long";
|
|
139
|
+
return await db.query("SELECT * FROM products WHERE slug = ?", [slug]);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
On cache hit for the route, the handler doesn't run and `getProductData` is never
|
|
144
|
+
called. On cache miss, the handler runs and `getProductData` may itself return a
|
|
145
|
+
cached value from a previous call with the same slug.
|
|
146
|
+
|
|
147
|
+
## Headers and Cookies
|
|
148
|
+
|
|
149
|
+
Neither mechanism caches response headers or cookies.
|
|
150
|
+
|
|
151
|
+
- **cache()**: Headers set by handlers are naturally absent on hit because no
|
|
152
|
+
handler runs. If you need headers on every response, set them in middleware
|
|
153
|
+
(which runs before cache lookup).
|
|
154
|
+
- **"use cache"**: cookies() and headers() throw inside the cached function
|
|
155
|
+
(both reads and writes). ctx.header() also throws. Move them outside.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Set headers that must appear on every response in middleware
|
|
159
|
+
middleware(async (ctx, next) => {
|
|
160
|
+
ctx.header("X-Frame-Options", "DENY");
|
|
161
|
+
await next();
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Loaders Are Always Fresh
|
|
166
|
+
|
|
167
|
+
Loaders are **never cached** by route-level `cache()`. Even on a full cache hit
|
|
168
|
+
where all UI segments are served from cache, loaders are re-resolved fresh on
|
|
169
|
+
every request. This is enforced at two levels:
|
|
170
|
+
|
|
171
|
+
1. **Storage**: `cacheRoute()` filters out loader segments before serialization
|
|
172
|
+
(`segments.filter(s => s.type !== "loader")`).
|
|
173
|
+
2. **Retrieval**: On cache hit, `resolveLoadersOnly()` runs after yielding cached
|
|
174
|
+
UI segments, ensuring fresh data regardless of cache state.
|
|
175
|
+
|
|
176
|
+
This means `cache()` gives you cached UI + fresh data by default. To also cache
|
|
177
|
+
a loader's data, explicitly opt in with `loader(Fn, () => [cache({...})])`.
|
|
178
|
+
|
|
179
|
+
## cache() Placement Patterns
|
|
180
|
+
|
|
181
|
+
### Wrapping children of a path
|
|
182
|
+
|
|
183
|
+
An orphan `cache()` inside a path's children becomes the parent for all
|
|
184
|
+
subsequent siblings. Everything below the cache boundary is cached as one unit:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
|
|
188
|
+
cache("long"),
|
|
189
|
+
layout(DashboardSidebar, () => [
|
|
190
|
+
parallel("@stats", StatsPanel),
|
|
191
|
+
parallel("@activity", ActivityFeed),
|
|
192
|
+
]),
|
|
193
|
+
]),
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
On hit: DashboardPage, DashboardSidebar, StatsPanel, and ActivityFeed are all
|
|
197
|
+
served from cache. On miss: all handlers run, all segments cached together.
|
|
198
|
+
|
|
199
|
+
### Uncached layout with cached children
|
|
200
|
+
|
|
201
|
+
The cache boundary only covers what's inside it. Parent segments above the
|
|
202
|
+
boundary are not cached and always re-render:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
layout(RootLayout, () => [
|
|
206
|
+
// RootLayout is NOT cached — runs every request
|
|
207
|
+
path("/products/:slug", ProductPage, { name: "product" }, () => [
|
|
208
|
+
cache("long"),
|
|
209
|
+
layout(ProductSidebar),
|
|
210
|
+
parallel("@reviews", ReviewsPanel),
|
|
211
|
+
parallel("@related", RelatedProducts),
|
|
212
|
+
]),
|
|
213
|
+
]),
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
RootLayout renders fresh every request. ProductPage, ProductSidebar,
|
|
217
|
+
ReviewsPanel, and RelatedProducts are all inside the cache boundary and
|
|
218
|
+
served from cache on hit. This is useful when the root layout depends on
|
|
219
|
+
request-specific data (user session, theme) but the product content is
|
|
220
|
+
cacheable.
|
|
221
|
+
|
|
222
|
+
### Loader-level caching
|
|
223
|
+
|
|
224
|
+
Loaders are excluded from route-level `cache()` by default — they always
|
|
225
|
+
resolve fresh. To opt a specific loader into caching, give it its own
|
|
226
|
+
`cache()` child:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
230
|
+
// This loader is cached for 5 minutes
|
|
231
|
+
loader(ProductLoader, () => [cache({ ttl: 300 })]),
|
|
232
|
+
|
|
233
|
+
// This loader is always fresh
|
|
234
|
+
loader(CartLoader),
|
|
235
|
+
]),
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
This attaches the cache config directly to the loader entry. The loader's
|
|
239
|
+
data is cached independently from the route's segment cache. Loader caching
|
|
240
|
+
supports custom keys, tags, SWR, conditional bypass, and per-loader store
|
|
241
|
+
overrides — see `/loader` for the full reference.
|
|
242
|
+
|
|
243
|
+
## Decision Flowchart
|
|
244
|
+
|
|
245
|
+
1. Do you want to cache an entire route or group of routes?
|
|
246
|
+
**Yes** -> `cache()`
|
|
247
|
+
2. Do you need runtime conditions (skip for auth users, key by locale)?
|
|
248
|
+
**Yes** -> `cache()` with `condition` / `key`
|
|
249
|
+
3. Do you want to cache a data fetch shared across routes?
|
|
250
|
+
**Yes** -> `"use cache"`
|
|
251
|
+
4. Do you need different cache entries for different arguments?
|
|
252
|
+
**Yes** -> `"use cache"` (keyed by args)
|
|
253
|
+
5. Is the expensive part rendering, not data fetching?
|
|
254
|
+
**Yes** -> `cache()` (caches rendered segments)
|
|
255
|
+
6. Is the expensive part a single query inside a larger handler?
|
|
256
|
+
**Yes** -> `"use cache"` on the query function
|
|
257
|
+
|
|
258
|
+
## See Also
|
|
259
|
+
|
|
260
|
+
- `/caching` — cache() DSL setup, stores, nested boundaries
|
|
261
|
+
- `/use-cache` — "use cache" directive details, profiles, transforms, guards
|
|
262
|
+
- `/document-cache` — Edge caching with Cache-Control headers (different layer)
|