@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +426 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2500 -158
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +29 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +177 -35
- package/src/index.ts +255 -71
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +452 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +40 -72
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -50,42 +50,51 @@ export const urlpatterns = urls(({ path, layout, loader, loading }) => [
|
|
|
50
50
|
The `urls()` function provides a callback with all available DSL functions:
|
|
51
51
|
|
|
52
52
|
```typescript
|
|
53
|
-
urls(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
urls(
|
|
54
|
+
({
|
|
55
|
+
path, // Define a route
|
|
56
|
+
layout, // Wrap routes in a layout
|
|
57
|
+
parallel, // Define parallel routes (slots)
|
|
58
|
+
loader, // Add data loader
|
|
59
|
+
loading, // Add loading skeleton
|
|
60
|
+
cache, // Configure caching
|
|
61
|
+
middleware, // Add middleware
|
|
62
|
+
revalidate, // Control revalidation
|
|
63
|
+
intercept, // Intercept routes for modals (conditional via intercept(..., { when }))
|
|
64
|
+
errorBoundary, // Add an error boundary
|
|
65
|
+
notFoundBoundary, // Add a not-found boundary
|
|
66
|
+
transition, // Configure view transitions
|
|
67
|
+
}) => [
|
|
68
|
+
// Route definitions here
|
|
69
|
+
],
|
|
70
|
+
);
|
|
67
71
|
```
|
|
68
72
|
|
|
69
73
|
## Router Options
|
|
70
74
|
|
|
71
75
|
```typescript
|
|
72
|
-
interface
|
|
76
|
+
interface RangoOptions<TEnv> {
|
|
73
77
|
// URL patterns from urls() function
|
|
74
78
|
urls: UrlPatterns;
|
|
75
79
|
|
|
76
80
|
// Document component wrapping entire app
|
|
77
81
|
document?: ComponentType<{ children: ReactNode }>;
|
|
78
82
|
|
|
79
|
-
//
|
|
83
|
+
// URL prefix for sub-path deployments (e.g. "/admin")
|
|
84
|
+
// All routes, reverse(), href(), Link, redirect(), and router.use()
|
|
85
|
+
// patterns are automatically prefixed. Route names stay unprefixed.
|
|
86
|
+
basename?: string;
|
|
87
|
+
|
|
88
|
+
// Enable per-request performance timeline (console waterfall + Server-Timing header)
|
|
80
89
|
debugPerformance?: boolean;
|
|
81
90
|
|
|
82
91
|
// Default error boundary
|
|
83
92
|
defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler;
|
|
84
93
|
|
|
85
|
-
// Default not-found boundary
|
|
94
|
+
// Default not-found boundary for notFound() thrown in handlers/loaders
|
|
86
95
|
defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler;
|
|
87
96
|
|
|
88
|
-
// Component for 404
|
|
97
|
+
// Component for 404 (no route match, or notFound() without a boundary)
|
|
89
98
|
notFound?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
90
99
|
|
|
91
100
|
// Error logging callback
|
|
@@ -97,17 +106,61 @@ interface RSCRouterOptions<TEnv> {
|
|
|
97
106
|
// Theme configuration
|
|
98
107
|
theme?: ThemeConfig | true;
|
|
99
108
|
|
|
109
|
+
// SSR options (streaming policy)
|
|
110
|
+
ssr?: SSROptions<TEnv>;
|
|
111
|
+
|
|
112
|
+
// Telemetry sink for structured lifecycle events
|
|
113
|
+
telemetry?: TelemetrySink;
|
|
114
|
+
|
|
100
115
|
// Connection warmup (default: true)
|
|
101
116
|
warmup?: boolean;
|
|
102
117
|
|
|
118
|
+
// Prefetch cache TTL in seconds (default: 300)
|
|
119
|
+
// Controls in-memory cache duration and Cache-Control max-age for prefetch responses.
|
|
120
|
+
// Set to false to disable prefetch caching.
|
|
121
|
+
prefetchCacheTTL?: number | false;
|
|
122
|
+
|
|
103
123
|
// CSP nonce provider (for router.fetch)
|
|
104
|
-
nonce?: (
|
|
124
|
+
nonce?: (
|
|
125
|
+
request: Request,
|
|
126
|
+
env: TEnv,
|
|
127
|
+
) => string | true | Promise<string | true>;
|
|
105
128
|
|
|
106
129
|
// RSC version string (for router.fetch)
|
|
107
130
|
version?: string;
|
|
108
131
|
}
|
|
109
132
|
```
|
|
110
133
|
|
|
134
|
+
## Basename (Sub-Path Deployment)
|
|
135
|
+
|
|
136
|
+
When your app is served under a sub-path (e.g. `/admin` or `/v2`), set `basename`:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const router = createRouter({
|
|
140
|
+
basename: "/admin",
|
|
141
|
+
document: Document,
|
|
142
|
+
}).routes(({ path, include }) => [
|
|
143
|
+
path("/", Dashboard, { name: "home" }), // matches /admin
|
|
144
|
+
path("/users", Users, { name: "users" }), // matches /admin/users
|
|
145
|
+
include("/api", apiPatterns, { name: "api" }), // matches /admin/api/*
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
router.reverse("home"); // "/admin"
|
|
149
|
+
router.reverse("users"); // "/admin/users"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Router-owned APIs are basename-aware:
|
|
153
|
+
|
|
154
|
+
- `reverse()` returns prefixed paths
|
|
155
|
+
- `<Link to="/users">` renders `<a href="/admin/users">`
|
|
156
|
+
- `redirect("/login")` redirects to `"/admin/login"`
|
|
157
|
+
- `router.use("/users/*", mw)` matches `/admin/users/*`
|
|
158
|
+
- `useRouter().push("/users")` navigates to `/admin/users`
|
|
159
|
+
- Route names stay unprefixed (`"home"`, not `"admin.home"`)
|
|
160
|
+
|
|
161
|
+
Note: `href()` is a raw path helper and does **not** auto-prefix with basename.
|
|
162
|
+
Use `reverse()` or `<Link>` for basename-aware URLs.
|
|
163
|
+
|
|
111
164
|
## Using the Request Handler
|
|
112
165
|
|
|
113
166
|
The router provides a `fetch` method to handle RSC requests:
|
|
@@ -160,7 +213,7 @@ import { createRouter } from "@rangojs/router";
|
|
|
160
213
|
import { Document } from "./document";
|
|
161
214
|
import { urlpatterns } from "./urls";
|
|
162
215
|
|
|
163
|
-
export const router = createRouter<
|
|
216
|
+
export const router = createRouter<AppBindings>({
|
|
164
217
|
document: Document,
|
|
165
218
|
urls: urlpatterns,
|
|
166
219
|
});
|
|
@@ -170,7 +223,7 @@ import { router } from "./router";
|
|
|
170
223
|
|
|
171
224
|
export default {
|
|
172
225
|
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
173
|
-
return router.fetch(request, {
|
|
226
|
+
return router.fetch(request, { env, ctx });
|
|
174
227
|
},
|
|
175
228
|
};
|
|
176
229
|
```
|
|
@@ -184,12 +237,12 @@ For per-request cache configuration (e.g., Cloudflare Workers with ExecutionCont
|
|
|
184
237
|
import { createRouter } from "@rangojs/router";
|
|
185
238
|
import { CFCacheStore } from "@rangojs/router/cache";
|
|
186
239
|
|
|
187
|
-
export const router = createRouter<
|
|
240
|
+
export const router = createRouter<AppBindings>({
|
|
188
241
|
document: Document,
|
|
189
242
|
urls: urlpatterns,
|
|
190
|
-
// Cache config receives env
|
|
191
|
-
cache: (
|
|
192
|
-
store: new CFCacheStore({ ctx:
|
|
243
|
+
// Cache config receives (env, ctx) separately
|
|
244
|
+
cache: (_env, ctx) => ({
|
|
245
|
+
store: new CFCacheStore({ ctx: ctx!, defaults: { ttl: 60 } }),
|
|
193
246
|
}),
|
|
194
247
|
});
|
|
195
248
|
|
|
@@ -198,7 +251,7 @@ import { router } from "./router";
|
|
|
198
251
|
|
|
199
252
|
export default {
|
|
200
253
|
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
|
201
|
-
return router.fetch(request, {
|
|
254
|
+
return router.fetch(request, { env, ctx });
|
|
202
255
|
},
|
|
203
256
|
};
|
|
204
257
|
```
|
|
@@ -274,6 +327,56 @@ const router = createRouter({
|
|
|
274
327
|
export default router;
|
|
275
328
|
```
|
|
276
329
|
|
|
330
|
+
## Not Found Handling
|
|
331
|
+
|
|
332
|
+
Two distinct 404 scenarios:
|
|
333
|
+
|
|
334
|
+
**1. No route matches the URL** — the router renders the `notFound` component from `createRouter()` config. This is automatic.
|
|
335
|
+
|
|
336
|
+
**2. A handler/loader calls `notFound()`** — signals that the route matched but the data doesn't exist (e.g., invalid product ID).
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { notFound } from "@rangojs/router";
|
|
340
|
+
|
|
341
|
+
// In a handler or loader
|
|
342
|
+
path("/product/:slug", async (ctx) => {
|
|
343
|
+
const product = await db.getProduct(ctx.params.slug);
|
|
344
|
+
if (!product) notFound("Product not found");
|
|
345
|
+
return <ProductPage product={product} />;
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Fallback chain for `notFound()`
|
|
350
|
+
|
|
351
|
+
When `notFound()` is thrown, the router looks for a fallback in this order:
|
|
352
|
+
|
|
353
|
+
1. **`notFoundBoundary()`** — nearest boundary in the route tree (route-level)
|
|
354
|
+
2. **`defaultNotFoundBoundary`** — from `createRouter()` config (app-level)
|
|
355
|
+
3. **`notFound`** — from `createRouter()` config (same component used for no-route-match)
|
|
356
|
+
4. **Default `<h1>Not Found</h1>`** — built-in fallback
|
|
357
|
+
|
|
358
|
+
All cases set HTTP 404 status.
|
|
359
|
+
|
|
360
|
+
### notFoundBoundary
|
|
361
|
+
|
|
362
|
+
Wrap routes with `notFoundBoundary()` for route-specific not-found UI:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
urls(({ path, layout }) => [
|
|
366
|
+
layout(ShopLayout, () => [
|
|
367
|
+
notFoundBoundary(({ notFound: info }) => (
|
|
368
|
+
<div>
|
|
369
|
+
<h1>Not Found</h1>
|
|
370
|
+
<p>{info.message}</p>
|
|
371
|
+
</div>
|
|
372
|
+
)),
|
|
373
|
+
path("/product/:slug", ProductPage),
|
|
374
|
+
]),
|
|
375
|
+
]);
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
`notFoundBoundary` receives `{ notFound: NotFoundInfo }` where `NotFoundInfo` contains `message`, `segmentId`, `segmentType`, and `pathname`.
|
|
379
|
+
|
|
277
380
|
## Including Sub-patterns
|
|
278
381
|
|
|
279
382
|
```typescript
|
|
@@ -286,35 +389,53 @@ export const shopPatterns = urls(({ path, layout }) => [
|
|
|
286
389
|
]);
|
|
287
390
|
|
|
288
391
|
// src/urls.tsx
|
|
289
|
-
import { urls
|
|
392
|
+
import { urls } from "@rangojs/router";
|
|
290
393
|
import { shopPatterns } from "./urls/shop";
|
|
291
394
|
|
|
292
|
-
export const urlpatterns = urls(({ path }) => [
|
|
395
|
+
export const urlpatterns = urls(({ path, include }) => [
|
|
293
396
|
path("/", HomePage, { name: "home" }),
|
|
294
397
|
include("/shop", shopPatterns, { name: "shop" }),
|
|
295
398
|
]);
|
|
296
399
|
```
|
|
297
400
|
|
|
298
|
-
|
|
401
|
+
`include()` also accepts an async provider to code-split that group into its own
|
|
402
|
+
chunk, imported on the first request reaching the prefix instead of at startup:
|
|
299
403
|
|
|
300
404
|
```typescript
|
|
301
|
-
|
|
405
|
+
// urls/shop.tsx: `export default shopPatterns`
|
|
406
|
+
include("/shop", () => import("./urls/shop"), { name: "shop" }),
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Build-time discovery still `await`s the provider, so route types, `href()`, and
|
|
410
|
+
prerender see every route in the split group. Reach for it when a group is a
|
|
411
|
+
large, independently-loadable unit — see `/composability`.
|
|
302
412
|
|
|
413
|
+
## Environment Types
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Bindings passed as TEnv to createRouter<TEnv>()
|
|
303
417
|
interface AppBindings {
|
|
304
418
|
DB: D1Database;
|
|
305
419
|
KV: KVNamespace;
|
|
306
420
|
}
|
|
307
421
|
|
|
422
|
+
// Variables declared via global namespace augmentation
|
|
308
423
|
interface AppVariables {
|
|
309
424
|
user?: { id: string; name: string };
|
|
310
425
|
}
|
|
311
426
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const router = createRouter<AppEnv>({
|
|
427
|
+
const router = createRouter<AppBindings>({
|
|
315
428
|
document: Document,
|
|
316
429
|
urls: urlpatterns,
|
|
317
430
|
});
|
|
431
|
+
|
|
432
|
+
// Register types globally for implicit typing
|
|
433
|
+
declare global {
|
|
434
|
+
namespace Rango {
|
|
435
|
+
interface Env extends AppBindings {}
|
|
436
|
+
interface Vars extends AppVariables {}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
318
439
|
```
|
|
319
440
|
|
|
320
441
|
## Connection Warmup
|
|
@@ -344,3 +465,95 @@ const router = createRouter({
|
|
|
344
465
|
|
|
345
466
|
The warmup request is relative to the current page path, so it works correctly
|
|
346
467
|
with subpath deployments (reverse proxy, base path).
|
|
468
|
+
|
|
469
|
+
## Telemetry
|
|
470
|
+
|
|
471
|
+
The router emits structured lifecycle events through a pluggable telemetry sink.
|
|
472
|
+
Zero overhead when not configured.
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// Console sink for development
|
|
476
|
+
import { createRouter, createConsoleSink } from "@rangojs/router";
|
|
477
|
+
|
|
478
|
+
const router = createRouter({
|
|
479
|
+
document: Document,
|
|
480
|
+
urls: urlpatterns,
|
|
481
|
+
telemetry: createConsoleSink(),
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// OpenTelemetry for production: phase spans via the tracing slot,
|
|
487
|
+
// discrete-fact spans via the telemetry sink.
|
|
488
|
+
import {
|
|
489
|
+
createRouter,
|
|
490
|
+
createOTelTracing,
|
|
491
|
+
createOTelSink,
|
|
492
|
+
} from "@rangojs/router";
|
|
493
|
+
import { trace } from "@opentelemetry/api";
|
|
494
|
+
|
|
495
|
+
const tracer = trace.getTracer("my-app");
|
|
496
|
+
|
|
497
|
+
const router = createRouter({
|
|
498
|
+
document: Document,
|
|
499
|
+
urls: urlpatterns,
|
|
500
|
+
tracing: createOTelTracing(tracer),
|
|
501
|
+
telemetry: createOTelSink(tracer),
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
// On Cloudflare Workers, swap the tracing factory for native custom spans
|
|
507
|
+
// (no @opentelemetry/api dependency); the telemetry slot is unchanged.
|
|
508
|
+
// On Vercel (Node runtime) use createVercelTracing() from @rangojs/router/vercel.
|
|
509
|
+
import { createCloudflareTracing } from "@rangojs/router/cloudflare";
|
|
510
|
+
|
|
511
|
+
const router = createRouter({
|
|
512
|
+
document: Document,
|
|
513
|
+
urls: urlpatterns,
|
|
514
|
+
tracing: createCloudflareTracing(), // { spans: { ssr: false } } to toggle phases
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// Custom sink
|
|
520
|
+
const router = createRouter({
|
|
521
|
+
telemetry: {
|
|
522
|
+
emit(event) {
|
|
523
|
+
// Send to any observability backend
|
|
524
|
+
myTracer.record(event);
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Events emitted: `request.start/end/error`, `loader.start/end/error`,
|
|
531
|
+
`handler.error`, `cache.decision`, `revalidation.decision`.
|
|
532
|
+
|
|
533
|
+
## SSR Streaming Policy
|
|
534
|
+
|
|
535
|
+
Control whether HTML SSR responses stream progressively or wait for all content:
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
import { createRouter, type SSRStreamMode } from "@rangojs/router";
|
|
539
|
+
|
|
540
|
+
const router = createRouter({
|
|
541
|
+
ssr: {
|
|
542
|
+
resolveStreaming: ({ request }) => {
|
|
543
|
+
const ua = request.headers.get("user-agent") ?? "";
|
|
544
|
+
// Bots that can't process streamed HTML get a fully resolved page
|
|
545
|
+
if (/Googlebot|bingbot/i.test(ua)) return "allReady";
|
|
546
|
+
return "stream";
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
`SSRStreamMode` is `"stream" | "allReady"`:
|
|
553
|
+
|
|
554
|
+
- `"stream"` (default) — flush HTML as React renders. Suspense fallbacks appear first, then resolved content streams in. Best for real users (fastest TTFB).
|
|
555
|
+
- `"allReady"` — await `stream.allReady` before flushing. The full page arrives in one shot. Use for bots that cannot execute JavaScript or process chunked HTML.
|
|
556
|
+
|
|
557
|
+
The resolver receives `{ request, env, url }` and may be sync or async. It only runs on HTML SSR paths — RSC partials, `__rsc` requests, and response routes are unaffected.
|
|
558
|
+
|
|
559
|
+
When `resolveStreaming` is not configured, the default is `"stream"`.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scripts
|
|
3
|
+
description: Inject third-party scripts (GTM, analytics, widgets) into the document head/body via the Script handle
|
|
4
|
+
argument-hint: "[vendor]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Scripts
|
|
8
|
+
|
|
9
|
+
Inject `<script>` tags into the document the idiomatic Rango way: push a config
|
|
10
|
+
from a **server** route/layout handler with `ctx.use(Script)(config)`, and render
|
|
11
|
+
them with the built-in **`<Scripts />`** component (the `Meta` / `<MetaTags>`
|
|
12
|
+
pair, but for scripts). The request CSP **nonce is applied automatically to
|
|
13
|
+
document-rendered scripts** — you never read or pass it. (The one exception is an
|
|
14
|
+
async script first encountered on a soft navigation; see the nonce caveat under
|
|
15
|
+
"Execution contract".)
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
`<Scripts />` is a client component; place it in your Document (which is
|
|
20
|
+
`"use client"`). The default Document already includes both sites; a custom one
|
|
21
|
+
adds them next to `<MetaTags />`:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// document.tsx ("use client")
|
|
25
|
+
import { MetaTags, Scripts } from "@rangojs/router/client";
|
|
26
|
+
|
|
27
|
+
export function Document({ children }) {
|
|
28
|
+
return (
|
|
29
|
+
<html lang="en" suppressHydrationWarning>
|
|
30
|
+
<head>
|
|
31
|
+
<MetaTags />
|
|
32
|
+
<Scripts /> {/* renders position: "head" scripts (the default) */}
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<Scripts position="body" /> {/* renders position: "body" scripts */}
|
|
36
|
+
{children}
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Push from a handler
|
|
44
|
+
|
|
45
|
+
`ScriptConfig` is a discriminated union — exactly one of three shapes, so invalid
|
|
46
|
+
combinations are compile errors:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Script } from "@rangojs/router";
|
|
50
|
+
|
|
51
|
+
// 1. External ASYNC — a React resource. Loads once when first encountered,
|
|
52
|
+
// including after a soft navigation, deduped by src. The fire-and-forget case.
|
|
53
|
+
ctx.use(Script)({ id: "stripe", src: "https://js.stripe.com/v3", async: true });
|
|
54
|
+
|
|
55
|
+
// 2. External ORDERED — in-place, optional `defer`. Document-load (see below).
|
|
56
|
+
ctx.use(Script)({
|
|
57
|
+
id: "plausible",
|
|
58
|
+
src: "https://plausible.io/js/script.js",
|
|
59
|
+
defer: true,
|
|
60
|
+
attributes: { "data-domain": "example.com" },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 3. INLINE — `id` REQUIRED, raw JS body (escaped against </script> by <Scripts>).
|
|
64
|
+
// For GTM/GA4/Segment let the body self-inject its loader (see below).
|
|
65
|
+
ctx.use(Script)({ id: "gtm", children: gtmBootstrap("GTM-XXXX") });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Shape | Required | Optional | Forbidden |
|
|
69
|
+
| ---------------- | -------------------- | ----------------------------------------------- | ----------------------- |
|
|
70
|
+
| Inline | `id`, `children` | `position`, `type`, `attributes` | `src`, `async`, `defer` |
|
|
71
|
+
| External async | `src`, `async: true` | `id`, `position`, `type`, `attributes` | `children`, `defer` |
|
|
72
|
+
| External ordered | `src` | `defer`, `id`, `position`, `type`, `attributes` | `children`, `async` |
|
|
73
|
+
|
|
74
|
+
- `id` — dedup key (last-push-wins), and rendered as the script's DOM `id` (for
|
|
75
|
+
vendors that target `<script id="…">`). Required for inline (React never dedups
|
|
76
|
+
inline scripts); for ordered external it falls back to `src`. Async externals
|
|
77
|
+
dedup by `src` (matching React), so there `id` is the DOM id only.
|
|
78
|
+
- `position` — `"head"` (default) or `"body"`. An async script is hoisted to
|
|
79
|
+
`<head>` by React regardless.
|
|
80
|
+
- `type` — free string: `"module"`, `"application/ld+json"`, `"text/partytown"`, …
|
|
81
|
+
- `attributes` — React-cased (`crossOrigin`, not `crossorigin`) and React-typed
|
|
82
|
+
(`data-*`, `integrity`, `referrerPolicy`, …). Excluded: the fields the handle
|
|
83
|
+
manages (`id`/`src`/`async`/`defer`/`type`/`children`/`nonce`) and all `on*`
|
|
84
|
+
handlers (`onLoad`/`onError`/… — a config is serialized to the client, so a
|
|
85
|
+
function can't survive; use a `"use client"` component for callbacks).
|
|
86
|
+
|
|
87
|
+
## Execution contract (read this)
|
|
88
|
+
|
|
89
|
+
React makes a `<script>` it mounts on the client INERT (it creates the element via
|
|
90
|
+
innerHTML, which the HTML spec never executes). So:
|
|
91
|
+
|
|
92
|
+
| Script | Runs on hard load | Runs on soft (`<Link>`) navigation |
|
|
93
|
+
| -------------------------------- | ------------------------------ | ----------------------------------------------------- |
|
|
94
|
+
| Inline (`children`) | Yes (it's in the initial HTML) | **No** — it is document-load only |
|
|
95
|
+
| External ordered (`defer`/plain) | Yes | **No** — document-load only |
|
|
96
|
+
| External `async` | Yes | **Yes** — React loads the resource on first encounter |
|
|
97
|
+
|
|
98
|
+
`<Scripts>` enforces this honestly: after hydration it **freezes** the inline +
|
|
99
|
+
ordered set to what was in the initial HTML, so a navigation never inserts an
|
|
100
|
+
inert (silently dead) `<script>`. Async configs stay reactive. Reusing an `id`
|
|
101
|
+
shapes the INITIAL document output (last-push-wins) — it does not re-run a script
|
|
102
|
+
during navigation.
|
|
103
|
+
|
|
104
|
+
**Nonce caveat for soft-nav async.** The "nonce is applied automatically" claim
|
|
105
|
+
holds for DOCUMENT-RENDERED scripts (they carry the nonce in the SSR HTML). An
|
|
106
|
+
async script first encountered on a soft navigation is injected by React on the
|
|
107
|
+
client, where `useNonce()` is `undefined` by design (the router does not serialize
|
|
108
|
+
the nonce to the client — that would weaken CSP), so it has no nonce attribute. It
|
|
109
|
+
still loads under `'strict-dynamic'` (React's nonced runtime injects it, so the
|
|
110
|
+
trust propagates) — which is the recommended policy — or if your `script-src`
|
|
111
|
+
allows the host. A nonce-only policy without `'strict-dynamic'` would block it.
|
|
112
|
+
|
|
113
|
+
**Per-navigation behavior belongs in a client component or hook**, not in a
|
|
114
|
+
re-pushed inline script. The GTM demo does exactly this: a root-layout `Script`
|
|
115
|
+
bootstrap fires the first page_view on document load, and a `"use client"`
|
|
116
|
+
`<GtmPageViews>` component fires a page_view on every subsequent soft navigation.
|
|
117
|
+
|
|
118
|
+
## The inline-self-inject rule (GTM/GA4/Segment)
|
|
119
|
+
|
|
120
|
+
If an inline bootstrap must run **before** an external loader, do NOT push the
|
|
121
|
+
loader as a separate `{ src, async }` config: React 19 hoists a declarative
|
|
122
|
+
`<script async src>` to the **top** of `<head>`, above your inline bootstrap, so
|
|
123
|
+
the loader could run before the bootstrap. Instead let the bootstrap inject its
|
|
124
|
+
own loader (Google's snippet does exactly this):
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
function gtmBootstrap(id: string): string {
|
|
128
|
+
return [
|
|
129
|
+
"window.dataLayer=window.dataLayer||[];",
|
|
130
|
+
'window.dataLayer.push({"gtm.start":new Date().getTime(),event:"gtm.js"});',
|
|
131
|
+
`(function(d,s,i){var j=d.createElement(s);j.async=true;j.src="https://www.googletagmanager.com/gtm.js?id="+encodeURIComponent(i);var f=d.getElementsByTagName(s)[0];f.parentNode.insertBefore(j,f);})(document,"script",${JSON.stringify(id)});`,
|
|
132
|
+
].join("");
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Under a `'strict-dynamic'` CSP the nonced inline script vouches for the loader it
|
|
137
|
+
creates, so the injected loader needs no nonce of its own.
|
|
138
|
+
|
|
139
|
+
### Per-route tagging on the first render
|
|
140
|
+
|
|
141
|
+
A route can **override** a layout's bootstrap by reusing the `id`, baking
|
|
142
|
+
per-route data into the FIRST (hard-load) page_view server-side — the Script
|
|
143
|
+
handle is collected after handlers run (parent → child, last-wins):
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// root layout: generic bootstrap
|
|
147
|
+
ctx.use(Script)({ id: "gtm", children: gtmBootstrap("GTM-XXXX") });
|
|
148
|
+
// a route: same id, with content_group baked in
|
|
149
|
+
ctx.use(Script)({
|
|
150
|
+
id: "gtm",
|
|
151
|
+
children: gtmBootstrapWith({ content_group: "blog" }),
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## CSP
|
|
156
|
+
|
|
157
|
+
The nonce is automatic for document-rendered scripts. Include `'strict-dynamic'`
|
|
158
|
+
in `script-src` (recommended): besides letting a nonced loader vouch for the
|
|
159
|
+
scripts it injects, it also covers the one nonce-less case — an async script first
|
|
160
|
+
loaded on a soft navigation is injected client-side without a nonce (see the
|
|
161
|
+
caveat above), and `'strict-dynamic'` trusts it via React's nonced runtime.
|
|
162
|
+
Otherwise allow the vendor hosts. For GTM/GA4 (Google's wildcards): `script-src
|
|
163
|
+
'self' 'nonce-…' 'strict-dynamic' https://*.googletagmanager.com`, plus `img-src`
|
|
164
|
+
/ `connect-src` for `*.google-analytics.com` / `*.analytics.google.com`, and
|
|
165
|
+
`frame-src https://*.googletagmanager.com` for the GTM `<noscript>` iframe. See
|
|
166
|
+
[Google's CSP guide](https://developers.google.com/tag-platform/security/guides/csp).
|
|
167
|
+
|
|
168
|
+
## Not covered (do it yourself)
|
|
169
|
+
|
|
170
|
+
- **`onLoad` / `onReady` / `onError`** — callbacks can't cross the server handle
|
|
171
|
+
boundary. Render your own `"use client"` component with a load listener keyed
|
|
172
|
+
off the script id.
|
|
173
|
+
- **`<noscript>` fallbacks** (e.g. the GTM body iframe) — not a `<script>`;
|
|
174
|
+
render it directly in your Document `<body>`.
|
|
175
|
+
- **Partytown / web-worker offloading** — push the worker config with
|
|
176
|
+
`type: "text/partytown"` and wire Partytown's own nonce config manually.
|
|
177
|
+
|
|
178
|
+
A full GTM + GA4-style integration (page_view on first render + soft nav, nonce,
|
|
179
|
+
ecommerce events) lives in `tests/vite-rsc-demo`.
|