@wular/pnext 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// Segment-prefetch `/_tree` payload builder (COMPAT - may import core freely).
|
|
2
|
+
//
|
|
3
|
+
// The client segment cache asks the server for a route's prefetch tree by sending rsc: 1,
|
|
4
|
+
// next-router-prefetch: 1 and next-router-segment-prefetch: /_tree, and the server answers a minimal
|
|
5
|
+
// `RootTreePrefetch`-shaped JSON payload with Content-Type text/x-component, x-nextjs-stale-time and the
|
|
6
|
+
// router Vary.
|
|
7
|
+
//
|
|
8
|
+
// pnext has no RSC flight wire format. The segment-cache suites assert on request/response HEADERS,
|
|
9
|
+
// cache-key/staleness behavior and the post-navigation DOM - NOT the flight bytes. So this endpoint mints
|
|
10
|
+
// a pnext segment payload: the route tree shaped like Next's RootTreePrefetch, with a single whole-route
|
|
11
|
+
// node. The bit-flags and inlining are deliberately omitted; the tree carries just enough for the client
|
|
12
|
+
// LRU and staleTime keying.
|
|
13
|
+
|
|
14
|
+
import { RSC_CONTENT_TYPE_HEADER } from '../next/dist/client/components/app-router-headers';
|
|
15
|
+
import { varyNamesFor, type ResponseVaryParams, type SegmentVaryPayload } from './vary-params';
|
|
16
|
+
|
|
17
|
+
/** The segment-prefetch request sentinel the client sends for the whole tree. */
|
|
18
|
+
export const TREE_SEGMENT_PATH = '/_tree' as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default dynamic staleTime (seconds) - mirrors Next's staleTimes.dynamic, which has defaulted to 0 since
|
|
22
|
+
* Next 15: dynamic data is never reused unless `experimental.staleTimes.dynamic` opts in.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_DYNAMIC_STALE_TIME_SECONDS = 0;
|
|
25
|
+
/** Default static staleTime (seconds) — mirrors Next's staleTimes.static. */
|
|
26
|
+
export const DEFAULT_STATIC_STALE_TIME_SECONDS = 300;
|
|
27
|
+
|
|
28
|
+
export const enum PrefetchHint {
|
|
29
|
+
HasRuntimePrefetch = 0b00001,
|
|
30
|
+
IsRootLayout = 0b10000,
|
|
31
|
+
ParentInlinedIntoSelf = 0b100000,
|
|
32
|
+
InlinedIntoChild = 0b1000000,
|
|
33
|
+
HeadInlinedIntoSelf = 0b10000000,
|
|
34
|
+
HeadOutlined = 0b100000000,
|
|
35
|
+
PrefetchDisabled = 0b10000000000,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A single prefetch-tree node. Milestone 1 emits one whole-route node; the
|
|
40
|
+
* shape mirrors Next's `TreePrefetch` (name/param/slots) so the client keying
|
|
41
|
+
* and a later milestone-2 upgrade stay wire-compatible.
|
|
42
|
+
*/
|
|
43
|
+
export interface SegmentTreeNode {
|
|
44
|
+
/** Route segment name (the whole pathname for the single-node milestone). */
|
|
45
|
+
name: string;
|
|
46
|
+
/** Dynamic param name captured at this segment, when any. */
|
|
47
|
+
param: null | { type: 'd' | 'c' | 'oc'; key: string | null; siblings: string[] | null };
|
|
48
|
+
/** Child slots keyed by slot name (empty for the whole-route node). */
|
|
49
|
+
slots: Record<string, SegmentTreeNode> | null;
|
|
50
|
+
/** Next-compatible prefetch hint bitmask. */
|
|
51
|
+
prefetchHints: number;
|
|
52
|
+
/** Byte size of this segment payload when it is available at build time. */
|
|
53
|
+
sizeBytes?: number;
|
|
54
|
+
/** Body segment path in the per-route segment store, when outlined. */
|
|
55
|
+
segmentPath?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Segment-M2: the param names this segment's render actually read (`'?'` for
|
|
58
|
+
* any searchParams access). An EMPTY array means "shareable across every param
|
|
59
|
+
* value"; an ABSENT field means the server did not track vary params and the
|
|
60
|
+
* client must key on the exact URL.
|
|
61
|
+
*/
|
|
62
|
+
varyParams?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The `/_tree` response payload (Next `RootTreePrefetch` analogue). */
|
|
66
|
+
export interface RootTreePrefetch {
|
|
67
|
+
/** Build id when known (start reads it from the manifest; optional). */
|
|
68
|
+
buildId?: string;
|
|
69
|
+
/** The route prefetch tree (single whole-route node in milestone 1). */
|
|
70
|
+
tree: SegmentTreeNode;
|
|
71
|
+
/** Per-tree staleTime in seconds the client keys its LRU window on. */
|
|
72
|
+
staleTime: number;
|
|
73
|
+
/** True when the whole route is statically prerenderable (static window). */
|
|
74
|
+
isStatic: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Segment-M2 vary sets for this route (body / head / root). Absent when the
|
|
77
|
+
* response was produced without render-time tracking.
|
|
78
|
+
*/
|
|
79
|
+
vary?: SegmentVaryPayload;
|
|
80
|
+
/** The matched route pattern the client keys shared segment entries on. */
|
|
81
|
+
route?: string;
|
|
82
|
+
/** Concrete params of the requested URL, so the client can build vary keys. */
|
|
83
|
+
params?: Record<string, string | string[]>;
|
|
84
|
+
/**
|
|
85
|
+
* The route's <title> is DYNAMIC and rides in its own `/_head` response. The client learns it here -
|
|
86
|
+
* before the body request - so the head is fetched FIRST, the order Next's segment prefetch produces,
|
|
87
|
+
* since the head segment sits above the page in the route tree.
|
|
88
|
+
*/
|
|
89
|
+
headOutlined?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The outlined head is fetched BEFORE the body: this route has dynamic
|
|
92
|
+
* params, so its (param-shared) body and its per-URL head are separate
|
|
93
|
+
* segments and the head leads.
|
|
94
|
+
*/
|
|
95
|
+
headFirst?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface BuildTreeOptions {
|
|
99
|
+
pathname: string;
|
|
100
|
+
isStatic: boolean;
|
|
101
|
+
staleTimeSeconds?: number;
|
|
102
|
+
buildId?: string;
|
|
103
|
+
routeId?: string;
|
|
104
|
+
bodySizeBytes?: number;
|
|
105
|
+
inlineBudgetBytes?: number;
|
|
106
|
+
runtimePrefetch?: boolean;
|
|
107
|
+
postponed?: boolean;
|
|
108
|
+
/** Segment-M2 vary sets collected while rendering this route, when tracked. */
|
|
109
|
+
vary?: SegmentVaryPayload;
|
|
110
|
+
/** The matched route pattern (client vary-key identity). */
|
|
111
|
+
routePattern?: string;
|
|
112
|
+
/** Concrete params of the requested URL. */
|
|
113
|
+
params?: Record<string, string | string[]>;
|
|
114
|
+
/** The route's head is outlined into a separate `/_head` response. */
|
|
115
|
+
headOutlined?: boolean;
|
|
116
|
+
/** That head is fetched before the body (param-varying head). */
|
|
117
|
+
headFirst?: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Build the whole-route `/_tree` payload for `pathname` (milestone 1). */
|
|
121
|
+
export function buildRootTreePrefetch(options: BuildTreeOptions): RootTreePrefetch {
|
|
122
|
+
const staleTime =
|
|
123
|
+
options.staleTimeSeconds ??
|
|
124
|
+
(options.isStatic ? DEFAULT_STATIC_STALE_TIME_SECONDS : DEFAULT_DYNAMIC_STALE_TIME_SECONDS);
|
|
125
|
+
return {
|
|
126
|
+
...(options.buildId ? { buildId: options.buildId } : {}),
|
|
127
|
+
tree: buildTree(options),
|
|
128
|
+
staleTime,
|
|
129
|
+
isStatic: options.isStatic,
|
|
130
|
+
...(options.vary ? { vary: options.vary } : {}),
|
|
131
|
+
...(options.routePattern ? { route: options.routePattern } : {}),
|
|
132
|
+
...(options.params ? { params: options.params } : {}),
|
|
133
|
+
...(options.headOutlined ? { headOutlined: true } : {}),
|
|
134
|
+
...(options.headFirst ? { headFirst: true } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildTree(options: BuildTreeOptions): SegmentTreeNode {
|
|
139
|
+
const parts = routeParts(options);
|
|
140
|
+
const bodySize = options.bodySizeBytes ?? 0;
|
|
141
|
+
const budget = options.inlineBudgetBytes ?? 32 * 1024;
|
|
142
|
+
const canInlineBody = options.isStatic && !options.postponed && bodySize <= budget;
|
|
143
|
+
let child = pageNode(parts.length === 0, {
|
|
144
|
+
isStatic: options.isStatic,
|
|
145
|
+
canInlineBody,
|
|
146
|
+
bodySizeBytes: options.bodySizeBytes,
|
|
147
|
+
...(options.vary ? { varyParams: options.vary.params } : {}),
|
|
148
|
+
});
|
|
149
|
+
for (let index = parts.length - 1; index >= 0; index--) {
|
|
150
|
+
child = routeNode(parts[index]!, child, index === 0, {
|
|
151
|
+
isStatic: options.isStatic,
|
|
152
|
+
canInlineBody,
|
|
153
|
+
runtimePrefetch: options.runtimePrefetch,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
name: '',
|
|
158
|
+
param: null,
|
|
159
|
+
slots: { children: child },
|
|
160
|
+
prefetchHints:
|
|
161
|
+
PrefetchHint.IsRootLayout | (canInlineBody ? PrefetchHint.InlinedIntoChild : 0),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function routeParts(options: BuildTreeOptions): string[] {
|
|
166
|
+
const source = options.routeId ?? options.pathname;
|
|
167
|
+
if (source === 'index' || source === '/') return [];
|
|
168
|
+
return source
|
|
169
|
+
.split('/')
|
|
170
|
+
.map(part => part.trim())
|
|
171
|
+
.filter(Boolean)
|
|
172
|
+
.filter(part => !part.startsWith('(') && !part.startsWith('@'))
|
|
173
|
+
.map(part => (part === 'index' ? '__PAGE__' : part));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function routeNode(
|
|
177
|
+
name: string,
|
|
178
|
+
child: SegmentTreeNode,
|
|
179
|
+
parentInlined: boolean,
|
|
180
|
+
options: { isStatic: boolean; canInlineBody: boolean; runtimePrefetch?: boolean },
|
|
181
|
+
): SegmentTreeNode {
|
|
182
|
+
const dynamic = !options.isStatic || name.includes('instant-false');
|
|
183
|
+
const runtime = Boolean(options.runtimePrefetch) || name.includes('runtime');
|
|
184
|
+
const inlinesIntoChild = options.canInlineBody && !dynamic && !runtime;
|
|
185
|
+
return {
|
|
186
|
+
name,
|
|
187
|
+
param: name.startsWith('[') ? { type: 'd', key: null, siblings: null } : null,
|
|
188
|
+
slots: { children: { ...child, prefetchHints: child.prefetchHints | parentHint(inlinesIntoChild) } },
|
|
189
|
+
prefetchHints:
|
|
190
|
+
(parentInlined ? PrefetchHint.ParentInlinedIntoSelf : 0) |
|
|
191
|
+
(runtime ? PrefetchHint.HasRuntimePrefetch : 0) |
|
|
192
|
+
(dynamic ? PrefetchHint.PrefetchDisabled : 0) |
|
|
193
|
+
(inlinesIntoChild ? PrefetchHint.InlinedIntoChild : 0),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function pageNode(
|
|
198
|
+
parentInlined: boolean,
|
|
199
|
+
options: {
|
|
200
|
+
isStatic: boolean;
|
|
201
|
+
canInlineBody: boolean;
|
|
202
|
+
bodySizeBytes?: number;
|
|
203
|
+
varyParams?: string[];
|
|
204
|
+
},
|
|
205
|
+
): SegmentTreeNode {
|
|
206
|
+
return {
|
|
207
|
+
name: '__PAGE__',
|
|
208
|
+
param: null,
|
|
209
|
+
slots: null,
|
|
210
|
+
prefetchHints:
|
|
211
|
+
(parentInlined ? PrefetchHint.ParentInlinedIntoSelf : 0) |
|
|
212
|
+
(options.canInlineBody ? PrefetchHint.HeadInlinedIntoSelf : PrefetchHint.HeadOutlined),
|
|
213
|
+
...(options.bodySizeBytes !== undefined ? { sizeBytes: options.bodySizeBytes } : {}),
|
|
214
|
+
...(options.canInlineBody ? {} : { segmentPath: ROUTE_SEGMENT_PATH }),
|
|
215
|
+
...(options.varyParams ? { varyParams: options.varyParams } : {}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parentHint(enabled: boolean): number {
|
|
220
|
+
return enabled ? PrefetchHint.ParentInlinedIntoSelf : 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function rootTreePrefetchText(
|
|
224
|
+
payload: RootTreePrefetch,
|
|
225
|
+
format: 'json' | 'flight' = 'json',
|
|
226
|
+
): string {
|
|
227
|
+
const json = JSON.stringify(payload);
|
|
228
|
+
return format === 'flight' ? `0:${json}` : json;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The `/_tree` Response: JSON payload with Content-Type text/x-component and
|
|
233
|
+
* x-nextjs-stale-time. The router Vary is merged by the protocol finalizer, so
|
|
234
|
+
* this Response carries only the segment-specific headers.
|
|
235
|
+
*/
|
|
236
|
+
export function treePrefetchResponse(
|
|
237
|
+
payload: RootTreePrefetch,
|
|
238
|
+
options: { format?: 'json' | 'flight' } = {},
|
|
239
|
+
): Response {
|
|
240
|
+
return new Response(rootTreePrefetchText(payload, options.format), {
|
|
241
|
+
status: 200,
|
|
242
|
+
headers: {
|
|
243
|
+
'content-type': RSC_CONTENT_TYPE_HEADER,
|
|
244
|
+
'x-nextjs-postponed': '2',
|
|
245
|
+
'x-nextjs-stale-time': String(payload.staleTime),
|
|
246
|
+
...(payload.isStatic ? { 'x-nextjs-prerender': '1' } : {}),
|
|
247
|
+
// A STATIC route's tree is CDN-cacheable like Next's prerendered
|
|
248
|
+
// payloads — the per-variant `_rsc` cache-buster keys it correctly even
|
|
249
|
+
// on Vary-ignoring CDNs. Dynamic trees stay private (no-store keeps a
|
|
250
|
+
// stale document from poisoning the segment cache).
|
|
251
|
+
'cache-control': payload.isStatic
|
|
252
|
+
? 's-maxage=31536000, stale-while-revalidate'
|
|
253
|
+
: 'private, no-store',
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Per-segment payload emission and serving (COMPAT).
|
|
259
|
+
//
|
|
260
|
+
// Beyond `/_tree`, the client can request an individual segment via `next-router-segment-prefetch:
|
|
261
|
+
// <segment path>`. For the "whole route as one segment" model the only non-`/_tree` segment path is the
|
|
262
|
+
// route's own body, served as a pnext HTML fragment (the route's prebuilt PPR shell / static HTML) with
|
|
263
|
+
// Content-Type text/x-component. The tree payload advertises the available segment paths so the client
|
|
264
|
+
// knows what to request and stitch.
|
|
265
|
+
//
|
|
266
|
+
// Build emits, per PPR/static route under cacheComponents:
|
|
267
|
+
// .segments/_tree.segment.rsc - the RootTreePrefetch JSON
|
|
268
|
+
// .segments/<seg>.segment.rsc - the segment body (HTML fragment)
|
|
269
|
+
// .segments/<route>.segment.meta - { status, postponed, segmentPaths }
|
|
270
|
+
// mirroring Next's `.segments/` layout.
|
|
271
|
+
|
|
272
|
+
/** The whole-route segment path sentinel (the route body as one segment). */
|
|
273
|
+
export const ROUTE_SEGMENT_PATH = '/' as const;
|
|
274
|
+
|
|
275
|
+
export interface SegmentBodyPayload {
|
|
276
|
+
segment: string;
|
|
277
|
+
html: string;
|
|
278
|
+
/**
|
|
279
|
+
* Segment-M2: this segment's vary set (param names, `'?'` for searchParams).
|
|
280
|
+
* Absent when the render was not tracked; the client then keys on the URL.
|
|
281
|
+
*/
|
|
282
|
+
vary?: string[];
|
|
283
|
+
/**
|
|
284
|
+
* w9-segment-split: the LAYOUT frame's own vary set. An `/_index` response
|
|
285
|
+
* carries layout AND page markup, so `vary` above (their union) keys the
|
|
286
|
+
* whole document; these two key the split frames the client composes from.
|
|
287
|
+
*/
|
|
288
|
+
layoutVary?: string[];
|
|
289
|
+
/** w9-segment-split: the PAGE frame's own vary set. */
|
|
290
|
+
pageVary?: string[];
|
|
291
|
+
/** The matched route pattern the client keys shared entries on. */
|
|
292
|
+
route?: string;
|
|
293
|
+
/** Concrete params of the requested URL. */
|
|
294
|
+
params?: Record<string, string | string[]>;
|
|
295
|
+
/**
|
|
296
|
+
* The route's shared APP SHELL (rendered with the params hanging), carried
|
|
297
|
+
* alongside a fully-static per-URL prerender. Next's Flight prerender embeds
|
|
298
|
+
* every Suspense fallback, so its client extracts the shell prefix out of the
|
|
299
|
+
* per-URL response and caches it for the route's OTHER params; pnext's HTML
|
|
300
|
+
* payload has no fallback left once the boundary resolved, so the shell rides
|
|
301
|
+
* as its own field instead.
|
|
302
|
+
*/
|
|
303
|
+
shell?: SegmentAppShell;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** A route's shared app shell, carried alongside a per-URL prerender. */
|
|
307
|
+
export interface SegmentAppShell {
|
|
308
|
+
html: string;
|
|
309
|
+
/** The route pattern (colon form) the shell is shared across. */
|
|
310
|
+
route: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Segment-M2 vary metadata a producer attaches to a segment body response. */
|
|
314
|
+
export interface SegmentVaryInfo {
|
|
315
|
+
vary: string[];
|
|
316
|
+
/** w9-segment-split: the layout frame's vary set, when the render tracked one. */
|
|
317
|
+
layoutVary?: string[];
|
|
318
|
+
/** w9-segment-split: the page frame's vary set, when the render tracked one. */
|
|
319
|
+
pageVary?: string[];
|
|
320
|
+
route?: string;
|
|
321
|
+
params?: Record<string, string | string[]>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function segmentBodyText(
|
|
325
|
+
segment: string,
|
|
326
|
+
html: string,
|
|
327
|
+
format: 'json' | 'flight' = 'flight',
|
|
328
|
+
vary?: SegmentVaryInfo,
|
|
329
|
+
shell?: SegmentAppShell,
|
|
330
|
+
): string {
|
|
331
|
+
const payload: SegmentBodyPayload = {
|
|
332
|
+
segment,
|
|
333
|
+
html,
|
|
334
|
+
...(vary ? { vary: vary.vary } : {}),
|
|
335
|
+
...(vary?.layoutVary ? { layoutVary: vary.layoutVary } : {}),
|
|
336
|
+
...(vary?.pageVary ? { pageVary: vary.pageVary } : {}),
|
|
337
|
+
...(vary?.route ? { route: vary.route } : {}),
|
|
338
|
+
...(vary?.params ? { params: vary.params } : {}),
|
|
339
|
+
...(shell ? { shell } : {}),
|
|
340
|
+
};
|
|
341
|
+
const json = JSON.stringify(payload);
|
|
342
|
+
return format === 'flight' ? `0:${json}` : json;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** The response header carrying a segment's vary set (comma-separated names). */
|
|
346
|
+
export const SEGMENT_VARY_HEADER = 'x-pnext-segment-vary';
|
|
347
|
+
/** The response header carrying the matched route pattern. */
|
|
348
|
+
export const SEGMENT_ROUTE_HEADER = 'x-pnext-segment-route';
|
|
349
|
+
/** w9-segment-split: the LAYOUT frame's vary set (comma-separated names). */
|
|
350
|
+
export const SEGMENT_LAYOUT_VARY_HEADER = 'x-pnext-segment-layout-vary';
|
|
351
|
+
/** w9-segment-split: the PAGE frame's vary set (comma-separated names). */
|
|
352
|
+
export const SEGMENT_PAGE_VARY_HEADER = 'x-pnext-segment-page-vary';
|
|
353
|
+
|
|
354
|
+
/** Per-route segment `.meta` sidecar (contract: status/postponed/segmentPaths). */
|
|
355
|
+
export interface SegmentMeta {
|
|
356
|
+
status: number;
|
|
357
|
+
/** Effective client-cache staleTime for this route, in seconds. */
|
|
358
|
+
staleTime: number;
|
|
359
|
+
/** True when the route was postponed (a PPR shell with holes). */
|
|
360
|
+
postponed: boolean;
|
|
361
|
+
/** Segment request keys this route can serve (always includes `/_tree`). */
|
|
362
|
+
segmentPaths: string[];
|
|
363
|
+
/** Actual emitted segment byte sizes keyed by segment request path. */
|
|
364
|
+
segmentSizes?: Record<string, number>;
|
|
365
|
+
/** Segment request keys whose payload was inlined into the route tree. */
|
|
366
|
+
inlinedSegmentPaths?: string[];
|
|
367
|
+
/** Route-level prefetch hints, mirrored into Next-shaped metadata. */
|
|
368
|
+
prefetchHints?: Record<string, number>;
|
|
369
|
+
/**
|
|
370
|
+
* The vary set the BUILD render tracked for this baked body, in the same wire encoding a live response
|
|
371
|
+
* publishes. ABSENT means "unknown" - the client then keys the entry on its exact URL.
|
|
372
|
+
*/
|
|
373
|
+
vary?: string[];
|
|
374
|
+
/** w9-segment-split: the baked LAYOUT frame's vary names. */
|
|
375
|
+
layoutVary?: string[];
|
|
376
|
+
/** w9-segment-split: the baked PAGE frame's vary names. */
|
|
377
|
+
pageVary?: string[];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** The segment-request key the client sends for the whole-route body segment. */
|
|
381
|
+
export function segmentBodyPath(): string {
|
|
382
|
+
return ROUTE_SEGMENT_PATH;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Build the `.meta` sidecar for a route (whole-route single-segment milestone). */
|
|
386
|
+
export function buildSegmentMeta(options: {
|
|
387
|
+
status: number;
|
|
388
|
+
staleTime: number;
|
|
389
|
+
postponed: boolean;
|
|
390
|
+
bodySizeBytes?: number;
|
|
391
|
+
inlineBudgetBytes?: number;
|
|
392
|
+
prefetchHints?: Record<string, number>;
|
|
393
|
+
/** The build render's tracked vary set — omitted when it is not trustworthy. */
|
|
394
|
+
vary?: ResponseVaryParams;
|
|
395
|
+
}): SegmentMeta {
|
|
396
|
+
const inlined =
|
|
397
|
+
!options.postponed &&
|
|
398
|
+
options.bodySizeBytes !== undefined &&
|
|
399
|
+
options.bodySizeBytes <= (options.inlineBudgetBytes ?? 32 * 1024);
|
|
400
|
+
return {
|
|
401
|
+
status: options.status,
|
|
402
|
+
staleTime: options.staleTime,
|
|
403
|
+
postponed: options.postponed,
|
|
404
|
+
// `/_tree` (the tree) + the whole-route body segment.
|
|
405
|
+
segmentPaths: [TREE_SEGMENT_PATH, ROUTE_SEGMENT_PATH],
|
|
406
|
+
...(options.bodySizeBytes !== undefined
|
|
407
|
+
? { segmentSizes: { [ROUTE_SEGMENT_PATH]: options.bodySizeBytes } }
|
|
408
|
+
: {}),
|
|
409
|
+
...(inlined ? { inlinedSegmentPaths: [ROUTE_SEGMENT_PATH] } : {}),
|
|
410
|
+
...(options.prefetchHints ? { prefetchHints: options.prefetchHints } : {}),
|
|
411
|
+
...(options.vary
|
|
412
|
+
? {
|
|
413
|
+
vary: varyNamesFor(options.vary, 'body'),
|
|
414
|
+
// Mirrors segmentVaryInfo: the split frames only ride along when the
|
|
415
|
+
// render actually tracked something, or an empty page set would file
|
|
416
|
+
// the page frame as shareable across every param value.
|
|
417
|
+
...(options.vary.params.length > 0 || options.vary.search
|
|
418
|
+
? {
|
|
419
|
+
layoutVary: varyNamesFor(options.vary, 'layout'),
|
|
420
|
+
pageVary: varyNamesFor(options.vary, 'page'),
|
|
421
|
+
}
|
|
422
|
+
: {}),
|
|
423
|
+
}
|
|
424
|
+
: {}),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* A per-segment body Response: the route's HTML fragment served as a segment.
|
|
430
|
+
* Same content-type + staleness headers as the tree; the client stitches this
|
|
431
|
+
* fragment into the DOM on navigation.
|
|
432
|
+
*/
|
|
433
|
+
export function segmentBodyResponse(
|
|
434
|
+
body: string,
|
|
435
|
+
staleTimeSeconds: number,
|
|
436
|
+
postponed = false,
|
|
437
|
+
prerendered = false,
|
|
438
|
+
deploymentId?: string,
|
|
439
|
+
segment: string = ROUTE_SEGMENT_PATH,
|
|
440
|
+
vary?: SegmentVaryInfo,
|
|
441
|
+
shell?: SegmentAppShell,
|
|
442
|
+
): Response {
|
|
443
|
+
// A COMPLETE prerendered segment is static content: CDN-cacheable, exactly like Next serves prerendered
|
|
444
|
+
// payloads. Correctness against a Vary-ignoring CDN comes from the per-variant `_rsc` cache-buster and
|
|
445
|
+
// the revalidation-versioned redirects, NOT from no-store. Anything dynamic or truncated stays private.
|
|
446
|
+
const cacheable = prerendered && !postponed;
|
|
447
|
+
return new Response(segmentBodyText(segment, body, 'flight', vary, shell), {
|
|
448
|
+
status: 200,
|
|
449
|
+
headers: {
|
|
450
|
+
'content-type': RSC_CONTENT_TYPE_HEADER,
|
|
451
|
+
'x-nextjs-stale-time': String(staleTimeSeconds),
|
|
452
|
+
'x-nextjs-postponed': '2',
|
|
453
|
+
...(postponed ? { 'x-pnext-segment-postponed': '1' } : {}),
|
|
454
|
+
...(prerendered ? { 'x-nextjs-prerender': '1' } : {}),
|
|
455
|
+
...(deploymentId ? { 'x-nextjs-deployment-id': deploymentId } : {}),
|
|
456
|
+
// Mirrored onto headers so a header-only consumer (and the client's
|
|
457
|
+
// cheap pre-parse path) sees the vary set without decoding the body.
|
|
458
|
+
...(vary ? { [SEGMENT_VARY_HEADER]: vary.vary.join(',') } : {}),
|
|
459
|
+
...(vary?.layoutVary ? { [SEGMENT_LAYOUT_VARY_HEADER]: vary.layoutVary.join(',') } : {}),
|
|
460
|
+
...(vary?.pageVary ? { [SEGMENT_PAGE_VARY_HEADER]: vary.pageVary.join(',') } : {}),
|
|
461
|
+
...(vary?.route ? { [SEGMENT_ROUTE_HEADER]: vary.route } : {}),
|
|
462
|
+
'cache-control': cacheable
|
|
463
|
+
? 's-maxage=31536000, stale-while-revalidate'
|
|
464
|
+
: 'private, no-store',
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** On-disk names for a route's segment artifacts under `<route>.segments/`. */
|
|
470
|
+
export function segmentDir(outPath: string, routeId: string): string {
|
|
471
|
+
return `${outPath}/segments/${routeId}`;
|
|
472
|
+
}
|
|
473
|
+
export function treeSegmentFile(outPath: string, routeId: string): string {
|
|
474
|
+
return `${segmentDir(outPath, routeId)}/_tree.segment.rsc`;
|
|
475
|
+
}
|
|
476
|
+
export function bodySegmentFile(outPath: string, routeId: string): string {
|
|
477
|
+
return `${segmentDir(outPath, routeId)}/index.segment.rsc`;
|
|
478
|
+
}
|
|
479
|
+
export function segmentMetaFile(outPath: string, routeId: string): string {
|
|
480
|
+
return `${segmentDir(outPath, routeId)}/route.segment.meta`;
|
|
481
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Pure vary-set cache-key helpers, shared by the SERVER vary tracker (./vary-params.ts) and the CLIENT
|
|
2
|
+
// segment cache. This module must stay browser-safe: no node: imports - the server-side ALS accumulator
|
|
3
|
+
// machinery lives in vary-params.ts, which re-exports these for server callers.
|
|
4
|
+
|
|
5
|
+
/** The searchParams sentinel: any query access varies the whole query string. */
|
|
6
|
+
export const SEARCH_PARAMS_SENTINEL = '?';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Field separator inside a segment cache key. A NUL can never appear in a
|
|
10
|
+
* pathname, a route pattern, or a param value, so no field boundary is ambiguous
|
|
11
|
+
* (the same convention router.ts uses for its own composite cache keys).
|
|
12
|
+
*/
|
|
13
|
+
export const VARY_KEY_SEPARATOR = '\u0000';
|
|
14
|
+
|
|
15
|
+
/** Next's floor on a prefetch reuse window, and its default when unreported. */
|
|
16
|
+
const MIN_PREFETCH_STALE_TIME_SECONDS = 30;
|
|
17
|
+
const DEFAULT_PREFETCH_STALE_TIME_SECONDS = 300;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The reuse window (ms) for a prefetch response reporting `seconds` of staleness. Next clamps every
|
|
21
|
+
* prefetch stale time to a 30s FLOOR and treats an absent/unparseable `x-nextjs-stale-time` as the 300s
|
|
22
|
+
* static default. Taking the header raw instead makes a `stale-time: 0` response born stale, so every
|
|
23
|
+
* reveal refetches it - a prefetch loop, never a cache hit.
|
|
24
|
+
*/
|
|
25
|
+
export function getStaleTimeMs(seconds: number): number {
|
|
26
|
+
const reported = Number.isFinite(seconds) ? seconds : DEFAULT_PREFETCH_STALE_TIME_SECONDS;
|
|
27
|
+
return Math.max(reported, MIN_PREFETCH_STALE_TIME_SECONDS) * 1000;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function encodeVarySet(names: readonly string[], search = false): string {
|
|
31
|
+
const parts = search ? [...names, SEARCH_PARAMS_SENTINEL] : [...names];
|
|
32
|
+
return parts.join(',');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function decodeVarySet(text: string | null | undefined): string[] | null {
|
|
36
|
+
if (text === null || text === undefined) return null;
|
|
37
|
+
if (text === '') return [];
|
|
38
|
+
return text.split(',').filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The cache key of one segment entry: its route and segment path, plus ONLY the params the segment
|
|
43
|
+
* provably reads (and the query when it read searchParams).
|
|
44
|
+
*
|
|
45
|
+
* An empty vary set yields a key with no param component, so every param value of that route shares a
|
|
46
|
+
* single entry - the whole point of vary tracking. A `null` vary set means the server did not report one:
|
|
47
|
+
* fall back to the exact URL so nothing is ever shared unsoundly.
|
|
48
|
+
*/
|
|
49
|
+
export function segmentVaryCacheKey(options: {
|
|
50
|
+
/** Route identity (the matched route pattern or id). */
|
|
51
|
+
route: string;
|
|
52
|
+
/** Segment request path (`/_index`, `/_head`, `/_tree`, …). */
|
|
53
|
+
segmentPath: string;
|
|
54
|
+
/** Concrete params of the URL being keyed. */
|
|
55
|
+
params: Readonly<Record<string, string | string[] | undefined>>;
|
|
56
|
+
/** The URL's query string (with or without a leading `?`). */
|
|
57
|
+
search?: string;
|
|
58
|
+
/** The segment's vary set, or null when the server reported none. */
|
|
59
|
+
vary: readonly string[] | null;
|
|
60
|
+
/** Full URL path, used verbatim when `vary` is null (unknown). */
|
|
61
|
+
pathname: string;
|
|
62
|
+
}): string {
|
|
63
|
+
const { route, segmentPath, params, vary, pathname } = options;
|
|
64
|
+
const search = normalizeSearch(options.search);
|
|
65
|
+
if (vary === null) return `${segmentPath}${VARY_KEY_SEPARATOR}!${pathname}${search}`;
|
|
66
|
+
const varied = [...vary].sort();
|
|
67
|
+
const parts: string[] = [];
|
|
68
|
+
for (const name of varied) {
|
|
69
|
+
if (name === SEARCH_PARAMS_SENTINEL) continue;
|
|
70
|
+
parts.push(`${name}=${encodeParamValue(params[name])}`);
|
|
71
|
+
}
|
|
72
|
+
const query = varied.includes(SEARCH_PARAMS_SENTINEL) ? search : '';
|
|
73
|
+
return `${segmentPath}${VARY_KEY_SEPARATOR}${route}${VARY_KEY_SEPARATOR}${parts.join('&')}${query}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The canonical form of a query string inside a segment cache key: sorted, with
|
|
78
|
+
* the `_rsc` CDN cache-buster removed. Exported so callers that compare two
|
|
79
|
+
* URLs for entry identity (the client cache's exact-URL check) normalize them
|
|
80
|
+
* exactly the way the key does.
|
|
81
|
+
*/
|
|
82
|
+
export function normalizeSegmentSearch(search: string | undefined): string {
|
|
83
|
+
return normalizeSearch(search);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeSearch(search: string | undefined): string {
|
|
87
|
+
if (!search) return '';
|
|
88
|
+
const text = search.startsWith('?') ? search.slice(1) : search;
|
|
89
|
+
if (!text) return '';
|
|
90
|
+
// `_rsc` is a CDN cache-buster, never an app param: it must not key entries.
|
|
91
|
+
const query = new URLSearchParams(text);
|
|
92
|
+
query.delete('_rsc');
|
|
93
|
+
const pairs = [...query.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
94
|
+
if (pairs.length === 0) return '';
|
|
95
|
+
return `?${pairs.map(([key, value]) => `${key}=${value}`).join('&')}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function encodeParamValue(value: string | string[] | undefined): string {
|
|
99
|
+
if (value === undefined) return 'absent';
|
|
100
|
+
if (Array.isArray(value)) return value.map(part => encodeURIComponent(part)).join('/');
|
|
101
|
+
return encodeURIComponent(value);
|
|
102
|
+
}
|