@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
package/skills/layout/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: layout
|
|
3
|
-
description: Define layout routes that wrap child routes in @rangojs/router
|
|
3
|
+
description: Define layout routes that wrap child routes in @rangojs/router. Use when sharing a persistent UI shell (nav, sidebar) across nested routes, or asking how to wrap child pages with a common layout.
|
|
4
4
|
argument-hint: [component]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -147,12 +147,21 @@ A layout as a child of `path()` wraps the route content and can read
|
|
|
147
147
|
data set by the route handler via `ctx.get()`. The handler always
|
|
148
148
|
executes before its children.
|
|
149
149
|
|
|
150
|
-
This
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
This is the recommended way to pass handler data downward, and it is
|
|
151
|
+
safe under partial action revalidation with zero configuration: orphan
|
|
152
|
+
layouts (and their parallels) belong to the route entry, and on an
|
|
153
|
+
action the whole entry re-runs together by default — route segment,
|
|
154
|
+
loaders, and `belongsToRoute` children all seed revalidate-true, with
|
|
155
|
+
handler-first ordering preserved. Producer and consumer cannot desync
|
|
156
|
+
unless you narrow one side with a predicate that returns a hard `false`
|
|
157
|
+
(then put the same contract on both — see "Revalidation Contracts").
|
|
158
|
+
|
|
159
|
+
Data from an **outer** handler or layout entry is the opposite case:
|
|
160
|
+
outer entries do not revalidate on actions by default (parent-chain
|
|
161
|
+
skip). If an orphan layout depends on data established above its own
|
|
162
|
+
route entry, that outer segment must share a revalidation contract, or
|
|
163
|
+
the orphan must guard/reload the data independently. See `/rango` →
|
|
164
|
+
"Passing data down the tree" for the full safest-first ladder.
|
|
156
165
|
|
|
157
166
|
```typescript
|
|
158
167
|
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
@@ -191,7 +200,10 @@ orphan layouts to read them.
|
|
|
191
200
|
|
|
192
201
|
## Layout Revalidation
|
|
193
202
|
|
|
194
|
-
|
|
203
|
+
Standalone `layout()` entries don't revalidate by default — on an action,
|
|
204
|
+
parent-chain segments are skipped unless a `revalidate()` opts them in.
|
|
205
|
+
(Orphan layouts inside a `path()` are the opposite: they ride along with
|
|
206
|
+
the route entry by default.) Control with `revalidate()`:
|
|
195
207
|
|
|
196
208
|
```typescript
|
|
197
209
|
layout(<ShopLayout />, () => [
|
|
@@ -202,8 +214,10 @@ layout(<ShopLayout />, () => [
|
|
|
202
214
|
])
|
|
203
215
|
|
|
204
216
|
// Or revalidate based on conditions
|
|
217
|
+
import * as CartActions from "./actions/cart";
|
|
218
|
+
|
|
205
219
|
layout(<CartLayout />, () => [
|
|
206
|
-
revalidate((
|
|
220
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
207
221
|
|
|
208
222
|
path("/cart", CartPage, { name: "cart" }),
|
|
209
223
|
])
|
|
@@ -216,13 +230,19 @@ their `ctx.set()` state.
|
|
|
216
230
|
|
|
217
231
|
### Revalidation Contracts
|
|
218
232
|
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
Contracts are the tool for cross-entry sharing — the bottom rung of the
|
|
234
|
+
data-passing ladder (`/rango` → "Passing data down the tree"). Before
|
|
235
|
+
writing one, check whether the producer can move down a rung: into the
|
|
236
|
+
consumer's own entry as an orphan layout, into middleware, or into a
|
|
237
|
+
loader. When the data genuinely must flow from an outer entry, define
|
|
238
|
+
named revalidation functions and reuse them on both producer and
|
|
239
|
+
consumer segments:
|
|
221
240
|
|
|
222
241
|
```typescript
|
|
223
242
|
// revalidation-contracts.ts
|
|
224
|
-
|
|
225
|
-
|
|
243
|
+
import { addToCart } from "./actions/cart";
|
|
244
|
+
|
|
245
|
+
export const revalidateCartData = (ctx) => ctx.isAction(addToCart) || undefined;
|
|
226
246
|
```
|
|
227
247
|
|
|
228
248
|
```typescript
|
|
@@ -242,9 +262,10 @@ You can also package them as importable handoff helpers:
|
|
|
242
262
|
```typescript
|
|
243
263
|
// revalidation-contracts.ts
|
|
244
264
|
import { revalidate } from "@rangojs/router";
|
|
265
|
+
import * as AuthActions from "./actions/auth";
|
|
245
266
|
|
|
246
|
-
export const revalidateAuthData = (
|
|
247
|
-
|
|
267
|
+
export const revalidateAuthData = (ctx) =>
|
|
268
|
+
ctx.isAction(AuthActions) || undefined;
|
|
248
269
|
export const revalidateAuth = () => [revalidate(revalidateAuthData)];
|
|
249
270
|
```
|
|
250
271
|
|
|
@@ -262,6 +283,7 @@ layout(<ShellLayout />, () => [
|
|
|
262
283
|
```typescript
|
|
263
284
|
import { urls } from "@rangojs/router";
|
|
264
285
|
import { Outlet, ParallelOutlet } from "@rangojs/router/client";
|
|
286
|
+
import * as CartActions from "./actions/cart";
|
|
265
287
|
|
|
266
288
|
function ShopLayout() {
|
|
267
289
|
return (
|
|
@@ -291,7 +313,7 @@ export const shopPatterns = urls(({ path, layout, parallel, loader, revalidate }
|
|
|
291
313
|
}, () => [
|
|
292
314
|
// Layout loaders
|
|
293
315
|
loader(CartLoader, () => [
|
|
294
|
-
revalidate((
|
|
316
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
295
317
|
]),
|
|
296
318
|
|
|
297
319
|
// Parallel routes
|
package/skills/links/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: links
|
|
3
|
-
description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse
|
|
3
|
+
description: URL generation with ctx.reverse (server default), href (client), useHref (mounted), useMount, useReverse, and scopedReverse. Use when generating a link to a route by name instead of hardcoding a path, or a link breaks after routes move or get mounted elsewhere.
|
|
4
4
|
argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
|
|
5
5
|
---
|
|
6
6
|
|
package/skills/loader/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: loader
|
|
3
|
-
description: Define data loaders for fetching data in routes with createLoader
|
|
3
|
+
description: Define data loaders for fetching data in routes with createLoader. Use when pages need per-request data that stays fresh, data should stream while the page renders, or client components need reactive server data.
|
|
4
4
|
argument-hint: [loader]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -11,6 +11,13 @@ Loaders fetch data on the server and stream it to the client. For mutations
|
|
|
11
11
|
`/server-actions`. Loaders re-resolve after an action runs, so the typical
|
|
12
12
|
flow is _action mutates → loader re-reads → UI updates_.
|
|
13
13
|
|
|
14
|
+
## Not this skill if…
|
|
15
|
+
|
|
16
|
+
- You want to mutate state — mutations are `"use server"` actions: see
|
|
17
|
+
`/server-actions`. Loaders read per-request live data.
|
|
18
|
+
- You want to cache a function's return value — loaders are fresh every request
|
|
19
|
+
by default; caching one function is `"use cache"`: see `/use-cache`.
|
|
20
|
+
|
|
14
21
|
## Creating a Loader
|
|
15
22
|
|
|
16
23
|
```typescript
|
|
@@ -140,6 +147,11 @@ same memoized result — loaders never run twice per request.
|
|
|
140
147
|
- The handler output depends on the loader data. If the route is inside
|
|
141
148
|
`cache()`, the handler is cached with the loader result baked in —
|
|
142
149
|
defeating the live data guarantee.
|
|
150
|
+
- The same holds under a PPR shell capture (`/ppr`): handler consumption is
|
|
151
|
+
the BAKED lane — the loader executes at capture (identity reads permitted)
|
|
152
|
+
and the rendered value is a capture-time copy; `useLoader` client-side is
|
|
153
|
+
the live lane. One rule across `cache()`, `"use cache"`, and PPR: the
|
|
154
|
+
consumption-lane rule (`/rango` → Invariants).
|
|
143
155
|
- Non-cacheable variable reads (`createVar({ cache: false })`) inside the
|
|
144
156
|
handler still throw, even if the data came from a loader.
|
|
145
157
|
- Prefer DSL `loader()` + client `useLoader()` for data that depends on
|
|
@@ -160,23 +172,23 @@ Loaders receive the same context shape as route handlers.
|
|
|
160
172
|
|
|
161
173
|
### Full field surface
|
|
162
174
|
|
|
163
|
-
| Field | Type | Notes
|
|
164
|
-
| -------------- | ------------------------------ |
|
|
165
|
-
| `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`.
|
|
166
|
-
| `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden.
|
|
167
|
-
| `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort).
|
|
168
|
-
| `url` | `URL` | Parsed request URL.
|
|
169
|
-
| `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`).
|
|
170
|
-
| `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`.
|
|
171
|
-
| `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise.
|
|
172
|
-
| `env` | `TEnv` | Plain bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.).
|
|
173
|
-
| `get` | `(key \| ContextVar) => value` | Reads variables/context-vars set by middleware.
|
|
174
|
-
| `use` | `(loader \| handle) => T` | Access another loader's data (Promise) or a handle's collected data (after `await ctx.rendered()`).
|
|
175
|
-
| `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for non-loader segments before reading handle data.
|
|
176
|
-
| `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders.
|
|
177
|
-
| `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls.
|
|
178
|
-
| `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission.
|
|
179
|
-
| `reverse` | `ScopedReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers).
|
|
175
|
+
| Field | Type | Notes |
|
|
176
|
+
| -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `params` | `TParams` | Merged route + explicit loader params; overridable by fetchable `load({ params })`. |
|
|
178
|
+
| `routeParams` | `Record<string, string>` | Server-trusted route params from URL pattern matching; cannot be overridden. |
|
|
179
|
+
| `request` | `Request` | The incoming `Request` (headers, method, body, `signal` for abort). |
|
|
180
|
+
| `url` | `URL` | Parsed request URL. |
|
|
181
|
+
| `pathname` | `string` | URL pathname (shortcut for `ctx.url.pathname`). |
|
|
182
|
+
| `searchParams` | `URLSearchParams` | Shortcut for `ctx.url.searchParams`. |
|
|
183
|
+
| `search` | `ResolveSearchSchema<TSearch>` | Typed query params when a search schema is declared on the route; `{}` otherwise. |
|
|
184
|
+
| `env` | `TEnv` | Plain bindings from `createRouter<TEnv>()` (DB, KV, secrets, etc.). |
|
|
185
|
+
| `get` | `(key \| ContextVar) => value` | Reads variables/context-vars set by middleware. |
|
|
186
|
+
| `use` | `(loader \| handle) => T` | Access another loader's data (Promise) or a handle's collected data (after `await ctx.rendered()`). |
|
|
187
|
+
| `rendered` | `() => Promise<void>` | **Experimental.** DSL loaders only — waits for all non-loader segments (including `loading()` streaming handlers) to settle before reading handle data. |
|
|
188
|
+
| `method` | `string` | HTTP method. `"GET"` for SSR loader runs; reflects real method for fetchable loaders. |
|
|
189
|
+
| `body` | `TBody \| undefined` | Parsed request body for fetchable POST/PUT/PATCH/DELETE calls. |
|
|
190
|
+
| `formData` | `FormData \| undefined` | Present when a fetchable loader is invoked via form submission. |
|
|
191
|
+
| `reverse` | `ScopedReverseFunction` | Generate type-checked URLs from route names (same scoped semantics as route handlers). |
|
|
180
192
|
|
|
181
193
|
### Example
|
|
182
194
|
|
|
@@ -249,6 +261,8 @@ export const OrderLoader = createLoader(async (ctx) => {
|
|
|
249
261
|
Add caching or revalidation to specific loaders:
|
|
250
262
|
|
|
251
263
|
```typescript
|
|
264
|
+
import * as CartActions from "./actions/cart";
|
|
265
|
+
|
|
252
266
|
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
253
267
|
// Cached loader
|
|
254
268
|
loader(ProductLoader, () => [cache({ ttl: 300 })]),
|
|
@@ -261,7 +275,7 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
261
275
|
// Loader that revalidates after cart actions (defer otherwise — keeps the
|
|
262
276
|
// permissive loader defaults for navigation and other actions intact)
|
|
263
277
|
loader(CartLoader, () => [
|
|
264
|
-
revalidate((
|
|
278
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
265
279
|
]),
|
|
266
280
|
]);
|
|
267
281
|
```
|
|
@@ -559,6 +573,8 @@ entirely (no read, no write).
|
|
|
559
573
|
### Per-Loader Store Override
|
|
560
574
|
|
|
561
575
|
```typescript
|
|
576
|
+
import { MemorySegmentCacheStore } from "@rangojs/router/cache";
|
|
577
|
+
|
|
562
578
|
const hotStore = new MemorySegmentCacheStore({ defaults: { ttl: 10 } });
|
|
563
579
|
|
|
564
580
|
loader(PricingLoader, () => [
|
|
@@ -667,6 +683,16 @@ export const SearchLoader = createLoader(async (ctx) => {
|
|
|
667
683
|
}, true); // true = fetchable
|
|
668
684
|
```
|
|
669
685
|
|
|
686
|
+
> **No registration needed — and no worker-entry import.** A fetchable loader
|
|
687
|
+
> does not have to be registered with `loader()` in the route DSL, and it does
|
|
688
|
+
> not have to be imported by any server module. Importing it into the client
|
|
689
|
+
> component that calls `useFetchLoader()` / `load()` is enough. Rango discovers
|
|
690
|
+
> every `createLoader(fn, true)` at build time and registers it for the
|
|
691
|
+
> `_rsc_loader` endpoint, so a loader reachable only through a client component
|
|
692
|
+
> still resolves in production — on both the generated entry and a hand-written
|
|
693
|
+
> worker entry (e.g. a Cloudflare `worker.rsc.tsx`). You do **not** need to
|
|
694
|
+
> force-import the loader in your worker entry to make it resolve.
|
|
695
|
+
|
|
670
696
|
### Fetchable Loader with Middleware
|
|
671
697
|
|
|
672
698
|
Pass an options object instead of `true` to attach per-loader middleware.
|
|
@@ -781,10 +807,12 @@ export const CartLoader = createLoader(async (ctx) => {
|
|
|
781
807
|
});
|
|
782
808
|
|
|
783
809
|
// urls.tsx — register loaders in the DSL
|
|
810
|
+
import * as CartActions from "./actions/cart";
|
|
811
|
+
|
|
784
812
|
export const urlpatterns = urls(({ path, layout, loader, loading, cache, revalidate }) => [
|
|
785
813
|
layout(<ShopLayout />, () => [
|
|
786
814
|
loader(CartLoader, () => [
|
|
787
|
-
revalidate((
|
|
815
|
+
revalidate((ctx) => ctx.isAction(CartActions) || undefined),
|
|
788
816
|
]),
|
|
789
817
|
|
|
790
818
|
path("/shop/product/:slug", ProductPage, { name: "product" }, () => [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: middleware
|
|
3
|
-
description: Define middleware for authentication, logging, and request processing in @rangojs/router
|
|
3
|
+
description: Define middleware for authentication, logging, and request processing in @rangojs/router. Use when gating routes behind auth checks, logging requests, or running shared logic before a handler runs.
|
|
4
4
|
argument-hint: [middleware-name]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -60,15 +60,21 @@ data itself.
|
|
|
60
60
|
### Revalidation Contracts with Middleware-Backed Trees
|
|
61
61
|
|
|
62
62
|
Middleware can establish request-level context (`ctx.set`) for segments that
|
|
63
|
-
execute in the current render pass.
|
|
64
|
-
|
|
63
|
+
execute in the current render pass. Because route middleware wraps **every**
|
|
64
|
+
render pass — normal renders, post-action revalidation, PE re-renders — its
|
|
65
|
+
variables are never stale: middleware is the safest `ctx.set` rung on the
|
|
66
|
+
data-passing ladder (`/rango` → "Passing data down the tree"). But it does
|
|
67
|
+
not change partial revalidation boundaries between handler/layout/parallel
|
|
68
|
+
segments.
|
|
65
69
|
|
|
66
70
|
For shared segment data, use named revalidation contracts on both the producer
|
|
67
71
|
and consumer segments, even when middleware is present in the chain.
|
|
68
72
|
|
|
69
73
|
```typescript
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
import * as CartActions from "./actions/cart";
|
|
75
|
+
|
|
76
|
+
export const revalidateCartData = (ctx) =>
|
|
77
|
+
ctx.isAction(CartActions) || undefined;
|
|
72
78
|
|
|
73
79
|
layout(CartLayout, () => [
|
|
74
80
|
middleware(cartRenderMiddleware),
|
|
@@ -32,6 +32,10 @@ Common reasons to migrate:
|
|
|
32
32
|
- **Build-time rendering** — `Static()` and `Prerender()` provide explicit
|
|
33
33
|
build-time rendering instead of mixing rendering and caching behind conventions.
|
|
34
34
|
See: `/prerender`
|
|
35
|
+
- **Partial prerendering, shipped** — the `ppr` path option caches a page's
|
|
36
|
+
HTML shell and resumes only the live holes on each request; loaders stay
|
|
37
|
+
fresh. The equivalent of Next's `experimental_ppr`, stable and per-route.
|
|
38
|
+
See: `/ppr`
|
|
35
39
|
- **Composable route tree** — layouts, includes, middleware, parallels, and
|
|
36
40
|
intercepts compose directly in the route definition.
|
|
37
41
|
See: `/composability`, `/parallel`, `/intercept`
|
|
@@ -43,6 +47,34 @@ Common reasons to migrate:
|
|
|
43
47
|
|
|
44
48
|
Work route-by-route, bottom-up. Start with leaf pages, then layouts, then middleware. Verify each route works before moving to the next.
|
|
45
49
|
|
|
50
|
+
## Replace imports, never shim Next
|
|
51
|
+
|
|
52
|
+
Do NOT create mock `next/*` modules, Vite aliases for `next/*`, or compatibility
|
|
53
|
+
wrapper components (a local `Link` that forwards `href` to `to`, a fake
|
|
54
|
+
`useRouter`, a stubbed `next/headers`). Shims freeze Next semantics into the
|
|
55
|
+
app, hide unsupported behavior until runtime, and keep `next` in the dependency
|
|
56
|
+
graph — the migration looks done but isn't. Replace every `next/*` import at
|
|
57
|
+
its call site with the real Rango API:
|
|
58
|
+
|
|
59
|
+
| Next import | Replace with |
|
|
60
|
+
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `next/link` `Link` | `Link` from `@rangojs/router/client` — rename `href` to `to` (see §6) |
|
|
62
|
+
| `next/navigation` `useRouter`, `usePathname`, `useSearchParams`, `useParams` | same names from `@rangojs/router/client` |
|
|
63
|
+
| `next/navigation` `redirect`, `notFound` | `redirect`, `notFound` from `@rangojs/router` |
|
|
64
|
+
| `next/headers` `cookies`, `headers` | `cookies()`, `headers()` from `@rangojs/router` (server-only) |
|
|
65
|
+
| `next/cache` `revalidateTag`, `unstable_cache` | `updateTag`/`revalidateTag` from `@rangojs/router`; `"use cache"` (see §3 and `/use-cache`) |
|
|
66
|
+
| `next/server` `NextResponse`, `NextRequest` | web-standard `Response`/`Request`; middleware via `router.use()` (see §4) |
|
|
67
|
+
| `next/image` `Image` | plain `<img>` (keep explicit `width`/`height`) or your CDN's image URL — no built-in optimizer |
|
|
68
|
+
| `next/font` | see `/fonts` |
|
|
69
|
+
| `next/script` `Script` | see `/scripts` |
|
|
70
|
+
| `next-themes` | `theme: true` in `createRouter` (see §10) |
|
|
71
|
+
|
|
72
|
+
If an import has no row here and no obvious Rango equivalent, stop and surface
|
|
73
|
+
it to the user — do not mock it to keep the build green.
|
|
74
|
+
|
|
75
|
+
Done means: `grep -rn "from ['\"]next" src/ app/` returns nothing, and `next`
|
|
76
|
+
is gone from `package.json`.
|
|
77
|
+
|
|
46
78
|
## 1. Project Setup
|
|
47
79
|
|
|
48
80
|
Replace Next.js tooling with Vite + Rango:
|
|
@@ -88,6 +120,21 @@ The Document component replaces `app/layout.tsx`'s `<html>` wrapper. See `/route
|
|
|
88
120
|
| `app/shop/[...path]/page.tsx` | `path("/shop/:path+", CatchAll, { name: "shopCatchAll" })` |
|
|
89
121
|
| `app/docs/[[...slug]]/page.tsx` | `path("/docs/:slug*", Docs, { name: "docs" })` |
|
|
90
122
|
|
|
123
|
+
The catch-all remainder is a single string at `ctx.params.<name>` with the `/`
|
|
124
|
+
separators preserved — split it to recover the array Next gives you:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// app/docs/[[...slug]]/page.tsx -> params.slug is string[] | undefined in Next
|
|
128
|
+
path("/docs/:slug*", (ctx) => {
|
|
129
|
+
// "" for /docs, "a/b/c" for /docs/a/b/c
|
|
130
|
+
const slug = ctx.params.slug === "" ? [] : ctx.params.slug.split("/");
|
|
131
|
+
return <Docs slug={slug} />;
|
|
132
|
+
}, { name: "docs" });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`[...path]` (required, ≥1 segment) maps to `:path+`; `[[...slug]]` (optional,
|
|
136
|
+
matches the bare parent too) maps to `:slug*` — which binds `""` at `/docs`.
|
|
137
|
+
|
|
91
138
|
### Layouts
|
|
92
139
|
|
|
93
140
|
```typescript
|
|
@@ -153,6 +200,18 @@ export const marketingPatterns = urls(({ path }) => [
|
|
|
153
200
|
include("/", marketingPatterns, { name: "marketing" }),
|
|
154
201
|
```
|
|
155
202
|
|
|
203
|
+
Next.js code-splits each route segment automatically. Rango's eager `include()`
|
|
204
|
+
bundles the group into the entry chunk; to get Next-style per-section splitting,
|
|
205
|
+
pass an async provider so the group loads on the first request under its prefix:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// urls/admin.tsx: `export default adminPatterns` — loads on first /admin request
|
|
209
|
+
include("/admin", () => import("./urls/admin"), { name: "admin" }),
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Route types, `href()`, and prerender still see every route in the split group.
|
|
213
|
+
See `/composability`.
|
|
214
|
+
|
|
156
215
|
### Parallel routes
|
|
157
216
|
|
|
158
217
|
In Next.js, `@sidebar` and `@main` are both named slots. In Rango, the main content
|
|
@@ -193,9 +252,9 @@ The main content always goes through `<Outlet />` via the `path()` handler.
|
|
|
193
252
|
// Rango: explicit intercept in layout
|
|
194
253
|
layout(<ShopLayout />, () => [
|
|
195
254
|
path("/product/:id", ProductPage, { name: "product" }),
|
|
196
|
-
intercept("@modal", ".product", <ProductModal />,
|
|
197
|
-
when(
|
|
198
|
-
|
|
255
|
+
intercept("@modal", ".product", <ProductModal />, {
|
|
256
|
+
when: ({ from }) => from.pathname.startsWith("/shop"),
|
|
257
|
+
}),
|
|
199
258
|
])
|
|
200
259
|
```
|
|
201
260
|
|
|
@@ -288,21 +347,136 @@ export const Product = Passthrough(ProductDef, async (ctx) => {
|
|
|
288
347
|
Use `Passthrough()` whenever the Next.js route has `dynamicParams: true` (the
|
|
289
348
|
default) or serves an open-ended param space. See `/prerender` for full API.
|
|
290
349
|
|
|
291
|
-
###
|
|
350
|
+
### Rendering-mode segment config
|
|
351
|
+
|
|
352
|
+
Next.js route segment config maps onto Rango's explicit primitives:
|
|
292
353
|
|
|
293
|
-
Next.js
|
|
294
|
-
|
|
354
|
+
| Next.js segment config | Rango |
|
|
355
|
+
| --------------------------------------------------- | ------------------------------------------------------------ |
|
|
356
|
+
| `dynamic = "force-static"` + `generateStaticParams` | `Static()` / `Prerender()` (see `/prerender`) |
|
|
357
|
+
| `revalidate = 60` (ISR) | `cache({ ttl: 60, swr: ... })` on the route (see `/caching`) |
|
|
358
|
+
| `dynamic = "force-dynamic"` | the default — routes are dynamic unless you cache them |
|
|
359
|
+
| `dynamicParams = true` | `Passthrough()` (above) |
|
|
360
|
+
| `experimental_ppr = true` | the `ppr` path option (below, and `/ppr`) |
|
|
295
361
|
|
|
296
|
-
|
|
362
|
+
### Partial prerendering → the `ppr` path option
|
|
297
363
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
364
|
+
Next.js PPR statically prerenders a shell at build time and streams the parts
|
|
365
|
+
inside `<Suspense>` at request time. Rango ships the same model as a path
|
|
366
|
+
option — the shell is captured at runtime into the app cache store and resumed
|
|
367
|
+
on later requests, with the holes rendered fresh per request:
|
|
301
368
|
|
|
302
369
|
```typescript
|
|
370
|
+
// Next.js: app/products/[id]/page.tsx
|
|
371
|
+
export const experimental_ppr = true;
|
|
372
|
+
export default async function Page({ params }) {
|
|
373
|
+
return (
|
|
374
|
+
<ProductShell>
|
|
375
|
+
<Suspense fallback={<PriceSkeleton />}>
|
|
376
|
+
<LivePrice id={params.id} />
|
|
377
|
+
</Suspense>
|
|
378
|
+
</ProductShell>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Rango, step 1 — direct carry-over. Your Suspense tree IS the hole model:
|
|
383
|
+
// hand the un-awaited promise down, keep the boundary, add the ppr option.
|
|
384
|
+
// No loader, no loading(), no restructuring.
|
|
385
|
+
function ProductPage(ctx: HandlerContext) {
|
|
386
|
+
const price = fetchPrice(ctx.params.id); // pending promise — NOT awaited
|
|
387
|
+
return (
|
|
388
|
+
<ProductShell>
|
|
389
|
+
<Suspense fallback={<PriceSkeleton />}>
|
|
390
|
+
<LivePrice price={price} /> {/* use(price) inside */}
|
|
391
|
+
</Suspense>
|
|
392
|
+
</ProductShell>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
path("/products/:id", ProductPage, {
|
|
396
|
+
name: "product",
|
|
397
|
+
ppr: { ttl: 600, swr: 120 }, // or ppr: true (default ttl 300s)
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Rango, step 2 (optional refinement) — promote the fetch to a loader for a
|
|
401
|
+
// GUARANTEED hole: loaders are masked at capture and fresh on every serve,
|
|
402
|
+
// even when the value resolves instantly (a raw promise that settles fast
|
|
403
|
+
// would bake into the shell). loading() is the loader's hole boundary.
|
|
404
|
+
path(
|
|
405
|
+
"/products/:id",
|
|
406
|
+
ProductPage,
|
|
407
|
+
{ name: "product", ppr: { ttl: 600, swr: 120 } },
|
|
408
|
+
() => [loader(LivePriceLoader), loading(<PriceSkeleton />)],
|
|
409
|
+
),
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Differences that matter during migration:
|
|
413
|
+
|
|
414
|
+
- **The Suspense/promise model carries over.** As in Next, a still-pending
|
|
415
|
+
promise handed to a component that suspends under its own `<Suspense>`
|
|
416
|
+
postpones at capture and becomes a hole — existing Next PPR trees keep
|
|
417
|
+
working as-is, no `loading()` required. One container rule everywhere
|
|
418
|
+
(handlers, handles, loaders): awaited/settled data bakes into the shell; a
|
|
419
|
+
promise nested inside your data stays a live hole. For loaders, `loading()`
|
|
420
|
+
selects the lane: present = guaranteed live (masked at capture, fresh every
|
|
421
|
+
serve, immune to fast resolution — prefer it for per-request data); absent =
|
|
422
|
+
the bake lane (the settled container bakes and is snapshot-pinned per shell,
|
|
423
|
+
nested promises stay live). Identity reads (`cookies()`/`headers()`) where
|
|
424
|
+
the value would bake refuse the capture by construction.
|
|
425
|
+
- **Shell freshness is explicit.** Next's PPR shell is fixed until the next
|
|
426
|
+
build; Rango's has `ttl`/`swr`/`tags` per route, and `updateTag()` /
|
|
427
|
+
`revalidateTag()` drop the shell (`revalidate()` does not — it is a data
|
|
428
|
+
lever and never touches shell HTML).
|
|
429
|
+
- **`cookies()`/`headers()` in shell material THROW during capture** (in Next
|
|
430
|
+
they silently force dynamic rendering). Per-user reads must move behind a
|
|
431
|
+
`loading()` boundary (the live loader lane) or into a nested promise — the
|
|
432
|
+
refusal surfaces at migration time, which is the point.
|
|
433
|
+
- **A store is required.** PPR needs the app-level `createRouter({ cache })`
|
|
434
|
+
store to implement the shell family (`MemorySegmentCacheStore`,
|
|
435
|
+
`CFCacheStore`, `VercelCacheStore`). Without one the route quietly stays
|
|
436
|
+
fully dynamic with a once-per-key warning.
|
|
437
|
+
- **Middleware still guards every serve.** Auth middleware (global or route
|
|
438
|
+
DSL) runs before any shell byte on HIT and MISS alike — no Next-style "PPR
|
|
439
|
+
bypasses middleware" caveats to migrate around.
|
|
440
|
+
|
|
441
|
+
A route without `ppr` pays zero cost. See `/ppr` for the full execution matrix,
|
|
442
|
+
hole rules, and pitfalls.
|
|
443
|
+
|
|
444
|
+
### Revalidation: two distinct axes
|
|
445
|
+
|
|
446
|
+
Next.js conflates two things under "revalidation." Rango separates them — and
|
|
447
|
+
tag-based cache invalidation now maps directly.
|
|
448
|
+
|
|
449
|
+
**1. Cache invalidation (bust cached values) — direct equivalent.** Tag entries
|
|
450
|
+
with `cache({ tags })` or runtime `cacheTag(...tags)`. `cacheTag()` works inside a
|
|
451
|
+
`"use cache"` function (tags that entry) AND render-callable in a plain server
|
|
452
|
+
component (no `"use cache"` needed — it tags the document / PPR shell the component
|
|
453
|
+
renders into). Then invalidate by tag:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// Next.js Rango
|
|
457
|
+
// revalidateTag("products") → await updateTag("products") // in a server action: awaitable,
|
|
458
|
+
// // read-your-own-writes (next render is fresh)
|
|
459
|
+
// or revalidateTag("products") // in a route handler / webhook:
|
|
460
|
+
// // background, non-blocking (hard-purge)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`updateTag` is awaitable and immediate; `revalidateTag` is fire-and-forget. Both
|
|
464
|
+
hard-purge (the next read re-renders fresh); the only difference is awaitability —
|
|
465
|
+
despite the Next.js name, `revalidateTag` here is NOT stale-while-revalidate.
|
|
466
|
+
Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`) index by tag. Next's
|
|
467
|
+
`revalidatePath` has no path-based equivalent — tag the relevant entries instead.
|
|
468
|
+
|
|
469
|
+
**2. Partial-render selection (which segments re-run after an action).** This is
|
|
470
|
+
NOT cache invalidation — it is `revalidate()`, controlling which segments
|
|
471
|
+
(layouts, paths, loaders, parallels) recompute during partial action
|
|
472
|
+
re-rendering:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { updateBlog } from "./actions/blog";
|
|
476
|
+
|
|
303
477
|
// Re-run this layout when a blog action fires
|
|
304
478
|
layout(BlogLayout, () => [
|
|
305
|
-
revalidate((
|
|
479
|
+
revalidate((ctx) => ctx.isAction(updateBlog) || undefined),
|
|
306
480
|
path("/blog/:slug", BlogPost, { name: "blogPost" }),
|
|
307
481
|
]);
|
|
308
482
|
|
|
@@ -323,15 +497,18 @@ cache({ ttl: 60, swr: 300 }, () => [
|
|
|
323
497
|
]);
|
|
324
498
|
```
|
|
325
499
|
|
|
326
|
-
The
|
|
500
|
+
The two axes compose: `updateTag()` / `revalidateTag()` bust cached values;
|
|
501
|
+
`revalidate()` selects which segments re-render and stream to the client after an
|
|
502
|
+
action.
|
|
327
503
|
|
|
328
|
-
|
|
329
|
-
- Rango asks "which segments should re-run after this action?"
|
|
504
|
+
When migrating:
|
|
330
505
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
`
|
|
506
|
+
- `revalidateTag(tag)` → `await updateTag(tag)` (in a server action) or
|
|
507
|
+
`revalidateTag(tag)` (in a route handler / webhook). Effectively 1:1.
|
|
508
|
+
- `revalidatePath(path)` → no path-based equivalent; tag the entries on that
|
|
509
|
+
route (`cache({ tags })` / `cacheTag(...)`) and invalidate by tag.
|
|
510
|
+
- To also force specific segments to re-render after the action (independent of
|
|
511
|
+
cache busting), attach a `revalidate()` rule at those segment boundaries.
|
|
335
512
|
|
|
336
513
|
## 4. Middleware
|
|
337
514
|
|
|
@@ -463,7 +640,7 @@ Server actions work the same way — `"use server"` directive, `useActionState`,
|
|
|
463
640
|
|
|
464
641
|
Key difference: in Rango, route middleware does NOT wrap action execution. Actions only see global middleware context. Use `getRequestContext()` in actions to access `ctx.set()`/`ctx.get()`.
|
|
465
642
|
|
|
466
|
-
Next.js's `
|
|
643
|
+
Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data; `revalidateTag(tag)` is a background (non-blocking) hard-purge and is NOT read-your-own-writes, so reserve it for route handlers / webhooks (calling it from an action can leave that action's re-render stale). `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, to force specific matched segments (path/layout/parallel/intercept) and their loaders to re-render after an action, attach a `revalidate(({ actionId }) => ...)` rule to that segment or loader registration. See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
|
|
467
644
|
|
|
468
645
|
## 8. Metadata / Head
|
|
469
646
|
|
|
@@ -559,4 +736,10 @@ See `/theme` for full API including system detection and cookie persistence.
|
|
|
559
736
|
10. [ ] Migrate API routes to `path.json()` / `path.text()`
|
|
560
737
|
11. [ ] Update metadata to use `Meta` handle + `<MetaTags />` in document head
|
|
561
738
|
12. [ ] Replace `next-themes` with `theme: true` in createRouter (see `/theme`)
|
|
562
|
-
13. [ ]
|
|
739
|
+
13. [ ] Map rendering-mode segment config: `revalidate = N` → `cache({ ttl })`,
|
|
740
|
+
`force-static` → `Static()`/`Prerender()`, `experimental_ppr` → the
|
|
741
|
+
`ppr` path option (loader + `loading()` as the hole)
|
|
742
|
+
14. [ ] Run `npx rango generate src/` to generate route types
|
|
743
|
+
15. [ ] Verify no shims: `grep -rn "from ['\"]next" src/ app/` returns nothing,
|
|
744
|
+
no mock `next/*` modules or aliases exist, and `next` is out of
|
|
745
|
+
`package.json`
|