@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.
- package/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -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 +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- 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.
|