@wular/pnext 0.0.4 → 0.0.7
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 +22 -16
- package/bin/pnext +6 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +87 -22
- package/reference/config.md +40 -93
- package/reference/css.md +42 -33
- package/reference/dev.md +26 -33
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +31 -46
- package/reference/navigation.md +65 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +78 -75
- package/reference/routing.md +97 -107
- package/reference/typegen.md +27 -29
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +121 -114
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +52 -53
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +72 -64
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +94 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +802 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +32 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +2022 -1995
- package/src/{api → client}/router/types.ts +98 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +86 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2087 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +87 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +954 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +428 -406
- package/src/styles.d.ts +9 -0
- package/src/types.ts +324 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/reference/overview.md +0 -35
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
|
@@ -4,37 +4,37 @@
|
|
|
4
4
|
* routing, runtime — ~700 KB of the bundle) is dynamically imported after the
|
|
5
5
|
* port is bound and the ready banner is printed.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync } from 'node:fs'
|
|
8
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
9
|
-
import { stat } from 'node:fs/promises'
|
|
10
|
-
import path from 'node:path'
|
|
11
|
-
import { constants as zlibConstants, createGzip, gzipSync } from 'node:zlib'
|
|
12
|
-
import { Readable } from 'node:stream'
|
|
13
|
-
import { loadConfig, pathToFileHref } from '
|
|
14
|
-
import type { ResolvedConfig } from '
|
|
15
|
-
import { bootstrapCompat } from '
|
|
16
|
-
import { registerServerRuntime, serverBundleTargetForRuntime } from '
|
|
17
|
-
import { applyProxyResponse, createProxyRunner } from '
|
|
7
|
+
import { existsSync } from 'node:fs'
|
|
8
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
9
|
+
import { stat } from 'node:fs/promises'
|
|
10
|
+
import path from 'node:path'
|
|
11
|
+
import { constants as zlibConstants, createGzip, gzipSync } from 'node:zlib'
|
|
12
|
+
import { Readable } from 'node:stream'
|
|
13
|
+
import { loadConfig, pathToFileHref } from '../../config'
|
|
14
|
+
import type { ResolvedConfig } from '../../config'
|
|
15
|
+
import { bootstrapCompat } from '../../compat-bootstrap'
|
|
16
|
+
import { registerServerRuntime, serverBundleTargetForRuntime } from '../../runtime/loader'
|
|
17
|
+
import { applyProxyResponse, createProxyRunner } from '../../routing/proxy'
|
|
18
18
|
import {
|
|
19
19
|
isPprShellUpgradeEligible,
|
|
20
20
|
renderGlobalNotFoundResponse,
|
|
21
21
|
renderPageResponse,
|
|
22
22
|
renderPageWithStatus,
|
|
23
|
-
} from '
|
|
23
|
+
} from '../../render/renderer'
|
|
24
24
|
import {
|
|
25
25
|
handleRouteModule,
|
|
26
26
|
routeParamsFromPath,
|
|
27
27
|
type RouteHandlerModule,
|
|
28
|
-
} from '
|
|
28
|
+
} from '../../routing/handler'
|
|
29
29
|
import {
|
|
30
30
|
canonicalTrailingSlashPath,
|
|
31
31
|
malformedUrlResponse,
|
|
32
32
|
trailingSlashRedirect,
|
|
33
|
-
} from '
|
|
34
|
-
import { withForwardedHeaders } from '
|
|
35
|
-
import { normalizePathname, parseNavState, selectRouteForRequest } from '
|
|
36
|
-
import { runWithCacheScope } from '
|
|
37
|
-
import { metadataRouteHandlerModule } from '
|
|
33
|
+
} from '../../routing/href'
|
|
34
|
+
import { withForwardedHeaders } from '../../routing/forwarded'
|
|
35
|
+
import { normalizePathname, parseNavState, selectRouteForRequest } from '../../routing/routes'
|
|
36
|
+
import { runWithCacheScope } from '../../request/cache'
|
|
37
|
+
import { metadataRouteHandlerModule } from '../../routing/metadata-files'
|
|
38
38
|
import {
|
|
39
39
|
finalizeResponse,
|
|
40
40
|
getAssetExtensions,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
runInitHooks,
|
|
46
46
|
setRequestExtensions,
|
|
47
47
|
withRouteRuntime,
|
|
48
|
-
} from '
|
|
48
|
+
} from '../../extensions'
|
|
49
49
|
import {
|
|
50
50
|
flushWorkUnit,
|
|
51
51
|
flushWorkUnitOnClose,
|
|
@@ -53,28 +53,28 @@ import {
|
|
|
53
53
|
runWithWorkUnit,
|
|
54
54
|
setPhase,
|
|
55
55
|
setWorkUnitRoute,
|
|
56
|
-
} from '
|
|
57
|
-
import { setRequestRuntime } from '
|
|
58
|
-
import { getRenderSpanExtensions } from '
|
|
59
|
-
import { contentType } from '
|
|
60
|
-
import { stopEsbuildService } from '
|
|
61
|
-
import { markErrorLogged } from '
|
|
56
|
+
} from '../../request/context'
|
|
57
|
+
import { setRequestRuntime } from '../../routing/request-environment'
|
|
58
|
+
import { getRenderSpanExtensions } from '../../render/hooks'
|
|
59
|
+
import { contentType } from '../../utils/content-type'
|
|
60
|
+
import { stopEsbuildService } from '../../utils/esbuild'
|
|
61
|
+
import { markErrorLogged } from '../../utils/error-log'
|
|
62
62
|
import type {
|
|
63
63
|
BuildManifest,
|
|
64
64
|
RouteManifestEntry,
|
|
65
65
|
RouteParamValue,
|
|
66
66
|
StaticFileMetadata,
|
|
67
|
-
} from '
|
|
67
|
+
} from '../../types'
|
|
68
68
|
|
|
69
69
|
/** Compat module-mode route hrefs are the only prod use of the dev import layer; keep it off the start graph. */
|
|
70
70
|
async function moduleHrefForRoute(
|
|
71
71
|
config: ResolvedConfig,
|
|
72
72
|
route: RouteManifestEntry,
|
|
73
73
|
): Promise<string> {
|
|
74
|
-
const { devServerModuleHref } = await import('
|
|
74
|
+
const { devServerModuleHref } = await import('../../runtime/modules')
|
|
75
75
|
return devServerModuleHref(config, route.file, 'build', {
|
|
76
76
|
conditionTarget: serverBundleTargetForRuntime(route.segmentConfig?.runtime),
|
|
77
|
-
})
|
|
77
|
+
})
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -88,35 +88,35 @@ export async function createRequestHandler(
|
|
|
88
88
|
// `start` already resolved (and bootstrapped) the config for its output-mode
|
|
89
89
|
// guard; reusing it skips a second loadConfig — env load, pnext.config +
|
|
90
90
|
// next.config imports, app-path resolution — before the ready banner.
|
|
91
|
-
const config = options.config ?? (await loadConfig(options.root))
|
|
91
|
+
const config = options.config ?? (await loadConfig(options.root))
|
|
92
92
|
// Compat plugin loader: the single gated seam that populates the core
|
|
93
93
|
// extension registries when compat is enabled (no-op for pure-core apps).
|
|
94
94
|
// A prod server answers the very next request, so it takes both tiers here.
|
|
95
|
-
await bootstrapCompat(config)
|
|
96
|
-
const manifestPath = path.join(config.outPath, 'manifest.json')
|
|
95
|
+
await bootstrapCompat(config)
|
|
96
|
+
const manifestPath = path.join(config.outPath, 'manifest.json')
|
|
97
97
|
// `start` reads the manifest before binding the port (a missing build must
|
|
98
98
|
// still fail there, not on the first request) and hands it over.
|
|
99
99
|
const manifest =
|
|
100
|
-
options.manifest ?? (JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest)
|
|
101
|
-
const persistManifest = () => writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
100
|
+
options.manifest ?? (JSON.parse(await readFile(manifestPath, 'utf8')) as BuildManifest)
|
|
101
|
+
const persistManifest = () => writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
102
102
|
// Compat action-registry arming + serverActions.bodySizeLimit resolution +
|
|
103
103
|
// fetch-cache install now run through the extension registry: the action
|
|
104
104
|
// dispatch interceptor rebuilds the registry from manifest.actions on the
|
|
105
105
|
// first action request, and runInitHooks installs the Next fetch-cache patch
|
|
106
106
|
// so runtime renders observe force-cache / revalidate TTLs / tags exactly
|
|
107
107
|
// like build prerenders. No-op for pure-core apps.
|
|
108
|
-
runInitHooks(config)
|
|
108
|
+
runInitHooks(config)
|
|
109
109
|
// Publish the live routing state the compat request interceptors (action
|
|
110
110
|
// dispatch, rewrites) read; prod loads the route table once.
|
|
111
|
-
setRequestRuntime({ config, routes: manifest.routes, dev: false })
|
|
111
|
+
setRequestRuntime({ config, routes: manifest.routes, dev: false })
|
|
112
112
|
const proxyRunner = createProxyRunner(config, {
|
|
113
113
|
compiledModuleHref: manifest.proxyModule
|
|
114
114
|
? pathToFileHref(path.resolve(config.outPath, manifest.proxyModule))
|
|
115
115
|
: undefined,
|
|
116
|
-
})
|
|
117
|
-
const routesById = new Map(manifest.routes.map(route => [route.id, route]))
|
|
118
|
-
const nextCompat = Boolean(config.compat?.next)
|
|
119
|
-
const regenerating = new Map<string, Promise<void>>()
|
|
116
|
+
})
|
|
117
|
+
const routesById = new Map(manifest.routes.map(route => [route.id, route]))
|
|
118
|
+
const nextCompat = Boolean(config.compat?.next)
|
|
119
|
+
const regenerating = new Map<string, Promise<void>>()
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* ISR background regeneration (stale-while-revalidate): re-render the route
|
|
@@ -131,22 +131,22 @@ export async function createRequestHandler(
|
|
|
131
131
|
metadata: StaticFileMetadata,
|
|
132
132
|
reason: 'stale' | 'on-demand',
|
|
133
133
|
) {
|
|
134
|
-
if (!metadata.routeId) return
|
|
135
|
-
const route = routesById.get(metadata.routeId)
|
|
136
|
-
if (!route) return
|
|
134
|
+
if (!metadata.routeId) return
|
|
135
|
+
const route = routesById.get(metadata.routeId)
|
|
136
|
+
if (!route) return
|
|
137
137
|
await runWithWorkUnit('render', async () => {
|
|
138
|
-
const url = new URL(`http://pnext.local${pathname}`)
|
|
139
|
-
const params = routeParamsFromPath(route, pathname)
|
|
140
|
-
const fetchCache = route.segmentConfig?.fetchCache
|
|
138
|
+
const url = new URL(`http://pnext.local${pathname}`)
|
|
139
|
+
const params = routeParamsFromPath(route, pathname)
|
|
140
|
+
const fetchCache = route.segmentConfig?.fetchCache
|
|
141
141
|
try {
|
|
142
142
|
if (route.kind === 'handler') {
|
|
143
|
-
setWorkUnitRoute('route-handler', 'isr', { revalidateReason: reason })
|
|
144
|
-
setPhase('handler')
|
|
145
|
-
registerServerRuntime(config, route.sourceFiles)
|
|
143
|
+
setWorkUnitRoute('route-handler', 'isr', { revalidateReason: reason })
|
|
144
|
+
setPhase('handler')
|
|
145
|
+
registerServerRuntime(config, route.sourceFiles)
|
|
146
146
|
const href = compatModuleMode(config)
|
|
147
147
|
? await moduleHrefForRoute(config, route)
|
|
148
|
-
: pathToFileHref(route.file)
|
|
149
|
-
const module = (await import(href)) as Parameters<typeof handleRouteModule>[0]
|
|
148
|
+
: pathToFileHref(route.file)
|
|
149
|
+
const module = (await import(href)) as Parameters<typeof handleRouteModule>[0]
|
|
150
150
|
const rendered = await getRenderExtensions().collectRenderMeta(
|
|
151
151
|
() =>
|
|
152
152
|
withRouteRuntime(route.segmentConfig?.runtime, () =>
|
|
@@ -155,18 +155,18 @@ export async function createRequestHandler(
|
|
|
155
155
|
),
|
|
156
156
|
),
|
|
157
157
|
{ fetchCache, blockingStaleFetches: true, route: pathname, handler: true },
|
|
158
|
-
)
|
|
159
|
-
if (rendered.value.status >= 500) return
|
|
160
|
-
await writeFile(file, new Uint8Array(await rendered.value.arrayBuffer()))
|
|
158
|
+
)
|
|
159
|
+
if (rendered.value.status >= 500) return
|
|
160
|
+
await writeFile(file, new Uint8Array(await rendered.value.arrayBuffer()))
|
|
161
161
|
manifest.staticFiles![relative] = {
|
|
162
162
|
...metadata,
|
|
163
163
|
status: rendered.value.status,
|
|
164
164
|
headers: [...rendered.value.headers.entries()],
|
|
165
165
|
...(rendered.tags.length > 0 ? { tags: rendered.tags } : {}),
|
|
166
|
-
}
|
|
166
|
+
}
|
|
167
167
|
} else {
|
|
168
|
-
setWorkUnitRoute('html', 'isr', { revalidateReason: reason })
|
|
169
|
-
setPhase('render')
|
|
168
|
+
setWorkUnitRoute('html', 'isr', { revalidateReason: reason })
|
|
169
|
+
setPhase('render')
|
|
170
170
|
const rendered = await getRenderExtensions().collectRenderMeta(
|
|
171
171
|
() =>
|
|
172
172
|
withRouteRuntime(route.segmentConfig?.runtime, () =>
|
|
@@ -182,20 +182,20 @@ export async function createRequestHandler(
|
|
|
182
182
|
}),
|
|
183
183
|
),
|
|
184
184
|
{ fetchCache, blockingStaleFetches: true, route: pathname },
|
|
185
|
-
)
|
|
185
|
+
)
|
|
186
186
|
// A failed regeneration (render threw → 5xx) must not overwrite the
|
|
187
187
|
// good prebuilt copy: Next discards the error and keeps serving the
|
|
188
188
|
// stale page (the error is still logged via the renderer's funnel).
|
|
189
|
-
if (rendered.value.status >= 500) return
|
|
190
|
-
await writeFile(file, rendered.value.html)
|
|
189
|
+
if (rendered.value.status >= 500) return
|
|
190
|
+
await writeFile(file, rendered.value.html)
|
|
191
191
|
manifest.staticFiles![relative] = {
|
|
192
192
|
...metadata,
|
|
193
193
|
status: rendered.value.status,
|
|
194
194
|
...(rendered.tags.length > 0 ? { tags: rendered.tags } : {}),
|
|
195
|
-
}
|
|
195
|
+
}
|
|
196
196
|
}
|
|
197
197
|
} catch (error) {
|
|
198
|
-
const unit = getWorkUnit()
|
|
198
|
+
const unit = getWorkUnit()
|
|
199
199
|
await reportRequestError(
|
|
200
200
|
error,
|
|
201
201
|
{ method: 'GET', url: url.href, headers: new Headers() },
|
|
@@ -204,10 +204,10 @@ export async function createRequestHandler(
|
|
|
204
204
|
routeKind: unit?.routeKind,
|
|
205
205
|
...(unit?.responseHints ?? {}),
|
|
206
206
|
},
|
|
207
|
-
)
|
|
208
|
-
throw error
|
|
207
|
+
)
|
|
208
|
+
throw error
|
|
209
209
|
}
|
|
210
|
-
})
|
|
210
|
+
})
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
function runRegeneration(
|
|
@@ -217,17 +217,17 @@ export async function createRequestHandler(
|
|
|
217
217
|
metadata: StaticFileMetadata,
|
|
218
218
|
reason: 'stale' | 'on-demand',
|
|
219
219
|
) {
|
|
220
|
-
const existing = regenerating.get(file)
|
|
221
|
-
if (existing) return existing
|
|
220
|
+
const existing = regenerating.get(file)
|
|
221
|
+
if (existing) return existing
|
|
222
222
|
const task = regenerateStaticFile(pathname, file, relative, metadata, reason)
|
|
223
223
|
.catch(error => {
|
|
224
|
-
console.warn(`pnext start: background revalidate failed for ${pathname}:`, error)
|
|
224
|
+
console.warn(`pnext start: background revalidate failed for ${pathname}:`, error)
|
|
225
225
|
})
|
|
226
226
|
.finally(() => {
|
|
227
|
-
regenerating.delete(file)
|
|
228
|
-
})
|
|
229
|
-
regenerating.set(file, task)
|
|
230
|
-
return task
|
|
227
|
+
regenerating.delete(file)
|
|
228
|
+
})
|
|
229
|
+
regenerating.set(file, task)
|
|
230
|
+
return task
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
function scheduleRegen(
|
|
@@ -236,16 +236,16 @@ export async function createRequestHandler(
|
|
|
236
236
|
relative: string,
|
|
237
237
|
metadata: StaticFileMetadata,
|
|
238
238
|
) {
|
|
239
|
-
void runRegeneration(pathname, file, relative, metadata, 'stale')
|
|
239
|
+
void runRegeneration(pathname, file, relative, metadata, 'stale')
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
setRequestExtensions({
|
|
243
243
|
onDemandRevalidatePath: async pathname => {
|
|
244
|
-
const built = await builtFileInfo(config.outPath, pathname, manifest.staticFiles)
|
|
245
|
-
if (!built?.metadata) return
|
|
246
|
-
await runRegeneration(pathname, built.file, built.relative, built.metadata, 'on-demand')
|
|
244
|
+
const built = await builtFileInfo(config.outPath, pathname, manifest.staticFiles)
|
|
245
|
+
if (!built?.metadata) return
|
|
246
|
+
await runRegeneration(pathname, built.file, built.relative, built.metadata, 'on-demand')
|
|
247
247
|
},
|
|
248
|
-
})
|
|
248
|
+
})
|
|
249
249
|
|
|
250
250
|
async function renderNotFound(request: Request): Promise<Response> {
|
|
251
251
|
return compressResponse(
|
|
@@ -258,20 +258,20 @@ export async function createRequestHandler(
|
|
|
258
258
|
staticRouteMetadata: manifest.staticRouteMetadata,
|
|
259
259
|
}),
|
|
260
260
|
request,
|
|
261
|
-
)
|
|
261
|
+
)
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
// The boot config compile is the only build work a prod server does; drop the
|
|
265
265
|
// resident esbuild service child (~10+ MB RSS) — it respawns if ever needed.
|
|
266
|
-
stopEsbuildService()
|
|
266
|
+
stopEsbuildService()
|
|
267
267
|
|
|
268
268
|
return function handleRequest(request: Request): Promise<Response> {
|
|
269
269
|
// One work unit spans the whole request; its after-queue flushes once the
|
|
270
270
|
// response fully closes (stream end, redirect, notFound, error, abort).
|
|
271
271
|
return runWithWorkUnit('render', async () => {
|
|
272
|
-
const unit = getWorkUnit()
|
|
272
|
+
const unit = getWorkUnit()
|
|
273
273
|
try {
|
|
274
|
-
const raw = await handle(request)
|
|
274
|
+
const raw = await handle(request)
|
|
275
275
|
const finalized = await finalizeResponse(
|
|
276
276
|
raw,
|
|
277
277
|
{ method: request.method, url: new URL(request.url), headers: request.headers },
|
|
@@ -280,9 +280,9 @@ export async function createRequestHandler(
|
|
|
280
280
|
routeMode: unit?.routeMode,
|
|
281
281
|
hints: unit?.responseHints,
|
|
282
282
|
},
|
|
283
|
-
)
|
|
284
|
-
const response = maybeCloseNodeFetchConnection(finalized, request)
|
|
285
|
-
return flushWorkUnitOnClose(response, unit, request.signal)
|
|
283
|
+
)
|
|
284
|
+
const response = maybeCloseNodeFetchConnection(finalized, request)
|
|
285
|
+
return flushWorkUnitOnClose(response, unit, request.signal)
|
|
286
286
|
} catch (error) {
|
|
287
287
|
// A dropped promise here would close the socket without a response
|
|
288
288
|
// (client sees "socket hang up"); surface a 500 instead. The error
|
|
@@ -291,74 +291,74 @@ export async function createRequestHandler(
|
|
|
291
291
|
error,
|
|
292
292
|
{ method: request.method, url: request.url, headers: request.headers },
|
|
293
293
|
{ phase: unit?.phase, routeKind: unit?.routeKind },
|
|
294
|
-
)
|
|
294
|
+
)
|
|
295
295
|
// Dedupe: the renderer may have already logged this SSR error inline
|
|
296
296
|
// before it propagated here. Log once per error object across log sites.
|
|
297
297
|
if (markErrorLogged(error)) {
|
|
298
|
-
console.error(`pnext start: request failed for ${request.url}:`, error)
|
|
298
|
+
console.error(`pnext start: request failed for ${request.url}:`, error)
|
|
299
299
|
}
|
|
300
|
-
flushWorkUnit(unit)
|
|
301
|
-
return new Response('Internal Server Error', { status: 500 })
|
|
300
|
+
flushWorkUnit(unit)
|
|
301
|
+
return new Response('Internal Server Error', { status: 500 })
|
|
302
302
|
}
|
|
303
|
-
})
|
|
304
|
-
}
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
305
|
|
|
306
306
|
async function handle(request: Request): Promise<Response> {
|
|
307
|
-
const badRequest = malformedUrlResponse(request)
|
|
308
|
-
if (badRequest) return badRequest
|
|
309
|
-
request = withForwardedHeaders(request)
|
|
310
|
-
const requestedUrl = new URL(request.url)
|
|
307
|
+
const badRequest = malformedUrlResponse(request)
|
|
308
|
+
if (badRequest) return badRequest
|
|
309
|
+
request = withForwardedHeaders(request)
|
|
310
|
+
const requestedUrl = new URL(request.url)
|
|
311
311
|
// assetPrefix is independent from basePath. Strip a path-style asset
|
|
312
312
|
// prefix first so `/cdn/_next/static/*` remains servable even when the app
|
|
313
313
|
// itself lives under a different basePath.
|
|
314
|
-
const assetStripped = stripAssetPrefix(request, config.assetPrefix)
|
|
315
|
-
if (assetStripped) request = assetStripped
|
|
314
|
+
const assetStripped = stripAssetPrefix(request, config.assetPrefix)
|
|
315
|
+
if (assetStripped) request = assetStripped
|
|
316
316
|
// basePath: strip the configured prefix so all downstream routing, static
|
|
317
317
|
// lookup, and handler context see the app-relative path (Next serves the
|
|
318
318
|
// app under basePath; `usePathname`/`req.nextUrl.pathname` exclude it). A
|
|
319
319
|
// request that does not carry the prefix is outside the app → 404.
|
|
320
320
|
if (config.basePath && !assetStripped) {
|
|
321
|
-
const stripped = stripBasePath(request, config.basePath)
|
|
321
|
+
const stripped = stripBasePath(request, config.basePath)
|
|
322
322
|
if (!stripped) {
|
|
323
323
|
// Outside the basePath: only a rule that opted out of it (next.config's
|
|
324
324
|
// `basePath: false` rewrites/redirects) may answer. If one rewrites the
|
|
325
325
|
// request back into the app, re-apply the strip; otherwise this is a 404
|
|
326
326
|
// exactly as before.
|
|
327
|
-
const answered = await runOutsideBasePathInterceptors(request, config)
|
|
328
|
-
if (answered instanceof Response) return answered
|
|
329
|
-
const rewritten = answered && stripBasePath(answered.request, config.basePath)
|
|
330
|
-
if (!rewritten) return await renderNotFound(request)
|
|
331
|
-
request = rewritten
|
|
327
|
+
const answered = await runOutsideBasePathInterceptors(request, config)
|
|
328
|
+
if (answered instanceof Response) return answered
|
|
329
|
+
const rewritten = answered && stripBasePath(answered.request, config.basePath)
|
|
330
|
+
if (!rewritten) return await renderNotFound(request)
|
|
331
|
+
request = rewritten
|
|
332
332
|
} else {
|
|
333
|
-
request = stripped
|
|
333
|
+
request = stripped
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
// `/_next/data/<buildId>/<page>.json` is the pages-router data protocol:
|
|
337
337
|
// Next serves it as a request for the PAGE path (trailing slash applied),
|
|
338
338
|
// normalized BEFORE middleware so the handler sees the page URL — or, with
|
|
339
339
|
// skipMiddlewareUrlNormalize, after it (middleware sees the raw data URL).
|
|
340
|
-
const skipProxyNormalize = getProxyExtensions().skipUrlNormalize()
|
|
340
|
+
const skipProxyNormalize = getProxyExtensions().skipUrlNormalize()
|
|
341
341
|
if (nextCompat && !skipProxyNormalize) {
|
|
342
|
-
request = normalizeDataRequest(request, config) ?? request
|
|
342
|
+
request = normalizeDataRequest(request, config) ?? request
|
|
343
343
|
}
|
|
344
344
|
// A request authorized by the preview-mode id is Next's on-demand
|
|
345
345
|
// revalidation: middleware never runs for it and the route re-renders
|
|
346
346
|
// fresh below (the static fast path is skipped).
|
|
347
|
-
const bypassToken = getRequestExtensions().revalidateBypassToken()
|
|
347
|
+
const bypassToken = getRequestExtensions().revalidateBypassToken()
|
|
348
348
|
const revalidateBypass = Boolean(
|
|
349
349
|
bypassToken && request.headers.get('x-prerender-revalidate') === bypassToken,
|
|
350
|
-
)
|
|
351
|
-
const canonicalUrl = new URL(request.url)
|
|
352
|
-
const canonicalHeaders = request.headers
|
|
353
|
-
let proxyResponse: Response | undefined
|
|
354
|
-
const proxyResult = revalidateBypass ? undefined : await proxyRunner(request)
|
|
355
|
-
if (proxyResult instanceof Response) return proxyResult
|
|
350
|
+
)
|
|
351
|
+
const canonicalUrl = new URL(request.url)
|
|
352
|
+
const canonicalHeaders = request.headers
|
|
353
|
+
let proxyResponse: Response | undefined
|
|
354
|
+
const proxyResult = revalidateBypass ? undefined : await proxyRunner(request)
|
|
355
|
+
if (proxyResult instanceof Response) return proxyResult
|
|
356
356
|
if (proxyResult) {
|
|
357
|
-
request = proxyResult.request
|
|
358
|
-
proxyResponse = proxyResult.response
|
|
357
|
+
request = proxyResult.request
|
|
358
|
+
proxyResponse = proxyResult.response
|
|
359
359
|
}
|
|
360
360
|
if (nextCompat && skipProxyNormalize) {
|
|
361
|
-
request = normalizeDataRequest(request, config) ?? request
|
|
361
|
+
request = normalizeDataRequest(request, config) ?? request
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
// The compat request interceptors run after the proxy, before route
|
|
@@ -370,38 +370,38 @@ export async function createRequestHandler(
|
|
|
370
370
|
// use the (possibly rewritten) url. Pure-core apps register no
|
|
371
371
|
// interceptors (a no-op).
|
|
372
372
|
for (const interceptor of getRequestExtensions().interceptors) {
|
|
373
|
-
const result = await interceptor(request, { config })
|
|
373
|
+
const result = await interceptor(request, { config })
|
|
374
374
|
// Interceptor responses (segment/flight payloads, action results) sit in
|
|
375
375
|
// front of Next's compression middleware too — run them through the same
|
|
376
376
|
// gzip/Vary negotiation as rendered pages.
|
|
377
377
|
if (result instanceof Response)
|
|
378
|
-
return applyProxyResponse(compressResponse(result, request), proxyResponse)
|
|
379
|
-
if (result) request = result.request
|
|
378
|
+
return applyProxyResponse(compressResponse(result, request), proxyResponse)
|
|
379
|
+
if (result) request = result.request
|
|
380
380
|
}
|
|
381
|
-
let url = new URL(request.url)
|
|
382
|
-
const rewritten = url.href !== canonicalUrl.href
|
|
381
|
+
let url = new URL(request.url)
|
|
382
|
+
const rewritten = url.href !== canonicalUrl.href
|
|
383
383
|
// Unlike pathname, Next DOES thread a rewrite's destination query into the page's `searchParams`
|
|
384
384
|
// prop - only the address bar and usePathname() stay on the as-requested path - for both
|
|
385
385
|
// middleware and next.config rewrites. So the canonical request keeps the original pathname but
|
|
386
386
|
// picks up whatever search ended up on the fully-resolved request.
|
|
387
|
-
const canonicalRequestUrl = new URL(canonicalUrl)
|
|
388
|
-
canonicalRequestUrl.search = url.search
|
|
387
|
+
const canonicalRequestUrl = new URL(canonicalUrl)
|
|
388
|
+
canonicalRequestUrl.search = url.search
|
|
389
389
|
const canonicalRequest =
|
|
390
390
|
canonicalRequestUrl.href === request.url
|
|
391
391
|
? request
|
|
392
|
-
: new Request(canonicalRequestUrl, { headers: canonicalHeaders })
|
|
392
|
+
: new Request(canonicalRequestUrl, { headers: canonicalHeaders })
|
|
393
393
|
|
|
394
|
-
const method = request.method.toUpperCase()
|
|
394
|
+
const method = request.method.toUpperCase()
|
|
395
395
|
// Trailing-slash normalization applies to the browser-visible (as-requested)
|
|
396
396
|
// URL, never to an internal rewrite destination. A middleware/next.config
|
|
397
397
|
// rewrite to `/en/` must render internally, not 308 the visitor to `/en`.
|
|
398
|
-
const canonicalRedirect = trailingSlashRedirect(config, requestedUrl, method)
|
|
399
|
-
if (canonicalRedirect) return applyProxyResponse(canonicalRedirect, proxyResponse)
|
|
398
|
+
const canonicalRedirect = trailingSlashRedirect(config, requestedUrl, method)
|
|
399
|
+
if (canonicalRedirect) return applyProxyResponse(canonicalRedirect, proxyResponse)
|
|
400
400
|
// Soft navigations carry the client's parallel-route state; when the
|
|
401
401
|
// target involves slots, interception, or a host render, the response is
|
|
402
402
|
// state-dependent and must render dynamically (never from prebuilt html).
|
|
403
|
-
const nav = parseNavState(request)
|
|
404
|
-
const selection = selectRouteForRequest(manifest.routes, url.pathname, nav)
|
|
403
|
+
const nav = parseNavState(request)
|
|
404
|
+
const selection = selectRouteForRequest(manifest.routes, url.pathname, nav)
|
|
405
405
|
const softDynamic = Boolean(
|
|
406
406
|
nav &&
|
|
407
407
|
selection?.route.kind === 'page' &&
|
|
@@ -409,30 +409,25 @@ export async function createRequestHandler(
|
|
|
409
409
|
selection.route.synthetic ||
|
|
410
410
|
selection.route.interception ||
|
|
411
411
|
selection.childrenPath !== normalizePathname(url.pathname)),
|
|
412
|
-
)
|
|
412
|
+
)
|
|
413
413
|
// PPR shells carry an RDC sidecar and must resume through the renderer.
|
|
414
414
|
// Their persisted HTML is only the shell; serving it directly skips the
|
|
415
415
|
// dynamic continuation (and its RDC seed). Non-PPR static pages retain the
|
|
416
416
|
// normal static-file fast path.
|
|
417
|
-
const pprDocumentResume = selection?.route.kind === 'page' && selection.route.ppr
|
|
417
|
+
const pprDocumentResume = selection?.route.kind === 'page' && selection.route.ppr
|
|
418
418
|
// Draft mode: a __prerender_bypass cookie skips prebuilt page html so the
|
|
419
419
|
// request falls through to a fresh dynamic render below; non-page assets
|
|
420
420
|
// still serve statically and the stale-page write-back is disabled so a
|
|
421
421
|
// draft render never overwrites the prerendered copy.
|
|
422
|
-
const draftBypass = hasDraftBypassCookie(request.headers)
|
|
422
|
+
const draftBypass = hasDraftBypassCookie(request.headers)
|
|
423
423
|
// revalidatePath()/revalidateTag() mark prebuilt output stale: skip the static copy (even on a
|
|
424
424
|
// matching etag - the bytes are stale) and fall through to a fresh blocking render. TTL expiry is
|
|
425
425
|
// served stale-while-revalidate instead: the stale copy goes out while a background regeneration
|
|
426
426
|
// rewrites it.
|
|
427
427
|
const built =
|
|
428
428
|
method === 'GET' || method === 'HEAD'
|
|
429
|
-
? await builtFileInfo(
|
|
430
|
-
|
|
431
|
-
url.pathname,
|
|
432
|
-
manifest.staticFiles,
|
|
433
|
-
nextCompat,
|
|
434
|
-
)
|
|
435
|
-
: null;
|
|
429
|
+
? await builtFileInfo(config.outPath, url.pathname, manifest.staticFiles, nextCompat)
|
|
430
|
+
: null
|
|
436
431
|
const hardStaticStale =
|
|
437
432
|
built !== null &&
|
|
438
433
|
// Content-hashed build assets are immutable and are never route outputs, so a
|
|
@@ -445,17 +440,16 @@ export async function createRequestHandler(
|
|
|
445
440
|
url.pathname,
|
|
446
441
|
built.mtimeMs,
|
|
447
442
|
built.metadata?.tags ?? [],
|
|
448
|
-
)
|
|
449
|
-
const staleReason =
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
const staleOnDemand = hardStaticStale && !softStale;
|
|
443
|
+
)
|
|
444
|
+
const staleReason = built
|
|
445
|
+
? (getRequestExtensions().staticStalenessReason(
|
|
446
|
+
url.pathname,
|
|
447
|
+
built.mtimeMs,
|
|
448
|
+
built.metadata?.tags ?? [],
|
|
449
|
+
) ?? 'on-demand')
|
|
450
|
+
: undefined
|
|
451
|
+
const softStale = staleReason === 'soft'
|
|
452
|
+
const staleOnDemand = hardStaticStale && !softStale
|
|
459
453
|
// A `use cache` hard-expiry (cacheLife expire) elapsed: the stale copy is no
|
|
460
454
|
// longer servable — fall through to a fresh blocking render (unlike ISR/SWR,
|
|
461
455
|
// which serves stale while regenerating). Only page HTML is regenerated
|
|
@@ -463,12 +457,12 @@ export async function createRequestHandler(
|
|
|
463
457
|
const hardExpired =
|
|
464
458
|
built?.metadata?.expireSeconds !== undefined &&
|
|
465
459
|
isHtmlPageFile(built.file) &&
|
|
466
|
-
Date.now() - built.mtimeMs >= built.metadata.expireSeconds * 1000
|
|
460
|
+
Date.now() - built.mtimeMs >= built.metadata.expireSeconds * 1000
|
|
467
461
|
// Route-shape bypasses (a PPR page resumes through the renderer; a soft navigation with
|
|
468
462
|
// parallel-slot state re-renders) only apply when the built file IS the page's HTML. A plain ASSET
|
|
469
463
|
// whose path merely matches a page route pattern must always serve statically - rendering the
|
|
470
464
|
// route for it hands the browser HTML for a module script.
|
|
471
|
-
const builtIsPageHtml = built !== null && isHtmlPageFile(built.file)
|
|
465
|
+
const builtIsPageHtml = built !== null && isHtmlPageFile(built.file)
|
|
472
466
|
if (
|
|
473
467
|
built &&
|
|
474
468
|
!staleOnDemand &&
|
|
@@ -477,14 +471,14 @@ export async function createRequestHandler(
|
|
|
477
471
|
!hardExpired &&
|
|
478
472
|
!revalidateBypass
|
|
479
473
|
) {
|
|
480
|
-
const servedBuilt = built
|
|
481
|
-
const metadata = built.metadata
|
|
482
|
-
const route = metadata?.routeId ? routesById.get(metadata.routeId) : undefined
|
|
474
|
+
const servedBuilt = built
|
|
475
|
+
const metadata = built.metadata
|
|
476
|
+
const route = metadata?.routeId ? routesById.get(metadata.routeId) : undefined
|
|
483
477
|
if (
|
|
484
478
|
route?.segmentConfig?.runtime === 'edge' ||
|
|
485
479
|
route?.segmentConfig?.runtime === 'experimental-edge'
|
|
486
480
|
) {
|
|
487
|
-
withRouteRuntime(route.segmentConfig.runtime, () => undefined)
|
|
481
|
+
withRouteRuntime(route.segmentConfig.runtime, () => undefined)
|
|
488
482
|
}
|
|
489
483
|
// A stale static handler body keeps stale-while-revalidate semantics: the generic serving path
|
|
490
484
|
// below serves the stale bytes immediately with an x-nextjs-cache: STALE marker and schedules
|
|
@@ -497,51 +491,50 @@ export async function createRequestHandler(
|
|
|
497
491
|
request.method,
|
|
498
492
|
request.headers,
|
|
499
493
|
nextCompat,
|
|
500
|
-
)
|
|
494
|
+
)
|
|
501
495
|
if (staticFile && !(draftBypass && isHtmlResponse(staticFile))) {
|
|
502
|
-
const staticMode = servedBuilt.metadata?.revalidateSeconds !== undefined ? 'isr' : 'static'
|
|
496
|
+
const staticMode = servedBuilt.metadata?.revalidateSeconds !== undefined ? 'isr' : 'static'
|
|
503
497
|
setWorkUnitRoute(
|
|
504
498
|
isHtmlResponse(staticFile) ? 'html' : 'static-asset',
|
|
505
499
|
staticMode,
|
|
506
500
|
servedBuilt.metadata?.revalidateSeconds !== undefined
|
|
507
501
|
? { revalidateSeconds: servedBuilt.metadata.revalidateSeconds }
|
|
508
502
|
: undefined,
|
|
509
|
-
)
|
|
503
|
+
)
|
|
510
504
|
if (servedBuilt.metadata) {
|
|
511
|
-
const servedTtl = servedBuilt.metadata.revalidateSeconds
|
|
505
|
+
const servedTtl = servedBuilt.metadata.revalidateSeconds
|
|
512
506
|
const expired =
|
|
513
507
|
softStale ||
|
|
514
|
-
(servedTtl !== undefined &&
|
|
515
|
-
Date.now() - servedBuilt.mtimeMs >= servedTtl * 1000);
|
|
508
|
+
(servedTtl !== undefined && Date.now() - servedBuilt.mtimeMs >= servedTtl * 1000)
|
|
516
509
|
if (expired) {
|
|
517
510
|
scheduleRegen(
|
|
518
511
|
url.pathname,
|
|
519
512
|
servedBuilt.file,
|
|
520
513
|
servedBuilt.relative,
|
|
521
514
|
servedBuilt.metadata,
|
|
522
|
-
)
|
|
523
|
-
staticFile.headers.set('x-nextjs-cache', 'STALE')
|
|
515
|
+
)
|
|
516
|
+
staticFile.headers.set('x-nextjs-cache', 'STALE')
|
|
524
517
|
} else {
|
|
525
|
-
staticFile.headers.set('x-nextjs-cache', 'HIT')
|
|
518
|
+
staticFile.headers.set('x-nextjs-cache', 'HIT')
|
|
526
519
|
}
|
|
527
520
|
}
|
|
528
|
-
return applyProxyResponse(compressResponse(staticFile, request), proxyResponse)
|
|
521
|
+
return applyProxyResponse(compressResponse(staticFile, request), proxyResponse)
|
|
529
522
|
}
|
|
530
523
|
}
|
|
531
524
|
if (url.pathname.startsWith('/_next/static/') && !built) {
|
|
532
|
-
setWorkUnitRoute('static-asset', 'static')
|
|
525
|
+
setWorkUnitRoute('static-asset', 'static')
|
|
533
526
|
return applyProxyResponse(
|
|
534
527
|
new Response('Not Found', {
|
|
535
528
|
status: 404,
|
|
536
529
|
headers: { 'content-type': 'text/plain' },
|
|
537
530
|
}),
|
|
538
531
|
proxyResponse,
|
|
539
|
-
)
|
|
532
|
+
)
|
|
540
533
|
}
|
|
541
534
|
const stalePage =
|
|
542
|
-
built && (staleOnDemand || hardExpired) && isHtmlPageFile(built.file) ? built.file : null
|
|
535
|
+
built && (staleOnDemand || hardExpired) && isHtmlPageFile(built.file) ? built.file : null
|
|
543
536
|
|
|
544
|
-
const matched = selection
|
|
537
|
+
const matched = selection
|
|
545
538
|
if (!matched) {
|
|
546
539
|
return applyProxyResponse(
|
|
547
540
|
compressResponse(
|
|
@@ -556,15 +549,15 @@ export async function createRequestHandler(
|
|
|
556
549
|
request,
|
|
557
550
|
),
|
|
558
551
|
proxyResponse,
|
|
559
|
-
)
|
|
552
|
+
)
|
|
560
553
|
}
|
|
561
554
|
if (matched.route.kind === 'handler') {
|
|
562
555
|
setWorkUnitRoute(
|
|
563
556
|
'route-handler',
|
|
564
557
|
staleOnDemand ? 'isr' : routeModeOf(matched.route),
|
|
565
558
|
responseHintsFor(matched.route, staleReason),
|
|
566
|
-
)
|
|
567
|
-
setPhase('handler')
|
|
559
|
+
)
|
|
560
|
+
setPhase('handler')
|
|
568
561
|
// On-demand revalidated renders refetch their data caches (Next's
|
|
569
562
|
// isOnDemandRevalidate semantics) and persist the fresh bytes so
|
|
570
563
|
// subsequent requests serve the regenerated static copy again.
|
|
@@ -576,8 +569,8 @@ export async function createRequestHandler(
|
|
|
576
569
|
route: url.pathname,
|
|
577
570
|
handler: true,
|
|
578
571
|
},
|
|
579
|
-
)
|
|
580
|
-
const handlerResponse = renderedHandler.value
|
|
572
|
+
)
|
|
573
|
+
const handlerResponse = renderedHandler.value
|
|
581
574
|
if (
|
|
582
575
|
built &&
|
|
583
576
|
staleOnDemand &&
|
|
@@ -585,8 +578,8 @@ export async function createRequestHandler(
|
|
|
585
578
|
method === 'GET' &&
|
|
586
579
|
handlerResponse.status === 200
|
|
587
580
|
) {
|
|
588
|
-
await writeFile(built.file, new Uint8Array(await handlerResponse.clone().arrayBuffer()))
|
|
589
|
-
handlerResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
581
|
+
await writeFile(built.file, new Uint8Array(await handlerResponse.clone().arrayBuffer()))
|
|
582
|
+
handlerResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
590
583
|
}
|
|
591
584
|
if (
|
|
592
585
|
!built &&
|
|
@@ -599,34 +592,34 @@ export async function createRequestHandler(
|
|
|
599
592
|
method === 'GET' &&
|
|
600
593
|
handlerResponse.status < 500
|
|
601
594
|
) {
|
|
602
|
-
const file = lazyStaticHandlerPath(config.outPath, url.pathname)
|
|
595
|
+
const file = lazyStaticHandlerPath(config.outPath, url.pathname)
|
|
603
596
|
if (file) {
|
|
604
|
-
await mkdir(path.dirname(file), { recursive: true })
|
|
605
|
-
await writeFile(file, new Uint8Array(await handlerResponse.clone().arrayBuffer()))
|
|
597
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
598
|
+
await writeFile(file, new Uint8Array(await handlerResponse.clone().arrayBuffer()))
|
|
606
599
|
const relative = path
|
|
607
600
|
.relative(path.join(config.outPath, 'public'), file)
|
|
608
601
|
.split(path.sep)
|
|
609
|
-
.join('/')
|
|
610
|
-
const routeRevalidate = matched.route.segmentConfig?.revalidate
|
|
602
|
+
.join('/')
|
|
603
|
+
const routeRevalidate = matched.route.segmentConfig?.revalidate
|
|
611
604
|
const revalidateSeconds =
|
|
612
605
|
typeof routeRevalidate === 'number' && routeRevalidate > 0
|
|
613
606
|
? renderedHandler.revalidateSeconds === undefined
|
|
614
607
|
? routeRevalidate
|
|
615
608
|
: Math.min(routeRevalidate, renderedHandler.revalidateSeconds)
|
|
616
|
-
: renderedHandler.revalidateSeconds
|
|
617
|
-
manifest.staticFiles ??= {}
|
|
609
|
+
: renderedHandler.revalidateSeconds
|
|
610
|
+
manifest.staticFiles ??= {}
|
|
618
611
|
manifest.staticFiles[relative] = {
|
|
619
612
|
status: handlerResponse.status,
|
|
620
613
|
headers: [...handlerResponse.headers.entries()],
|
|
621
614
|
routeId: matched.route.id,
|
|
622
615
|
...(revalidateSeconds !== undefined ? { revalidateSeconds } : {}),
|
|
623
616
|
...(renderedHandler.tags.length > 0 ? { tags: renderedHandler.tags } : {}),
|
|
624
|
-
}
|
|
625
|
-
await persistManifest()
|
|
626
|
-
handlerResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
617
|
+
}
|
|
618
|
+
await persistManifest()
|
|
619
|
+
handlerResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
627
620
|
}
|
|
628
621
|
}
|
|
629
|
-
return applyProxyResponse(handlerResponse, proxyResponse)
|
|
622
|
+
return applyProxyResponse(handlerResponse, proxyResponse)
|
|
630
623
|
}
|
|
631
624
|
|
|
632
625
|
// dynamic = 'error': request-data access is a hard error instead of a
|
|
@@ -638,7 +631,7 @@ export async function createRequestHandler(
|
|
|
638
631
|
{ status: 500 },
|
|
639
632
|
),
|
|
640
633
|
proxyResponse,
|
|
641
|
-
)
|
|
634
|
+
)
|
|
642
635
|
}
|
|
643
636
|
|
|
644
637
|
// dynamicParams=false: dynamic segments governed by it must resolve to a
|
|
@@ -658,15 +651,15 @@ export async function createRequestHandler(
|
|
|
658
651
|
request,
|
|
659
652
|
),
|
|
660
653
|
proxyResponse,
|
|
661
|
-
)
|
|
654
|
+
)
|
|
662
655
|
}
|
|
663
656
|
|
|
664
657
|
// force-static routes render with an empty request and no search params
|
|
665
658
|
// even when served dynamically (Next returns empty cookies/headers/params).
|
|
666
|
-
const forceStaticRoute = matched.route.segmentConfig?.dynamic === 'force-static'
|
|
659
|
+
const forceStaticRoute = matched.route.segmentConfig?.dynamic === 'force-static'
|
|
667
660
|
const renderUrl = forceStaticRoute
|
|
668
661
|
? new URL(canonicalUrl.pathname, canonicalUrl.origin)
|
|
669
|
-
: canonicalRequestUrl
|
|
662
|
+
: canonicalRequestUrl
|
|
670
663
|
// Non-action POSTs to a page render the page like Next's MPA fallback (a
|
|
671
664
|
// plain <form method="POST"> submit, or a browser following a 307/308 from
|
|
672
665
|
// a route handler) instead of a 405.
|
|
@@ -674,13 +667,13 @@ export async function createRequestHandler(
|
|
|
674
667
|
? new Request(renderUrl)
|
|
675
668
|
: method === 'POST'
|
|
676
669
|
? new Request(canonicalRequest.url, { headers: canonicalRequest.headers })
|
|
677
|
-
: canonicalRequest
|
|
678
|
-
const pageRevalidateReason = stalePage ? staleReason : hardExpired ? 'on-demand' : undefined
|
|
670
|
+
: canonicalRequest
|
|
671
|
+
const pageRevalidateReason = stalePage ? staleReason : hardExpired ? 'on-demand' : undefined
|
|
679
672
|
setWorkUnitRoute(
|
|
680
673
|
'html',
|
|
681
674
|
pageRevalidateReason ? 'isr' : routeModeOf(matched.route),
|
|
682
675
|
responseHintsFor(matched.route, pageRevalidateReason),
|
|
683
|
-
)
|
|
676
|
+
)
|
|
684
677
|
const renderMatchedPage = () =>
|
|
685
678
|
withRouteRuntime(matched.route.segmentConfig?.runtime, () =>
|
|
686
679
|
renderPageResponse({
|
|
@@ -702,7 +695,7 @@ export async function createRequestHandler(
|
|
|
702
695
|
}
|
|
703
696
|
: {}),
|
|
704
697
|
}),
|
|
705
|
-
)
|
|
698
|
+
)
|
|
706
699
|
// Every render carries a cache-meta scope: the route's fetchCache config
|
|
707
700
|
// drives fetch cache-mode defaults; on-demand revalidated renders also
|
|
708
701
|
// refetch their data caches (Next's isOnDemandRevalidate semantics).
|
|
@@ -710,12 +703,12 @@ export async function createRequestHandler(
|
|
|
710
703
|
fetchCache: matched.route.segmentConfig?.fetchCache,
|
|
711
704
|
refreshFetches: Boolean(stalePage),
|
|
712
705
|
route: url.pathname,
|
|
713
|
-
})
|
|
706
|
+
})
|
|
714
707
|
const renderRevalidateSeconds =
|
|
715
708
|
renderedPage.revalidateSeconds ??
|
|
716
709
|
(typeof matched.route.segmentConfig?.revalidate === 'number'
|
|
717
710
|
? matched.route.segmentConfig.revalidate
|
|
718
|
-
: undefined)
|
|
711
|
+
: undefined)
|
|
719
712
|
// Only a POSITIVE revalidate window is ISR: `revalidate = 0` is Next's fully-dynamic opt-out
|
|
720
713
|
// (never cached, dynamic staleTime). Promoting a 0-second window to 'isr' handed the route the
|
|
721
714
|
// STATIC client staleTime via the segment finalizer, so a soft push to an already-visited dynamic
|
|
@@ -727,32 +720,32 @@ export async function createRequestHandler(
|
|
|
727
720
|
// so after a server action revalidated the path the browser replayed the pre-revalidation payload.
|
|
728
721
|
// A `segmentConfig.revalidate > 0` route is already classified 'isr' by routeModeOf above.
|
|
729
722
|
const renderWasDynamic =
|
|
730
|
-
matched.route.mode === 'dynamic' || matched.route.usesRequest || renderedPage.noStore === true
|
|
723
|
+
matched.route.mode === 'dynamic' || matched.route.usesRequest || renderedPage.noStore === true
|
|
731
724
|
if (renderRevalidateSeconds !== undefined && renderRevalidateSeconds > 0 && !renderWasDynamic) {
|
|
732
725
|
setWorkUnitRoute('html', 'isr', {
|
|
733
726
|
...(getWorkUnit()?.responseHints ?? {}),
|
|
734
727
|
revalidateSeconds: renderRevalidateSeconds,
|
|
735
|
-
})
|
|
728
|
+
})
|
|
736
729
|
}
|
|
737
|
-
const pageResponse = renderedPage.value
|
|
730
|
+
const pageResponse = renderedPage.value
|
|
738
731
|
|
|
739
732
|
// A PPR resume replays the prebuilt shell without re-running the
|
|
740
733
|
// components whose react-dom preload()/font hints produced the prerender's
|
|
741
734
|
// `Link` header — re-emit the build-captured value (Next serves the
|
|
742
735
|
// prerender's stored headers the same way).
|
|
743
736
|
if (matched.route.linkHeader && !pageResponse.headers.has('link')) {
|
|
744
|
-
pageResponse.headers.set('link', matched.route.linkHeader)
|
|
737
|
+
pageResponse.headers.set('link', matched.route.linkHeader)
|
|
745
738
|
}
|
|
746
739
|
|
|
747
740
|
// A prebuilt `use cache` page never re-runs its cache scopes, so the render
|
|
748
741
|
// response finalizer can't stamp its SWR cache-control / x-nextjs-stale-time
|
|
749
742
|
// (for PPR routes the cacheLife is stashed on the shell-resume work unit, not
|
|
750
743
|
// this one). Re-emit them from the route's build-captured cacheLife.
|
|
751
|
-
const cacheLifeHeaders = cacheLifeResponseHeaders(matched.route.cacheLife)
|
|
744
|
+
const cacheLifeHeaders = cacheLifeResponseHeaders(matched.route.cacheLife)
|
|
752
745
|
if (cacheLifeHeaders.length > 0 && matched.route.kind === 'page') {
|
|
753
746
|
for (const [key, value] of cacheLifeHeaders) {
|
|
754
|
-
if (key === 'cache-control' && pageResponse.headers.has('cache-control')) continue
|
|
755
|
-
pageResponse.headers.set(key, value)
|
|
747
|
+
if (key === 'cache-control' && pageResponse.headers.has('cache-control')) continue
|
|
748
|
+
pageResponse.headers.set(key, value)
|
|
756
749
|
}
|
|
757
750
|
}
|
|
758
751
|
|
|
@@ -772,7 +765,7 @@ export async function createRequestHandler(
|
|
|
772
765
|
// excluded from build prerendering too — see build.ts); their data
|
|
773
766
|
// stability comes from the fetch cache alone.
|
|
774
767
|
matched.route.segmentConfig?.runtime !== 'edge' &&
|
|
775
|
-
(matched.route.hasStaticParams || matched.route.mode === 'static')))
|
|
768
|
+
(matched.route.hasStaticParams || matched.route.mode === 'static')))
|
|
776
769
|
if (
|
|
777
770
|
!stalePage &&
|
|
778
771
|
!built &&
|
|
@@ -786,15 +779,15 @@ export async function createRequestHandler(
|
|
|
786
779
|
(pageResponse.headers.get('content-type') ?? '').includes('text/html')
|
|
787
780
|
) {
|
|
788
781
|
try {
|
|
789
|
-
const file = lazyStaticHtmlPath(config.outPath, url.pathname)
|
|
790
|
-
if (!file) throw new Error('unsafe path')
|
|
791
|
-
await mkdir(path.dirname(file), { recursive: true })
|
|
792
|
-
await writeFile(file, await pageResponse.clone().text())
|
|
782
|
+
const file = lazyStaticHtmlPath(config.outPath, url.pathname)
|
|
783
|
+
if (!file) throw new Error('unsafe path')
|
|
784
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
785
|
+
await writeFile(file, await pageResponse.clone().text())
|
|
793
786
|
const relative = path
|
|
794
787
|
.relative(path.join(config.outPath, 'public'), file)
|
|
795
788
|
.split(path.sep)
|
|
796
|
-
.join('/')
|
|
797
|
-
manifest.staticFiles ??= {}
|
|
789
|
+
.join('/')
|
|
790
|
+
manifest.staticFiles ??= {}
|
|
798
791
|
manifest.staticFiles[relative] = {
|
|
799
792
|
status: 200,
|
|
800
793
|
// Persist the cacheLife SWR headers (and the route's prerendered
|
|
@@ -818,8 +811,8 @@ export async function createRequestHandler(
|
|
|
818
811
|
? { staleSeconds: matched.route.cacheLife.staleSeconds }
|
|
819
812
|
: {}),
|
|
820
813
|
...(renderedPage.tags.length > 0 ? { tags: renderedPage.tags } : {}),
|
|
821
|
-
}
|
|
822
|
-
pageResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
814
|
+
}
|
|
815
|
+
pageResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
823
816
|
} catch {
|
|
824
817
|
// Lazy caching is best-effort; the response is still served.
|
|
825
818
|
}
|
|
@@ -838,8 +831,8 @@ export async function createRequestHandler(
|
|
|
838
831
|
pageResponse.status === 200 &&
|
|
839
832
|
(pageResponse.headers.get('content-type') ?? '').includes('text/html')
|
|
840
833
|
) {
|
|
841
|
-
await writeFile(stalePage, await pageResponse.clone().text())
|
|
842
|
-
pageResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
834
|
+
await writeFile(stalePage, await pageResponse.clone().text())
|
|
835
|
+
pageResponse.headers.set('x-nextjs-cache', 'MISS')
|
|
843
836
|
}
|
|
844
837
|
// On-demand revalidation (valid x-prerender-revalidate): the fresh render
|
|
845
838
|
// replaces any prebuilt copy and is reported as REVALIDATED (Next's
|
|
@@ -851,11 +844,11 @@ export async function createRequestHandler(
|
|
|
851
844
|
pageResponse.status === 200 &&
|
|
852
845
|
(pageResponse.headers.get('content-type') ?? '').includes('text/html')
|
|
853
846
|
) {
|
|
854
|
-
await writeFile(built.file, await pageResponse.clone().text())
|
|
847
|
+
await writeFile(built.file, await pageResponse.clone().text())
|
|
855
848
|
}
|
|
856
|
-
pageResponse.headers.set('x-nextjs-cache', 'REVALIDATED')
|
|
849
|
+
pageResponse.headers.set('x-nextjs-cache', 'REVALIDATED')
|
|
857
850
|
}
|
|
858
|
-
return applyProxyResponse(compressResponse(pageResponse, request), proxyResponse)
|
|
851
|
+
return applyProxyResponse(compressResponse(pageResponse, request), proxyResponse)
|
|
859
852
|
}
|
|
860
853
|
}
|
|
861
854
|
|
|
@@ -869,16 +862,16 @@ function normalizeDataRequest(
|
|
|
869
862
|
request: Request,
|
|
870
863
|
config: { trailingSlash?: boolean },
|
|
871
864
|
): Request | null {
|
|
872
|
-
const method = request.method.toUpperCase()
|
|
873
|
-
if (method !== 'GET' && method !== 'HEAD') return null
|
|
874
|
-
const url = new URL(request.url)
|
|
875
|
-
const match = /^\/_next\/data\/[^/]+(\/.+)\.json$/.exec(url.pathname)
|
|
876
|
-
if (!match) return null
|
|
877
|
-
const page = match[1] === '/index' ? '/' : match[1]
|
|
878
|
-
url.pathname = canonicalTrailingSlashPath(page, Boolean(config.trailingSlash))
|
|
879
|
-
const headers = new Headers(request.headers)
|
|
880
|
-
headers.set('x-nextjs-data', '1')
|
|
881
|
-
return new Request(url, { method, headers })
|
|
865
|
+
const method = request.method.toUpperCase()
|
|
866
|
+
if (method !== 'GET' && method !== 'HEAD') return null
|
|
867
|
+
const url = new URL(request.url)
|
|
868
|
+
const match = /^\/_next\/data\/[^/]+(\/.+)\.json$/.exec(url.pathname)
|
|
869
|
+
if (!match) return null
|
|
870
|
+
const page = match[1] === '/index' ? '/' : match[1]!
|
|
871
|
+
url.pathname = canonicalTrailingSlashPath(page, Boolean(config.trailingSlash))
|
|
872
|
+
const headers = new Headers(request.headers)
|
|
873
|
+
headers.set('x-nextjs-data', '1')
|
|
874
|
+
return new Request(url, { method, headers })
|
|
882
875
|
}
|
|
883
876
|
|
|
884
877
|
// Give the basePath-independent interceptors (compat's `basePath: false`
|
|
@@ -890,10 +883,10 @@ async function runOutsideBasePathInterceptors(
|
|
|
890
883
|
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
891
884
|
): Promise<Response | { request: Request } | undefined> {
|
|
892
885
|
for (const interceptor of getRequestExtensions().outsideBasePathInterceptors) {
|
|
893
|
-
const result = await interceptor(request, { config, outsideBasePath: true })
|
|
894
|
-
if (result) return result
|
|
886
|
+
const result = await interceptor(request, { config, outsideBasePath: true })
|
|
887
|
+
if (result) return result
|
|
895
888
|
}
|
|
896
|
-
return undefined
|
|
889
|
+
return undefined
|
|
897
890
|
}
|
|
898
891
|
|
|
899
892
|
// Remove the configured basePath prefix from the request URL, returning a
|
|
@@ -901,11 +894,11 @@ async function runOutsideBasePathInterceptors(
|
|
|
901
894
|
// null when the path is outside basePath (Next 404s those). The root of the
|
|
902
895
|
// app (`/basePath`) maps to `/`.
|
|
903
896
|
function stripBasePath(request: Request, basePath: string): Request | null {
|
|
904
|
-
const url = new URL(request.url)
|
|
905
|
-
const { pathname } = url
|
|
906
|
-
if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null
|
|
907
|
-
url.pathname = pathname.slice(basePath.length) || '/'
|
|
908
|
-
return new Request(url, request)
|
|
897
|
+
const url = new URL(request.url)
|
|
898
|
+
const { pathname } = url
|
|
899
|
+
if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null
|
|
900
|
+
url.pathname = pathname.slice(basePath.length) || '/'
|
|
901
|
+
return new Request(url, request)
|
|
909
902
|
}
|
|
910
903
|
|
|
911
904
|
// Remove a configured PATH-style assetPrefix from an asset request URL so the
|
|
@@ -915,38 +908,38 @@ function stripBasePath(request: Request, basePath: string): Request | null {
|
|
|
915
908
|
// absolute prefix, only its pathname is matched so local production starts can
|
|
916
909
|
// serve the same generated asset URLs.
|
|
917
910
|
function stripAssetPrefix(request: Request, assetPrefix: string | undefined): Request | null {
|
|
918
|
-
if (!assetPrefix) return null
|
|
919
|
-
let prefixValue = assetPrefix
|
|
911
|
+
if (!assetPrefix) return null
|
|
912
|
+
let prefixValue = assetPrefix
|
|
920
913
|
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(assetPrefix)) {
|
|
921
|
-
prefixValue = new URL(assetPrefix).pathname
|
|
914
|
+
prefixValue = new URL(assetPrefix).pathname
|
|
922
915
|
}
|
|
923
|
-
const prefix = `/${prefixValue.replace(/^\/+|\/+$/g, '')}
|
|
924
|
-
if (prefix === '/') return null
|
|
925
|
-
const url = new URL(request.url)
|
|
926
|
-
const { pathname } = url
|
|
916
|
+
const prefix = `/${prefixValue.replace(/^\/+|\/+$/g, '')}`
|
|
917
|
+
if (prefix === '/') return null
|
|
918
|
+
const url = new URL(request.url)
|
|
919
|
+
const { pathname } = url
|
|
927
920
|
if (
|
|
928
921
|
!pathname.startsWith(`${prefix}/assets/`) &&
|
|
929
922
|
!pathname.startsWith(`${prefix}/_next/static/`)
|
|
930
923
|
) {
|
|
931
|
-
return null
|
|
924
|
+
return null
|
|
932
925
|
}
|
|
933
|
-
url.pathname = pathname.slice(prefix.length)
|
|
934
|
-
return new Request(url, request)
|
|
926
|
+
url.pathname = pathname.slice(prefix.length)
|
|
927
|
+
return new Request(url, request)
|
|
935
928
|
}
|
|
936
929
|
|
|
937
930
|
function maybeCloseNodeFetchConnection(response: Response, request: Request) {
|
|
938
|
-
const userAgent = request.headers.get('user-agent') ?? ''
|
|
939
|
-
if (!userAgent.includes('node-fetch')) return response
|
|
940
|
-
response.headers.set('connection', 'close')
|
|
941
|
-
return response
|
|
931
|
+
const userAgent = request.headers.get('user-agent') ?? ''
|
|
932
|
+
if (!userAgent.includes('node-fetch')) return response
|
|
933
|
+
response.headers.set('connection', 'close')
|
|
934
|
+
return response
|
|
942
935
|
}
|
|
943
936
|
|
|
944
937
|
// Compat mode gate (a pure read of config.compat, no compat import): under
|
|
945
938
|
// compat the compiled module is loaded so bare next/* / react aliases resolve
|
|
946
|
-
// to the compat layer. Mirrors compat/
|
|
939
|
+
// to the compat layer. Mirrors compat/aliases.ts reactCompatEnabled — kept inline
|
|
947
940
|
// so start.ts carries no static edge into compat.
|
|
948
941
|
function compatModuleMode(config: Awaited<ReturnType<typeof loadConfig>>): boolean {
|
|
949
|
-
return Boolean(config.compat?.next || config.compat?.react || config.compat?.reactCompiler)
|
|
942
|
+
return Boolean(config.compat?.next || config.compat?.react || config.compat?.reactCompiler)
|
|
950
943
|
}
|
|
951
944
|
|
|
952
945
|
async function handleRoute(
|
|
@@ -956,11 +949,11 @@ async function handleRoute(
|
|
|
956
949
|
params: Record<string, RouteParamValue>,
|
|
957
950
|
) {
|
|
958
951
|
return withRouteRuntime(route.segmentConfig?.runtime, async () => {
|
|
959
|
-
registerServerRuntime(config, route.sourceFiles)
|
|
952
|
+
registerServerRuntime(config, route.sourceFiles)
|
|
960
953
|
// Module resolution and handler invocation run through the render-span seam
|
|
961
954
|
// (compat/otel emits `resolve page components` + `executing api route`;
|
|
962
955
|
// pass-through for pure-core apps).
|
|
963
|
-
const spans = getRenderSpanExtensions()
|
|
956
|
+
const spans = getRenderSpanExtensions()
|
|
964
957
|
const module = await spans.withFindPageComponentsSpan(route.route, async () => {
|
|
965
958
|
// Compat mode loads the compiled module (aliases baked in): a raw import
|
|
966
959
|
// would resolve bare next/* specifiers (next/headers etc.) against whatever
|
|
@@ -968,37 +961,37 @@ async function handleRoute(
|
|
|
968
961
|
// onResolve plugin cannot alias bare imports. Same pattern as pageRender.
|
|
969
962
|
const href = compatModuleMode(config)
|
|
970
963
|
? await moduleHrefForRoute(config, route)
|
|
971
|
-
: new URL(`file://${route.file}`).href
|
|
964
|
+
: new URL(`file://${route.file}`).href
|
|
972
965
|
const imported = (await import(href)) as RouteHandlerModule &
|
|
973
|
-
Parameters<typeof metadataRouteHandlerModule>[0]
|
|
974
|
-
return (metadataRouteHandlerModule(imported, route) ?? imported) as RouteHandlerModule
|
|
975
|
-
})
|
|
966
|
+
Parameters<typeof metadataRouteHandlerModule>[0]
|
|
967
|
+
return (metadataRouteHandlerModule(imported, route) ?? imported) as RouteHandlerModule
|
|
968
|
+
})
|
|
976
969
|
return spans.withRouteHandlerSpan(route.route, () =>
|
|
977
970
|
runWithCacheScope(() =>
|
|
978
971
|
handleRouteModule(module, request, params, { routeFile: route.file }),
|
|
979
972
|
),
|
|
980
|
-
)
|
|
981
|
-
})
|
|
973
|
+
)
|
|
974
|
+
})
|
|
982
975
|
}
|
|
983
976
|
|
|
984
977
|
// Draft mode (draftMode().enable() from next/headers) marks the browser with
|
|
985
978
|
// a bypass cookie; requests carrying it must not see prerendered page html.
|
|
986
979
|
function hasDraftBypassCookie(headers: Headers) {
|
|
987
|
-
return /(?:^|;\s*)__prerender_bypass=/.test(headers.get('cookie') ?? '')
|
|
980
|
+
return /(?:^|;\s*)__prerender_bypass=/.test(headers.get('cookie') ?? '')
|
|
988
981
|
}
|
|
989
982
|
|
|
990
983
|
function isHtmlResponse(response: Response) {
|
|
991
|
-
return (response.headers.get('content-type') ?? '').includes('text/html')
|
|
984
|
+
return (response.headers.get('content-type') ?? '').includes('text/html')
|
|
992
985
|
}
|
|
993
986
|
|
|
994
987
|
// Core's coarse route caching disposition for the work unit / finalizers. ISR
|
|
995
988
|
// (a static route with a revalidate TTL) is reported as 'isr'; compat can refine
|
|
996
989
|
// the exact cache-control strings in its response finalizers.
|
|
997
990
|
function routeModeOf(route: RouteManifestEntry): 'static' | 'isr' | 'dynamic' {
|
|
998
|
-
if (route.segmentConfig?.dynamic === 'force-dynamic') return 'dynamic'
|
|
999
|
-
const revalidate = route.segmentConfig?.revalidate
|
|
1000
|
-
if (typeof revalidate === 'number' && revalidate > 0) return 'isr'
|
|
1001
|
-
return route.mode === 'static' ? 'static' : 'dynamic'
|
|
991
|
+
if (route.segmentConfig?.dynamic === 'force-dynamic') return 'dynamic'
|
|
992
|
+
const revalidate = route.segmentConfig?.revalidate
|
|
993
|
+
if (typeof revalidate === 'number' && revalidate > 0) return 'isr'
|
|
994
|
+
return route.mode === 'static' ? 'static' : 'dynamic'
|
|
1002
995
|
}
|
|
1003
996
|
|
|
1004
997
|
// Compat classification hints carried on the work unit for response finalizers:
|
|
@@ -1008,23 +1001,21 @@ function responseHintsFor(
|
|
|
1008
1001
|
route: RouteManifestEntry,
|
|
1009
1002
|
revalidateReason: string | undefined,
|
|
1010
1003
|
): Record<string, unknown> | undefined {
|
|
1011
|
-
const runtime = route.segmentConfig?.runtime
|
|
1012
|
-
const revalidate = route.segmentConfig?.revalidate
|
|
1013
|
-
if (!revalidateReason && !runtime && typeof revalidate !== 'number') return undefined
|
|
1004
|
+
const runtime = route.segmentConfig?.runtime
|
|
1005
|
+
const revalidate = route.segmentConfig?.revalidate
|
|
1006
|
+
if (!revalidateReason && !runtime && typeof revalidate !== 'number') return undefined
|
|
1014
1007
|
return {
|
|
1015
1008
|
...(revalidateReason ? { revalidateReason } : {}),
|
|
1016
1009
|
...(runtime ? { runtime } : {}),
|
|
1017
|
-
...(typeof revalidate === 'number' && revalidate > 0
|
|
1018
|
-
|
|
1019
|
-
: {}),
|
|
1020
|
-
};
|
|
1010
|
+
...(typeof revalidate === 'number' && revalidate > 0 ? { revalidateSeconds: revalidate } : {}),
|
|
1011
|
+
}
|
|
1021
1012
|
}
|
|
1022
1013
|
|
|
1023
1014
|
interface BuiltFileInfo {
|
|
1024
|
-
file: string
|
|
1025
|
-
relative: string
|
|
1026
|
-
mtimeMs: number
|
|
1027
|
-
metadata?: StaticFileMetadata
|
|
1015
|
+
file: string
|
|
1016
|
+
relative: string
|
|
1017
|
+
mtimeMs: number
|
|
1018
|
+
metadata?: StaticFileMetadata
|
|
1028
1019
|
}
|
|
1029
1020
|
|
|
1030
1021
|
// The prebuilt output file a GET for `pathname` would serve (page html or a
|
|
@@ -1038,71 +1029,75 @@ async function builtFileInfo(
|
|
|
1038
1029
|
const file = await firstFile(
|
|
1039
1030
|
path.join(outPath, 'public'),
|
|
1040
1031
|
builtFileCandidates(outPath, pathname, nextStaticFallback),
|
|
1041
|
-
)
|
|
1042
|
-
if (!file) return null
|
|
1043
|
-
const publicPath = path.join(outPath, 'public')
|
|
1044
|
-
const fileStat = await stat(file)
|
|
1045
|
-
const relative = path.relative(publicPath, file).split(path.sep).join('/')
|
|
1046
|
-
return { file, relative, mtimeMs: fileStat.mtimeMs, metadata: staticFiles[relative] }
|
|
1032
|
+
)
|
|
1033
|
+
if (!file) return null
|
|
1034
|
+
const publicPath = path.join(outPath, 'public')
|
|
1035
|
+
const fileStat = await stat(file)
|
|
1036
|
+
const relative = path.relative(publicPath, file).split(path.sep).join('/')
|
|
1037
|
+
return { file, relative, mtimeMs: fileStat.mtimeMs, metadata: staticFiles[relative] }
|
|
1047
1038
|
}
|
|
1048
1039
|
|
|
1049
1040
|
// The prebuilt files a GET for `pathname` could resolve to, most-specific
|
|
1050
1041
|
// first: the dir layout (`/a/index.html`), a raw static asset (`/a`), and the
|
|
1051
1042
|
// flat export layout (`/a.html`, output:'export' + trailingSlash:false).
|
|
1052
|
-
function builtFileCandidates(
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1043
|
+
function builtFileCandidates(
|
|
1044
|
+
outPath: string,
|
|
1045
|
+
pathname: string,
|
|
1046
|
+
nextStaticFallback = false,
|
|
1047
|
+
): string[] {
|
|
1048
|
+
const publicPath = path.join(outPath, 'public')
|
|
1049
|
+
const trimmed = pathname.replace(/^\/+/, '')
|
|
1050
|
+
if (pathname === '/') return [path.join(publicPath, 'index.html')]
|
|
1056
1051
|
const candidates = [
|
|
1057
1052
|
path.join(publicPath, trimmed, 'index.html'),
|
|
1058
1053
|
path.join(publicPath, trimmed),
|
|
1059
1054
|
path.join(publicPath, `${trimmed.replace(/\/+$/, '')}.html`),
|
|
1060
|
-
]
|
|
1055
|
+
]
|
|
1061
1056
|
// Prerendered pages for generateStaticParams values with special characters
|
|
1062
1057
|
// live under the DECODED segment (`sticks & stones/`), while the request
|
|
1063
1058
|
// pathname arrives percent-encoded (prerender-encoding suite).
|
|
1064
1059
|
try {
|
|
1065
|
-
const decoded = decodeURIComponent(trimmed)
|
|
1060
|
+
const decoded = decodeURIComponent(trimmed)
|
|
1066
1061
|
if (decoded !== trimmed && !decoded.split('/').some(seg => seg === '..' || seg === '')) {
|
|
1067
1062
|
candidates.push(
|
|
1068
1063
|
path.join(publicPath, decoded, 'index.html'),
|
|
1069
1064
|
path.join(publicPath, `${decoded.replace(/\/+$/, '')}.html`),
|
|
1070
|
-
)
|
|
1065
|
+
)
|
|
1071
1066
|
}
|
|
1072
1067
|
} catch {
|
|
1073
1068
|
// malformed escape — encoded candidates only
|
|
1074
1069
|
}
|
|
1075
1070
|
if (nextStaticFallback && trimmed.startsWith('_next/static/')) {
|
|
1076
|
-
const asset = trimmed.slice('_next/static/'.length)
|
|
1077
|
-
candidates.push(path.join(publicPath, 'assets', asset))
|
|
1071
|
+
const asset = trimmed.slice('_next/static/'.length)
|
|
1072
|
+
candidates.push(path.join(publicPath, 'assets', asset))
|
|
1078
1073
|
}
|
|
1079
|
-
return candidates
|
|
1074
|
+
return candidates
|
|
1080
1075
|
}
|
|
1081
1076
|
|
|
1082
1077
|
// Where a lazily generated page for `pathname` is persisted (mirrors the
|
|
1083
1078
|
// build's staticHtmlPath); null when the path would escape public/.
|
|
1084
1079
|
function lazyStaticHtmlPath(outPath: string, pathname: string): string | null {
|
|
1085
|
-
const publicPath = path.join(outPath, 'public')
|
|
1080
|
+
const publicPath = path.join(outPath, 'public')
|
|
1086
1081
|
const file =
|
|
1087
1082
|
pathname === '/'
|
|
1088
1083
|
? path.join(publicPath, 'index.html')
|
|
1089
|
-
: path.join(publicPath, pathname.replace(/^\/+|\/+$/g, ''), 'index.html')
|
|
1090
|
-
const relative = path.relative(publicPath, file)
|
|
1091
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) return null
|
|
1092
|
-
return file
|
|
1084
|
+
: path.join(publicPath, pathname.replace(/^\/+|\/+$/g, ''), 'index.html')
|
|
1085
|
+
const relative = path.relative(publicPath, file)
|
|
1086
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) return null
|
|
1087
|
+
return file
|
|
1093
1088
|
}
|
|
1094
1089
|
|
|
1095
1090
|
function lazyStaticHandlerPath(outPath: string, pathname: string): string | null {
|
|
1096
|
-
if (pathname === '/') return null
|
|
1097
|
-
const publicPath = path.join(outPath, 'public')
|
|
1098
|
-
const file = path.join(publicPath, pathname.replace(/^\/+|\/+$/g, ''))
|
|
1099
|
-
const relative = path.relative(publicPath, file)
|
|
1100
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) return null
|
|
1101
|
-
return file
|
|
1091
|
+
if (pathname === '/') return null
|
|
1092
|
+
const publicPath = path.join(outPath, 'public')
|
|
1093
|
+
const file = path.join(publicPath, pathname.replace(/^\/+|\/+$/g, ''))
|
|
1094
|
+
const relative = path.relative(publicPath, file)
|
|
1095
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) return null
|
|
1096
|
+
return file
|
|
1102
1097
|
}
|
|
1103
1098
|
|
|
1104
1099
|
function isHtmlPageFile(file: string) {
|
|
1105
|
-
return file.endsWith('.html')
|
|
1100
|
+
return file.endsWith('.html')
|
|
1106
1101
|
}
|
|
1107
1102
|
|
|
1108
1103
|
/**
|
|
@@ -1110,18 +1105,16 @@ function isHtmlPageFile(file: string) {
|
|
|
1110
1105
|
* cacheLife implies. A pure static HIT never re-runs the render header finalizer, so start.ts
|
|
1111
1106
|
* re-emits these from the persisted cacheLife. Returns [] when none applies.
|
|
1112
1107
|
*/
|
|
1113
|
-
function cacheLifeResponseHeaders(
|
|
1114
|
-
life
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const headers: [string, string][] = [];
|
|
1118
|
-
const { revalidateSeconds, expireSeconds, staleSeconds } = life;
|
|
1108
|
+
function cacheLifeResponseHeaders(life: RouteManifestEntry['cacheLife']): [string, string][] {
|
|
1109
|
+
if (!life) return []
|
|
1110
|
+
const headers: [string, string][] = []
|
|
1111
|
+
const { revalidateSeconds, expireSeconds, staleSeconds } = life
|
|
1119
1112
|
if (revalidateSeconds !== undefined && expireSeconds !== undefined) {
|
|
1120
|
-
const swr = Math.max(0, expireSeconds - revalidateSeconds)
|
|
1121
|
-
headers.push(['cache-control', `s-maxage=${revalidateSeconds}, stale-while-revalidate=${swr}`])
|
|
1113
|
+
const swr = Math.max(0, expireSeconds - revalidateSeconds)
|
|
1114
|
+
headers.push(['cache-control', `s-maxage=${revalidateSeconds}, stale-while-revalidate=${swr}`])
|
|
1122
1115
|
}
|
|
1123
|
-
if (staleSeconds !== undefined) headers.push(['x-nextjs-stale-time', String(staleSeconds)])
|
|
1124
|
-
return headers
|
|
1116
|
+
if (staleSeconds !== undefined) headers.push(['x-nextjs-stale-time', String(staleSeconds)])
|
|
1117
|
+
return headers
|
|
1125
1118
|
}
|
|
1126
1119
|
|
|
1127
1120
|
// A content-hashed, immutable build asset (client runtime chunks under
|
|
@@ -1130,7 +1123,7 @@ function cacheLifeResponseHeaders(
|
|
|
1130
1123
|
// revalidatePath/revalidateTag staleness, unlike prebuilt page HTML and
|
|
1131
1124
|
// route-handler bodies. `relative` uses forward slashes (see builtFileInfo).
|
|
1132
1125
|
function isImmutableAssetFile(relative: string) {
|
|
1133
|
-
return relative.startsWith('assets/') || relative.startsWith('_next/static/')
|
|
1126
|
+
return relative.startsWith('assets/') || relative.startsWith('_next/static/')
|
|
1134
1127
|
}
|
|
1135
1128
|
|
|
1136
1129
|
// `dynamicParams = false` (route segment config): params governed by the
|
|
@@ -1138,19 +1131,19 @@ function isImmutableAssetFile(relative: string) {
|
|
|
1138
1131
|
// the page has its own static params export, the route behaves like Next's
|
|
1139
1132
|
// fallback: false — the full param tuple must match a prerendered path.
|
|
1140
1133
|
function dynamicParamsAllowed(route: RouteManifestEntry, params: Record<string, RouteParamValue>) {
|
|
1141
|
-
const governed = route.segmentConfig?.dynamicParamsFalse
|
|
1142
|
-
if (!governed?.length) return true
|
|
1143
|
-
const allowed = route.prerenderedParams ?? []
|
|
1134
|
+
const governed = route.segmentConfig?.dynamicParamsFalse
|
|
1135
|
+
if (!governed?.length) return true
|
|
1136
|
+
const allowed = route.prerenderedParams ?? []
|
|
1144
1137
|
const required = route.segmentConfig?.strictDynamicParams
|
|
1145
1138
|
? [...route.params, ...(route.catchAll ? [route.catchAll] : [])]
|
|
1146
|
-
: governed
|
|
1147
|
-
return allowed.some(set => required.every(param => paramValueEqual(set[param], params[param])))
|
|
1139
|
+
: governed
|
|
1140
|
+
return allowed.some(set => required.every(param => paramValueEqual(set[param], params[param])))
|
|
1148
1141
|
}
|
|
1149
1142
|
|
|
1150
1143
|
function paramValueEqual(a: RouteParamValue | undefined, b: RouteParamValue | undefined) {
|
|
1151
|
-
if (a === undefined || b === undefined) return false
|
|
1152
|
-
const join = (value: RouteParamValue) => (Array.isArray(value) ? value.join('/') : value)
|
|
1153
|
-
return join(a) === join(b)
|
|
1144
|
+
if (a === undefined || b === undefined) return false
|
|
1145
|
+
const join = (value: RouteParamValue) => (Array.isArray(value) ? value.join('/') : value)
|
|
1146
|
+
return join(a) === join(b)
|
|
1154
1147
|
}
|
|
1155
1148
|
|
|
1156
1149
|
export async function maybeBuiltFile(
|
|
@@ -1161,55 +1154,55 @@ export async function maybeBuiltFile(
|
|
|
1161
1154
|
requestHeaders?: Headers,
|
|
1162
1155
|
nextStaticFallback = false,
|
|
1163
1156
|
) {
|
|
1164
|
-
const normalizedMethod = method.toUpperCase()
|
|
1165
|
-
if (normalizedMethod !== 'GET' && normalizedMethod !== 'HEAD') return null
|
|
1157
|
+
const normalizedMethod = method.toUpperCase()
|
|
1158
|
+
if (normalizedMethod !== 'GET' && normalizedMethod !== 'HEAD') return null
|
|
1166
1159
|
|
|
1167
|
-
const publicPath = path.join(outPath, 'public')
|
|
1160
|
+
const publicPath = path.join(outPath, 'public')
|
|
1168
1161
|
const file = await firstFile(
|
|
1169
1162
|
publicPath,
|
|
1170
1163
|
builtFileCandidates(outPath, pathname, nextStaticFallback),
|
|
1171
|
-
)
|
|
1172
|
-
if (!file) return null
|
|
1173
|
-
const relative = path.relative(publicPath, file).split(path.sep).join('/')
|
|
1174
|
-
const metadata = staticFiles[relative]
|
|
1175
|
-
const headers = new Headers(metadata?.headers)
|
|
1176
|
-
if (!headers.has('content-type')) headers.set('content-type', contentType(file))
|
|
1164
|
+
)
|
|
1165
|
+
if (!file) return null
|
|
1166
|
+
const relative = path.relative(publicPath, file).split(path.sep).join('/')
|
|
1167
|
+
const metadata = staticFiles[relative]
|
|
1168
|
+
const headers = new Headers(metadata?.headers)
|
|
1169
|
+
if (!headers.has('content-type')) headers.set('content-type', contentType(file))
|
|
1177
1170
|
|
|
1178
|
-
const fileStat = await stat(file)
|
|
1171
|
+
const fileStat = await stat(file)
|
|
1179
1172
|
// Vary discriminator: a router-originated fetch gets its Content-Type rewritten downstream (compat
|
|
1180
1173
|
// swaps it to text/x-component for soft-nav requests) without this file or its etag changing. The
|
|
1181
1174
|
// response's `Vary` header already lists `rsc` as differentiating, so the validator must differ too
|
|
1182
1175
|
// - otherwise a browser that cached one representation gets a 304 on a later request for the OTHER
|
|
1183
1176
|
// and silently reuses the wrong body and content-type.
|
|
1184
|
-
const variant = requestHeaders?.get('rsc') === '1' ? '-rsc' : ''
|
|
1185
|
-
const etag = `W/"${fileStat.size.toString(16)}-${Math.trunc(fileStat.mtimeMs).toString(16)}${variant}"
|
|
1177
|
+
const variant = requestHeaders?.get('rsc') === '1' ? '-rsc' : ''
|
|
1178
|
+
const etag = `W/"${fileStat.size.toString(16)}-${Math.trunc(fileStat.mtimeMs).toString(16)}${variant}"`
|
|
1186
1179
|
if (!headers.has('cache-control')) {
|
|
1187
|
-
headers.set('cache-control', immutableAssetPath(relative) ? immutableCacheControl : 'no-cache')
|
|
1188
|
-
headers.set('etag', etag)
|
|
1180
|
+
headers.set('cache-control', immutableAssetPath(relative) ? immutableCacheControl : 'no-cache')
|
|
1181
|
+
headers.set('etag', etag)
|
|
1189
1182
|
if (requestHeaders?.get('if-none-match') === etag) {
|
|
1190
|
-
return new Response(null, { status: 304, headers })
|
|
1183
|
+
return new Response(null, { status: 304, headers })
|
|
1191
1184
|
}
|
|
1192
1185
|
}
|
|
1193
1186
|
|
|
1194
|
-
const raw = await readFile(file)
|
|
1195
|
-
let body: BodyInit = raw
|
|
1187
|
+
const raw = await readFile(file)
|
|
1188
|
+
let body: BodyInit = raw
|
|
1196
1189
|
if (
|
|
1197
1190
|
compressibleContentType(headers.get('content-type') ?? '') &&
|
|
1198
1191
|
raw.length > 1024 &&
|
|
1199
1192
|
acceptsGzip(requestHeaders)
|
|
1200
1193
|
) {
|
|
1201
|
-
const zipped = cachedGzip(`${file}:${etag}`, raw)
|
|
1194
|
+
const zipped = cachedGzip(`${file}:${etag}`, raw)
|
|
1202
1195
|
if (zipped.length < raw.length) {
|
|
1203
|
-
body = zipped as Uint8Array<ArrayBuffer
|
|
1204
|
-
headers.set('content-encoding', 'gzip')
|
|
1205
|
-
headers.append('vary', 'accept-encoding')
|
|
1196
|
+
body = zipped as Uint8Array<ArrayBuffer>
|
|
1197
|
+
headers.set('content-encoding', 'gzip')
|
|
1198
|
+
headers.append('vary', 'accept-encoding')
|
|
1206
1199
|
}
|
|
1207
1200
|
}
|
|
1208
1201
|
|
|
1209
1202
|
return new Response(normalizedMethod === 'HEAD' ? null : body, {
|
|
1210
1203
|
status: metadata?.status ?? 200,
|
|
1211
1204
|
headers,
|
|
1212
|
-
})
|
|
1205
|
+
})
|
|
1213
1206
|
}
|
|
1214
1207
|
|
|
1215
1208
|
// Wrap dynamic responses (rendered pages, not-found) in streaming gzip.
|
|
@@ -1217,42 +1210,42 @@ export async function maybeBuiltFile(
|
|
|
1217
1210
|
// encoded or non-text passes through untouched.
|
|
1218
1211
|
// Append a token to the Vary header without duplicating existing entries.
|
|
1219
1212
|
function appendVaryToken(headers: Headers, token: string): void {
|
|
1220
|
-
const existing = headers.get('vary')
|
|
1213
|
+
const existing = headers.get('vary')
|
|
1221
1214
|
if (!existing) {
|
|
1222
|
-
headers.set('vary', token)
|
|
1223
|
-
return
|
|
1215
|
+
headers.set('vary', token)
|
|
1216
|
+
return
|
|
1224
1217
|
}
|
|
1225
|
-
const tokens = existing.split(',').map(part => part.trim().toLowerCase())
|
|
1226
|
-
if (tokens.includes(token.toLowerCase()) || tokens.includes('*')) return
|
|
1227
|
-
headers.set('vary', `${existing}, ${token}`)
|
|
1218
|
+
const tokens = existing.split(',').map(part => part.trim().toLowerCase())
|
|
1219
|
+
if (tokens.includes(token.toLowerCase()) || tokens.includes('*')) return
|
|
1220
|
+
headers.set('vary', `${existing}, ${token}`)
|
|
1228
1221
|
}
|
|
1229
1222
|
|
|
1230
1223
|
export function compressResponse(response: Response, request: Request) {
|
|
1231
|
-
if (!response.body || response.status === 304 || response.status === 204) return response
|
|
1232
|
-
if (response.headers.get('content-encoding')) return response
|
|
1233
|
-
if (!compressibleContentType(response.headers.get('content-type') ?? '')) return response
|
|
1224
|
+
if (!response.body || response.status === 304 || response.status === 204) return response
|
|
1225
|
+
if (response.headers.get('content-encoding')) return response
|
|
1226
|
+
if (!compressibleContentType(response.headers.get('content-type') ?? '')) return response
|
|
1234
1227
|
|
|
1235
|
-
const headers = new Headers(response.headers)
|
|
1228
|
+
const headers = new Headers(response.headers)
|
|
1236
1229
|
// `compress: true` advertises encoding negotiability on every compressible
|
|
1237
1230
|
// response — even when the client didn't send Accept-Encoding — so caches key
|
|
1238
1231
|
// on it (matches Next's compression middleware, which sets Vary before it
|
|
1239
1232
|
// decides whether to gzip). The flight-request vary assertion expects this.
|
|
1240
|
-
appendVaryToken(headers, 'accept-encoding')
|
|
1233
|
+
appendVaryToken(headers, 'accept-encoding')
|
|
1241
1234
|
if (!acceptsGzip(request.headers)) {
|
|
1242
|
-
return new Response(response.body, { status: response.status, headers })
|
|
1235
|
+
return new Response(response.body, { status: response.status, headers })
|
|
1243
1236
|
}
|
|
1244
1237
|
// Match Next's compression middleware threshold: bodies with a known
|
|
1245
1238
|
// length at or below 1KB are served identity-encoded (Content-Length kept).
|
|
1246
|
-
const knownLength = Number(headers.get('content-length'))
|
|
1239
|
+
const knownLength = Number(headers.get('content-length'))
|
|
1247
1240
|
if (Number.isFinite(knownLength) && knownLength <= 1024) {
|
|
1248
|
-
return new Response(response.body, { status: response.status, headers })
|
|
1241
|
+
return new Response(response.body, { status: response.status, headers })
|
|
1249
1242
|
}
|
|
1250
|
-
headers.set('content-encoding', 'gzip')
|
|
1251
|
-
headers.delete('content-length')
|
|
1243
|
+
headers.set('content-encoding', 'gzip')
|
|
1244
|
+
headers.delete('content-length')
|
|
1252
1245
|
return new Response(streamingGzip(response.body), {
|
|
1253
1246
|
status: response.status,
|
|
1254
1247
|
headers,
|
|
1255
|
-
})
|
|
1248
|
+
})
|
|
1256
1249
|
}
|
|
1257
1250
|
|
|
1258
1251
|
// Gzip a streaming response, flushing the compressor after every source chunk
|
|
@@ -1264,52 +1257,52 @@ export function compressResponse(response: Response, request: Request) {
|
|
|
1264
1257
|
// general). A per-chunk sync flush keeps the stream observable at the cost of a
|
|
1265
1258
|
// slightly worse ratio on tiny early chunks.
|
|
1266
1259
|
function streamingGzip(body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
|
|
1267
|
-
const gzip = createGzip()
|
|
1268
|
-
const reader = body.getReader()
|
|
1260
|
+
const gzip = createGzip()
|
|
1261
|
+
const reader = body.getReader()
|
|
1269
1262
|
void (async () => {
|
|
1270
1263
|
try {
|
|
1271
1264
|
for (;;) {
|
|
1272
|
-
const { done, value } = await reader.read()
|
|
1273
|
-
if (done) break
|
|
1274
|
-
gzip.write(value)
|
|
1265
|
+
const { done, value } = await reader.read()
|
|
1266
|
+
if (done) break
|
|
1267
|
+
gzip.write(value)
|
|
1275
1268
|
// Emit the bytes buffered so far as a flushable block.
|
|
1276
|
-
await new Promise<void>(resolve => gzip.flush(zlibConstants.Z_SYNC_FLUSH, resolve))
|
|
1269
|
+
await new Promise<void>(resolve => gzip.flush(zlibConstants.Z_SYNC_FLUSH, resolve))
|
|
1277
1270
|
}
|
|
1278
|
-
gzip.end()
|
|
1271
|
+
gzip.end()
|
|
1279
1272
|
} catch (error) {
|
|
1280
|
-
gzip.destroy(error as Error)
|
|
1281
|
-
reader.cancel(error).catch(() => undefined)
|
|
1273
|
+
gzip.destroy(error as Error)
|
|
1274
|
+
reader.cancel(error).catch(() => undefined)
|
|
1282
1275
|
}
|
|
1283
|
-
})()
|
|
1284
|
-
return Readable.toWeb(gzip) as unknown as ReadableStream<Uint8Array
|
|
1276
|
+
})()
|
|
1277
|
+
return Readable.toWeb(gzip) as unknown as ReadableStream<Uint8Array>
|
|
1285
1278
|
}
|
|
1286
1279
|
|
|
1287
1280
|
function compressibleContentType(type: string) {
|
|
1288
|
-
return /^(text\/|application\/(javascript|json|manifest|xml)|image\/svg)/.test(type)
|
|
1281
|
+
return /^(text\/|application\/(javascript|json|manifest|xml)|image\/svg)/.test(type)
|
|
1289
1282
|
}
|
|
1290
1283
|
|
|
1291
1284
|
function acceptsGzip(requestHeaders?: Headers) {
|
|
1292
|
-
return requestHeaders?.get('accept-encoding')?.includes('gzip') ?? false
|
|
1285
|
+
return requestHeaders?.get('accept-encoding')?.includes('gzip') ?? false
|
|
1293
1286
|
}
|
|
1294
1287
|
|
|
1295
1288
|
// Bounded by the build's compressible assets; entries are keyed on content
|
|
1296
1289
|
// identity (path + etag), so a rebuild naturally replaces stale ones.
|
|
1297
|
-
const gzipCache = new Map<string, Buffer>()
|
|
1298
|
-
const gzipCacheLimit = 512
|
|
1290
|
+
const gzipCache = new Map<string, Buffer>()
|
|
1291
|
+
const gzipCacheLimit = 512
|
|
1299
1292
|
|
|
1300
1293
|
function cachedGzip(key: string, body: Buffer) {
|
|
1301
|
-
const cached = gzipCache.get(key)
|
|
1302
|
-
if (cached) return cached
|
|
1303
|
-
const zipped = gzipSync(body, { level: 6 })
|
|
1294
|
+
const cached = gzipCache.get(key)
|
|
1295
|
+
if (cached) return cached
|
|
1296
|
+
const zipped = gzipSync(body, { level: 6 })
|
|
1304
1297
|
if (gzipCache.size >= gzipCacheLimit) {
|
|
1305
|
-
const oldest = gzipCache.keys().next().value
|
|
1306
|
-
if (oldest !== undefined) gzipCache.delete(oldest)
|
|
1298
|
+
const oldest = gzipCache.keys().next().value
|
|
1299
|
+
if (oldest !== undefined) gzipCache.delete(oldest)
|
|
1307
1300
|
}
|
|
1308
|
-
gzipCache.set(key, zipped)
|
|
1309
|
-
return zipped
|
|
1301
|
+
gzipCache.set(key, zipped)
|
|
1302
|
+
return zipped
|
|
1310
1303
|
}
|
|
1311
1304
|
|
|
1312
|
-
export const immutableCacheControl = 'public, max-age=31536000, immutable'
|
|
1305
|
+
export const immutableCacheControl = 'public, max-age=31536000, immutable'
|
|
1313
1306
|
|
|
1314
1307
|
// Chunk and font filenames are content-hashed, so their bytes can never change under a given URL;
|
|
1315
1308
|
// entries, css and html keep stable names and must revalidate (no-cache still allows storing, so
|
|
@@ -1317,24 +1310,23 @@ export const immutableCacheControl = 'public, max-age=31536000, immutable';
|
|
|
1317
1310
|
// way, so they qualify too.
|
|
1318
1311
|
export function immutableAssetPath(relativePath: string) {
|
|
1319
1312
|
if (relativePath.startsWith('assets/chunks/') || relativePath.startsWith('assets/fonts/')) {
|
|
1320
|
-
return true
|
|
1313
|
+
return true
|
|
1321
1314
|
}
|
|
1322
1315
|
return getAssetExtensions()
|
|
1323
1316
|
.staticAssetPublicPrefixes()
|
|
1324
|
-
.some(prefix => relativePath.startsWith(prefix.replace(/^\/+/, '')))
|
|
1317
|
+
.some(prefix => relativePath.startsWith(prefix.replace(/^\/+/, '')))
|
|
1325
1318
|
}
|
|
1326
1319
|
|
|
1327
1320
|
async function firstFile(root: string, files: string[]) {
|
|
1328
1321
|
for (const file of files) {
|
|
1329
|
-
if (!isInside(root, file)) continue
|
|
1330
|
-
if (!existsSync(file)) continue
|
|
1331
|
-
if ((await stat(file)).isFile()) return file
|
|
1322
|
+
if (!isInside(root, file)) continue
|
|
1323
|
+
if (!existsSync(file)) continue
|
|
1324
|
+
if ((await stat(file)).isFile()) return file
|
|
1332
1325
|
}
|
|
1333
|
-
return null
|
|
1326
|
+
return null
|
|
1334
1327
|
}
|
|
1335
1328
|
|
|
1336
1329
|
function isInside(root: string, file: string) {
|
|
1337
|
-
const relative = path.relative(root, file)
|
|
1338
|
-
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
1330
|
+
const relative = path.relative(root, file)
|
|
1331
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
1339
1332
|
}
|
|
1340
|
-
|