@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3373 -1176
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +70 -5
- package/skills/router-setup/SKILL.md +65 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- 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 +129 -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 +84 -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/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- 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 +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- 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 +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +131 -30
- package/src/browser/navigation-client.ts +186 -100
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +165 -112
- package/src/browser/prefetch/cache.ts +205 -62
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +295 -119
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +114 -24
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -72
- package/src/browser/types.ts +102 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- 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 +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- 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 +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +143 -69
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +52 -31
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +268 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +170 -276
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +266 -169
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +115 -35
- package/src/router/router-options.ts +172 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +162 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +351 -321
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +157 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +655 -529
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +122 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +131 -14
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +109 -74
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -115
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +29 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +236 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +224 -52
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +401 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -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 +183 -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 +581 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +125 -71
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +214 -132
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +64 -170
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +118 -114
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- 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 +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -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`.
|