@rangojs/router 0.0.0-experimental.10

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.
Files changed (172) hide show
  1. package/CLAUDE.md +43 -0
  2. package/README.md +19 -0
  3. package/dist/bin/rango.js +227 -0
  4. package/dist/vite/index.js +3039 -0
  5. package/package.json +171 -0
  6. package/skills/caching/SKILL.md +191 -0
  7. package/skills/debug-manifest/SKILL.md +108 -0
  8. package/skills/document-cache/SKILL.md +180 -0
  9. package/skills/fonts/SKILL.md +165 -0
  10. package/skills/hooks/SKILL.md +442 -0
  11. package/skills/intercept/SKILL.md +190 -0
  12. package/skills/layout/SKILL.md +213 -0
  13. package/skills/links/SKILL.md +180 -0
  14. package/skills/loader/SKILL.md +246 -0
  15. package/skills/middleware/SKILL.md +202 -0
  16. package/skills/mime-routes/SKILL.md +124 -0
  17. package/skills/parallel/SKILL.md +228 -0
  18. package/skills/prerender/SKILL.md +283 -0
  19. package/skills/rango/SKILL.md +54 -0
  20. package/skills/response-routes/SKILL.md +358 -0
  21. package/skills/route/SKILL.md +173 -0
  22. package/skills/router-setup/SKILL.md +346 -0
  23. package/skills/tailwind/SKILL.md +129 -0
  24. package/skills/theme/SKILL.md +78 -0
  25. package/skills/typesafety/SKILL.md +394 -0
  26. package/src/__internal.ts +175 -0
  27. package/src/bin/rango.ts +24 -0
  28. package/src/browser/event-controller.ts +876 -0
  29. package/src/browser/index.ts +18 -0
  30. package/src/browser/link-interceptor.ts +121 -0
  31. package/src/browser/lru-cache.ts +69 -0
  32. package/src/browser/merge-segment-loaders.ts +126 -0
  33. package/src/browser/navigation-bridge.ts +913 -0
  34. package/src/browser/navigation-client.ts +165 -0
  35. package/src/browser/navigation-store.ts +823 -0
  36. package/src/browser/partial-update.ts +600 -0
  37. package/src/browser/react/Link.tsx +248 -0
  38. package/src/browser/react/NavigationProvider.tsx +346 -0
  39. package/src/browser/react/ScrollRestoration.tsx +94 -0
  40. package/src/browser/react/context.ts +53 -0
  41. package/src/browser/react/index.ts +52 -0
  42. package/src/browser/react/location-state-shared.ts +120 -0
  43. package/src/browser/react/location-state.ts +62 -0
  44. package/src/browser/react/mount-context.ts +32 -0
  45. package/src/browser/react/use-action.ts +240 -0
  46. package/src/browser/react/use-client-cache.ts +56 -0
  47. package/src/browser/react/use-handle.ts +203 -0
  48. package/src/browser/react/use-href.tsx +40 -0
  49. package/src/browser/react/use-link-status.ts +134 -0
  50. package/src/browser/react/use-mount.ts +31 -0
  51. package/src/browser/react/use-navigation.ts +140 -0
  52. package/src/browser/react/use-segments.ts +188 -0
  53. package/src/browser/request-controller.ts +164 -0
  54. package/src/browser/rsc-router.tsx +352 -0
  55. package/src/browser/scroll-restoration.ts +324 -0
  56. package/src/browser/segment-structure-assert.ts +67 -0
  57. package/src/browser/server-action-bridge.ts +762 -0
  58. package/src/browser/shallow.ts +35 -0
  59. package/src/browser/types.ts +478 -0
  60. package/src/build/generate-manifest.ts +377 -0
  61. package/src/build/generate-route-types.ts +828 -0
  62. package/src/build/index.ts +36 -0
  63. package/src/build/route-trie.ts +239 -0
  64. package/src/cache/cache-scope.ts +563 -0
  65. package/src/cache/cf/cf-cache-store.ts +428 -0
  66. package/src/cache/cf/index.ts +19 -0
  67. package/src/cache/document-cache.ts +340 -0
  68. package/src/cache/index.ts +58 -0
  69. package/src/cache/memory-segment-store.ts +150 -0
  70. package/src/cache/memory-store.ts +253 -0
  71. package/src/cache/types.ts +392 -0
  72. package/src/client.rsc.tsx +83 -0
  73. package/src/client.tsx +643 -0
  74. package/src/component-utils.ts +76 -0
  75. package/src/components/DefaultDocument.tsx +23 -0
  76. package/src/debug.ts +233 -0
  77. package/src/default-error-boundary.tsx +88 -0
  78. package/src/deps/browser.ts +8 -0
  79. package/src/deps/html-stream-client.ts +2 -0
  80. package/src/deps/html-stream-server.ts +2 -0
  81. package/src/deps/rsc.ts +10 -0
  82. package/src/deps/ssr.ts +2 -0
  83. package/src/errors.ts +295 -0
  84. package/src/handle.ts +130 -0
  85. package/src/handles/MetaTags.tsx +193 -0
  86. package/src/handles/index.ts +6 -0
  87. package/src/handles/meta.ts +247 -0
  88. package/src/host/cookie-handler.ts +159 -0
  89. package/src/host/errors.ts +97 -0
  90. package/src/host/index.ts +56 -0
  91. package/src/host/pattern-matcher.ts +214 -0
  92. package/src/host/router.ts +330 -0
  93. package/src/host/testing.ts +79 -0
  94. package/src/host/types.ts +138 -0
  95. package/src/host/utils.ts +25 -0
  96. package/src/href-client.ts +202 -0
  97. package/src/href-context.ts +33 -0
  98. package/src/index.rsc.ts +121 -0
  99. package/src/index.ts +165 -0
  100. package/src/loader.rsc.ts +207 -0
  101. package/src/loader.ts +47 -0
  102. package/src/network-error-thrower.tsx +21 -0
  103. package/src/outlet-context.ts +15 -0
  104. package/src/prerender/param-hash.ts +35 -0
  105. package/src/prerender/store.ts +40 -0
  106. package/src/prerender.ts +156 -0
  107. package/src/reverse.ts +267 -0
  108. package/src/root-error-boundary.tsx +277 -0
  109. package/src/route-content-wrapper.tsx +193 -0
  110. package/src/route-definition.ts +1431 -0
  111. package/src/route-map-builder.ts +242 -0
  112. package/src/route-types.ts +220 -0
  113. package/src/router/error-handling.ts +287 -0
  114. package/src/router/handler-context.ts +158 -0
  115. package/src/router/intercept-resolution.ts +387 -0
  116. package/src/router/loader-resolution.ts +327 -0
  117. package/src/router/manifest.ts +216 -0
  118. package/src/router/match-api.ts +621 -0
  119. package/src/router/match-context.ts +264 -0
  120. package/src/router/match-middleware/background-revalidation.ts +236 -0
  121. package/src/router/match-middleware/cache-lookup.ts +382 -0
  122. package/src/router/match-middleware/cache-store.ts +276 -0
  123. package/src/router/match-middleware/index.ts +81 -0
  124. package/src/router/match-middleware/intercept-resolution.ts +281 -0
  125. package/src/router/match-middleware/segment-resolution.ts +184 -0
  126. package/src/router/match-pipelines.ts +214 -0
  127. package/src/router/match-result.ts +213 -0
  128. package/src/router/metrics.ts +62 -0
  129. package/src/router/middleware.ts +791 -0
  130. package/src/router/pattern-matching.ts +407 -0
  131. package/src/router/revalidation.ts +190 -0
  132. package/src/router/router-context.ts +301 -0
  133. package/src/router/segment-resolution.ts +1315 -0
  134. package/src/router/trie-matching.ts +172 -0
  135. package/src/router/types.ts +163 -0
  136. package/src/router.gen.ts +6 -0
  137. package/src/router.ts +2423 -0
  138. package/src/rsc/handler.ts +1443 -0
  139. package/src/rsc/helpers.ts +64 -0
  140. package/src/rsc/index.ts +56 -0
  141. package/src/rsc/nonce.ts +18 -0
  142. package/src/rsc/types.ts +236 -0
  143. package/src/segment-system.tsx +442 -0
  144. package/src/server/context.ts +466 -0
  145. package/src/server/handle-store.ts +229 -0
  146. package/src/server/loader-registry.ts +174 -0
  147. package/src/server/request-context.ts +554 -0
  148. package/src/server/root-layout.tsx +10 -0
  149. package/src/server/tsconfig.json +14 -0
  150. package/src/server.ts +171 -0
  151. package/src/ssr/index.tsx +296 -0
  152. package/src/theme/ThemeProvider.tsx +291 -0
  153. package/src/theme/ThemeScript.tsx +61 -0
  154. package/src/theme/constants.ts +59 -0
  155. package/src/theme/index.ts +58 -0
  156. package/src/theme/theme-context.ts +70 -0
  157. package/src/theme/theme-script.ts +152 -0
  158. package/src/theme/types.ts +182 -0
  159. package/src/theme/use-theme.ts +44 -0
  160. package/src/types.ts +1757 -0
  161. package/src/urls.gen.ts +8 -0
  162. package/src/urls.ts +1282 -0
  163. package/src/use-loader.tsx +346 -0
  164. package/src/vite/expose-action-id.ts +344 -0
  165. package/src/vite/expose-handle-id.ts +209 -0
  166. package/src/vite/expose-loader-id.ts +426 -0
  167. package/src/vite/expose-location-state-id.ts +177 -0
  168. package/src/vite/expose-prerender-handler-id.ts +429 -0
  169. package/src/vite/index.ts +2068 -0
  170. package/src/vite/package-resolution.ts +125 -0
  171. package/src/vite/version.d.ts +12 -0
  172. package/src/vite/virtual-entries.ts +114 -0
