@rangojs/router 0.0.0-experimental.140 → 0.0.0-experimental.142
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 +6 -10
- package/README.md +17 -0
- package/dist/__internal.d.ts +83 -0
- package/dist/__internal.d.ts.map +1 -0
- package/dist/__internal.js +19 -0
- package/dist/__internal.js.map +1 -0
- package/dist/__mocks__/version.d.ts +7 -0
- package/dist/__mocks__/version.d.ts.map +1 -0
- package/dist/__mocks__/version.js +7 -0
- package/dist/__mocks__/version.js.map +1 -0
- package/dist/__tests__/client-href.test.d.ts +2 -0
- package/dist/__tests__/client-href.test.d.ts.map +1 -0
- package/dist/__tests__/client-href.test.js +74 -0
- package/dist/__tests__/client-href.test.js.map +1 -0
- package/dist/__tests__/component-utils.test.d.ts +2 -0
- package/dist/__tests__/component-utils.test.d.ts.map +1 -0
- package/dist/__tests__/component-utils.test.js +51 -0
- package/dist/__tests__/component-utils.test.js.map +1 -0
- package/dist/__tests__/event-controller.test.d.ts +2 -0
- package/dist/__tests__/event-controller.test.d.ts.map +1 -0
- package/dist/__tests__/event-controller.test.js +538 -0
- package/dist/__tests__/event-controller.test.js.map +1 -0
- package/dist/__tests__/helpers/route-tree.d.ts +118 -0
- package/dist/__tests__/helpers/route-tree.d.ts.map +1 -0
- package/dist/__tests__/helpers/route-tree.js +374 -0
- package/dist/__tests__/helpers/route-tree.js.map +1 -0
- package/dist/__tests__/match-result.test.d.ts +2 -0
- package/dist/__tests__/match-result.test.d.ts.map +1 -0
- package/dist/__tests__/match-result.test.js +154 -0
- package/dist/__tests__/match-result.test.js.map +1 -0
- package/dist/__tests__/navigation-store.test.d.ts +2 -0
- package/dist/__tests__/navigation-store.test.d.ts.map +1 -0
- package/dist/__tests__/navigation-store.test.js +440 -0
- package/dist/__tests__/navigation-store.test.js.map +1 -0
- package/dist/__tests__/partial-update.test.d.ts +2 -0
- package/dist/__tests__/partial-update.test.d.ts.map +1 -0
- package/dist/__tests__/partial-update.test.js +1009 -0
- package/dist/__tests__/partial-update.test.js.map +1 -0
- package/dist/__tests__/reverse-types.test.d.ts +8 -0
- package/dist/__tests__/reverse-types.test.d.ts.map +1 -0
- package/dist/__tests__/reverse-types.test.js +656 -0
- package/dist/__tests__/reverse-types.test.js.map +1 -0
- package/dist/__tests__/route-definition.test.d.ts +2 -0
- package/dist/__tests__/route-definition.test.d.ts.map +1 -0
- package/dist/__tests__/route-definition.test.js +55 -0
- package/dist/__tests__/route-definition.test.js.map +1 -0
- package/dist/__tests__/router-helpers.test.d.ts +2 -0
- package/dist/__tests__/router-helpers.test.d.ts.map +1 -0
- package/dist/__tests__/router-helpers.test.js +377 -0
- package/dist/__tests__/router-helpers.test.js.map +1 -0
- package/dist/__tests__/router-integration-2.test.d.ts +2 -0
- package/dist/__tests__/router-integration-2.test.d.ts.map +1 -0
- package/dist/__tests__/router-integration-2.test.js +426 -0
- package/dist/__tests__/router-integration-2.test.js.map +1 -0
- package/dist/__tests__/router-integration.test.d.ts +2 -0
- package/dist/__tests__/router-integration.test.d.ts.map +1 -0
- package/dist/__tests__/router-integration.test.js +1051 -0
- package/dist/__tests__/router-integration.test.js.map +1 -0
- package/dist/__tests__/search-params.test.d.ts +5 -0
- package/dist/__tests__/search-params.test.d.ts.map +1 -0
- package/dist/__tests__/search-params.test.js +306 -0
- package/dist/__tests__/search-params.test.js.map +1 -0
- package/dist/__tests__/segment-system.test.d.ts +2 -0
- package/dist/__tests__/segment-system.test.d.ts.map +1 -0
- package/dist/__tests__/segment-system.test.js +627 -0
- package/dist/__tests__/segment-system.test.js.map +1 -0
- package/dist/__tests__/static-handler-types.test.d.ts +8 -0
- package/dist/__tests__/static-handler-types.test.d.ts.map +1 -0
- package/dist/__tests__/static-handler-types.test.js +63 -0
- package/dist/__tests__/static-handler-types.test.js.map +1 -0
- package/dist/__tests__/urls.test.d.ts +2 -0
- package/dist/__tests__/urls.test.d.ts.map +1 -0
- package/dist/__tests__/urls.test.js +421 -0
- package/dist/__tests__/urls.test.js.map +1 -0
- package/dist/__tests__/use-mount.test.d.ts +2 -0
- package/dist/__tests__/use-mount.test.d.ts.map +1 -0
- package/dist/__tests__/use-mount.test.js +35 -0
- package/dist/__tests__/use-mount.test.js.map +1 -0
- package/dist/bin/rango.d.ts +2 -0
- package/dist/bin/rango.d.ts.map +1 -0
- package/dist/bin/rango.js.map +1 -0
- package/dist/browser/event-controller.d.ts +191 -0
- package/dist/browser/event-controller.d.ts.map +1 -0
- package/dist/browser/event-controller.js +559 -0
- package/dist/browser/event-controller.js.map +1 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/link-interceptor.d.ts +38 -0
- package/dist/browser/link-interceptor.d.ts.map +1 -0
- package/dist/browser/link-interceptor.js +99 -0
- package/dist/browser/link-interceptor.js.map +1 -0
- package/dist/browser/logging.d.ts +10 -0
- package/dist/browser/logging.d.ts.map +1 -0
- package/dist/browser/logging.js +29 -0
- package/dist/browser/logging.js.map +1 -0
- package/dist/browser/lru-cache.d.ts +17 -0
- package/dist/browser/lru-cache.d.ts.map +1 -0
- package/dist/browser/lru-cache.js +50 -0
- package/dist/browser/lru-cache.js.map +1 -0
- package/dist/browser/merge-segment-loaders.d.ts +39 -0
- package/dist/browser/merge-segment-loaders.d.ts.map +1 -0
- package/dist/browser/merge-segment-loaders.js +102 -0
- package/dist/browser/merge-segment-loaders.js.map +1 -0
- package/dist/browser/navigation-bridge.d.ts +102 -0
- package/dist/browser/navigation-bridge.d.ts.map +1 -0
- package/dist/browser/navigation-bridge.js +708 -0
- package/dist/browser/navigation-bridge.js.map +1 -0
- package/dist/browser/navigation-client.d.ts +25 -0
- package/dist/browser/navigation-client.d.ts.map +1 -0
- package/dist/browser/navigation-client.js +157 -0
- package/dist/browser/navigation-client.js.map +1 -0
- package/dist/browser/navigation-store.d.ts +101 -0
- package/dist/browser/navigation-store.d.ts.map +1 -0
- package/dist/browser/navigation-store.js +625 -0
- package/dist/browser/navigation-store.js.map +1 -0
- package/dist/browser/partial-update.d.ts +75 -0
- package/dist/browser/partial-update.d.ts.map +1 -0
- package/dist/browser/partial-update.js +426 -0
- package/dist/browser/partial-update.js.map +1 -0
- package/dist/browser/react/Link.d.ts +86 -0
- package/dist/browser/react/Link.d.ts.map +1 -0
- package/dist/browser/react/Link.js +128 -0
- package/dist/browser/react/Link.js.map +1 -0
- package/dist/browser/react/NavigationProvider.d.ts +63 -0
- package/dist/browser/react/NavigationProvider.d.ts.map +1 -0
- package/dist/browser/react/NavigationProvider.js +216 -0
- package/dist/browser/react/NavigationProvider.js.map +1 -0
- package/dist/browser/react/ScrollRestoration.d.ts +75 -0
- package/dist/browser/react/ScrollRestoration.d.ts.map +1 -0
- package/dist/browser/react/ScrollRestoration.js +57 -0
- package/dist/browser/react/ScrollRestoration.js.map +1 -0
- package/dist/browser/react/context.d.ts +46 -0
- package/dist/browser/react/context.d.ts.map +1 -0
- package/dist/browser/react/context.js +10 -0
- package/dist/browser/react/context.js.map +1 -0
- package/dist/browser/react/index.d.ts +11 -0
- package/dist/browser/react/index.d.ts.map +1 -0
- package/dist/browser/react/index.js +22 -0
- package/dist/browser/react/index.js.map +1 -0
- package/dist/browser/react/location-state-shared.d.ts +63 -0
- package/dist/browser/react/location-state-shared.d.ts.map +1 -0
- package/dist/browser/react/location-state-shared.js +81 -0
- package/dist/browser/react/location-state-shared.js.map +1 -0
- package/dist/browser/react/location-state.d.ts +23 -0
- package/dist/browser/react/location-state.d.ts.map +1 -0
- package/dist/browser/react/location-state.js +29 -0
- package/dist/browser/react/location-state.js.map +1 -0
- package/dist/browser/react/mount-context.d.ts +24 -0
- package/dist/browser/react/mount-context.d.ts.map +1 -0
- package/dist/browser/react/mount-context.js +24 -0
- package/dist/browser/react/mount-context.js.map +1 -0
- package/dist/browser/react/use-action.d.ts +64 -0
- package/dist/browser/react/use-action.d.ts.map +1 -0
- package/dist/browser/react/use-action.js +134 -0
- package/dist/browser/react/use-action.js.map +1 -0
- package/dist/browser/react/use-client-cache.d.ts +41 -0
- package/dist/browser/react/use-client-cache.d.ts.map +1 -0
- package/dist/browser/react/use-client-cache.js +39 -0
- package/dist/browser/react/use-client-cache.js.map +1 -0
- package/dist/browser/react/use-handle.d.ts +31 -0
- package/dist/browser/react/use-handle.d.ts.map +1 -0
- package/dist/browser/react/use-handle.js +144 -0
- package/dist/browser/react/use-handle.js.map +1 -0
- package/dist/browser/react/use-href.d.ts +33 -0
- package/dist/browser/react/use-href.d.ts.map +1 -0
- package/dist/browser/react/use-href.js +39 -0
- package/dist/browser/react/use-href.js.map +1 -0
- package/dist/browser/react/use-link-status.d.ts +37 -0
- package/dist/browser/react/use-link-status.d.ts.map +1 -0
- package/dist/browser/react/use-link-status.js +99 -0
- package/dist/browser/react/use-link-status.js.map +1 -0
- package/dist/browser/react/use-mount.d.ts +25 -0
- package/dist/browser/react/use-mount.d.ts.map +1 -0
- package/dist/browser/react/use-mount.js +30 -0
- package/dist/browser/react/use-mount.js.map +1 -0
- package/dist/browser/react/use-navigation.d.ts +27 -0
- package/dist/browser/react/use-navigation.d.ts.map +1 -0
- package/dist/browser/react/use-navigation.js +87 -0
- package/dist/browser/react/use-navigation.js.map +1 -0
- package/dist/browser/react/use-segments.d.ts +38 -0
- package/dist/browser/react/use-segments.d.ts.map +1 -0
- package/dist/browser/react/use-segments.js +130 -0
- package/dist/browser/react/use-segments.js.map +1 -0
- package/dist/browser/request-controller.d.ts +26 -0
- package/dist/browser/request-controller.d.ts.map +1 -0
- package/dist/browser/request-controller.js +147 -0
- package/dist/browser/request-controller.js.map +1 -0
- package/dist/browser/rsc-router.d.ts +129 -0
- package/dist/browser/rsc-router.d.ts.map +1 -0
- package/dist/browser/rsc-router.js +195 -0
- package/dist/browser/rsc-router.js.map +1 -0
- package/dist/browser/scroll-restoration.d.ts +93 -0
- package/dist/browser/scroll-restoration.d.ts.map +1 -0
- package/dist/browser/scroll-restoration.js +321 -0
- package/dist/browser/scroll-restoration.js.map +1 -0
- package/dist/browser/segment-structure-assert.d.ts +17 -0
- package/dist/browser/segment-structure-assert.d.ts.map +1 -0
- package/dist/browser/segment-structure-assert.js +59 -0
- package/dist/browser/segment-structure-assert.js.map +1 -0
- package/dist/browser/server-action-bridge.d.ts +26 -0
- package/dist/browser/server-action-bridge.d.ts.map +1 -0
- package/dist/browser/server-action-bridge.js +668 -0
- package/dist/browser/server-action-bridge.js.map +1 -0
- package/dist/browser/shallow.d.ts +12 -0
- package/dist/browser/shallow.d.ts.map +1 -0
- package/dist/browser/shallow.js +34 -0
- package/dist/browser/shallow.js.map +1 -0
- package/dist/browser/types.d.ts +369 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +2 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/build/__tests__/generate-cli.test.d.ts +2 -0
- package/dist/build/__tests__/generate-cli.test.d.ts.map +1 -0
- package/dist/build/__tests__/generate-cli.test.js +237 -0
- package/dist/build/__tests__/generate-cli.test.js.map +1 -0
- package/dist/build/__tests__/generate-manifest.test.d.ts +2 -0
- package/dist/build/__tests__/generate-manifest.test.d.ts.map +1 -0
- package/dist/build/__tests__/generate-manifest.test.js +119 -0
- package/dist/build/__tests__/generate-manifest.test.js.map +1 -0
- package/dist/build/__tests__/generate-route-types.test.d.ts +2 -0
- package/dist/build/__tests__/generate-route-types.test.d.ts.map +1 -0
- package/dist/build/__tests__/generate-route-types.test.js +620 -0
- package/dist/build/__tests__/generate-route-types.test.js.map +1 -0
- package/dist/build/__tests__/per-router-manifest.test.d.ts +2 -0
- package/dist/build/__tests__/per-router-manifest.test.d.ts.map +1 -0
- package/dist/build/__tests__/per-router-manifest.test.js +308 -0
- package/dist/build/__tests__/per-router-manifest.test.js.map +1 -0
- package/dist/build/generate-manifest.d.ts +81 -0
- package/dist/build/generate-manifest.d.ts.map +1 -0
- package/dist/build/generate-manifest.js +276 -0
- package/dist/build/generate-manifest.js.map +1 -0
- package/dist/build/generate-route-types.d.ts +115 -0
- package/dist/build/generate-route-types.d.ts.map +1 -0
- package/dist/build/generate-route-types.js +740 -0
- package/dist/build/generate-route-types.js.map +1 -0
- package/dist/build/index.d.ts +21 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +21 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/route-trie.d.ts +71 -0
- package/dist/build/route-trie.d.ts.map +1 -0
- package/dist/build/route-trie.js +175 -0
- package/dist/build/route-trie.js.map +1 -0
- package/dist/cache/__tests__/cache-scope.test.d.ts +2 -0
- package/dist/cache/__tests__/cache-scope.test.d.ts.map +1 -0
- package/dist/cache/__tests__/cache-scope.test.js +208 -0
- package/dist/cache/__tests__/cache-scope.test.js.map +1 -0
- package/dist/cache/__tests__/document-cache.test.d.ts +2 -0
- package/dist/cache/__tests__/document-cache.test.d.ts.map +1 -0
- package/dist/cache/__tests__/document-cache.test.js +345 -0
- package/dist/cache/__tests__/document-cache.test.js.map +1 -0
- package/dist/cache/__tests__/memory-segment-store.test.d.ts +2 -0
- package/dist/cache/__tests__/memory-segment-store.test.d.ts.map +1 -0
- package/dist/cache/__tests__/memory-segment-store.test.js +425 -0
- package/dist/cache/__tests__/memory-segment-store.test.js.map +1 -0
- package/dist/cache/__tests__/memory-store.test.d.ts +2 -0
- package/dist/cache/__tests__/memory-store.test.d.ts.map +1 -0
- package/dist/cache/__tests__/memory-store.test.js +367 -0
- package/dist/cache/__tests__/memory-store.test.js.map +1 -0
- package/dist/cache/cache-scope.d.ts +102 -0
- package/dist/cache/cache-scope.d.ts.map +1 -0
- package/dist/cache/cache-scope.js +440 -0
- package/dist/cache/cache-scope.js.map +1 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts +2 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts.map +1 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.js +330 -0
- package/dist/cache/cf/__tests__/cf-cache-store.test.js.map +1 -0
- package/dist/cache/cf/cf-cache-store.d.ts +165 -0
- package/dist/cache/cf/cf-cache-store.d.ts.map +1 -0
- package/dist/cache/cf/cf-cache-store.js +242 -0
- package/dist/cache/cf/cf-cache-store.js.map +1 -0
- package/dist/cache/cf/index.d.ts +14 -0
- package/dist/cache/cf/index.d.ts.map +1 -0
- package/dist/cache/cf/index.js +17 -0
- package/dist/cache/cf/index.js.map +1 -0
- package/dist/cache/document-cache.d.ts +64 -0
- package/dist/cache/document-cache.d.ts.map +1 -0
- package/dist/cache/document-cache.js +228 -0
- package/dist/cache/document-cache.js.map +1 -0
- package/dist/cache/index.d.ts +19 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +21 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/memory-segment-store.d.ts +110 -0
- package/dist/cache/memory-segment-store.d.ts.map +1 -0
- package/dist/cache/memory-segment-store.js +117 -0
- package/dist/cache/memory-segment-store.js.map +1 -0
- package/dist/cache/memory-store.d.ts +41 -0
- package/dist/cache/memory-store.d.ts.map +1 -0
- package/dist/cache/memory-store.js +191 -0
- package/dist/cache/memory-store.js.map +1 -0
- package/dist/cache/types.d.ts +317 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +12 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/client.d.ts +248 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +367 -0
- package/dist/client.js.map +1 -0
- package/dist/client.rsc.d.ts +26 -0
- package/dist/client.rsc.d.ts.map +1 -0
- package/dist/client.rsc.js +46 -0
- package/dist/client.rsc.js.map +1 -0
- package/dist/component-utils.d.ts +36 -0
- package/dist/component-utils.d.ts.map +1 -0
- package/dist/component-utils.js +61 -0
- package/dist/component-utils.js.map +1 -0
- package/dist/components/DefaultDocument.d.ts +13 -0
- package/dist/components/DefaultDocument.d.ts.map +1 -0
- package/dist/components/DefaultDocument.js +15 -0
- package/dist/components/DefaultDocument.js.map +1 -0
- package/dist/debug.d.ts +58 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +157 -0
- package/dist/debug.js.map +1 -0
- package/dist/default-error-boundary.d.ts +11 -0
- package/dist/default-error-boundary.d.ts.map +1 -0
- package/dist/default-error-boundary.js +45 -0
- package/dist/default-error-boundary.js.map +1 -0
- package/dist/deps/browser.d.ts +2 -0
- package/dist/deps/browser.d.ts.map +1 -0
- package/dist/deps/browser.js +3 -0
- package/dist/deps/browser.js.map +1 -0
- package/dist/deps/html-stream-client.d.ts +2 -0
- package/dist/deps/html-stream-client.d.ts.map +1 -0
- package/dist/deps/html-stream-client.js +3 -0
- package/dist/deps/html-stream-client.js.map +1 -0
- package/dist/deps/html-stream-server.d.ts +2 -0
- package/dist/deps/html-stream-server.d.ts.map +1 -0
- package/dist/deps/html-stream-server.js +3 -0
- package/dist/deps/html-stream-server.js.map +1 -0
- package/dist/deps/rsc.d.ts +2 -0
- package/dist/deps/rsc.d.ts.map +1 -0
- package/dist/deps/rsc.js +4 -0
- package/dist/deps/rsc.js.map +1 -0
- package/dist/deps/ssr.d.ts +2 -0
- package/dist/deps/ssr.d.ts.map +1 -0
- package/dist/deps/ssr.js +3 -0
- package/dist/deps/ssr.js.map +1 -0
- package/dist/errors.d.ts +174 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +241 -0
- package/dist/errors.js.map +1 -0
- package/dist/handle.d.ts +78 -0
- package/dist/handle.d.ts.map +1 -0
- package/dist/handle.js +82 -0
- package/dist/handle.js.map +1 -0
- package/dist/handles/MetaTags.d.ts +14 -0
- package/dist/handles/MetaTags.d.ts.map +1 -0
- package/dist/handles/MetaTags.js +136 -0
- package/dist/handles/MetaTags.js.map +1 -0
- package/dist/handles/index.d.ts +6 -0
- package/dist/handles/index.d.ts.map +1 -0
- package/dist/handles/index.js +6 -0
- package/dist/handles/index.js.map +1 -0
- package/dist/handles/meta.d.ts +39 -0
- package/dist/handles/meta.d.ts.map +1 -0
- package/dist/handles/meta.js +202 -0
- package/dist/handles/meta.js.map +1 -0
- package/dist/host/__tests__/errors.test.d.ts +2 -0
- package/dist/host/__tests__/errors.test.d.ts.map +1 -0
- package/dist/host/__tests__/errors.test.js +76 -0
- package/dist/host/__tests__/errors.test.js.map +1 -0
- package/dist/host/__tests__/pattern-comprehensive.test.d.ts +2 -0
- package/dist/host/__tests__/pattern-comprehensive.test.d.ts.map +1 -0
- package/dist/host/__tests__/pattern-comprehensive.test.js +732 -0
- package/dist/host/__tests__/pattern-comprehensive.test.js.map +1 -0
- package/dist/host/__tests__/pattern-matcher.test.d.ts +2 -0
- package/dist/host/__tests__/pattern-matcher.test.d.ts.map +1 -0
- package/dist/host/__tests__/pattern-matcher.test.js +251 -0
- package/dist/host/__tests__/pattern-matcher.test.js.map +1 -0
- package/dist/host/__tests__/router.test.d.ts +2 -0
- package/dist/host/__tests__/router.test.d.ts.map +1 -0
- package/dist/host/__tests__/router.test.js +241 -0
- package/dist/host/__tests__/router.test.js.map +1 -0
- package/dist/host/__tests__/testing.test.d.ts +2 -0
- package/dist/host/__tests__/testing.test.d.ts.map +1 -0
- package/dist/host/__tests__/testing.test.js +64 -0
- package/dist/host/__tests__/testing.test.js.map +1 -0
- package/dist/host/__tests__/utils.test.d.ts +2 -0
- package/dist/host/__tests__/utils.test.d.ts.map +1 -0
- package/dist/host/__tests__/utils.test.js +29 -0
- package/dist/host/__tests__/utils.test.js.map +1 -0
- package/dist/host/cookie-handler.d.ts +34 -0
- package/dist/host/cookie-handler.d.ts.map +1 -0
- package/dist/host/cookie-handler.js +124 -0
- package/dist/host/cookie-handler.js.map +1 -0
- package/dist/host/errors.d.ts +56 -0
- package/dist/host/errors.d.ts.map +1 -0
- package/dist/host/errors.js +79 -0
- package/dist/host/errors.js.map +1 -0
- package/dist/host/index.d.ts +29 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +32 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/pattern-matcher.d.ts +36 -0
- package/dist/host/pattern-matcher.d.ts.map +1 -0
- package/dist/host/pattern-matcher.js +172 -0
- package/dist/host/pattern-matcher.js.map +1 -0
- package/dist/host/router.d.ts +26 -0
- package/dist/host/router.d.ts.map +1 -0
- package/dist/host/router.js +218 -0
- package/dist/host/router.js.map +1 -0
- package/dist/host/testing.d.ts +36 -0
- package/dist/host/testing.d.ts.map +1 -0
- package/dist/host/testing.js +55 -0
- package/dist/host/testing.js.map +1 -0
- package/dist/host/types.d.ts +115 -0
- package/dist/host/types.d.ts.map +1 -0
- package/dist/host/types.js +7 -0
- package/dist/host/types.js.map +1 -0
- package/dist/host/utils.d.ts +21 -0
- package/dist/host/utils.d.ts.map +1 -0
- package/dist/host/utils.js +23 -0
- package/dist/host/utils.js.map +1 -0
- package/dist/href-client.d.ts +131 -0
- package/dist/href-client.d.ts.map +1 -0
- package/dist/href-client.js +64 -0
- package/dist/href-client.js.map +1 -0
- package/dist/href-context.d.ts +29 -0
- package/dist/href-context.d.ts.map +1 -0
- package/dist/href-context.js +21 -0
- package/dist/href-context.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/index.rsc.d.ts +32 -0
- package/dist/index.rsc.d.ts.map +1 -0
- package/dist/index.rsc.js +40 -0
- package/dist/index.rsc.js.map +1 -0
- package/dist/internal-debug.d.ts +2 -0
- package/dist/internal-debug.d.ts.map +1 -0
- package/dist/internal-debug.js +5 -0
- package/dist/internal-debug.js.map +1 -0
- package/dist/loader.d.ts +14 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +20 -0
- package/dist/loader.js.map +1 -0
- package/dist/loader.rsc.d.ts +19 -0
- package/dist/loader.rsc.d.ts.map +1 -0
- package/dist/loader.rsc.js +99 -0
- package/dist/loader.rsc.js.map +1 -0
- package/dist/network-error-thrower.d.ts +17 -0
- package/dist/network-error-thrower.d.ts.map +1 -0
- package/dist/network-error-thrower.js +14 -0
- package/dist/network-error-thrower.js.map +1 -0
- package/dist/outlet-context.d.ts +13 -0
- package/dist/outlet-context.d.ts.map +1 -0
- package/dist/outlet-context.js +3 -0
- package/dist/outlet-context.js.map +1 -0
- package/dist/prerender/__tests__/param-hash.test.d.ts +2 -0
- package/dist/prerender/__tests__/param-hash.test.d.ts.map +1 -0
- package/dist/prerender/__tests__/param-hash.test.js +148 -0
- package/dist/prerender/__tests__/param-hash.test.js.map +1 -0
- package/dist/prerender/param-hash.d.ts +16 -0
- package/dist/prerender/param-hash.d.ts.map +1 -0
- package/dist/prerender/param-hash.js +36 -0
- package/dist/prerender/param-hash.js.map +1 -0
- package/dist/prerender/store.d.ts +38 -0
- package/dist/prerender/store.d.ts.map +1 -0
- package/dist/prerender/store.js +61 -0
- package/dist/prerender/store.js.map +1 -0
- package/dist/prerender.d.ts +66 -0
- package/dist/prerender.d.ts.map +1 -0
- package/dist/prerender.js +57 -0
- package/dist/prerender.js.map +1 -0
- package/dist/reverse.d.ts +196 -0
- package/dist/reverse.d.ts.map +1 -0
- package/dist/reverse.js +78 -0
- package/dist/reverse.js.map +1 -0
- package/dist/root-error-boundary.d.ts +33 -0
- package/dist/root-error-boundary.d.ts.map +1 -0
- package/dist/root-error-boundary.js +165 -0
- package/dist/root-error-boundary.js.map +1 -0
- package/dist/route-content-wrapper.d.ts +46 -0
- package/dist/route-content-wrapper.d.ts.map +1 -0
- package/dist/route-content-wrapper.js +77 -0
- package/dist/route-content-wrapper.js.map +1 -0
- package/dist/route-definition.d.ts +421 -0
- package/dist/route-definition.d.ts.map +1 -0
- package/dist/route-definition.js +868 -0
- package/dist/route-definition.js.map +1 -0
- package/dist/route-map-builder.d.ts +155 -0
- package/dist/route-map-builder.d.ts.map +1 -0
- package/dist/route-map-builder.js +237 -0
- package/dist/route-map-builder.js.map +1 -0
- package/dist/route-types.d.ts +165 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +7 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router/__tests__/handler-context.test.d.ts +2 -0
- package/dist/router/__tests__/handler-context.test.d.ts.map +1 -0
- package/dist/router/__tests__/handler-context.test.js +65 -0
- package/dist/router/__tests__/handler-context.test.js.map +1 -0
- package/dist/router/__tests__/loader-cycle-detection.test.d.ts +2 -0
- package/dist/router/__tests__/loader-cycle-detection.test.d.ts.map +1 -0
- package/dist/router/__tests__/loader-cycle-detection.test.js +221 -0
- package/dist/router/__tests__/loader-cycle-detection.test.js.map +1 -0
- package/dist/router/__tests__/match-context.test.d.ts +2 -0
- package/dist/router/__tests__/match-context.test.d.ts.map +1 -0
- package/dist/router/__tests__/match-context.test.js +92 -0
- package/dist/router/__tests__/match-context.test.js.map +1 -0
- package/dist/router/__tests__/match-pipelines.test.d.ts +2 -0
- package/dist/router/__tests__/match-pipelines.test.d.ts.map +1 -0
- package/dist/router/__tests__/match-pipelines.test.js +417 -0
- package/dist/router/__tests__/match-pipelines.test.js.map +1 -0
- package/dist/router/__tests__/match-result.test.d.ts +2 -0
- package/dist/router/__tests__/match-result.test.d.ts.map +1 -0
- package/dist/router/__tests__/match-result.test.js +457 -0
- package/dist/router/__tests__/match-result.test.js.map +1 -0
- package/dist/router/__tests__/on-error.test.d.ts +2 -0
- package/dist/router/__tests__/on-error.test.d.ts.map +1 -0
- package/dist/router/__tests__/on-error.test.js +678 -0
- package/dist/router/__tests__/on-error.test.js.map +1 -0
- package/dist/router/__tests__/pattern-matching.test.d.ts +2 -0
- package/dist/router/__tests__/pattern-matching.test.d.ts.map +1 -0
- package/dist/router/__tests__/pattern-matching.test.js +629 -0
- package/dist/router/__tests__/pattern-matching.test.js.map +1 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts +2 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts.map +1 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.js +155 -0
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.js.map +1 -0
- package/dist/router/error-handling.d.ts +77 -0
- package/dist/router/error-handling.d.ts.map +1 -0
- package/dist/router/error-handling.js +202 -0
- package/dist/router/error-handling.js.map +1 -0
- package/dist/router/handler-context.d.ts +20 -0
- package/dist/router/handler-context.d.ts.map +1 -0
- package/dist/router/handler-context.js +198 -0
- package/dist/router/handler-context.js.map +1 -0
- package/dist/router/intercept-resolution.d.ts +66 -0
- package/dist/router/intercept-resolution.d.ts.map +1 -0
- package/dist/router/intercept-resolution.js +246 -0
- package/dist/router/intercept-resolution.js.map +1 -0
- package/dist/router/loader-resolution.d.ts +64 -0
- package/dist/router/loader-resolution.d.ts.map +1 -0
- package/dist/router/loader-resolution.js +284 -0
- package/dist/router/loader-resolution.js.map +1 -0
- package/dist/router/logging.d.ts +15 -0
- package/dist/router/logging.d.ts.map +1 -0
- package/dist/router/logging.js +99 -0
- package/dist/router/logging.js.map +1 -0
- package/dist/router/manifest.d.ts +22 -0
- package/dist/router/manifest.d.ts.map +1 -0
- package/dist/router/manifest.js +181 -0
- package/dist/router/manifest.js.map +1 -0
- package/dist/router/match-api.d.ts +35 -0
- package/dist/router/match-api.d.ts.map +1 -0
- package/dist/router/match-api.js +406 -0
- package/dist/router/match-api.js.map +1 -0
- package/dist/router/match-context.d.ts +206 -0
- package/dist/router/match-context.d.ts.map +1 -0
- package/dist/router/match-context.js +17 -0
- package/dist/router/match-context.js.map +1 -0
- package/dist/router/match-middleware/background-revalidation.d.ts +127 -0
- package/dist/router/match-middleware/background-revalidation.d.ts.map +1 -0
- package/dist/router/match-middleware/background-revalidation.js +75 -0
- package/dist/router/match-middleware/background-revalidation.js.map +1 -0
- package/dist/router/match-middleware/cache-lookup.d.ts +112 -0
- package/dist/router/match-middleware/cache-lookup.d.ts.map +1 -0
- package/dist/router/match-middleware/cache-lookup.js +257 -0
- package/dist/router/match-middleware/cache-lookup.js.map +1 -0
- package/dist/router/match-middleware/cache-store.d.ts +113 -0
- package/dist/router/match-middleware/cache-store.d.ts.map +1 -0
- package/dist/router/match-middleware/cache-store.js +108 -0
- package/dist/router/match-middleware/cache-store.js.map +1 -0
- package/dist/router/match-middleware/index.d.ts +81 -0
- package/dist/router/match-middleware/index.d.ts.map +1 -0
- package/dist/router/match-middleware/index.js +80 -0
- package/dist/router/match-middleware/index.js.map +1 -0
- package/dist/router/match-middleware/intercept-resolution.d.ts +117 -0
- package/dist/router/match-middleware/intercept-resolution.d.ts.map +1 -0
- package/dist/router/match-middleware/intercept-resolution.js +134 -0
- package/dist/router/match-middleware/intercept-resolution.js.map +1 -0
- package/dist/router/match-middleware/segment-resolution.d.ts +99 -0
- package/dist/router/match-middleware/segment-resolution.d.ts.map +1 -0
- package/dist/router/match-middleware/segment-resolution.js +53 -0
- package/dist/router/match-middleware/segment-resolution.js.map +1 -0
- package/dist/router/match-pipelines.d.ts +147 -0
- package/dist/router/match-pipelines.d.ts.map +1 -0
- package/dist/router/match-pipelines.js +82 -0
- package/dist/router/match-pipelines.js.map +1 -0
- package/dist/router/match-result.d.ts +126 -0
- package/dist/router/match-result.d.ts.map +1 -0
- package/dist/router/match-result.js +93 -0
- package/dist/router/match-result.js.map +1 -0
- package/dist/router/metrics.d.ts +20 -0
- package/dist/router/metrics.d.ts.map +1 -0
- package/dist/router/metrics.js +47 -0
- package/dist/router/metrics.js.map +1 -0
- package/dist/router/middleware.d.ts +249 -0
- package/dist/router/middleware.d.ts.map +1 -0
- package/dist/router/middleware.js +434 -0
- package/dist/router/middleware.js.map +1 -0
- package/dist/router/middleware.test.d.ts +2 -0
- package/dist/router/middleware.test.d.ts.map +1 -0
- package/dist/router/middleware.test.js +816 -0
- package/dist/router/middleware.test.js.map +1 -0
- package/dist/router/pattern-matching.d.ts +149 -0
- package/dist/router/pattern-matching.d.ts.map +1 -0
- package/dist/router/pattern-matching.js +349 -0
- package/dist/router/pattern-matching.js.map +1 -0
- package/dist/router/revalidation.d.ts +44 -0
- package/dist/router/revalidation.d.ts.map +1 -0
- package/dist/router/revalidation.js +147 -0
- package/dist/router/revalidation.js.map +1 -0
- package/dist/router/router-context.d.ts +135 -0
- package/dist/router/router-context.d.ts.map +1 -0
- package/dist/router/router-context.js +36 -0
- package/dist/router/router-context.js.map +1 -0
- package/dist/router/segment-resolution.d.ts +127 -0
- package/dist/router/segment-resolution.d.ts.map +1 -0
- package/dist/router/segment-resolution.js +919 -0
- package/dist/router/segment-resolution.js.map +1 -0
- package/dist/router/trie-matching.d.ts +40 -0
- package/dist/router/trie-matching.d.ts.map +1 -0
- package/dist/router/trie-matching.js +127 -0
- package/dist/router/trie-matching.js.map +1 -0
- package/dist/router/types.d.ts +136 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.js +7 -0
- package/dist/router/types.js.map +1 -0
- package/dist/router.d.ts +753 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.gen.d.ts +6 -0
- package/dist/router.gen.d.ts.map +1 -0
- package/dist/router.gen.js +6 -0
- package/dist/router.gen.js.map +1 -0
- package/dist/router.js +1304 -0
- package/dist/router.js.map +1 -0
- package/dist/rsc/__tests__/helpers.test.d.ts +2 -0
- package/dist/rsc/__tests__/helpers.test.d.ts.map +1 -0
- package/dist/rsc/__tests__/helpers.test.js +140 -0
- package/dist/rsc/__tests__/helpers.test.js.map +1 -0
- package/dist/rsc/handler.d.ts +45 -0
- package/dist/rsc/handler.d.ts.map +1 -0
- package/dist/rsc/handler.js +1172 -0
- package/dist/rsc/handler.js.map +1 -0
- package/dist/rsc/helpers.d.ts +16 -0
- package/dist/rsc/helpers.d.ts.map +1 -0
- package/dist/rsc/helpers.js +55 -0
- package/dist/rsc/helpers.js.map +1 -0
- package/dist/rsc/index.d.ts +22 -0
- package/dist/rsc/index.d.ts.map +1 -0
- package/dist/rsc/index.js +23 -0
- package/dist/rsc/index.js.map +1 -0
- package/dist/rsc/nonce.d.ts +9 -0
- package/dist/rsc/nonce.d.ts.map +1 -0
- package/dist/rsc/nonce.js +18 -0
- package/dist/rsc/nonce.js.map +1 -0
- package/dist/rsc/types.d.ts +206 -0
- package/dist/rsc/types.d.ts.map +1 -0
- package/dist/rsc/types.js +8 -0
- package/dist/rsc/types.js.map +1 -0
- package/dist/search-params.d.ts +103 -0
- package/dist/search-params.d.ts.map +1 -0
- package/dist/search-params.js +74 -0
- package/dist/search-params.js.map +1 -0
- package/dist/segment-system.d.ts +75 -0
- package/dist/segment-system.d.ts.map +1 -0
- package/dist/segment-system.js +336 -0
- package/dist/segment-system.js.map +1 -0
- package/dist/server/context.d.ts +245 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +197 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/fetchable-loader-store.d.ts +18 -0
- package/dist/server/fetchable-loader-store.d.ts.map +1 -0
- package/dist/server/fetchable-loader-store.js +18 -0
- package/dist/server/fetchable-loader-store.js.map +1 -0
- package/dist/server/handle-store.d.ts +85 -0
- package/dist/server/handle-store.d.ts.map +1 -0
- package/dist/server/handle-store.js +142 -0
- package/dist/server/handle-store.js.map +1 -0
- package/dist/server/loader-registry.d.ts +55 -0
- package/dist/server/loader-registry.d.ts.map +1 -0
- package/dist/server/loader-registry.js +132 -0
- package/dist/server/loader-registry.js.map +1 -0
- package/dist/server/request-context.d.ts +226 -0
- package/dist/server/request-context.d.ts.map +1 -0
- package/dist/server/request-context.js +290 -0
- package/dist/server/request-context.js.map +1 -0
- package/dist/server/root-layout.d.ts +4 -0
- package/dist/server/root-layout.d.ts.map +1 -0
- package/dist/server/root-layout.js +5 -0
- package/dist/server/root-layout.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +20 -0
- package/dist/server.js.map +1 -0
- package/dist/ssr/__tests__/ssr-handler.test.d.ts +2 -0
- package/dist/ssr/__tests__/ssr-handler.test.d.ts.map +1 -0
- package/dist/ssr/__tests__/ssr-handler.test.js +132 -0
- package/dist/ssr/__tests__/ssr-handler.test.js.map +1 -0
- package/dist/ssr/index.d.ts +98 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +158 -0
- package/dist/ssr/index.js.map +1 -0
- package/dist/static-handler.d.ts +50 -0
- package/dist/static-handler.d.ts.map +1 -0
- package/dist/static-handler.gen.d.ts +5 -0
- package/dist/static-handler.gen.d.ts.map +1 -0
- package/dist/static-handler.gen.js +5 -0
- package/dist/static-handler.gen.js.map +1 -0
- package/dist/static-handler.js +29 -0
- package/dist/static-handler.js.map +1 -0
- package/dist/theme/ThemeProvider.d.ts +20 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.js +240 -0
- package/dist/theme/ThemeProvider.js.map +1 -0
- package/dist/theme/ThemeScript.d.ts +48 -0
- package/dist/theme/ThemeScript.d.ts.map +1 -0
- package/dist/theme/ThemeScript.js +13 -0
- package/dist/theme/ThemeScript.js.map +1 -0
- package/dist/theme/__tests__/theme.test.d.ts +2 -0
- package/dist/theme/__tests__/theme.test.d.ts.map +1 -0
- package/dist/theme/__tests__/theme.test.js +103 -0
- package/dist/theme/__tests__/theme.test.js.map +1 -0
- package/dist/theme/constants.d.ts +29 -0
- package/dist/theme/constants.d.ts.map +1 -0
- package/dist/theme/constants.js +48 -0
- package/dist/theme/constants.js.map +1 -0
- package/dist/theme/index.d.ts +31 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +36 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/theme-context.d.ts +40 -0
- package/dist/theme/theme-context.d.ts.map +1 -0
- package/dist/theme/theme-context.js +60 -0
- package/dist/theme/theme-context.js.map +1 -0
- package/dist/theme/theme-script.d.ts +27 -0
- package/dist/theme/theme-script.d.ts.map +1 -0
- package/dist/theme/theme-script.js +147 -0
- package/dist/theme/theme-script.js.map +1 -0
- package/dist/theme/types.d.ts +163 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +11 -0
- package/dist/theme/types.js.map +1 -0
- package/dist/theme/use-theme.d.ts +12 -0
- package/dist/theme/use-theme.d.ts.map +1 -0
- package/dist/theme/use-theme.js +40 -0
- package/dist/theme/use-theme.js.map +1 -0
- package/dist/types.d.ts +1479 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/urls.d.ts +441 -0
- package/dist/urls.d.ts.map +1 -0
- package/dist/urls.gen.d.ts +8 -0
- package/dist/urls.gen.d.ts.map +1 -0
- package/dist/urls.gen.js +8 -0
- package/dist/urls.gen.js.map +1 -0
- package/dist/urls.js +443 -0
- package/dist/urls.js.map +1 -0
- package/dist/use-loader.d.ts +127 -0
- package/dist/use-loader.d.ts.map +1 -0
- package/dist/use-loader.js +237 -0
- package/dist/use-loader.js.map +1 -0
- package/dist/vite/__tests__/ast-handler-extract.test.d.ts +2 -0
- package/dist/vite/__tests__/ast-handler-extract.test.d.ts.map +1 -0
- package/dist/vite/__tests__/ast-handler-extract.test.js +294 -0
- package/dist/vite/__tests__/ast-handler-extract.test.js.map +1 -0
- package/dist/vite/__tests__/expose-id-utils.test.d.ts +2 -0
- package/dist/vite/__tests__/expose-id-utils.test.d.ts.map +1 -0
- package/dist/vite/__tests__/expose-id-utils.test.js +224 -0
- package/dist/vite/__tests__/expose-id-utils.test.js.map +1 -0
- package/dist/vite/__tests__/expose-internal-ids.test.d.ts +2 -0
- package/dist/vite/__tests__/expose-internal-ids.test.d.ts.map +1 -0
- package/dist/vite/__tests__/expose-internal-ids.test.js +647 -0
- package/dist/vite/__tests__/expose-internal-ids.test.js.map +1 -0
- package/dist/vite/__tests__/expose-router-id.test.d.ts +2 -0
- package/dist/vite/__tests__/expose-router-id.test.d.ts.map +1 -0
- package/dist/vite/__tests__/expose-router-id.test.js +39 -0
- package/dist/vite/__tests__/expose-router-id.test.js.map +1 -0
- package/dist/vite/ast-handler-extract.d.ts +49 -0
- package/dist/vite/ast-handler-extract.d.ts.map +1 -0
- package/dist/vite/ast-handler-extract.js +249 -0
- package/dist/vite/ast-handler-extract.js.map +1 -0
- package/dist/vite/expose-action-id.d.ts +19 -0
- package/dist/vite/expose-action-id.d.ts.map +1 -0
- package/dist/vite/expose-action-id.js +250 -0
- package/dist/vite/expose-action-id.js.map +1 -0
- package/dist/vite/expose-id-utils.d.ts +69 -0
- package/dist/vite/expose-id-utils.d.ts.map +1 -0
- package/dist/vite/expose-id-utils.js +289 -0
- package/dist/vite/expose-id-utils.js.map +1 -0
- package/dist/vite/expose-internal-ids.d.ts +22 -0
- package/dist/vite/expose-internal-ids.d.ts.map +1 -0
- package/dist/vite/expose-internal-ids.js +886 -0
- package/dist/vite/expose-internal-ids.js.map +1 -0
- package/dist/vite/index.d.ts +149 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +82 -15
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/index.named-routes.gen.ts +103 -0
- package/dist/vite/package-resolution.d.ts +43 -0
- package/dist/vite/package-resolution.d.ts.map +1 -0
- package/dist/vite/package-resolution.js +112 -0
- package/dist/vite/package-resolution.js.map +1 -0
- package/dist/vite/virtual-entries.d.ts +25 -0
- package/dist/vite/virtual-entries.d.ts.map +1 -0
- package/dist/vite/virtual-entries.js +110 -0
- package/dist/vite/virtual-entries.js.map +1 -0
- package/package.json +19 -18
- package/skills/api-client/SKILL.md +1 -1
- package/skills/breadcrumbs/SKILL.md +1 -1
- package/skills/cache-guide/SKILL.md +1 -1
- package/skills/caching/SKILL.md +17 -1
- package/skills/catalog.json +265 -0
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/composability/SKILL.md +1 -1
- package/skills/debug-manifest/SKILL.md +1 -1
- package/skills/document-cache/SKILL.md +9 -1
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +54 -892
- 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 +1 -1
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +8 -1
- package/skills/layout/SKILL.md +1 -1
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +8 -1
- package/skills/middleware/SKILL.md +1 -1
- package/skills/migrate-nextjs/SKILL.md +133 -1
- package/skills/migrate-react-router/SKILL.md +42 -816
- 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 +1 -1
- package/skills/observability/SKILL.md +1 -1
- package/skills/parallel/SKILL.md +8 -1
- package/skills/ppr/SKILL.md +448 -348
- package/skills/prerender/SKILL.md +8 -1
- package/skills/rango/SKILL.md +14 -14
- package/skills/response-routes/SKILL.md +1 -1
- package/skills/route/SKILL.md +1 -1
- package/skills/router-setup/SKILL.md +1 -1
- package/skills/scripts/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +3 -2
- package/skills/shell-manifest/SKILL.md +1 -1
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +44 -919
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +305 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +7 -1
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/browser/event-controller.ts +41 -10
- package/src/browser/logging.ts +10 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-client.ts +5 -1
- package/src/browser/navigation-store.ts +46 -6
- package/src/browser/partial-update.ts +27 -15
- package/src/browser/prefetch/cache.ts +26 -4
- package/src/browser/prefetch/fetch.ts +27 -17
- package/src/browser/react/Link.tsx +13 -3
- package/src/browser/react/NavigationProvider.tsx +21 -19
- package/src/browser/scroll-restoration.ts +7 -5
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/types.ts +12 -0
- package/src/cache/cache-runtime.ts +203 -23
- package/src/cache/cf/cf-cache-store.ts +8 -0
- package/src/cache/document-cache.ts +20 -9
- package/src/cache/index.ts +0 -5
- package/src/cache/memory-segment-store.ts +53 -2
- package/src/cache/segment-codec.ts +4 -4
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/types.ts +86 -0
- package/src/cache/vercel/vercel-cache-store.ts +143 -108
- package/src/index.rsc.ts +1 -5
- package/src/index.ts +1 -17
- package/src/router/manifest.ts +13 -5
- package/src/router/match-api.ts +70 -30
- package/src/router/match-handlers.ts +6 -2
- package/src/router/match-result.ts +35 -15
- package/src/router/middleware.ts +10 -3
- package/src/router/request-classification.ts +20 -7
- package/src/router/route-snapshot.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -4
- package/src/router/segment-resolution/loader-cache.ts +57 -8
- package/src/router/segment-resolution/loader-mask.ts +26 -3
- package/src/router/segment-resolution/loader-snapshot.ts +170 -0
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/rsc-rendering.ts +310 -89
- package/src/rsc/shell-capture.ts +707 -66
- package/src/rsc/shell-serve.ts +150 -0
- package/src/server/context.ts +7 -0
- package/src/server/cookie-store.ts +6 -0
- package/src/server/request-context.ts +85 -46
- package/src/ssr/index.tsx +45 -6
- package/src/theme/ThemeProvider.tsx +36 -26
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper.ts +5 -0
- package/src/urls/pattern-types.ts +36 -0
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/router-discovery.ts +78 -15
- package/src/cache/shell-cache.ts +0 -386
- package/src/server/live.ts +0 -130
package/skills/ppr/SKILL.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ppr
|
|
3
|
-
description: PPR shell caching —
|
|
3
|
+
description: PPR shell caching — opt a page route in with the `ppr` path option; the router serves the cached HTML shell instantly and resumes the live holes. Use when a page should render instantly from a cached shell while specific parts stay live, or asking about partial prerendering in Rango.
|
|
4
4
|
argument-hint: "[setup]"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# PPR Shell Caching
|
|
8
8
|
|
|
9
|
-
Caches the rendered HTML **shell** of a route (React `prerender` prelude
|
|
10
|
-
plus `postponed` state) and, on a later request, flushes those bytes
|
|
11
|
-
render work happens, then resumes fizz for just the live
|
|
9
|
+
Caches the rendered HTML **shell** of a page route (React `prerender` prelude
|
|
10
|
+
bytes plus `postponed` state) and, on a later request, flushes those bytes
|
|
11
|
+
before any render work happens, then resumes fizz for just the live holes. The
|
|
12
12
|
browser sees one ordinary streamed document; loaders stay fresh on every
|
|
13
13
|
request. This is the second render axis — the default axis-1 path is untouched,
|
|
14
14
|
and every ineligible request falls open to it.
|
|
@@ -17,21 +17,43 @@ Compare `/document-cache`, which freezes the WHOLE response including loader
|
|
|
17
17
|
output. Shell caching is for pages that mix a stable shell with live data: the
|
|
18
18
|
shell is shared per host+URL, the holes are per request.
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Not this skill if…
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
- You want the WHOLE response frozen, loader output included — see
|
|
23
|
+
`/document-cache`.
|
|
24
|
+
- You want routes rendered at build time with `Static()`/`Prerender()` — the
|
|
25
|
+
`ppr` path option captures at runtime; see `/prerender`.
|
|
26
|
+
- You are unsure which cache layer you need — start at `/cache-guide`.
|
|
27
|
+
|
|
28
|
+
## Setup: one path option, no middleware
|
|
29
|
+
|
|
30
|
+
PPR is a DOCUMENT-level property declared on the page route via the `ppr` path
|
|
31
|
+
option. Serving is **integral to the router** — there is nothing to mount. The
|
|
32
|
+
only prerequisite is an app-level `createRouter({ cache })` store that
|
|
33
|
+
implements the shell family (`getShell`/`putShell`): `MemorySegmentCacheStore`
|
|
34
|
+
(dev/tests), `CFCacheStore` (Cloudflare KV), or `VercelCacheStore` (runtime
|
|
35
|
+
cache). A ppr route on a store without the family stays on axis 1 with a
|
|
36
|
+
once-per-key warning.
|
|
27
37
|
|
|
28
38
|
```typescript
|
|
29
|
-
import { createRouter } from "@rangojs/router";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
import { createRouter, urls } from "@rangojs/router";
|
|
40
|
+
import { CFCacheStore } from "@rangojs/router/cache";
|
|
41
|
+
|
|
42
|
+
export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
43
|
+
layout(ProductShell, () => [
|
|
44
|
+
path(
|
|
45
|
+
"/products/:id",
|
|
46
|
+
PricePage,
|
|
47
|
+
// `ppr` is the whole opt-in AND the policy. `ppr: true` uses the default
|
|
48
|
+
// ttl (300s); an object sets ttl/swr/tags (PartialPrerenderProps).
|
|
49
|
+
{ name: "product", ppr: { ttl: 600, swr: 120 } },
|
|
50
|
+
() => [
|
|
51
|
+
loader(LivePriceLoader),
|
|
52
|
+
loading(<PriceSkeleton />), // structural hole: the loader subtree stays live
|
|
53
|
+
],
|
|
54
|
+
),
|
|
55
|
+
]),
|
|
56
|
+
]);
|
|
35
57
|
|
|
36
58
|
const router = createRouter<AppBindings>({
|
|
37
59
|
document: Document,
|
|
@@ -40,383 +62,461 @@ const router = createRouter<AppBindings>({
|
|
|
40
62
|
store: new CFCacheStore({ kv: env.CACHE_KV, ctx: ctx! }),
|
|
41
63
|
}),
|
|
42
64
|
});
|
|
65
|
+
export default router;
|
|
66
|
+
```
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"/products",
|
|
47
|
-
createShellCacheMiddleware({ ttlSeconds: 600, swrSeconds: 120 }),
|
|
48
|
-
);
|
|
68
|
+
That is ONE of two hole mechanisms — the loader one. Do not conclude PPR
|
|
69
|
+
requires a loader:
|
|
49
70
|
|
|
50
|
-
|
|
71
|
+
### The same opt-in with NO loader and NO loading() — promise holes
|
|
72
|
+
|
|
73
|
+
A route (or its layouts) whose live regions are pending promises under
|
|
74
|
+
`<Suspense>` PPRs with no `loader()` and no `loading()` anywhere. Hand the
|
|
75
|
+
un-awaited promise over as a prop; the consumer suspends under its OWN
|
|
76
|
+
boundary; the boundary postpones at capture and becomes the hole:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Handler: kick off the fetch, do NOT await it.
|
|
80
|
+
function ProductPage(ctx: HandlerContext) {
|
|
81
|
+
const reviews = fetchReviews(ctx.params.id); // Promise<Review[]> — pending
|
|
82
|
+
return (
|
|
83
|
+
<main>
|
|
84
|
+
<h1>Product {ctx.params.id}</h1> {/* shell — baked into the prelude */}
|
|
85
|
+
<ReviewsSection promise={reviews} /> {/* hole — resumes per request */}
|
|
86
|
+
</main>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
51
89
|
```
|
|
52
90
|
|
|
91
|
+
```tsx
|
|
92
|
+
// ReviewsSection.tsx — the consumer owns its Suspense boundary.
|
|
93
|
+
"use client";
|
|
94
|
+
|
|
95
|
+
import { Suspense, use } from "react";
|
|
96
|
+
|
|
97
|
+
function Inner({ promise }: { promise: Promise<Review[]> }) {
|
|
98
|
+
return <ReviewList reviews={use(promise)} />;
|
|
99
|
+
}
|
|
100
|
+
export function ReviewsSection({ promise }: { promise: Promise<Review[]> }) {
|
|
101
|
+
return (
|
|
102
|
+
<Suspense fallback={<ReviewsSkeleton />}>
|
|
103
|
+
<Inner promise={promise} />
|
|
104
|
+
</Suspense>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
path("/products/:id", ProductPage, { name: "product", ppr: true });
|
|
111
|
+
// No use() list at all: no loader, no loading, still a shell + live hole.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
At capture the pending fetch cannot win the task-quantized quiet window, so
|
|
115
|
+
the boundary postpones — fallback in the frozen prelude, value resumed fresh
|
|
116
|
+
on every HIT. This is the PHYSICS class from the hole doctrine below, and it
|
|
117
|
+
is exactly how an existing Suspense-shaped tree (e.g. migrated from Next.js
|
|
118
|
+
PPR) works with zero restructuring. The e2e proof is
|
|
119
|
+
`e2e/test-app/src/components/ShellPhysicsValue.tsx` — a promise hole living in
|
|
120
|
+
a LAYOUT with no loader registration at all.
|
|
121
|
+
|
|
122
|
+
A route WITHOUT the `ppr` option is pure axis 1: no store read, no capture, no
|
|
123
|
+
logs, zero cost. `ppr` is per page route — declaring it on a layout is not
|
|
124
|
+
supported (subtree inheritance is a possible follow-up).
|
|
125
|
+
|
|
53
126
|
## Where PPR sits: the cache onion
|
|
54
127
|
|
|
55
128
|
Rango's caches layer like an onion — each ring stores a progressively more
|
|
56
|
-
"cooked" representation of the same page
|
|
57
|
-
|
|
58
|
-
(final bytes):
|
|
129
|
+
"cooked" representation of the same page. From innermost (raw values) to
|
|
130
|
+
outermost (final bytes):
|
|
59
131
|
|
|
60
132
|
| Ring | Primitive | What is stored | What stays live on a hit |
|
|
61
133
|
| ----------------------- | ---------------------------------------- | -------------------------------------------------- | -------------------------------------- |
|
|
62
134
|
| 1. Function values | `"use cache"` | a function's return value | everything around the call |
|
|
63
135
|
| 2. Loader values | `loader(Fn, () => [cache({...})])` | one loader's result (opt-in; loaders default live) | all other loaders, handlers, rendering |
|
|
64
136
|
| 3. Segments (Flight) | `cache()` route / build-time `prerender` | serialized rendered segments + replayed handles | loaders, HTML render |
|
|
65
|
-
| 4. **HTML shell (PPR)** | `
|
|
137
|
+
| 4. **HTML shell (PPR)** | `ppr` path option | rendered prelude bytes + React postponed state | the holes, hydration payload |
|
|
66
138
|
| 5. Whole response | `/document-cache` | final response bytes, headers included | nothing — all-or-nothing |
|
|
67
139
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
PPR is ORTHOGONAL to `cache()` (ring 3): a ppr route may be uncached (its
|
|
141
|
+
handlers run fresh on every serve and during capture), fully `cache()`d (its
|
|
142
|
+
segments replay), or mixed. One useful cache() property to know: the segment
|
|
143
|
+
codec **deep-settles promises at the ring-3 write**, so nothing inside a
|
|
144
|
+
`cache()` boundary can stay live — that is a cache() fact, not a ppr one.
|
|
145
|
+
|
|
146
|
+
Invalidation crosses rings: `updateTag()`/`revalidateTag()` reach segment,
|
|
147
|
+
shell, loader, and item entries in the same store, and shell entries
|
|
148
|
+
additionally self-invalidate on `React.version` change.
|
|
149
|
+
|
|
150
|
+
## The serve pipeline: commit after ALL middleware
|
|
151
|
+
|
|
152
|
+
On a document GET to a ppr route the router runs:
|
|
153
|
+
|
|
154
|
+
1. **match** — route identified, `ppr` config read from the matched route;
|
|
155
|
+
2. **the WHOLE middleware chain** — the global `router.use()` chain AND route
|
|
156
|
+
DSL `middleware()`; both are guards, and the COMMIT POINT is after all of
|
|
157
|
+
them: any rejection/redirect/401 wins before a single shell byte, on MISS
|
|
158
|
+
and on a warmed HIT alike;
|
|
159
|
+
3. **shell lookup** — `getShell(key)` on the app store (key =
|
|
160
|
+
host+pathname+sorted search);
|
|
161
|
+
4. **HIT** — the composed response is committed immediately: the stored prelude
|
|
162
|
+
bytes flush first, while segment resolution, the fresh Flight render (the
|
|
163
|
+
full hydration payload — there is no Flight-side resume), and the fizz
|
|
164
|
+
`resume` of just the holes run BEHIND them inside the response stream;
|
|
165
|
+
5. **MISS** — plain axis-1 serve, tagged `x-rango-shell: MISS`, plus a
|
|
166
|
+
background capture (stampede-guarded, retry-in-place, exponential backoff).
|
|
167
|
+
|
|
168
|
+
`x-rango-shell: HIT | MISS` is the observability header. Because the commit
|
|
169
|
+
point is after the chain, an unauthorized request NEVER sees shell bytes — put
|
|
170
|
+
auth middleware anywhere (global or route DSL) and it guards PPR for free.
|
|
171
|
+
|
|
172
|
+
## The hole doctrine (encode this in your head)
|
|
173
|
+
|
|
174
|
+
Holes are **render-defined**, decided by the shape of the tree, on three rules:
|
|
175
|
+
|
|
176
|
+
| Class | What makes the hole | At capture | At serve |
|
|
177
|
+
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
178
|
+
| **STRUCTURAL** | the ENTIRE segment subtree under a `loading()` registration — the loader LIVE lane | loaders masked; the LoaderBoundary postpones; the fallback bakes in as route structure | loaders run fresh; resume fills it |
|
|
179
|
+
| **PHYSICS** | any promise NESTED in handed-over data still pending at capture, under the consumer's own `<Suspense>` — handler props, handle containers (`push({ x: promise })`), loader-carried | real I/O cannot win the task-quantized quiet window; the boundary postpones | the promise settles and streams in |
|
|
180
|
+
| **SHELL** | awaited handler data, TOP-LEVEL `push(promise)` (awaited before SSR), resolved promises, replayed `cache()` segments, BAKE-lane loader containers | baked into the prelude | served from the frozen prelude |
|
|
181
|
+
|
|
182
|
+
ONE rule for promises, every lane — handlers, handles, AND loaders: **a promise
|
|
183
|
+
nested inside your data is never baked; the container settles.** A loader
|
|
184
|
+
without `loading()` is the BAKE lane (see below): its settled container is
|
|
185
|
+
shell material, exactly like awaited handler data and top-level handle pushes.
|
|
186
|
+
|
|
187
|
+
**`loading()` is NOT the gate for holes — it is the LANE SELECTOR for loader
|
|
188
|
+
data.** A pending promise under any plain `<Suspense>` postpones and is a hole
|
|
189
|
+
at whatever level it suspends — the PHYSICS row needs no `loading()` anywhere
|
|
190
|
+
(the e2e fixture's physics and nested-handle holes sit in a layout with none).
|
|
191
|
+
A route without a loader PPRs on pure promise/Suspense holes. For loaders,
|
|
192
|
+
`loading()` picks the lane: present = the GUARANTEED live lane (masked at
|
|
193
|
+
capture, fresh every serve, immune to fast resolution); absent = the bake lane
|
|
194
|
+
(the container is shell material, nested promises stay live).
|
|
195
|
+
|
|
196
|
+
The three promise positions, side by side:
|
|
116
197
|
|
|
117
198
|
```typescript
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
199
|
+
async function Handler(ctx: HandlerContext) {
|
|
200
|
+
const push = ctx.use(MyHandle);
|
|
201
|
+
|
|
202
|
+
push(fetchBadge()); // TOP-LEVEL push: awaited pre-SSR → BAKED
|
|
203
|
+
push({ label: "x", stat: fetchStat() }); // NESTED in container → container baked,
|
|
204
|
+
// stat streams → HOLE (consumer Suspends it)
|
|
205
|
+
|
|
206
|
+
const data = await fetchHeader(ctx); // awaited by the handler → BAKED
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<section>
|
|
210
|
+
<Header data={data} /> {/* shell */}
|
|
211
|
+
<StatsPanel promise={fetchStats(ctx)} /> {/* un-awaited prop + own
|
|
212
|
+
<Suspense> + use() → HOLE */}
|
|
213
|
+
</section>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
128
216
|
```
|
|
129
217
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
218
|
+
### Choosing the hole mechanism
|
|
219
|
+
|
|
220
|
+
| Your live region is… | Use | Why |
|
|
221
|
+
| ----------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
222
|
+
| loader data, must be fresh EVERY serve | `loader()` + `loading()` (the live lane) | the guaranteed structural hole — masked at capture, fresh every serve, immune to fast resolution |
|
|
223
|
+
| loader data, shell container + live parts | bake-lane loader (no `loading()`): `{ static, dynamic: promise }` | container bakes (snapshot-pinned per shell); nested promises hole at the consumer's `<Suspense>` |
|
|
224
|
+
| handler-fetched real I/O (db, fetch) | un-awaited promise prop + consumer `<Suspense>` + `use()` | no loader needed; real latency postpones by physics |
|
|
225
|
+
| per-segment metadata consumed elsewhere | handle container with a NESTED promise + consumer `<Suspense>` | container is shell, nested value streams — "nesting = liveness" |
|
|
226
|
+
| already-resolved / instant / synchronous values | `loader(() => Promise.resolve(x))` + `loading()` | a raw promise that settles inside the quiet window BAKES; only the live lane guarantees live |
|
|
227
|
+
| none of the above | nothing | it bakes — that is what the shell is for |
|
|
228
|
+
|
|
229
|
+
The physics caveat in one line: promise holes are holes because the I/O is
|
|
230
|
+
genuinely pending at capture. If the value can resolve near-instantly (memory
|
|
231
|
+
read, warmed cache), it may bake into the shell — when liveness must be
|
|
232
|
+
guaranteed rather than probable, use the live lane (`loading()`). The same
|
|
233
|
+
physics governs bake-lane nested promises.
|
|
234
|
+
|
|
235
|
+
### Handles: "nesting = liveness"
|
|
236
|
+
|
|
237
|
+
- `ctx.use(H)(promise)` — a TOP-LEVEL pushed promise is awaited server-side
|
|
238
|
+
before SSR (`resolvedHandleStream`) and BAKED into the shell. The capture
|
|
239
|
+
gate is held open for the same await, so real latency here is safe (bounded
|
|
240
|
+
by the capture's 5s guard).
|
|
241
|
+
- `ctx.use(H)({ x: promise })` — the container passes through verbatim
|
|
242
|
+
(resolution is shallow); the nested promise streams to the consumer, who must
|
|
243
|
+
`<Suspense>` it. Under capture that boundary postpones — a hole.
|
|
244
|
+
|
|
245
|
+
### Want a hole for already-resolved data?
|
|
246
|
+
|
|
247
|
+
Put it in a loader: `loader(() => Promise.resolve(x))` + `loading()`. Loaders
|
|
248
|
+
are always the live lane — masked at capture, fresh on every serve — no matter
|
|
249
|
+
how fast the value settles.
|
|
250
|
+
|
|
251
|
+
### The bake lane: loaders without loading() on THEIR entry
|
|
252
|
+
|
|
253
|
+
A loader on an entry with no renderable `loading()` EXECUTES during capture
|
|
254
|
+
(the capture gate holds open for its real latency, bounded by the 5s guard).
|
|
255
|
+
Its settled container bakes into the prelude; every promise still nested in it
|
|
256
|
+
postpones at the consumer's own `<Suspense>` — a hole. On every HIT the
|
|
257
|
+
capture snapshot's loader family overlays the recorded container onto the
|
|
258
|
+
fresh run, so the payload matches the frozen prelude byte-for-byte while the
|
|
259
|
+
nested promises run fresh. The return shape is the declaration:
|
|
157
260
|
|
|
158
261
|
```typescript
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
262
|
+
export const StorefrontContextLoader = createLoader(async (ctx) => {
|
|
263
|
+
const config = await loadSiteConfig(ctx.params.locale); // bakes (pinned per shell)
|
|
264
|
+
return {
|
|
265
|
+
config, // shell material
|
|
266
|
+
basket: fetchBasket(ctx), // hole — consumer <Suspense>s it, fresh per request
|
|
267
|
+
};
|
|
165
268
|
});
|
|
166
269
|
```
|
|
167
270
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
271
|
+
The lane is decided PER TREE NODE, at the entry that REGISTERS the loaders —
|
|
272
|
+
`loading()` on a CHILD route does not change a parent layout's lane, and
|
|
273
|
+
`loading()` IS valid on layout and parallel entries, not just routes.
|
|
274
|
+
|
|
275
|
+
Three hard edges (each e2e/unit-pinned):
|
|
276
|
+
|
|
277
|
+
- **Identity refuses.** `cookies()`/`headers()` inside a bake-lane loader
|
|
278
|
+
throws during capture and the capture REFUSES (deterministic, once-per-key
|
|
279
|
+
warned) — identity can never bake into the shared shell. Give that loader's
|
|
280
|
+
entry `loading()` (the live lane is exempt) or move the identity-dependent
|
|
281
|
+
part into a nested promise.
|
|
282
|
+
- **A rejecting bake-lane loader refuses.** Error UI never bakes.
|
|
283
|
+
- **Baked containers show CAPTURE-time data** for the shell's lifetime on
|
|
284
|
+
document GETs (client navigations stay fresh — axis 1). That IS the bake
|
|
285
|
+
lane's meaning; if a value must be fresh on every serve, it belongs on the
|
|
286
|
+
live lane (`loading()`) or in a nested promise.
|
|
287
|
+
|
|
288
|
+
### The layout-with-loaders playbook (the storefront case)
|
|
289
|
+
|
|
290
|
+
The most common real-app shape: an app-wide layout registers per-user loaders
|
|
291
|
+
(session context, basket, wishlist) and the page under it declares `ppr`.
|
|
292
|
+
Those loaders are on the BAKE lane (no `loading()` on the layout), so the page
|
|
293
|
+
captures and HITs — the question is which parts of their data should bake vs
|
|
294
|
+
stay live. Your levers, in order of preference:
|
|
295
|
+
|
|
296
|
+
1. **Shape the return value.** Shared/config data returns as plain values
|
|
297
|
+
(bakes, pinned per shell); per-request data returns as NESTED promises
|
|
298
|
+
consumed under the widget's own `<Suspense>` (live holes). No `loading()`,
|
|
299
|
+
no restructuring. One wall: a bake-lane loader that reads
|
|
300
|
+
`cookies()`/`headers()` refuses the capture — identity belongs in a nested
|
|
301
|
+
promise or on the live lane.
|
|
302
|
+
2. **Do NOT put `loading()` on the layout itself** — that flips the WHOLE
|
|
303
|
+
layout to the live lane and the LoaderBoundary fallback wraps the layout's
|
|
304
|
+
ENTIRE subtree: chrome (header, nav, footer) falls out of the shell into
|
|
305
|
+
the skeleton. Technically PPR, practically pointless.
|
|
306
|
+
3. **Guaranteed-fresh widgets: a parallel slot with its OWN `loading()`.**
|
|
307
|
+
Parallel-owned loaders get their own per-slot boundary (`fresh.ts` tags
|
|
308
|
+
them with the slot's loading; `segment-system.tsx` builds a per-slot
|
|
309
|
+
LoaderBoundary), so the chrome bakes into the shell and each widget is an
|
|
310
|
+
independent, widget-sized hole:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
layout(StoreChrome, () => [
|
|
314
|
+
// chrome renders NO loader data itself — it bakes into the shell
|
|
315
|
+
parallel({ "@basket": BasketBadge }, () => [
|
|
316
|
+
loader(BasketLoader),
|
|
317
|
+
loading(<BadgeSkeleton />), // hole the size of a badge, not a page
|
|
318
|
+
]),
|
|
319
|
+
parallel({ "@wishlist": WishlistBadge }, () => [
|
|
320
|
+
loader(WishlistLoader),
|
|
321
|
+
loading(<BadgeSkeleton />),
|
|
322
|
+
]),
|
|
323
|
+
path("/", HomePage, { name: "home", ppr: true }),
|
|
324
|
+
]),
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Slot-owned loaders are masked at capture and GUARANTEED fresh per serve —
|
|
328
|
+
use this where the bake lane's physics (a fast resolve bakes) or pinning
|
|
329
|
+
(capture-time data for the shell's lifetime) is not acceptable, at the cost
|
|
330
|
+
of a widget-sized fallback in the shell.
|
|
331
|
+
|
|
332
|
+
4. **Shared layout data can also leave the loader lane entirely**: an
|
|
333
|
+
un-awaited handler promise under the consumer's `<Suspense>` (a physics
|
|
334
|
+
hole) or `cache()`/`"use cache"` to bake it with tag-invalidation.
|
|
335
|
+
|
|
336
|
+
The identity rule, stated once: per-user data on a PPR page lives in a NESTED
|
|
337
|
+
promise (a hole, fresh per request) or behind `loading()` (the live lane).
|
|
338
|
+
Reading `cookies()`/`headers()` where the value would bake — handler shell
|
|
339
|
+
material or a bake-lane container — refuses the capture by construction.
|
|
173
340
|
|
|
174
|
-
|
|
175
|
-
return <span>{use(promise)}</span>;
|
|
176
|
-
}
|
|
341
|
+
## Execution matrix
|
|
177
342
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
343
|
+
| Phase | MISS (foreground) | Background capture | HIT (foreground) |
|
|
344
|
+
| ---------------- | ---------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------- |
|
|
345
|
+
| Middleware chain | runs (full) | **NOT re-run** — inherits the request's post-middleware context | runs (full) — commit point is after it |
|
|
346
|
+
| `router.match` | runs | re-runs under a derived context | runs (behind the flushed prelude) |
|
|
347
|
+
| Handlers | run | run on UNCACHED segments; `cache()`d segments replay (mixed-chain) | run (same mixed-chain rules as any render) |
|
|
348
|
+
| Loaders | run **fresh** | LIVE lane (`loading()`): MASKED; BAKE lane: execute + snapshot-pin | run **fresh** (bake containers overlaid from the snapshot) |
|
|
349
|
+
| Flight render | full | full | full (hydration needs the whole payload — no Flight resume) |
|
|
350
|
+
| HTML production | full fizz | `prerender` + abort → prelude + postponed | `resume` only the holes — O(paths to holes) |
|
|
351
|
+
| Shell store | schedules a bg capture | `putShell(key, …)` | `getShell(key)`; a stale/SWR hit also schedules a recapture |
|
|
352
|
+
| Prelude bytes | — | — | flushed FIRST, before segment resolution starts |
|
|
353
|
+
|
|
354
|
+
Middleware is not re-run during capture because it already ran for the
|
|
355
|
+
triggering request — the capture's derived context inherits the
|
|
356
|
+
post-middleware state (`ctx` variables included, which is what makes
|
|
357
|
+
middleware-derived shell content photograph correctly). Guarding is
|
|
358
|
+
serve-time: the commit point runs the full chain on EVERY serve.
|
|
359
|
+
|
|
360
|
+
Because handlers on uncached segments EXECUTE during capture — and BAKE-lane
|
|
361
|
+
loaders now do too — the `cookies()`/`headers()` capture guard is load-bearing:
|
|
362
|
+
those reads THROW during a capture render (`assertNotInsideShellCapture`), so
|
|
363
|
+
identity can never leak into a shared shell through them. Live-lane loaders
|
|
364
|
+
(behind `loading()`) are exempt: masked at capture, they never run there.
|
|
365
|
+
|
|
366
|
+
## allReady: the SEO/bot story
|
|
367
|
+
|
|
368
|
+
`ssr: { resolveStreaming: ... }` returning `"allReady"` (e.g. for bot user
|
|
369
|
+
agents) bypasses PPR entirely — the request gets one complete, fully-buffered
|
|
370
|
+
axis-1 document. Crawlers that dislike streamed shells get a finished page;
|
|
371
|
+
regular users get the streamed shell. No configuration interaction: allReady
|
|
372
|
+
wins.
|
|
190
373
|
|
|
191
|
-
|
|
192
|
-
**HIT** the resume streams three progressive layers in one response body:
|
|
374
|
+
## Security
|
|
193
375
|
|
|
194
|
-
|
|
195
|
-
instantly, before any render work;
|
|
196
|
-
2. the outer loader value fills the hole, carrying the inner `<Suspense>`
|
|
197
|
-
fallback;
|
|
198
|
-
3. the nested-promise inner value + React's `$RC` boundary stitch.
|
|
376
|
+
Shell caching shares one shell per host+URL across all users:
|
|
199
377
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
handler-passed promise below, which races the capture's quiet window. The
|
|
204
|
-
three-layer timeline is pinned in dev + production e2e
|
|
205
|
-
(`tests/cloudflare-basic/e2e/ppr-shell.test.ts`, `e2e/shell-cache.test.ts`).
|
|
378
|
+
**(a) Access control is sound by construction.** The commit point is after ALL
|
|
379
|
+
middleware on every serve. A 401/redirect short-circuit returns before any
|
|
380
|
+
shell byte.
|
|
206
381
|
|
|
207
|
-
|
|
208
|
-
|
|
382
|
+
**(b) Identity can't leak via cookies/headers.** `cookies()` and `headers()`
|
|
383
|
+
THROW during the background capture render — in handlers AND in bake-lane
|
|
384
|
+
loaders (whose containers would bake). A shell that reads them is
|
|
385
|
+
PPR-ineligible by construction; the live lane (`loading()`) stays exempt.
|
|
209
386
|
|
|
210
|
-
|
|
387
|
+
**(c) Residual hazard — middleware-derived per-user state.** A `ctx` variable
|
|
388
|
+
set by an upstream auth middleware and rendered by shell material is
|
|
389
|
+
photographed into the SHARED shell (the capture inherits post-middleware
|
|
390
|
+
state). That is scope fidelity working as designed — for shared values. If the
|
|
391
|
+
value is per-user: shell-cache only public/shared pages, put per-user content
|
|
392
|
+
in loaders, or key per variant at the CDN tier.
|
|
211
393
|
|
|
212
|
-
|
|
213
|
-
including one whose data is already resolved. During the background capture
|
|
214
|
-
`live()` behaves exactly like the loader mask: it returns a never-settling
|
|
215
|
-
promise, so the consuming `<Suspense>` postpones and the prelude freezes only the
|
|
216
|
-
fallback. On the serve pass (and on the client) it is a passthrough — the thunk
|
|
217
|
-
runs, or the promise passes through unchanged.
|
|
394
|
+
## What always stays on axis 1
|
|
218
395
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
396
|
+
Non-GET, RSC/partial/action/loader fetches, per-request CSP nonce,
|
|
397
|
+
`streamMode: "allReady"`, redirects, 404s, error renders, routes without `ppr`,
|
|
398
|
+
and any store without the shell family. A stored shell is invalidated when
|
|
399
|
+
`React.version` changes (postponed state is build-coupled), so deploys
|
|
400
|
+
self-heal via recapture.
|
|
401
|
+
|
|
402
|
+
The per-request CSP nonce guarantee covers BOTH ways a nonce arrives — the
|
|
403
|
+
`createRouter({ nonce })` provider AND a direct `ctx.set(nonce, value)` token
|
|
404
|
+
write in middleware (the `nonce` token from `@rangojs/router`). Either way the
|
|
405
|
+
nonce ends up rendered into the document (`useNonce()` puts the provider nonce
|
|
406
|
+
on every nonced script/style/meta; a token nonce is rendered by whatever app
|
|
407
|
+
code reads `ctx.get(nonce)`), so a shell shared per host+URL cannot bake it
|
|
408
|
+
without freezing one request's nonce for every visitor (the browser's CSP
|
|
409
|
+
would then reject the frozen nonce for all but the capture request). The serve
|
|
410
|
+
gate reads the token off the post-middleware request variables at the commit
|
|
411
|
+
point (which runs after the whole middleware chain), so a middleware-set nonce
|
|
412
|
+
blocks capture the same as a provider one. Because the route DECLARED `ppr`
|
|
413
|
+
but cannot be honored, it logs a once-per-key worker warning (same
|
|
414
|
+
declared-intent-cannot-be-honored doctrine as the missing-store warning) and
|
|
415
|
+
serves pure axis 1 with no `x-rango-shell` header. An undeclared route stays
|
|
416
|
+
silent.
|
|
417
|
+
|
|
418
|
+
### The proper way to supply a nonce
|
|
419
|
+
|
|
420
|
+
`createRouter({ nonce })` is the canonical path — supply the nonce THERE, not
|
|
421
|
+
via a token write. The provider value is threaded into the router's own SSR
|
|
422
|
+
machinery: `NonceContext`/`useNonce()`, automatic nonce attributes on
|
|
423
|
+
`<Scripts />` and `<MetaTags />` output, and the inlined Flight payload
|
|
424
|
+
scripts. It ALSO sets the `nonce` token, so `ctx.get(nonce)` works in
|
|
425
|
+
middleware and handlers for the CSP response header. A direct
|
|
426
|
+
`ctx.set(nonce, value)` write in middleware is app-managed only: the router
|
|
427
|
+
resolves its SSR nonce BEFORE middleware runs, so a token-set value is
|
|
428
|
+
readable via `ctx.get(nonce)` and gates PPR (this section), but the router
|
|
429
|
+
will NOT apply it to its own scripts — `useNonce()` stays undefined and the
|
|
430
|
+
Flight payload scripts carry no nonce, which a nonce-only `script-src` policy
|
|
431
|
+
would then block. If you need a per-request nonce, use the provider; reserve
|
|
432
|
+
the token for READING the value.
|
|
433
|
+
|
|
434
|
+
## Options: PartialPrerenderProps
|
|
229
435
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
436
|
+
```typescript
|
|
437
|
+
path("/products/:id", Page, { name: "product", ppr: true }, use);
|
|
438
|
+
path(
|
|
439
|
+
"/products/:id",
|
|
440
|
+
Page,
|
|
441
|
+
{ name: "product", ppr: { ttl: 600, swr: 120, tags: ["catalog"] } },
|
|
442
|
+
use,
|
|
443
|
+
);
|
|
234
444
|
```
|
|
235
445
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
during capture the real promise is DISCARDED and a hole returned in its place.
|
|
242
|
-
Use it only when you already hold the promise; prefer the thunk otherwise.
|
|
243
|
-
|
|
244
|
-
`live()` is what makes a resolved value a hole at all: a bare `Promise.resolve(x)`
|
|
245
|
-
under `<Suspense>` settles inside the capture's quiet window and freezes into the
|
|
246
|
-
shell. It is also the escape hatch for the passed-promise trap below. The
|
|
247
|
-
capture/serve split is pinned in dev + production e2e (the "live() makes a
|
|
248
|
-
resolved promise a HOLE" case in `tests/cloudflare-basic/e2e/ppr-shell.test.ts`
|
|
249
|
-
and `e2e/shell-cache.test.ts`).
|
|
250
|
-
|
|
251
|
-
## Passed promises are not holes
|
|
252
|
-
|
|
253
|
-
The pattern that looks like a hole but is not: a **handler** creates a promise
|
|
254
|
-
and passes it as a prop to a client component that `use()`s it inside its own
|
|
255
|
-
`<Suspense>`. Only route **loaders** (and `live()`) are masked at capture — a
|
|
256
|
-
handler and any promise it creates EXECUTE during the background capture render.
|
|
257
|
-
What happens next is decided by the promise's LATENCY CLASS against the
|
|
258
|
-
capture's quiet window (task-quantized: it closes a couple of macrotask hops
|
|
259
|
-
after the last Flight byte, not on a wall clock). Both sides are reliable —
|
|
260
|
-
just in opposite directions:
|
|
261
|
-
|
|
262
|
-
- Resolved or microtask-resolvable (`Promise.resolve`, a warm in-memory read):
|
|
263
|
-
reliably SHELL, every capture — it settles in the same window as plain JSX.
|
|
264
|
-
If the value is per-request, that is a deterministic bug: frozen into the
|
|
265
|
-
shared shell until TTL (hydration repairs it from the fresh payload —
|
|
266
|
-
degraded, not corrupt, but a drift you shipped).
|
|
267
|
-
- Genuinely pending real I/O: reliably a HOLE — it cannot win a task-quantized
|
|
268
|
-
window. Resume fills it at serve. The capture still paid the promise's
|
|
269
|
-
execution cost and side effects, though. The only nondeterministic sliver
|
|
270
|
-
left is I/O completing within ~2 event-loop turns of the shell going quiet
|
|
271
|
-
— freakishly fast, self-healing via TTL/recapture, and only reachable by
|
|
272
|
-
code that declared no intent.
|
|
273
|
-
|
|
274
|
-
An async HANDLER (a streamed `loading()` handler returning a promise) is the
|
|
275
|
-
deliberate opposite: it is tracked in the handle store, and capture WAITS for
|
|
276
|
-
handlers to settle before aborting — handler output is shell material by
|
|
277
|
-
design, never a hole.
|
|
278
|
-
|
|
279
|
-
Verdict: a promise's latency class picks its side — you can safely assume a
|
|
280
|
-
genuinely pending, unresolved promise becomes a hole. But that decision was
|
|
281
|
-
made by latency, not by you. Wherever intent and latency could disagree —
|
|
282
|
-
per-request data that might get cache-fast, a value that must never appear in
|
|
283
|
-
the shared shell — say it in code: **`live()`** for a guaranteed hole (masked
|
|
284
|
-
at capture like a loader; prefer the thunk form so nothing runs during
|
|
285
|
-
capture), a loader behind `loading()` for route-level live data (zero capture
|
|
286
|
-
cost, and its nested promises stream too, per above), a plain `await` for
|
|
287
|
-
shell-safe deterministic data. Unwrapped promises are for the cases where
|
|
288
|
-
either outcome is acceptable.
|
|
289
|
-
|
|
290
|
-
Note on `useLoader()`: it never observes pending data. Inside a `loading()`
|
|
291
|
-
route, `LoaderBoundary` resolves the loader promise INSIDE its own Suspense
|
|
292
|
-
before children render, so `useLoader().data` (the OUTER value) is always
|
|
293
|
-
resolved; `isLoading` is client-side refetch state, not a server pending signal.
|
|
294
|
-
A nested promise on that data is separate — it streams under the consumer's own
|
|
295
|
-
inner `<Suspense>` (above). Multiple holes per page (several `loading()`
|
|
296
|
-
routes/parallels) are fine: resume fills every postponed boundary.
|
|
446
|
+
| Field | Default | Notes |
|
|
447
|
+
| ------ | ------- | -------------------------------------------------------------------------------------------------- |
|
|
448
|
+
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default) |
|
|
449
|
+
| `swr` | — | stale window: serve the stale shell + background recapture |
|
|
450
|
+
| `tags` | — | operational tags UNIONED with the tags the capture render auto-collects — see "Invalidation" below |
|
|
297
451
|
|
|
298
|
-
|
|
452
|
+
The shell store is always the app-level `createRouter({ cache })` store; the
|
|
453
|
+
default key is `${host}${pathname}${sortedSearch}:shell` (host-scoped so
|
|
454
|
+
multi-tenant shells never collide).
|
|
299
455
|
|
|
300
|
-
|
|
301
|
-
blocked on the background capture.
|
|
302
|
-
|
|
303
|
-
| Phase | MISS (foreground) | Background capture | HIT (foreground) |
|
|
304
|
-
| ---------------- | ---------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
305
|
-
| Middleware chain | runs (full) | **NOT re-run** — inherits the request's post-middleware context | runs (full) |
|
|
306
|
-
| `router.match` | runs | re-runs under a derived context | runs |
|
|
307
|
-
| Handlers | run | run | run |
|
|
308
|
-
| Loaders | run **fresh** | **MASKED** (never execute) | run **fresh** |
|
|
309
|
-
| Flight render | full | full | full (hydration needs the whole payload — no Flight resume) |
|
|
310
|
-
| HTML production | full fizz | `prerender` + abort → prelude + postponed | `resume` only the holes — O(paths to holes) |
|
|
311
|
-
| Shell store | schedules a bg capture | `putShell(key, …)` | `getShell(key)`; a stale/SWR hit also schedules a recapture |
|
|
312
|
-
| Prelude bytes | — | — | prepended by the middleware before the resumed body |
|
|
313
|
-
|
|
314
|
-
Loader freshness under PPR is **identical to axis 1**: loaders — the outer value
|
|
315
|
-
AND any nested promise — run fresh on every request, including HITs. Only the
|
|
316
|
-
HTML _around_ the hole came from cache. Background capture is scheduled via
|
|
317
|
-
`runBackground` (`waitUntil` on workerd, fire-and-forget in Node dev), so it
|
|
318
|
-
never delays the served response. Re-deriving through `router.match()` rather
|
|
319
|
-
than a second `next()` is what keeps middleware from running twice
|
|
320
|
-
(`src/rsc/shell-capture.ts`).
|
|
456
|
+
## Invalidation: tags vs revalidate()
|
|
321
457
|
|
|
322
|
-
|
|
458
|
+
`updateTag()`/`revalidateTag()` is the ONLY lever that changes the frozen shell
|
|
459
|
+
HTML; `revalidate()` is a DATA lever that never touches it.
|
|
323
460
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
prelude ONLY when the live response carries the internal `x-rango-shell-resumed`
|
|
330
|
-
marker (`src/cache/shell-cache.ts`). Any middleware short-circuit — a 401, a
|
|
331
|
-
redirect, a 404 — never resumes, so it never carries the marker and passes
|
|
332
|
-
through **untouched**, never composed with a cached shell. Put auth middleware
|
|
333
|
-
upstream of the shell middleware and unauthorized users get their 401/redirect,
|
|
334
|
-
not someone else's cached page.
|
|
335
|
-
|
|
336
|
-
**(b) Identity can't leak into a shared shell.** `cookies()` and `headers()`
|
|
337
|
-
THROW during the background capture render (`assertNotInsideShellCapture`,
|
|
338
|
-
`src/server/cookie-store.ts`), the same guard family as `"use cache"` and
|
|
339
|
-
`cache()`. A shell that reads cookies is PPR-ineligible by construction.
|
|
340
|
-
|
|
341
|
-
**(c) Residual hazard — state it plainly.** Middleware-derived per-user state is
|
|
342
|
-
NOT guarded: a `ctx` variable set by an upstream auth middleware and read by a
|
|
343
|
-
handler WITHOUT `cookies()`/`headers()` is invisible to guard (b). The background
|
|
344
|
-
capture inherits the triggering request's post-middleware context and bakes that
|
|
345
|
-
state into the shared shell. Mitigations, in order of preference:
|
|
346
|
-
|
|
347
|
-
- shell-cache only **public/shared** pages;
|
|
348
|
-
- put all per-user content in **loaders** (the enforced, masked lane);
|
|
349
|
-
- `isEnabled` to disable the middleware for authenticated sessions;
|
|
350
|
-
- `keyGenerator` to add a per-variant dimension (it owns the FULL key identity,
|
|
351
|
-
including host — see Options).
|
|
352
|
-
|
|
353
|
-
Shell content that still varies per request degrades to a hydration repair
|
|
354
|
-
(bounded by TTL/SWR), not corruption — but it is a smell. `cache()` the route so
|
|
355
|
-
the same replayed segments feed the captured shell and every resumed render.
|
|
461
|
+
A captured shell auto-carries the UNION of the non-loader tags recorded during
|
|
462
|
+
the capture render — every `cacheTag(...)` from a `"use cache"` function or
|
|
463
|
+
`cache()` segment that ran as shell material. Loader tags never attach (the
|
|
464
|
+
holes are already live). `ppr.tags` adds operational tags the render cannot
|
|
465
|
+
know (a tenant id, a deploy marker).
|
|
356
466
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
`
|
|
361
|
-
the
|
|
362
|
-
changes (postponed state is build-coupled), so deploys self-heal via recapture.
|
|
363
|
-
|
|
364
|
-
First byte on a HIT does not wait on the shell render or the loader. Hydration
|
|
365
|
-
uses the fresh per-request Flight payload, so interactivity is unaffected.
|
|
366
|
-
|
|
367
|
-
## Partial navigations
|
|
368
|
-
|
|
369
|
-
Soft navigations (`_rsc_partial`) bypass this middleware, and that is by
|
|
370
|
-
design, not a gap: a partial response has no HTML tier — no fizz render to
|
|
371
|
-
skip, which is the entire cost document-PPR eliminates. On a `cache()` route a
|
|
372
|
-
partial navigation already delivers the PPR contract at the data tier:
|
|
373
|
-
replayed cached segments flush immediately (in-memory after the store read),
|
|
374
|
-
loaders run fresh and stream their rows into the same response, and the
|
|
375
|
-
client shows `loading()` fallbacks until they arrive. Shell instantly, live
|
|
376
|
-
holes revived — same semantics, different wire format.
|
|
377
|
-
|
|
378
|
-
For warm-navigation latency, combine `cache()` with prefetching: a prefetched
|
|
379
|
-
partial payload is the client-side analogue of the shell cache, and loaders
|
|
380
|
-
still stream fresh on arrival. Document-PPR covers the cold full-document
|
|
381
|
-
load; prefetch + `cache()` covers navigation.
|
|
382
|
-
|
|
383
|
-
What partial navigations do NOT get is the byte-level shortcut: the worker
|
|
384
|
-
still deserializes stored segments and re-encodes the Flight payload per
|
|
385
|
-
request. Serving a stored Flight byte-prefix and appending fresh loader rows
|
|
386
|
-
would require hand-managed row-ID alignment — React has no Flight-side
|
|
387
|
-
resume (no postponed-state equivalent exists for Flight) — and is a deferred
|
|
388
|
-
optimization, tracked in the design doc's out-of-scope list.
|
|
389
|
-
|
|
390
|
-
## Options
|
|
391
|
-
|
|
392
|
-
| Option | Default | Notes |
|
|
393
|
-
| -------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
394
|
-
| `store` | app-level `_cacheStore` | must implement `getShell`/`putShell`; the capture writes to the SAME store the middleware reads |
|
|
395
|
-
| `ttlSeconds` | `300` | shell freshness window |
|
|
396
|
-
| `swrSeconds` | — | stale window: serve stale + background recapture |
|
|
397
|
-
| `keyGenerator` | `${host}${pathname}${sortedSearch}` | custom keys own the FULL identity — include the host unless the store is provably single-host (multi-tenant shells must never collide) |
|
|
398
|
-
| `isEnabled` | — | per-request opt-out predicate (e.g. disable for authed sessions) |
|
|
399
|
-
| `skipPaths` | `[]` | path-prefix opt-out |
|
|
400
|
-
| `debug` | `false` | HIT/MISS/CAPTURED logging |
|
|
467
|
+
| Lever | Reaches the frozen shell? | Reaches the holes? |
|
|
468
|
+
| --------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------- |
|
|
469
|
+
| `updateTag` / `revalidateTag` on a SHELL tag | YES — drops the shell → MISS → recapture | n/a (holes are already live) |
|
|
470
|
+
| `updateTag` / `revalidateTag` on a LOADER tag | no — loader tags never attach to a shell | drops that loader's cached value (if it `cache()`s) |
|
|
471
|
+
| `revalidate()` (named revalidation contract) | **no** — re-runs segments/loaders for the PAYLOAD, never HTML | yes — the hole re-renders with fresh data |
|
|
401
472
|
|
|
402
473
|
## Pitfalls
|
|
403
474
|
|
|
404
|
-
- **
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
- **
|
|
408
|
-
the
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
475
|
+
- **A bake-lane loader that reads `cookies()`/`headers()`**: the capture is
|
|
476
|
+
REFUSED (deterministic, once-per-key warned) — the route stays on axis 1.
|
|
477
|
+
Move identity into a nested promise or behind `loading()`.
|
|
478
|
+
- **A bake-lane container that must be fresh per document GET**: it is
|
|
479
|
+
snapshot-pinned for the shell's lifetime by design. Use the live lane
|
|
480
|
+
(`loading()`) or a nested promise instead.
|
|
481
|
+
- **A bake-lane loader slower than the capture guard (~5s)**: the capture
|
|
482
|
+
cannot hold for it — eternal MISS with the once-per-key warning.
|
|
483
|
+
- **Per-user value in shell material**: baked into the shared shell —
|
|
484
|
+
deterministically, not by race (handler promises deep-settle at the ring-3
|
|
485
|
+
write on cached chains; awaited/resolved values bake everywhere). Put
|
|
486
|
+
per-user data in a loader.
|
|
487
|
+
- **Theme on a HIT is capture-then-corrected**: the resume tree replays the
|
|
488
|
+
CAPTURE's `initialTheme` (resume requires it to match the frozen prelude);
|
|
489
|
+
the visitor's cookie theme is applied pre-paint by the FOUC script and
|
|
490
|
+
re-synced post-mount by ThemeProvider. Nothing to configure — but a themed
|
|
491
|
+
component in the shell may briefly render the captured theme's markup before
|
|
492
|
+
the post-mount re-sync.
|
|
493
|
+
- **Shell shows CAPTURE-time data for the shell's lifetime**: a `cache()`/`"use
|
|
494
|
+
cache"` value baked into the shell is PINNED at capture (the capture data
|
|
495
|
+
snapshot) and replayed on every HIT, so the shell stays byte-identical to the
|
|
496
|
+
frozen prelude even after that cache entry expires, gets recomputed, or is
|
|
497
|
+
tag-invalidated. This is deliberate — parity beats freshness inside the shell.
|
|
498
|
+
If a shell region needs to be fresh, put it under a hole — `loading()` for
|
|
499
|
+
loader data, or an un-awaited promise under the consumer's `<Suspense>`
|
|
500
|
+
(holes are never pinned) — or make the SHELL itself invalidatable by adding
|
|
501
|
+
the tag to `ppr.tags`. Ring-1/ring-3 tag invalidation does NOT drop the shell.
|
|
502
|
+
- **Uncached nondeterminism in the shell is a hydration hazard**: a raw
|
|
503
|
+
`Date.now()` / `Math.random()` / uncached `fetch` rendered directly in shell
|
|
504
|
+
material (outside any cache ring) drifts between capture and hit and the
|
|
505
|
+
snapshot CANNOT pin it — it was never a cache read. It will mismatch the frozen
|
|
506
|
+
prelude and detonate hydration. Wrap it in `cache()`/`"use cache"` (then it is
|
|
507
|
+
pinned) or move it under a hole (`loading()`, or a pending-promise
|
|
508
|
+
`<Suspense>` region).
|
|
509
|
+
- **Stacking with `/document-cache`**: pick one per route — the document cache
|
|
510
|
+
would cache the composite.
|
|
417
511
|
- **Dev + HMR**: works, but edits produce stale shells until TTL/recapture.
|
|
418
|
-
-
|
|
419
|
-
|
|
512
|
+
- **Dev cold-start cadence**: expect `MISS -> (in-place retry) -> HIT`. A
|
|
513
|
+
refused capture is negatively cached with an exponential window (1s doubling
|
|
514
|
+
to a 60s cap), so declaring `ppr` on an ineligible route never re-renders it
|
|
515
|
+
on every request.
|
|
516
|
+
- **HIT status is committed at the flush**: a failing hole cannot become a
|
|
517
|
+
500/redirect after the first shell byte — error UI renders inline via
|
|
518
|
+
Suspense/error boundaries (the same property any streamed SSR page has after
|
|
519
|
+
its shell flushes).
|
|
420
520
|
|
|
421
521
|
## Related
|
|
422
522
|
|