@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
@@ -0,0 +1,228 @@
1
+ ---
2
+ name: parallel
3
+ description: Define parallel routes for multi-column layouts, sidebars, and modal slots in @rangojs/router
4
+ argument-hint: [@slot-name]
5
+ ---
6
+
7
+ # Parallel Routes
8
+
9
+ Parallel routes render multiple components simultaneously in named slots.
10
+
11
+ ## Basic Parallel Routes
12
+
13
+ ```typescript
14
+ import { urls } from "@rangojs/router";
15
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
16
+
17
+ function DashboardLayout() {
18
+ return (
19
+ <div className="dashboard">
20
+ <aside>
21
+ <ParallelOutlet name="@sidebar" />
22
+ </aside>
23
+ <main>
24
+ <Outlet />
25
+ </main>
26
+ <div className="notifications">
27
+ <ParallelOutlet name="@notifications" />
28
+ </div>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ export const urlpatterns = urls(({ path, layout, parallel }) => [
34
+ layout(<DashboardLayout />, () => [
35
+ parallel({
36
+ "@sidebar": () => <Sidebar />,
37
+ "@notifications": () => <NotificationPanel />,
38
+ }),
39
+
40
+ path("/dashboard", DashboardIndex, { name: "dashboard.index" }),
41
+ path("/dashboard/analytics", Analytics, { name: "dashboard.analytics" }),
42
+ ]),
43
+ ]);
44
+ ```
45
+
46
+ ## Parallel Routes with Context
47
+
48
+ Access route params and loaders in parallel slots:
49
+
50
+ ```typescript
51
+ parallel({
52
+ "@sidebar": (ctx) => <Sidebar userId={ctx.params.userId} />,
53
+ "@related": (ctx) => <RelatedProducts slug={ctx.params.slug} />,
54
+ })
55
+ ```
56
+
57
+ ## Parallel Routes with Loaders
58
+
59
+ Add loaders and loading states to parallel routes:
60
+
61
+ ```typescript
62
+ parallel(
63
+ {
64
+ "@sidebar": () => <CategorySidebar />,
65
+ },
66
+ () => [
67
+ loader(CategoriesLoader),
68
+ loading(<SidebarSkeleton />),
69
+ revalidate(() => false), // Never revalidate sidebar
70
+ ]
71
+ )
72
+ ```
73
+
74
+ ## Multiple Parallel Slots
75
+
76
+ ```typescript
77
+ layout(<ShopLayout />, () => [
78
+ parallel({
79
+ "@promoBanner": () => (
80
+ <div className="promo-banner">
81
+ Summer Sale! 50% off selected items
82
+ </div>
83
+ ),
84
+ "@sidebar": () => <CategorySidebar />,
85
+ "@cartPreview": () => <CartPreview />,
86
+ "@notification": () => <CartNotification />,
87
+ }),
88
+
89
+ path("/shop", ShopIndex, { name: "shop" }),
90
+ ])
91
+ ```
92
+
93
+ ## Conditional Parallel Content
94
+
95
+ Render different content based on context:
96
+
97
+ ```typescript
98
+ parallel({
99
+ "@sidebar": (ctx) => {
100
+ const user = ctx.env.Variables.user;
101
+ return user ? <UserSidebar user={user} /> : <GuestSidebar />;
102
+ },
103
+ })
104
+ ```
105
+
106
+ ## Parallel Routes with Revalidation
107
+
108
+ Control when parallel routes revalidate:
109
+
110
+ ```typescript
111
+ parallel(
112
+ {
113
+ "@cart": () => <CartSummary />,
114
+ },
115
+ () => [
116
+ loader(CartLoader),
117
+ // Revalidate when cart actions occur
118
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
119
+ ]
120
+ )
121
+ ```
122
+
123
+ ## Named Outlets
124
+
125
+ Use `ParallelOutlet` to render slots in layouts:
126
+
127
+ ```typescript
128
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
129
+
130
+ function MyLayout() {
131
+ return (
132
+ <div>
133
+ <header>
134
+ <ParallelOutlet name="@header" />
135
+ </header>
136
+
137
+ <div className="content">
138
+ <aside>
139
+ <ParallelOutlet name="@sidebar" />
140
+ </aside>
141
+
142
+ <main>
143
+ <Outlet /> {/* Main route content */}
144
+ </main>
145
+
146
+ <aside>
147
+ <ParallelOutlet name="@rightPanel" />
148
+ </aside>
149
+ </div>
150
+
151
+ <footer>
152
+ <ParallelOutlet name="@footer" />
153
+ </footer>
154
+ </div>
155
+ );
156
+ }
157
+ ```
158
+
159
+ ## Complete Example
160
+
161
+ ```typescript
162
+ import { urls } from "@rangojs/router";
163
+ import { Outlet, ParallelOutlet } from "@rangojs/router/client";
164
+
165
+ function ShopLayout() {
166
+ return (
167
+ <div className="shop">
168
+ <ParallelOutlet name="@promoBanner" />
169
+ <div className="content">
170
+ <aside>
171
+ <ParallelOutlet name="@sidebar" />
172
+ </aside>
173
+ <main>
174
+ <Outlet />
175
+ </main>
176
+ <aside>
177
+ <ParallelOutlet name="@cartPreview" />
178
+ </aside>
179
+ </div>
180
+ <ParallelOutlet name="@notification" />
181
+ </div>
182
+ );
183
+ }
184
+
185
+ export const shopPatterns = urls(({
186
+ path,
187
+ layout,
188
+ parallel,
189
+ loader,
190
+ loading,
191
+ revalidate,
192
+ }) => [
193
+ layout(<ShopLayout />, () => [
194
+ // Simple parallel slot
195
+ parallel({
196
+ "@promoBanner": () => <PromoBanner />,
197
+ }),
198
+
199
+ // Parallel slot with loader
200
+ parallel(
201
+ { "@sidebar": () => <CategorySidebar /> },
202
+ () => [
203
+ loader(CategoriesLoader),
204
+ revalidate(() => false),
205
+ ]
206
+ ),
207
+
208
+ // Parallel slot with revalidation
209
+ parallel(
210
+ { "@cartPreview": () => <CartPreview /> },
211
+ () => [
212
+ loader(CartLoader),
213
+ loading(<CartSkeleton />),
214
+ revalidate(({ actionId }) => actionId?.includes("Cart") ?? false),
215
+ ]
216
+ ),
217
+
218
+ // Notification slot
219
+ parallel({
220
+ "@notification": () => <CartNotification />,
221
+ }),
222
+
223
+ // Routes
224
+ path("/", ShopIndex, { name: "index" }),
225
+ path("/product/:slug", ProductPage, { name: "product" }),
226
+ ]),
227
+ ]);
228
+ ```
@@ -0,0 +1,283 @@
1
+ ---
2
+ name: prerender
3
+ description: Pre-render route segments at build time with createPrerenderHandler and passthrough fallback
4
+ argument-hint: [passthrough]
5
+ ---
6
+
7
+ # Pre-rendering with createPrerenderHandler
8
+
9
+ Pre-rendering is **caching at build time**. Same serialization format, same
10
+ deserialization path, same segment system. The worker handles every request --
11
+ there are NO static .html or .rsc files served from assets. The worker reads
12
+ pre-computed Flight payloads instead of executing handler code.
13
+
14
+ ## API: createPrerenderHandler
15
+
16
+ ### Static Route (no params)
17
+
18
+ ```typescript
19
+ import { createPrerenderHandler } from "@rangojs/router";
20
+
21
+ export const AboutPage = createPrerenderHandler(async (ctx) => {
22
+ const content = await fs.readFile("content/about.md", "utf-8");
23
+ return <Page content={markdownToJsx(content)} />;
24
+ });
25
+
26
+ // urls.tsx
27
+ path("/about", AboutPage, { name: "about" })
28
+ ```
29
+
30
+ ### Dynamic Route (with params)
31
+
32
+ Params come first, handler second:
33
+
34
+ ```typescript
35
+ export const BlogPost = createPrerenderHandler(
36
+ // 1. Params: which slugs to pre-render
37
+ async () => {
38
+ const files = await glob("content/blog/*.md");
39
+ return files.map(f => ({ slug: basename(f, ".md") }));
40
+ },
41
+ // 2. Handler: runs at build time with BuildContext
42
+ async (ctx) => {
43
+ const md = await fs.readFile(`content/${ctx.params.slug}.md`, "utf-8");
44
+ return <Article content={markdownToJsx(md)} />;
45
+ }
46
+ );
47
+
48
+ // urls.tsx
49
+ path("/blog/:slug", BlogPost, { name: "blog.post" })
50
+ ```
51
+
52
+ ### With Passthrough (live fallback for unknown params)
53
+
54
+ ```typescript
55
+ export const ProductPage = createPrerenderHandler(
56
+ async () => {
57
+ const top = await db.query("SELECT id FROM products WHERE featured");
58
+ return top.map(p => ({ id: p.id }));
59
+ },
60
+ async (ctx) => {
61
+ const product = await db.query("SELECT * FROM products WHERE id = ?", ctx.params.id);
62
+ return <Product data={product} />;
63
+ },
64
+ { passthrough: true }
65
+ );
66
+ ```
67
+
68
+ ## Passthrough Mode
69
+
70
+ Controls whether the handler stays in the RSC server bundle after build:
71
+
72
+ | | `passthrough: false` (default) | `passthrough: true` |
73
+ |---|---|---|
74
+ | Known params | Served from pre-rendered Flight payload | Served from pre-rendered Flight payload |
75
+ | Unknown params | Handler evicted, no live fallback | Handler runs live at request time |
76
+ | Bundle size | Handler code + imports removed | Handler code kept in RSC bundle |
77
+ | `revalidate()` | Not allowed (handler gone) | Allowed (handler can re-render) |
78
+ | `loading()` | Ignored (segments fully resolved) | Works for live fallback renders |
79
+
80
+ ### When to use passthrough
81
+
82
+ Use `passthrough: true` when:
83
+ - The route has a large or open-ended param space (e.g., user profiles, product pages)
84
+ - You want to pre-render popular/known params for speed but still serve unknown params live
85
+ - You need `revalidate()` on the route
86
+
87
+ Use `passthrough: false` (default) when:
88
+ - All possible params are known at build time (e.g., markdown files, config-driven pages)
89
+ - You want maximum bundle size reduction (handler code + node:fs imports removed)
90
+ - The route uses build-only APIs (node:fs, local files) not available at runtime
91
+
92
+ ## BuildContext
93
+
94
+ Handlers receive `BuildContext` at build time, a subset of the runtime `HandlerContext`:
95
+
96
+ ```typescript
97
+ interface BuildContext<TParams> {
98
+ params: TParams; // From getParams
99
+ use: <T>(handle: Handle<T>) => (data: T) => void; // Push handle data
100
+ url: URL; // Synthetic URL from pattern + params
101
+ pathname: string; // Pathname from synthetic URL
102
+ // NOT available: req, headers, cookies, env (throws descriptive errors)
103
+ }
104
+ ```
105
+
106
+ All items inside the path's use() callback (child layouts, parallels) also receive
107
+ `BuildContext` during pre-rendering. Loaders are the exception -- they run at
108
+ request time with full server context.
109
+
110
+ ## Handler Eviction
111
+
112
+ In production builds, `createPrerenderHandler` exports are replaced with stubs:
113
+
114
+ ```typescript
115
+ // Original
116
+ export const BlogPost = createPrerenderHandler(getParams, handler);
117
+
118
+ // Stubbed (ships to server bundle when passthrough: false)
119
+ export const BlogPost = { __brand: "prerenderHandler", $$id: "abc123#BlogPost" };
120
+ ```
121
+
122
+ The original module and its imports (node:fs, markdown libs) are excluded from
123
+ the bundle. With `passthrough: true`, the handler code stays in the RSC bundle.
124
+
125
+ In client and SSR environments, ALL prerender handlers are always stubbed
126
+ (passthrough only affects the RSC server bundle).
127
+
128
+ ## Sub-use Semantics
129
+
130
+ Everything inside the path's use() callback is part of the B segment and gets
131
+ pre-rendered:
132
+
133
+ ```typescript
134
+ path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
135
+ layout(<PostLayout />, () => [ // inside B -> pre-rendered
136
+ loader(PostMetaLoader), // live at runtime, bundled normally
137
+ ]),
138
+ parallel({ "@sidebar": BlogSidebar }), // inside B -> pre-rendered
139
+ ])
140
+ ```
141
+
142
+ If a parallel or child layout uses node APIs, wrap it in `createPrerenderHandler`
143
+ (static, no getParams) so the Vite plugin can stub it:
144
+
145
+ ```typescript
146
+ // sidebar.tsx -- uses node:fs, must be a createPrerenderHandler
147
+ export const BlogSidebar = createPrerenderHandler(async (ctx) => {
148
+ const files = await fs.readdir("content/blog/");
149
+ return <Sidebar posts={files.map(f => basename(f, ".md"))} />;
150
+ });
151
+
152
+ // urls.tsx
153
+ path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
154
+ parallel({ "@sidebar": BlogSidebar }), // stubbable, node:fs excluded
155
+ ])
156
+ ```
157
+
158
+ ## Interaction with DSL Items
159
+
160
+ | DSL item | Behavior with createPrerenderHandler |
161
+ |----------------|--------------------------------------|
162
+ | `loader()` | Live at runtime, bundled normally. Use `cache()` for caching. |
163
+ | `revalidate()` | Not allowed without passthrough. Allowed with passthrough. |
164
+ | `cache()` | Orthogonal -- use on parent layouts and loaders. |
165
+ | `layout()` | Child layouts inside path are pre-rendered. Parent layouts are live. |
166
+ | `parallel()` | Parallel slots inside path are pre-rendered. |
167
+ | `middleware()` | Skipped during pre-render (no request). Runs at request time for loaders. |
168
+ | `loading()` | Ignored without passthrough. Works for live fallback with passthrough. |
169
+ | `intercept()` | Not pre-rendered (intercepts are navigation-triggered). |
170
+
171
+ ## Dev Mode
172
+
173
+ In dev mode, `createPrerenderHandler` is a normal handler. Routes render live
174
+ on every request. No stubbing, no build-time pre-rendering. The handler runs
175
+ with full runtime context (not BuildContext).
176
+
177
+ ## Storage Layout
178
+
179
+ Pre-rendered Flight payloads are stored in the build output:
180
+
181
+ ```
182
+ dist/static/__<hash>/
183
+ routes.json
184
+ prefixes.json
185
+ prerender/
186
+ blog.post/
187
+ d4e5f6a7.flight # hash of { slug: "hello-world" }
188
+ b8c9d0e1.flight # hash of { slug: "getting-started" }
189
+ about/
190
+ _.flight # static route, no params
191
+ ```
192
+
193
+ ## Edge Cases and Constraints
194
+
195
+ ### Loaders are always live
196
+ Loaders on pre-rendered routes run at request time. They are bundled normally
197
+ and need `cache()` for caching. Do not use build-only APIs in loaders.
198
+
199
+ ### Handle data is frozen
200
+ Handle values pushed via `ctx.use()` during pre-rendering are baked into the
201
+ Flight payload. They do not update at request time.
202
+
203
+ ### Server actions work normally
204
+ Actions do not re-render the B segment. The pre-rendered handler output stays
205
+ frozen. Loaders are live and can be revalidated by actions. With `passthrough: true`
206
+ and `revalidate()`, the handler itself can re-render live.
207
+
208
+ ### Empty getParams
209
+ If `getParams` returns an empty array, no Flight payloads are written. No error.
210
+
211
+ ### Route name is required
212
+ Routes using `createPrerenderHandler` must have a `name` in path options.
213
+ The name is used as the storage key for Flight payloads.
214
+
215
+ ### No revalidate without passthrough
216
+ Using `revalidate()` with `passthrough: false` produces a build-time warning.
217
+ The handler is evicted -- there is nothing to re-render.
218
+
219
+ ### loading() is ignored without passthrough
220
+ Pre-rendered segments are fully resolved at build time and never suspend.
221
+ With `passthrough: true`, `loading()` works for live fallback renders.
222
+
223
+ ## Complete Example
224
+
225
+ ```typescript
226
+ // pages/guides-handler.tsx
227
+ import { createPrerenderHandler } from "@rangojs/router";
228
+ import { Link } from "@rangojs/router/client";
229
+ import { href } from "../router.js";
230
+
231
+ const knownGuides: Record<string, string> = {
232
+ routing: "Routing Guide",
233
+ caching: "Caching Guide",
234
+ };
235
+
236
+ export const GuidesDetail = createPrerenderHandler<{ slug: string }>(
237
+ async () => Object.keys(knownGuides).map((slug) => ({ slug })),
238
+ async (ctx) => {
239
+ const title = knownGuides[ctx.params.slug] ?? `Guide: ${ctx.params.slug}`;
240
+ return (
241
+ <div>
242
+ <h1>{title}</h1>
243
+ <p>Slug: {ctx.params.slug}</p>
244
+ <nav>
245
+ <Link to={href("guides.detail", { slug: "routing" })}>Routing</Link>
246
+ {" | "}
247
+ <Link to={href("guides.detail", { slug: "dynamic-test" })}>Dynamic</Link>
248
+ </nav>
249
+ </div>
250
+ );
251
+ },
252
+ { passthrough: true },
253
+ );
254
+
255
+ // pages/guides.tsx
256
+ import { urls } from "@rangojs/router";
257
+ import { GuidesDetail } from "./guides-handler.js";
258
+
259
+ export const guidesPatterns = urls(({ path }) => [
260
+ path("/:slug", GuidesDetail, { name: "detail" }),
261
+ ]);
262
+
263
+ // urls.tsx
264
+ import { urls, include } from "@rangojs/router";
265
+ import { guidesPatterns } from "./pages/guides.js";
266
+
267
+ export const urlpatterns = urls(({ path }) => [
268
+ path("/", HomePage, { name: "home" }),
269
+ include("/guides", guidesPatterns, { name: "guides" }),
270
+ ]);
271
+ ```
272
+
273
+ ## Trie Flags
274
+
275
+ Pre-rendered routes set flags on the route trie leaf at build time:
276
+
277
+ - `pr: true` -- route has pre-rendered B segment data
278
+ - `pt: true` -- passthrough mode (handler available for live fallback)
279
+
280
+ At runtime, the cache-lookup middleware uses these flags:
281
+ - `pr + hit` -- serve pre-rendered Flight payload
282
+ - `pr + pt + miss` -- fall through to live handler (handler kept in bundle)
283
+ - `pr + miss` (no pt) -- fall through (handler stubbed, no live render)
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: rango
3
+ description: Overview of @rangojs/router and available skills
4
+ argument-hint:
5
+ ---
6
+
7
+ # @rangojs/router
8
+
9
+ Django-inspired RSC router with composable URL patterns, type-safe href, and server components.
10
+
11
+ ## Skills
12
+
13
+ | Skill | Description |
14
+ |-------|-------------|
15
+ | `/router-setup` | Create and configure the RSC router |
16
+ | `/route` | Define routes with `urls()` and `path()` |
17
+ | `/layout` | Layouts that wrap child routes |
18
+ | `/loader` | Data loaders with `createLoader()` |
19
+ | `/middleware` | Request processing and authentication |
20
+ | `/intercept` | Modal/slide-over patterns for soft navigation |
21
+ | `/parallel` | Multi-column layouts and sidebars |
22
+ | `/caching` | Segment caching with memory or KV stores |
23
+ | `/document-cache` | Edge caching with Cache-Control headers |
24
+ | `/theme` | Light/dark mode with FOUC prevention |
25
+ | `/links` | URL generation: ctx.reverse, href, useHref, useMount, scopedReverse |
26
+ | `/hooks` | Client-side React hooks |
27
+ | `/typesafety` | Type-safe routes, params, href, and environment |
28
+ | `/host-router` | Multi-app host routing with domain/subdomain patterns |
29
+ | `/tailwind` | Set up Tailwind CSS v4 with `?url` imports |
30
+ | `/response-routes` | JSON/text/HTML/XML/stream endpoints with `path.json()`, `path.text()` |
31
+ | `/mime-routes` | Content negotiation — same URL, different response types via Accept header |
32
+ | `/fonts` | Load web fonts with preload hints |
33
+
34
+ ## Quick Start
35
+
36
+ ```typescript
37
+ // urls.tsx
38
+ import { urls } from "@rangojs/router";
39
+
40
+ export const urlpatterns = urls(({ path, layout }) => [
41
+ layout(RootLayout, () => [
42
+ path("/", HomePage, { name: "home" }),
43
+ path("/about", AboutPage, { name: "about" }),
44
+ ]),
45
+ ]);
46
+
47
+ // router.tsx
48
+ import { createRouter } from "@rangojs/router";
49
+ import { urlpatterns } from "./urls";
50
+
51
+ export default createRouter({ document: Document }).urls(urlpatterns);
52
+ ```
53
+
54
+ Use `/typesafety` for type-safe href and environment setup.