package/package.json ADDED
@@ -0,0 +1,171 @@
1
+ {
2
+ "name": "@rangojs/router",
3
+ "version": "0.0.0-experimental.10",
4
+ "type": "module",
5
+ "description": "Django-inspired RSC router with composable URL patterns",
6
+ "author": "Ivo Todorov",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ivogt/vite-rsc.git",
11
+ "directory": "packages/rangojs-router"
12
+ },
13
+ "homepage": "https://github.com/ivogt/vite-rsc#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/ivogt/vite-rsc/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "tag": "experimental"
20
+ },
21
+ "keywords": [
22
+ "react",
23
+ "rsc",
24
+ "react-server-components",
25
+ "router",
26
+ "vite"
27
+ ],
28
+ "exports": {
29
+ ".": {
30
+ "react-server": "./src/index.rsc.ts",
31
+ "types": "./src/index.rsc.ts",
32
+ "default": "./src/index.ts"
33
+ },
34
+ "./server": {
35
+ "types": "./src/server.ts",
36
+ "import": "./src/server.ts"
37
+ },
38
+ "./client": {
39
+ "react-server": "./src/client.rsc.tsx",
40
+ "types": "./src/client.tsx",
41
+ "default": "./src/client.tsx"
42
+ },
43
+ "./browser": {
44
+ "types": "./src/browser/index.ts",
45
+ "default": "./src/browser/index.ts"
46
+ },
47
+ "./ssr": {
48
+ "types": "./src/ssr/index.tsx",
49
+ "default": "./src/ssr/index.tsx"
50
+ },
51
+ "./rsc": {
52
+ "react-server": "./src/rsc/index.ts",
53
+ "types": "./src/rsc/index.ts",
54
+ "default": "./src/rsc/index.ts"
55
+ },
56
+ "./vite": {
57
+ "types": "./src/vite/index.ts",
58
+ "import": "./dist/vite/index.js"
59
+ },
60
+ "./types": {
61
+ "types": "./src/vite/version.d.ts"
62
+ },
63
+ "./__internal": {
64
+ "types": "./src/__internal.ts",
65
+ "default": "./src/__internal.ts"
66
+ },
67
+ "./internal/deps/browser": {
68
+ "types": "./src/deps/browser.ts",
69
+ "default": "./src/deps/browser.ts"
70
+ },
71
+ "./internal/deps/ssr": {
72
+ "types": "./src/deps/ssr.ts",
73
+ "default": "./src/deps/ssr.ts"
74
+ },
75
+ "./internal/deps/rsc": {
76
+ "react-server": "./src/deps/rsc.ts",
77
+ "types": "./src/deps/rsc.ts",
78
+ "default": "./src/deps/rsc.ts"
79
+ },
80
+ "./internal/deps/html-stream-client": {
81
+ "types": "./src/deps/html-stream-client.ts",
82
+ "default": "./src/deps/html-stream-client.ts"
83
+ },
84
+ "./internal/deps/html-stream-server": {
85
+ "types": "./src/deps/html-stream-server.ts",
86
+ "default": "./src/deps/html-stream-server.ts"
87
+ },
88
+ "./internal/rsc-handler": {
89
+ "react-server": "./src/rsc/handler.ts",
90
+ "types": "./src/rsc/handler.ts",
91
+ "default": "./src/rsc/handler.ts"
92
+ },
93
+ "./cache": {
94
+ "react-server": "./src/cache/index.ts",
95
+ "types": "./src/cache/index.ts",
96
+ "default": "./src/cache/index.ts"
97
+ },
98
+ "./theme": {
99
+ "types": "./src/theme/index.ts",
100
+ "default": "./src/theme/index.ts"
101
+ },
102
+ "./build": {
103
+ "types": "./src/build/index.ts",
104
+ "import": "./src/build/index.ts"
105
+ },
106
+ "./host": {
107
+ "react-server": "./src/host/index.ts",
108
+ "types": "./src/host/index.ts",
109
+ "default": "./src/host/index.ts"
110
+ },
111
+ "./host/testing": {
112
+ "types": "./src/host/testing.ts",
113
+ "default": "./src/host/testing.ts"
114
+ }
115
+ },
116
+ "files": [
117
+ "src",
118
+ "!src/**/__tests__",
119
+ "!src/**/__mocks__",
120
+ "!src/**/*.test.ts",
121
+ "!src/**/*.test.tsx",
122
+ "dist",
123
+ "skills",
124
+ "CLAUDE.md",
125
+ "README.md"
126
+ ],
127
+ "bin": {
128
+ "rango": "./dist/bin/rango.js"
129
+ },
130
+ "peerDependencies": {
131
+ "@cloudflare/vite-plugin": "^1.21.0",
132
+ "@vitejs/plugin-rsc": "^0.5.14",
133
+ "react": "^18.0.0 || ^19.0.0",
134
+ "vite": "^7.3.0"
135
+ },
136
+ "peerDependenciesMeta": {
137
+ "@cloudflare/vite-plugin": {
138
+ "optional": true
139
+ },
140
+ "vite": {
141
+ "optional": true
142
+ }
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": "^19.2.7",
154
+ "@types/react-dom": "^19.2.3",
155
+ "esbuild": "^0.27.0",
156
+ "jiti": "^2.6.1",
157
+ "react": "^19.2.1",
158
+ "react-dom": "^19.2.1",
159
+ "tinyexec": "^0.3.2",
160
+ "typescript": "^5.3.0",
161
+ "vitest": "^4.0.0"
162
+ },
163
+ "scripts": {
164
+ "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'",
165
+ "typecheck": "tsc --noEmit",
166
+ "test": "playwright test",
167
+ "test:ui": "playwright test --ui",
168
+ "test:unit": "vitest run",
169
+ "test:unit:watch": "vitest"
170
+ }
171
+ }
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: caching
3
+ description: Configure segment caching with memory or Cloudflare KV stores in @rangojs/router
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Caching
8
+
9
+ @rangojs/router supports segment-level caching with stale-while-revalidate (SWR) for optimal performance.
10
+
11
+ ## Route-Level Caching with cache()
12
+
13
+ Use the `cache()` DSL function to cache routes:
14
+
15
+ ```typescript
16
+ import { urls } from "@rangojs/router";
17
+
18
+ export const urlpatterns = urls(({ path, cache }) => [
19
+ // Cache these routes for 60 seconds, SWR for 5 minutes
20
+ cache({ ttl: 60, swr: 300 }, () => [
21
+ path("/blog", BlogIndex, { name: "blog" }),
22
+ path("/blog/:slug", BlogPost, { name: "blogPost" }),
23
+ ]),
24
+
25
+ // Uncached routes
26
+ path("/account", AccountPage, { name: "account" }),
27
+ ]);
28
+ ```
29
+
30
+ ## Cache Options
31
+
32
+ ```typescript
33
+ cache({
34
+ ttl: 60, // Time-to-live in seconds (default: 60)
35
+ swr: 300, // Stale-while-revalidate window (default: 300)
36
+ }, () => [
37
+ // Cached routes
38
+ ])
39
+ ```
40
+
41
+ ## Loader-Level Caching
42
+
43
+ Cache individual loaders:
44
+
45
+ ```typescript
46
+ path("/product/:slug", ProductPage, { name: "product" }, () => [
47
+ // Cache this loader's results
48
+ loader(ProductLoader, () => [
49
+ cache({ ttl: 300 }),
50
+ ]),
51
+
52
+ // This loader is not cached
53
+ loader(CartLoader),
54
+ ])
55
+ ```
56
+
57
+ ## Global Cache Configuration
58
+
59
+ Configure a cache store in the router:
60
+
61
+ ```typescript
62
+ import { createRouter } from "@rangojs/router";
63
+ import { MemorySegmentCacheStore } from "@rangojs/router/rsc";
64
+
65
+ const store = new MemorySegmentCacheStore({
66
+ defaults: { ttl: 60, swr: 300 },
67
+ });
68
+
69
+ const router = createRouter({
70
+ document: Document,
71
+ urls: urlpatterns,
72
+ cache: {
73
+ store,
74
+ enabled: true,
75
+ },
76
+ });
77
+ ```
78
+
79
+ ## Cache Stores
80
+
81
+ ### Memory Store
82
+
83
+ For single-instance deployments:
84
+
85
+ ```typescript
86
+ import { MemorySegmentCacheStore } from "@rangojs/router/rsc";
87
+
88
+ const store = new MemorySegmentCacheStore({
89
+ defaults: { ttl: 60, swr: 300 },
90
+ maxSize: 1000, // Max entries
91
+ });
92
+ ```
93
+
94
+ ### Cloudflare KV Store
95
+
96
+ For distributed caching on Cloudflare Workers:
97
+
98
+ ```typescript
99
+ import { CFCacheStore } from "@rangojs/router/cache/cf";
100
+
101
+ const router = createRouter({
102
+ document: Document,
103
+ urls: urlpatterns,
104
+ cache: (env) => ({
105
+ store: new CFCacheStore({
106
+ kv: env.Bindings.CACHE_KV,
107
+ waitUntil: (fn) => env.ctx.waitUntil(fn),
108
+ }),
109
+ enabled: true,
110
+ }),
111
+ });
112
+ ```
113
+
114
+ ## Nested Cache Boundaries
115
+
116
+ Override cache settings for specific sections:
117
+
118
+ ```typescript
119
+ // Global cache
120
+ cache({ ttl: 300 }, () => [
121
+ path("/blog", BlogIndex, { name: "blog" }),
122
+
123
+ // Override: shorter TTL for dynamic content
124
+ cache({ ttl: 30 }, () => [
125
+ path("/blog/:slug", BlogPost, { name: "blogPost" }),
126
+ ]),
127
+ ])
128
+ ```
129
+
130
+ ## Custom Cache Store
131
+
132
+ Create a dedicated store for specific routes:
133
+
134
+ ```typescript
135
+ const checkoutCache = new MemorySegmentCacheStore({
136
+ defaults: { ttl: 10 },
137
+ });
138
+
139
+ // In urls
140
+ cache({ store: checkoutCache }, () => [
141
+ path("/checkout", CheckoutPage, { name: "checkout" }),
142
+ ])
143
+ ```
144
+
145
+ ## Complete Example
146
+
147
+ ```typescript
148
+ import { urls } from "@rangojs/router";
149
+ import { MemorySegmentCacheStore } from "@rangojs/router/rsc";
150
+
151
+ // Custom store for checkout (short TTL)
152
+ const checkoutCache = new MemorySegmentCacheStore({
153
+ defaults: { ttl: 10 },
154
+ });
155
+
156
+ export const urlpatterns = urls(({ path, layout, cache, loader, revalidate }) => [
157
+ // Public routes with aggressive caching
158
+ cache({ ttl: 300, swr: 600 }, () => [
159
+ path("/", HomePage, { name: "home" }),
160
+ path("/about", AboutPage, { name: "about" }),
161
+ ]),
162
+
163
+ // Blog routes with moderate caching
164
+ cache({ ttl: 60, swr: 300 }, () => [
165
+ layout(<BlogLayout />, () => [
166
+ path("/blog", BlogIndex, { name: "blog" }),
167
+ path("/blog/:slug", BlogPost, { name: "blogPost" }, () => [
168
+ loader(BlogPostLoader, () => [cache()]), // Use boundary cache settings
169
+ ]),
170
+ ]),
171
+ ]),
172
+
173
+ // Shop routes with per-loader caching
174
+ layout(<ShopLayout />, () => [
175
+ path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
176
+ loader(ProductLoader, () => [cache({ ttl: 120 })]),
177
+ loader(CartLoader, () => [
178
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
179
+ ]),
180
+ ]),
181
+ ]),
182
+
183
+ // Checkout with custom cache store
184
+ cache({ store: checkoutCache }, () => [
185
+ path("/checkout", CheckoutPage, { name: "checkout" }),
186
+ ]),
187
+
188
+ // No cache for account pages
189
+ path("/account", AccountPage, { name: "account" }),
190
+ ]);
191
+ ```
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: debug-manifest
3
+ description: Debug and inspect route manifest structure
4
+ argument-hint:
5
+ ---
6
+
7
+ # Debug Manifest
8
+
9
+ Inspect the route manifest to verify parent relationships, shortCodes, and route structure.
10
+
11
+ ## Quick Access
12
+
13
+ In development, visit:
14
+ ```
15
+ http://localhost:PORT/__debug_manifest
16
+ ```
17
+
18
+ Returns formatted JSON with all routes and layouts.
19
+
20
+ ## Programmatic Access
21
+
22
+ ```typescript
23
+ import { router } from "./router.js";
24
+
25
+ // Only in development
26
+ if (process.env.NODE_ENV !== "production") {
27
+ const manifest = await router.debugManifest();
28
+ console.log(JSON.stringify(manifest, null, 2));
29
+ }
30
+ ```
31
+
32
+ ## Manifest Structure
33
+
34
+ ```json
35
+ {
36
+ "routes": {
37
+ "home.index": {
38
+ "id": "debug.M0.$root.$route.0.home.index",
39
+ "shortCode": "M0L0R0",
40
+ "type": "route",
41
+ "parentShortCode": "M0L0",
42
+ "pattern": "/",
43
+ "hasLoader": false,
44
+ "hasMiddleware": false,
45
+ "hasErrorBoundary": false,
46
+ "parallelCount": 0,
47
+ "interceptCount": 0
48
+ }
49
+ },
50
+ "layouts": {
51
+ "debug.M0.$root": {
52
+ "id": "debug.M0.$root",
53
+ "shortCode": "M0L0",
54
+ "type": "layout",
55
+ "parentShortCode": null
56
+ }
57
+ },
58
+ "totalRoutes": 45,
59
+ "totalLayouts": 18
60
+ }
61
+ ```
62
+
63
+ ## ShortCode Format
64
+
65
+ | Prefix | Meaning |
66
+ |--------|---------|
67
+ | **M** | Mount index (multiple `.routes()` calls) |
68
+ | **L** | Layout |
69
+ | **C** | Cache boundary |
70
+ | **R** | Route |
71
+ | **P** | Parallel slot |
72
+
73
+ Example: `M0L0L1C0R0` = Mount 0 → Root Layout → Nested Layout → Cache → Route
74
+
75
+ ## Debugging Checklist
76
+
77
+ 1. **Routes have parents**: `parentShortCode` should NOT be `null` (except root layout)
78
+ 2. **Correct hierarchy**: ShortCode should reflect nesting (e.g., `M0L0R0` not `M0R0`)
79
+ 3. **Loaders attached**: Check `hasLoader: true` for routes with data requirements
80
+ 4. **Intercepts registered**: `interceptCount > 0` for modal/overlay patterns
81
+
82
+ ## Comparing Manifests
83
+
84
+ ```typescript
85
+ import {
86
+ serializeManifest,
87
+ compareManifests,
88
+ formatManifestDiff
89
+ } from "@rangojs/router/__internal";
90
+
91
+ const oldManifest = await router.debugManifest();
92
+ // ... make changes ...
93
+ const newManifest = await router.debugManifest();
94
+
95
+ const diff = compareManifests(oldManifest, newManifest);
96
+ console.log(formatManifestDiff(diff));
97
+ ```
98
+
99
+ ## Common Issues
100
+
101
+ ### Routes have `parentShortCode: null`
102
+ Routes should have a layout parent. Check that `urls()` handler is being wrapped in root layout.
103
+
104
+ ### Missing layouts in hierarchy
105
+ Verify `layout()` calls wrap child routes correctly.
106
+
107
+ ### Wrong mount index
108
+ Multiple `.routes()` calls create separate mounts (M0, M1, etc.). Use `include()` to share context.
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: document-cache
3
+ description: Cache full HTTP responses at the edge with Cache-Control headers
4
+ argument-hint: [setup]
5
+ ---
6
+
7
+ # Document Cache
8
+
9
+ Caches complete HTTP responses (HTML/RSC) at the edge based on Cache-Control headers. Routes opt-in by setting `s-maxage`.
10
+
11
+ ## Setup
12
+
13
+ Configure document cache in router:
14
+
15
+ ```typescript
16
+ import { createRouter } from "@rangojs/router";
17
+ import { CFCacheStore } from "@rangojs/router/cache/cf";
18
+ import { urlpatterns } from "./urls";
19
+
20
+ const router = createRouter<AppEnv>({
21
+ document: Document,
22
+ urls: urlpatterns,
23
+ documentCache: (env) => ({
24
+ store: new CFCacheStore({ ctx: env.ctx }),
25
+ skipPaths: ["/api", "/admin"],
26
+ debug: process.env.NODE_ENV === "development",
27
+ }),
28
+ });
29
+
30
+ export default router;
31
+ ```
32
+
33
+ ## Route Opt-In with cache()
34
+
35
+ Routes opt-in to document caching using the `cache()` DSL with `documentCache` option:
36
+
37
+ ```typescript
38
+ import { urls } from "@rangojs/router";
39
+
40
+ export const urlpatterns = urls(({ path, cache }) => [
41
+ // Cache full page for 5 min, serve stale for 1 hour
42
+ cache({ documentCache: { sMaxAge: 300, swr: 3600 } }, () => [
43
+ path("/blog", BlogIndex, { name: "blog" }),
44
+ ]),
45
+
46
+ // Long cache for individual posts
47
+ cache({ documentCache: { sMaxAge: 3600, swr: 86400 } }, () => [
48
+ path("/blog/:slug", BlogPost, { name: "blogPost" }),
49
+ ]),
50
+
51
+ // No cache for dashboard (no documentCache option)
52
+ path("/dashboard", Dashboard, { name: "dashboard" }),
53
+ ]);
54
+ ```
55
+
56
+ ## Document Cache Options
57
+
58
+ ```typescript
59
+ createRouter({
60
+ // ...
61
+ documentCache: (env) => ({
62
+ // Cache store (required)
63
+ store: new CFCacheStore({ ctx: env.ctx }),
64
+
65
+ // Skip specific paths
66
+ skipPaths: ["/api", "/admin"],
67
+
68
+ // Custom cache key
69
+ keyGenerator: (url) => url.pathname,
70
+
71
+ // Conditional caching
72
+ isEnabled: (ctx) => !ctx.request.headers.has("x-preview"),
73
+
74
+ // Debug logging
75
+ debug: true,
76
+ }),
77
+ });
78
+ ```
79
+
80
+ ## How It Works
81
+
82
+ ```
83
+ Request → Check Cache
84
+
85
+ ┌──────┴──────┐
86
+ │ │
87
+ HIT MISS
88
+ │ │
89
+ ↓ ↓
90
+ Fresh? Run handler
91
+ │ │
92
+ Yes → Return Has documentCache?
93
+ │ │
94
+ No (stale) Yes → Cache + Return
95
+ │ │
96
+ ↓ No → Return (no cache)
97
+ Return stale,
98
+ revalidate in
99
+ background (SWR)
100
+ ```
101
+
102
+ ## Cache Status Header
103
+
104
+ Response includes `x-document-cache-status`:
105
+ - `HIT` - Fresh cache hit
106
+ - `STALE` - Served stale, revalidating in background
107
+ - `MISS` - Cache miss, response was generated fresh
108
+
109
+ ## Cache Key Generation
110
+
111
+ Default keys differentiate:
112
+ - HTML requests: `{pathname}:html`
113
+ - RSC partials: `{pathname}:{segmentHash}:rsc`
114
+
115
+ Segment hash ensures different cached responses for navigations from different source pages (with different layouts).
116
+
117
+ ## What Gets Cached
118
+
119
+ - Full HTML responses (document requests)
120
+ - RSC payloads (client navigation)
121
+ - Only 200 OK responses with documentCache enabled
122
+
123
+ ## What's NOT Cached
124
+
125
+ - Server actions (`_rsc_action`)
126
+ - Loader requests (`_rsc_loader`)
127
+ - Routes without `documentCache` option
128
+ - Non-200 responses
129
+
130
+ ## Complete Example
131
+
132
+ ```typescript
133
+ // router.tsx
134
+ import { createRouter } from "@rangojs/router";
135
+ import { CFCacheStore } from "@rangojs/router/cache/cf";
136
+ import { urlpatterns } from "./urls";
137
+
138
+ const router = createRouter<AppEnv>({
139
+ document: Document,
140
+ urls: urlpatterns,
141
+ documentCache: (env) => ({
142
+ store: new CFCacheStore({ ctx: env.ctx }),
143
+ skipPaths: ["/api"],
144
+ debug: process.env.NODE_ENV === "development",
145
+ }),
146
+ });
147
+
148
+ export default router;
149
+
150
+ // urls.tsx
151
+ import { urls } from "@rangojs/router";
152
+
153
+ export const urlpatterns = urls(({ path, layout, cache, loader }) => [
154
+ // Blog with document caching
155
+ cache({ documentCache: { sMaxAge: 300, swr: 3600 } }, () => [
156
+ layout(<BlogLayout />, () => [
157
+ path("/blog", BlogIndex, { name: "blog" }),
158
+ path("/blog/:slug", BlogPost, { name: "blogPost" }, () => [
159
+ loader(BlogPostLoader),
160
+ ]),
161
+ ]),
162
+ ]),
163
+
164
+ // Dashboard - no document cache (dynamic content)
165
+ layout(<DashboardLayout />, () => [
166
+ path("/dashboard", Dashboard, { name: "dashboard" }),
167
+ ]),
168
+ ]);
169
+ ```
170
+
171
+ ## Document Cache vs Segment Cache
172
+
173
+ | Feature | Document Cache | Segment Cache |
174
+ |---------|---------------|---------------|
175
+ | Granularity | Full response | Individual segments |
176
+ | Opt-in | `documentCache` in cache() | `cache({ ttl, swr })` |
177
+ | Use case | Static pages | Dynamic compositions |
178
+ | Key includes | URL + segment hash | Route params |
179
+
180
+ Use document cache for mostly-static pages. Use segment cache when different parts of a page have different cache requirements.