@rangojs/router 0.24.3 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-root.d.ts +4 -1
- package/dist/types/client-urls/client-urls.d.ts +28 -2
- package/dist/types/client-urls/navigation.d.ts +6 -8
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +20 -5
- package/dist/types/client-urls/types.d.ts +35 -17
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +19 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/urls/path-helper.d.ts +5 -0
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +195 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +119 -98
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-reverse.ts +4 -17
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-root.tsx +10 -5
- package/src/client-urls/client-urls.ts +246 -166
- package/src/client-urls/navigation.ts +45 -38
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +286 -75
- package/src/client-urls/types.ts +40 -16
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +37 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/urls/path-helper.ts +16 -3
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Several apps under createHostRouter()
|
|
2
|
+
|
|
3
|
+
One deployment, several independently routed apps: marketing on the apex
|
|
4
|
+
(`example.com`, `www.example.com`), the product on `app.`, the staff tools
|
|
5
|
+
on `admin.`, the docs on `docs.`. Each app is an ordinary `createRouter()`
|
|
6
|
+
app with its own `urls()`, Document and generated route map; the host router
|
|
7
|
+
only picks which one handles a request.
|
|
8
|
+
|
|
9
|
+
Reach for this when parts of the product want different documents, chrome,
|
|
10
|
+
deploy cadence or hostnames. Sections that share one document belong in one
|
|
11
|
+
router as `include()`s ([`./large-app.md`](./large-app.md)): a navigation
|
|
12
|
+
between apps is always a document load.
|
|
13
|
+
|
|
14
|
+
References in the router repository: `examples/cloudflare-multi-router` (a
|
|
15
|
+
Cloudflare host entry, path-mounted and host-mounted apps, a shared cache)
|
|
16
|
+
and `packages/rangojs-router/e2e/test-app/.host-fixture` (the Node preset,
|
|
17
|
+
the host-override cookie, two apps on one store).
|
|
18
|
+
|
|
19
|
+
## File tree
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
src/
|
|
23
|
+
worker.rsc.tsx the host router (Cloudflare: you own the entry)
|
|
24
|
+
env.ts bindings shared by every app
|
|
25
|
+
shared/
|
|
26
|
+
document.tsx "use client" Document the apps pass to createRouter()
|
|
27
|
+
styles.css one stylesheet (e.g. @import "tailwindcss")
|
|
28
|
+
theme.ts one ThemeConfig
|
|
29
|
+
cache.ts one cache store factory
|
|
30
|
+
origins.ts each app's origin, per environment
|
|
31
|
+
top-nav.tsx components every app renders
|
|
32
|
+
apps/
|
|
33
|
+
site/ router.tsx handler.ts urls.tsx router.named-routes.gen.ts tsconfig.json
|
|
34
|
+
app/ router.tsx handler.ts urls.tsx router.named-routes.gen.ts tsconfig.json …
|
|
35
|
+
admin/ …
|
|
36
|
+
docs/ …
|
|
37
|
+
tsconfig.json the host entry's program only
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The host router
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// src/worker.rsc.tsx
|
|
44
|
+
import {
|
|
45
|
+
createHostRouter,
|
|
46
|
+
defineHosts,
|
|
47
|
+
isNoRouteMatchError,
|
|
48
|
+
} from "@rangojs/router/host";
|
|
49
|
+
import type { AppBindings } from "./env";
|
|
50
|
+
|
|
51
|
+
export const hosts = defineHosts({
|
|
52
|
+
site: [".", "www.*"],
|
|
53
|
+
app: "app.*",
|
|
54
|
+
admin: "admin.*",
|
|
55
|
+
docs: "docs.*",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Dev: every app answers on localhost, and the x-rango-host cookie picks one
|
|
59
|
+
// (x-rango-host=admin.example.com). The request is forwarded unchanged.
|
|
60
|
+
const hostRouter = createHostRouter({
|
|
61
|
+
hostOverride: { cookieName: "x-rango-host", allowedHosts: ["localhost"] },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Lazy mounts: an app's module graph loads on its first request, and each app
|
|
65
|
+
// keeps its own cache version.
|
|
66
|
+
hostRouter.host(hosts.admin).lazy(() => import("./apps/admin/handler"));
|
|
67
|
+
hostRouter.host(hosts.app).lazy(() => import("./apps/app/handler"));
|
|
68
|
+
hostRouter.host(hosts.docs).lazy(() => import("./apps/docs/handler"));
|
|
69
|
+
hostRouter.host(hosts.site).lazy(() => import("./apps/site/handler"));
|
|
70
|
+
// localhost has one label: neither "." nor "*." matches it.
|
|
71
|
+
hostRouter.host(["localhost"]).lazy(() => import("./apps/site/handler"));
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
async fetch(request, env, ctx) {
|
|
75
|
+
try {
|
|
76
|
+
return await hostRouter.match(request, { env, ctx });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (isNoRouteMatchError(err))
|
|
79
|
+
return new Response("Not Found", { status: 404 });
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
} satisfies ExportedHandler<AppBindings>;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Patterns are tried in registration order, first match wins; "apex" means
|
|
87
|
+
exactly two labels (no public-suffix list), so `example.co.uk` needs its own
|
|
88
|
+
pattern (`/host-router` → "Pattern Syntax").
|
|
89
|
+
|
|
90
|
+
On the `node` and `vercel` presets rango owns the entry: export the instance
|
|
91
|
+
(`export default hostRouter`), drop the `fetch` wrapper, and an unmatched host
|
|
92
|
+
answers 404 on its own. Point `rango({ hostRouter: "./src/worker.rsc.tsx" })`
|
|
93
|
+
at the module when auto-detection cannot pick it.
|
|
94
|
+
|
|
95
|
+
## Each app
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// src/apps/app/handler.ts: what the host router's lazy mount imports
|
|
99
|
+
import { router } from "./router";
|
|
100
|
+
|
|
101
|
+
export default (request: Request, input: any) => router.fetch(request, input);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// src/apps/app/router.tsx
|
|
106
|
+
import { createRouter } from "@rangojs/router";
|
|
107
|
+
import { Document } from "../../shared/document";
|
|
108
|
+
import { appCache } from "../../shared/cache";
|
|
109
|
+
import { theme } from "../../shared/theme";
|
|
110
|
+
import type { AppBindings } from "../../env";
|
|
111
|
+
import { loadSession } from "./session";
|
|
112
|
+
import { appPatterns } from "./urls";
|
|
113
|
+
|
|
114
|
+
export const router = createRouter<AppBindings>({
|
|
115
|
+
document: Document,
|
|
116
|
+
cache: appCache,
|
|
117
|
+
theme,
|
|
118
|
+
})
|
|
119
|
+
.use(loadSession)
|
|
120
|
+
.routes(appPatterns);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// src/apps/app/urls.tsx
|
|
125
|
+
import { urls } from "@rangojs/router";
|
|
126
|
+
import { Outlet } from "@rangojs/router/client";
|
|
127
|
+
import { TopNav } from "../../shared/top-nav";
|
|
128
|
+
import { DashboardPage, LoginPage } from "./pages";
|
|
129
|
+
import { requireUser } from "./session";
|
|
130
|
+
|
|
131
|
+
function Shell() {
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<TopNav />
|
|
135
|
+
<Outlet />
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const appPatterns = urls(({ path, layout, middleware }) => [
|
|
141
|
+
path("/login", LoginPage, { name: "login" }),
|
|
142
|
+
layout(Shell, () => [
|
|
143
|
+
middleware(requireUser),
|
|
144
|
+
path("/", DashboardPage, { name: "dashboard" }),
|
|
145
|
+
// … the app's sections as includes, as in ./large-app.md
|
|
146
|
+
]),
|
|
147
|
+
]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Inside an app, everything in the other recipes applies unchanged: the `app.`
|
|
151
|
+
app is [`./large-app.md`](./large-app.md) without its marketing pages, the
|
|
152
|
+
`docs.` app is [`./content-site.md`](./content-site.md), and `site` is a
|
|
153
|
+
handful of `ppr` pages:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// src/apps/site/urls.tsx (excerpt)
|
|
157
|
+
export const sitePatterns = urls(({ path, layout }) => [
|
|
158
|
+
layout(SiteLayout, () => [
|
|
159
|
+
path("/", Home, { name: "home", ppr: true }),
|
|
160
|
+
path("/pricing", Pricing, { name: "pricing", ppr: true }),
|
|
161
|
+
]),
|
|
162
|
+
]);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## What is shared, and how
|
|
166
|
+
|
|
167
|
+
| Shared | How | Notes |
|
|
168
|
+
| ------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
169
|
+
| Components | ordinary imports from `src/shared/` | One Vite build and one client manifest: a shared client component ships as the same hashed asset to every app. Keep route-name-typed calls (`ctx.reverse("…")`, `href()`) out of shared code; each app's names are its own. |
|
|
170
|
+
| The Document | one `"use client"` component every `createRouter({ document })` passes, or one per app | Each document load is that app's own; apps may differ freely |
|
|
171
|
+
| CSS | `import styles from "../../shared/styles.css?url"` in the Document, `<link precedence>` | Compiles to one hashed asset, cached across apps. Apps need not agree on `precedence`: a cross-app navigation is a document load (`/css`) |
|
|
172
|
+
| Theme | one `ThemeConfig` object passed as `theme` to every router | The stored choice is a host-only cookie (`src/theme/ThemeProvider.tsx`, no `Domain`), so a theme picked on `app.` does not follow the user to `docs.` |
|
|
173
|
+
| Cache store | one factory (`cache: appCache`), one KV namespace | Keys holding one router's output start with that router's id, so two apps never read each other's shells, records or loader entries. Keep `createRouter({ ... })` an inline object literal (or pass `id`) so ids are stable |
|
|
174
|
+
| Session | a cookie on the parent domain, `cookies().set("session", token, { domain: "example.com", … })` | Set by the app that signs users in; every app resolves it in its own `router.use()` |
|
|
175
|
+
| Client caches | not shared | The state cookie is `rango-state_{routerId}`, one per app, so sibling apps on one origin do not collide |
|
|
176
|
+
|
|
177
|
+
## TypeScript: one program per app
|
|
178
|
+
|
|
179
|
+
Every router's `router.named-routes.gen.ts` augments the one global
|
|
180
|
+
`Rango.GeneratedRouteMap`, so two apps with overlapping route names
|
|
181
|
+
(`home`, `login`) collide in one program. Give each app its own
|
|
182
|
+
`tsconfig.json`, and keep the root program to the host entry:
|
|
183
|
+
|
|
184
|
+
```jsonc
|
|
185
|
+
// tsconfig.json: the host entry only
|
|
186
|
+
{ "extends": "./tsconfig.base.json", "include": ["src/env.ts", "src/worker.rsc.tsx"] }
|
|
187
|
+
|
|
188
|
+
// src/apps/app/tsconfig.json: one app, its generated map, the shared code
|
|
189
|
+
{ "extends": "../../../tsconfig.base.json", "include": ["**/*", "../../shared/**/*", "../../env.ts"] }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The root program still type-checks the apps' source through the lazy
|
|
193
|
+
`import()`s, but not their generated maps, as long as no app source imports
|
|
194
|
+
its own `*.named-routes.gen.ts`. Run `tsc -p` for each program in CI.
|
|
195
|
+
Generated and committed: one `router.named-routes.gen.ts` per app (plus
|
|
196
|
+
per-module `*.gen.ts` where an app uses `useReverse(routes)`). See
|
|
197
|
+
`/typesafety` → multi-project setup.
|
|
198
|
+
|
|
199
|
+
## Dev: the host-override cookie
|
|
200
|
+
|
|
201
|
+
`vite dev` serves every app on one origin, `localhost`. The
|
|
202
|
+
`hostOverride` option picks the app from a cookie when the request's real
|
|
203
|
+
host matches `allowedHosts`:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
// browser devtools, on http://localhost:5173
|
|
207
|
+
document.cookie = "x-rango-host=admin.example.com; path=/";
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Reload and the admin app answers; delete the cookie for the site. Without a
|
|
211
|
+
`fallback()`, a cookie the router refuses (a malformed hostname, a host not
|
|
212
|
+
in `allowedHosts`) answers 400 and clears the cookie. Playwright sets it with
|
|
213
|
+
`context.addCookies([{ name: "x-rango-host", value: "admin.example.com", url }])`,
|
|
214
|
+
which is how both reference apps run their dev and production e2e.
|
|
215
|
+
|
|
216
|
+
All apps answer under one origin in this mode, so links to another app's
|
|
217
|
+
production host leave the dev server; switch apps by changing the cookie.
|
|
218
|
+
|
|
219
|
+
## Links between apps
|
|
220
|
+
|
|
221
|
+
A navigation from one app to another is a full document load, by design. A
|
|
222
|
+
soft in-place swap between apps shipped once (#476) and was replaced by a
|
|
223
|
+
document navigation (#555): a stylesheet two apps share by `href` could drop
|
|
224
|
+
on the swap (React dedups stylesheets by `href`), and the theme, warmup and
|
|
225
|
+
prefetch settings, which are document-lifetime, would remount or never take
|
|
226
|
+
effect. A document load re-establishes the target app's whole document.
|
|
227
|
+
|
|
228
|
+
| The other app is | Link with | What happens |
|
|
229
|
+
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
230
|
+
| On another host (`app.example.com`) | an absolute URL: `<Link to="https://app.example.com/">` or `<a href>` | Another origin is external to `<Link>` (`src/browser/react/Link.tsx`) and to the plain-anchor interceptor: the browser navigates, nothing is prefetched |
|
|
231
|
+
| On the same origin under a path (`host(["example.com/docs"])`) | `<Link to="/docs" reloadDocument prefetch="none">` or `<a href="/docs" data-no-intercept="true">` | A document navigation at the click. A plain same-origin `<Link>` also ends in a document load, after a wasted round trip: the server answers the partial request with `X-RSC-Reload` |
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// src/shared/origins.ts
|
|
235
|
+
export const ORIGINS = {
|
|
236
|
+
site: import.meta.env.VITE_SITE_ORIGIN ?? "https://example.com",
|
|
237
|
+
app: import.meta.env.VITE_APP_ORIGIN ?? "https://app.example.com",
|
|
238
|
+
docs: import.meta.env.VITE_DOCS_ORIGIN ?? "https://docs.example.com",
|
|
239
|
+
} as const;
|
|
240
|
+
|
|
241
|
+
// src/shared/top-nav.tsx: a server component every app renders
|
|
242
|
+
import { Link } from "@rangojs/router/client";
|
|
243
|
+
import { ORIGINS } from "./origins";
|
|
244
|
+
|
|
245
|
+
export function TopNav() {
|
|
246
|
+
return (
|
|
247
|
+
<nav>
|
|
248
|
+
<Link to={`${ORIGINS.site}/`}>Home</Link>
|
|
249
|
+
<Link to={`${ORIGINS.docs}/`}>Docs</Link>
|
|
250
|
+
<Link to={`${ORIGINS.app}/`}>Open the app</Link>
|
|
251
|
+
</nav>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`reloadDocument` alone does not stop the prefetch: without
|
|
257
|
+
`prefetch="none"`, a visible link still sends one, which the other app
|
|
258
|
+
answers with `X-RSC-Reload` and the client drops. Make each app's entry pages
|
|
259
|
+
fast as documents (`ppr`, or `Prerender`), since every cross-app click lands
|
|
260
|
+
on one.
|
|
261
|
+
|
|
262
|
+
## Deploys
|
|
263
|
+
|
|
264
|
+
Each `createRouter()` app has its own cache version, computed from the code
|
|
265
|
+
its router can run up to a lazy mount. A deploy that changes the admin app's
|
|
266
|
+
server code clears the admin app's cached data and stored HTML and reloads
|
|
267
|
+
its open tabs; the other apps keep theirs. Code shared by statically
|
|
268
|
+
importing modules moves every app that imports it (`/host-router` →
|
|
269
|
+
"Deploys and the cache").
|
|
270
|
+
|
|
271
|
+
## Why
|
|
272
|
+
|
|
273
|
+
| Choice | Why |
|
|
274
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------- |
|
|
275
|
+
| One `createRouter()` per app | Own document, routes, names, middleware, cache version |
|
|
276
|
+
| `.lazy()` mounts | An app's graph loads on its first request, and the mount is where its cache version stops |
|
|
277
|
+
| `defineHosts` | One typed table of patterns, reused by tests |
|
|
278
|
+
| The cookie override in dev | One dev origin, hydration same-origin; the reference apps' e2e drive every app through it |
|
|
279
|
+
| Absolute URLs between hosts | Another origin is another document; no request is wasted on a prefetch or a reload round trip |
|
|
280
|
+
| A program per app | Route-name augmentations of different apps cannot collide |
|
|
@@ -137,6 +137,11 @@ path("/product/:id", async (ctx) => {
|
|
|
137
137
|
Async content is a `Promise<ReactNode>`. Resolve it in your component
|
|
138
138
|
with React's `use()` hook wrapped in `<Suspense>`.
|
|
139
139
|
|
|
140
|
+
A server component that pushes a crumb after an `await` throws
|
|
141
|
+
`LateHandlePushError`. Reserve the crumb in the handler with `.defer()` and
|
|
142
|
+
resolve it from the component (see "Deferred content" below); the same works
|
|
143
|
+
for a `Meta` title.
|
|
144
|
+
|
|
140
145
|
A crumb the HANDLER pushes is handler output. In a route `cache()` entry and
|
|
141
146
|
in a `ppr` shell it is stored resolved: the cache write, or the shell capture
|
|
142
147
|
(within `ppr.captureTimeout`), waits for a `content` promise and for a
|
|
@@ -202,7 +207,7 @@ consumer sees it — `useHandle(Breadcrumbs)` returns the resolved item, never a
|
|
|
202
207
|
`Promise`, so you read it like any sync crumb (no `use()`, no thenable narrowing).
|
|
203
208
|
On a full/SSR load the value is resolved server-side; on a soft navigation the
|
|
204
209
|
breadcrumbs HOLD the previous resolved value until the deferred value lands, then
|
|
205
|
-
swap in — no blank, no pending entry. If the slot times out to `else: null`/
|
|
210
|
+
swap in — no blank, no pending entry. The hold-then-swap is deliberate: a handle never holds the page and never shows the next page's values over the old one (see [hooks](../hooks/handle-and-actions.md#when-handle-values-change-by-design)). If the slot times out to `else: null`/
|
|
206
211
|
undefined, the entry is simply dropped. Use `.defer()` only when even
|
|
207
212
|
`label`/`href` are unknown at handler time AND the producer is a component,
|
|
208
213
|
not a loader — a loader-produced crumb pushes directly from the loader body
|
package/skills/caching/SKILL.md
CHANGED
|
@@ -894,7 +894,9 @@ and a read that worked only on direct visits would break the first time a
|
|
|
894
894
|
rewritten request reached it. A PPR shell capture and a render the document
|
|
895
895
|
cache stores read it as `undefined` instead of throwing: those renders are
|
|
896
896
|
the target page as a direct visit renders it, and the rewritten visitor's
|
|
897
|
-
own render (outside them) still sees its URL. A
|
|
897
|
+
own render (outside them) still sees its URL. A shell keyed by the visitor's
|
|
898
|
+
URL (`rewrite(...).shell("visitor")`, `/ppr`) reads it, since its key covers
|
|
899
|
+
that URL. A response route under
|
|
898
900
|
`cache()` whose handler read it is not stored.
|
|
899
901
|
|
|
900
902
|
A loader body invoked from a handler with `await ctx.use(Loader)` may read
|
package/skills/catalog.json
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
"argumentHint": "",
|
|
10
10
|
"path": "skills/api-client/SKILL.md"
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
"name": "app-structure",
|
|
14
|
+
"description": "Concrete urls() layouts for whole Rango apps, with the file tree, router.tsx, the root urls.tsx with sync include() of section modules, and where middleware, server layouts, clientUrls() groups, response routes, caching, prerender, PPR and boundaries sit. Recipes for a large signed-in app with a protected admin area (auth, onboarding, app, settings and admin as clientUrls() groups under one server shell), several apps under createHostRouter(), a docs site or blog, and a shop. Use when starting or restructuring a Rango app, laying out urls() files, splitting an app into sections or separate apps, or setting up a dashboard, SaaS, multi-app, static or content site, or e-commerce app.",
|
|
15
|
+
"argumentHint": "[large-app|multi-app|content-site|e-commerce]",
|
|
16
|
+
"path": "skills/app-structure/SKILL.md"
|
|
17
|
+
},
|
|
12
18
|
{
|
|
13
19
|
"name": "breadcrumbs",
|
|
14
20
|
"description": "Built-in Breadcrumbs handle for accumulating breadcrumb navigation across route segments. Use when building a breadcrumb trail for nested routes, or asking how to show the current navigation path in a layout.",
|
|
@@ -273,6 +279,12 @@
|
|
|
273
279
|
"argumentHint": "[profile-name]",
|
|
274
280
|
"path": "skills/use-cache/SKILL.md"
|
|
275
281
|
},
|
|
282
|
+
{
|
|
283
|
+
"name": "ux-architecture",
|
|
284
|
+
"description": "Architect a Rango app so navigation is instant and stable — when a server urls() page or a clientUrls() group wins, where layouts, loaders, Suspense boundaries, revalidate() rules, prefetch, transition(), intercept() modals and actions go, with worked recipes (shop, dashboard tabs, settings forms, search filters, a modal over a list) and the anti-patterns behind remounts, skeleton flashes and slow clicks. Use when designing or reviewing a Rango app's routing, layout, loading or navigation UX, choosing between urls() and clientUrls(), placing a loader or a boundary, writing revalidate() rules, or debugging a remount, a re-shown fallback or a click that waits.",
|
|
285
|
+
"argumentHint": "[shop|dashboard|settings|search|modal]",
|
|
286
|
+
"path": "skills/ux-architecture/SKILL.md"
|
|
287
|
+
},
|
|
276
288
|
{
|
|
277
289
|
"name": "vercel",
|
|
278
290
|
"description": "Deploy a Rango app to Vercel Functions (Build Output API v3). Use when deploying a Rango app to Vercel, or asking how the router works with Vercel Functions and the Build Output API.",
|