@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
package/src/routing/handler.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { isNotFoundError, isRedirectError } from '../api/navigation'
|
|
2
|
-
import { getWorkUnit, runWithRequest } from '../request/context'
|
|
3
|
-
import { nextResponseMeta, toNextRequest } from '../api/server'
|
|
4
|
-
import { reportRequestError } from '../extensions'
|
|
5
|
-
import type { NextRequest, RouteManifestEntry, RouteParamValue } from '../types'
|
|
1
|
+
import { isNotFoundError, isRedirectError } from '../api/navigation'
|
|
2
|
+
import { getWorkUnit, runWithRequest } from '../request/context'
|
|
3
|
+
import { nextResponseMeta, toNextRequest } from '../api/server'
|
|
4
|
+
import { reportRequestError } from '../extensions'
|
|
5
|
+
import type { NextRequest, RouteManifestEntry, RouteParamValue } from '../types'
|
|
6
|
+
import { safeDecode } from '../utils/decode'
|
|
6
7
|
|
|
7
|
-
const routeMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const
|
|
8
|
+
const routeMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const
|
|
8
9
|
|
|
9
|
-
type RouteMethod = (typeof routeMethods)[number]
|
|
10
|
+
type RouteMethod = (typeof routeMethods)[number]
|
|
10
11
|
|
|
11
12
|
// Methods that are valid HTTP verbs but may be unimplemented on a route (405),
|
|
12
13
|
// vs. tokens that are not HTTP methods at all (400). Next answers 400 for the
|
|
@@ -21,32 +22,32 @@ const httpMethods = new Set([
|
|
|
21
22
|
'OPTIONS',
|
|
22
23
|
'CONNECT',
|
|
23
24
|
'TRACE',
|
|
24
|
-
])
|
|
25
|
+
])
|
|
25
26
|
|
|
26
27
|
interface RouteHandlerContext {
|
|
27
|
-
params?: Promise<Record<string, RouteParamValue
|
|
28
|
+
params?: Promise<Record<string, RouteParamValue>>
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
interface HandleRouteOptions {
|
|
31
32
|
/** Absolute route file path, used in the "no response returned" CLI error. */
|
|
32
|
-
routeFile?: string
|
|
33
|
+
routeFile?: string
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export interface RouteHandlerModule {
|
|
36
|
-
params?: () => Record<string, RouteParamValue>[] | Promise<Record<string, RouteParamValue>[]
|
|
37
|
-
GET?: RouteHandler
|
|
38
|
-
HEAD?: RouteHandler
|
|
39
|
-
POST?: RouteHandler
|
|
40
|
-
PUT?: RouteHandler
|
|
41
|
-
PATCH?: RouteHandler
|
|
42
|
-
DELETE?: RouteHandler
|
|
43
|
-
OPTIONS?: RouteHandler
|
|
37
|
+
params?: () => Record<string, RouteParamValue>[] | Promise<Record<string, RouteParamValue>[]>
|
|
38
|
+
GET?: RouteHandler
|
|
39
|
+
HEAD?: RouteHandler
|
|
40
|
+
POST?: RouteHandler
|
|
41
|
+
PUT?: RouteHandler
|
|
42
|
+
PATCH?: RouteHandler
|
|
43
|
+
DELETE?: RouteHandler
|
|
44
|
+
OPTIONS?: RouteHandler
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
type RouteHandler = (
|
|
47
48
|
request: NextRequest,
|
|
48
49
|
context: RouteHandlerContext,
|
|
49
|
-
) => Response | undefined | void | Promise<Response | undefined | void
|
|
50
|
+
) => Response | undefined | void | Promise<Response | undefined | void>
|
|
50
51
|
|
|
51
52
|
export async function handleRouteModule(
|
|
52
53
|
module: RouteHandlerModule,
|
|
@@ -58,16 +59,16 @@ export async function handleRouteModule(
|
|
|
58
59
|
// draftMode().enable() append set-cookie here while the handler runs, and
|
|
59
60
|
// the headers are merged onto whatever response goes out — including the
|
|
60
61
|
// synthesized redirect response when the handler throws redirect().
|
|
61
|
-
const responseHeaders = new Headers()
|
|
62
|
-
const method = request.method.toUpperCase()
|
|
62
|
+
const responseHeaders = new Headers()
|
|
63
|
+
const method = request.method.toUpperCase()
|
|
63
64
|
try {
|
|
64
65
|
// A request method that is not a valid HTTP verb is a bad request, not an
|
|
65
66
|
// unimplemented one (Next returns 400 with an empty body).
|
|
66
|
-
if (!httpMethods.has(method)) return new Response(null, { status: 400 })
|
|
67
|
+
if (!httpMethods.has(method)) return new Response(null, { status: 400 })
|
|
67
68
|
|
|
68
69
|
const handler = routeMethods.includes(method as RouteMethod)
|
|
69
70
|
? module[method as RouteMethod]
|
|
70
|
-
: undefined
|
|
71
|
+
: undefined
|
|
71
72
|
|
|
72
73
|
if (!handler) {
|
|
73
74
|
if (method === 'HEAD' && module.GET) {
|
|
@@ -77,10 +78,10 @@ export async function handleRouteModule(
|
|
|
77
78
|
request,
|
|
78
79
|
responseHeaders,
|
|
79
80
|
options,
|
|
80
|
-
)
|
|
81
|
-
return new Response(null, { status: response.status, headers: response.headers })
|
|
81
|
+
)
|
|
82
|
+
return new Response(null, { status: response.status, headers: response.headers })
|
|
82
83
|
}
|
|
83
|
-
return missingMethodResponse(module, method)
|
|
84
|
+
return missingMethodResponse(module, method)
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
return finalizeResponse(
|
|
@@ -88,21 +89,21 @@ export async function handleRouteModule(
|
|
|
88
89
|
request,
|
|
89
90
|
responseHeaders,
|
|
90
91
|
options,
|
|
91
|
-
)
|
|
92
|
+
)
|
|
92
93
|
} catch (error) {
|
|
93
94
|
if (isRedirectError(error)) {
|
|
94
95
|
// POST submissions get a 303 (Next's behavior for redirect() and
|
|
95
96
|
// permanentRedirect() thrown in route handlers): the browser follows
|
|
96
97
|
// with a GET instead of replaying the POST against the target.
|
|
97
|
-
const status = method === 'POST' ? 303 : error.status
|
|
98
|
+
const status = method === 'POST' ? 303 : error.status
|
|
98
99
|
return withResponseHeaders(
|
|
99
100
|
new Response(null, { status, headers: { location: error.location } }),
|
|
100
101
|
responseHeaders,
|
|
101
|
-
)
|
|
102
|
+
)
|
|
102
103
|
}
|
|
103
104
|
// notFound() thrown inside a handler is a 404 with an empty body.
|
|
104
105
|
if (isNotFoundError(error)) {
|
|
105
|
-
return withResponseHeaders(new Response(null, { status: 404 }), responseHeaders)
|
|
106
|
+
return withResponseHeaders(new Response(null, { status: 404 }), responseHeaders)
|
|
106
107
|
}
|
|
107
108
|
// Any other throw is a 500 with an empty body. Report it through the error
|
|
108
109
|
// funnel (onRequestError) here since core no longer sees it as uncaught.
|
|
@@ -110,9 +111,9 @@ export async function handleRouteModule(
|
|
|
110
111
|
error,
|
|
111
112
|
{ method, url: request.url, headers: request.headers },
|
|
112
113
|
{ phase: 'handler', routeKind: 'route-handler', ...(getWorkUnit()?.responseHints ?? {}) },
|
|
113
|
-
)
|
|
114
|
-
console.error(error)
|
|
115
|
-
return new Response(null, { status: 500 })
|
|
114
|
+
)
|
|
115
|
+
console.error(error)
|
|
116
|
+
return new Response(null, { status: 500 })
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -128,21 +129,21 @@ function finalizeResponse(
|
|
|
128
129
|
responseHeaders: Headers,
|
|
129
130
|
options: HandleRouteOptions,
|
|
130
131
|
) {
|
|
131
|
-
const method = request.method.toUpperCase()
|
|
132
|
+
const method = request.method.toUpperCase()
|
|
132
133
|
if (!response) {
|
|
133
|
-
const file = options.routeFile ?? '<route>'
|
|
134
|
+
const file = options.routeFile ?? '<route>'
|
|
134
135
|
console.error(
|
|
135
136
|
`No response is returned from route handler '${file}'. Expected a Response object but ` +
|
|
136
137
|
`received '${typeof response}' (method: ${method}, url: ${request.url}). Ensure you ` +
|
|
137
138
|
'return a `Response` or a `NextResponse` in all branches of your handler.',
|
|
138
|
-
)
|
|
139
|
-
return new Response(null, { status: 500 })
|
|
139
|
+
)
|
|
140
|
+
return new Response(null, { status: 500 })
|
|
140
141
|
}
|
|
141
142
|
if (nextResponseMeta(response)?.kind === 'next') {
|
|
142
|
-
console.error('https://nextjs.org/docs/messages/next-response-next-in-app-route-handler')
|
|
143
|
-
return new Response(null, { status: 500 })
|
|
143
|
+
console.error('https://nextjs.org/docs/messages/next-response-next-in-app-route-handler')
|
|
144
|
+
return new Response(null, { status: 500 })
|
|
144
145
|
}
|
|
145
|
-
return withResponseHeaders(response, responseHeaders)
|
|
146
|
+
return withResponseHeaders(response, responseHeaders)
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
function callHandler(
|
|
@@ -151,121 +152,113 @@ function callHandler(
|
|
|
151
152
|
params: Record<string, RouteParamValue>,
|
|
152
153
|
responseHeaders: Headers,
|
|
153
154
|
) {
|
|
154
|
-
const nextRequest = toNextRequest(request)
|
|
155
|
+
const nextRequest = toNextRequest(request)
|
|
155
156
|
// Static routes (no dynamic segments) get no `params` in context — Next
|
|
156
157
|
// omits it, so `context.params` is `undefined` rather than a promise of `{}`.
|
|
157
158
|
const context: RouteHandlerContext =
|
|
158
|
-
Object.keys(params).length > 0 ? { params: Promise.resolve(params) } : {}
|
|
159
|
+
Object.keys(params).length > 0 ? { params: Promise.resolve(params) } : {}
|
|
159
160
|
return runWithRequest(nextRequest, () => handler(nextRequest, context), params, {
|
|
160
161
|
responseHeaders,
|
|
161
|
-
})
|
|
162
|
+
})
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
/** Merge scope-collected headers (set-cookie appends, everything else sets) onto the response. */
|
|
165
166
|
function withResponseHeaders(response: Response, extra: Headers) {
|
|
166
|
-
const withSetCookie = extra as Headers & { getSetCookie?: () => string[] }
|
|
167
|
-
const scopeCookies = withSetCookie.getSetCookie?.() ?? []
|
|
168
|
-
let hasOthers = false
|
|
167
|
+
const withSetCookie = extra as Headers & { getSetCookie?: () => string[] }
|
|
168
|
+
const scopeCookies = withSetCookie.getSetCookie?.() ?? []
|
|
169
|
+
let hasOthers = false
|
|
169
170
|
extra.forEach((_, key) => {
|
|
170
|
-
if (key !== 'set-cookie') hasOthers = true
|
|
171
|
-
})
|
|
172
|
-
if (scopeCookies.length === 0 && !hasOthers) return response
|
|
171
|
+
if (key !== 'set-cookie') hasOthers = true
|
|
172
|
+
})
|
|
173
|
+
if (scopeCookies.length === 0 && !hasOthers) return response
|
|
173
174
|
|
|
174
175
|
const apply = (headers: Headers) => {
|
|
175
176
|
extra.forEach((value, key) => {
|
|
176
|
-
if (key !== 'set-cookie') headers.set(key, value)
|
|
177
|
-
})
|
|
177
|
+
if (key !== 'set-cookie') headers.set(key, value)
|
|
178
|
+
})
|
|
178
179
|
// Cookie-jar merge (Next's ResponseCookies semantics): scope cookies
|
|
179
180
|
// (cookies().set during the handler) first, the response's own Set-Cookie
|
|
180
181
|
// headers override same-name entries, and every cookie is normalized with
|
|
181
182
|
// a default Path=/.
|
|
182
183
|
const responseCookies =
|
|
183
|
-
(headers as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? []
|
|
184
|
-
const jar = new Map<string, string>()
|
|
184
|
+
(headers as Headers & { getSetCookie?: () => string[] }).getSetCookie?.() ?? []
|
|
185
|
+
const jar = new Map<string, string>()
|
|
185
186
|
for (const cookie of [...scopeCookies, ...responseCookies]) {
|
|
186
|
-
jar.set(cookieName(cookie), normalizeSetCookie(cookie))
|
|
187
|
+
jar.set(cookieName(cookie), normalizeSetCookie(cookie))
|
|
187
188
|
}
|
|
188
|
-
headers.delete('set-cookie')
|
|
189
|
-
for (const cookie of jar.values()) headers.append('set-cookie', cookie)
|
|
190
|
-
}
|
|
189
|
+
headers.delete('set-cookie')
|
|
190
|
+
for (const cookie of jar.values()) headers.append('set-cookie', cookie)
|
|
191
|
+
}
|
|
191
192
|
try {
|
|
192
|
-
apply(response.headers)
|
|
193
|
-
return response
|
|
193
|
+
apply(response.headers)
|
|
194
|
+
return response
|
|
194
195
|
} catch {
|
|
195
196
|
// Immutable headers (e.g. a Response forwarded from fetch): re-wrap.
|
|
196
|
-
const headers = new Headers(response.headers)
|
|
197
|
-
apply(headers)
|
|
197
|
+
const headers = new Headers(response.headers)
|
|
198
|
+
apply(headers)
|
|
198
199
|
return new Response(response.body, {
|
|
199
200
|
status: response.status,
|
|
200
201
|
statusText: response.statusText,
|
|
201
202
|
headers,
|
|
202
|
-
})
|
|
203
|
+
})
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
function cookieName(setCookie: string) {
|
|
207
|
-
return (setCookie.split('=')[0] ?? '').trim()
|
|
208
|
+
return (setCookie.split('=')[0] ?? '').trim()
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
function normalizeSetCookie(setCookie: string) {
|
|
211
|
-
return /;\s*path=/i.test(setCookie) ? setCookie : `${setCookie}; Path
|
|
212
|
+
return /;\s*path=/i.test(setCookie) ? setCookie : `${setCookie}; Path=/`
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
export async function staticRouteParams(route: RouteManifestEntry, module: RouteHandlerModule) {
|
|
215
|
-
if (typeof module.GET !== 'function') return []
|
|
216
|
+
if (typeof module.GET !== 'function') return []
|
|
216
217
|
// `dynamic = 'force-static'` and `dynamic = 'error'` both evaluate a static
|
|
217
218
|
// request; the latter lets request APIs fail at their call sites. A
|
|
218
219
|
// `revalidate` export opts a request-independent GET into static/ISR output.
|
|
219
|
-
const config = route.segmentConfig
|
|
220
|
+
const config = route.segmentConfig
|
|
220
221
|
const optedStatic =
|
|
221
222
|
config?.dynamic === 'force-static' ||
|
|
222
223
|
config?.dynamic === 'error' ||
|
|
223
224
|
config?.revalidate === false ||
|
|
224
|
-
(typeof config?.revalidate === 'number' && config.revalidate > 0)
|
|
225
|
-
if (route.hasStaticParams) return module.params?.() ?? []
|
|
226
|
-
if (route.usesRequest && !optedStatic) return []
|
|
227
|
-
return route.mode === 'static' || optedStatic ? [{}] : []
|
|
225
|
+
(typeof config?.revalidate === 'number' && config.revalidate > 0)
|
|
226
|
+
if (route.hasStaticParams) return module.params?.() ?? []
|
|
227
|
+
if (route.usesRequest && !optedStatic) return []
|
|
228
|
+
return route.mode === 'static' || optedStatic ? [{}] : []
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
export function routeParamsFromPath(
|
|
231
232
|
route: Pick<RouteManifestEntry, 'catchAll' | 'params' | 'pattern'>,
|
|
232
233
|
pathname: string,
|
|
233
234
|
) {
|
|
234
|
-
const match = new RegExp(`^${route.pattern}$`).exec(pathname)
|
|
235
|
-
const params: Record<string, RouteParamValue> = {}
|
|
236
|
-
if (!match) return params
|
|
235
|
+
const match = new RegExp(`^${route.pattern}$`).exec(pathname)
|
|
236
|
+
const params: Record<string, RouteParamValue> = {}
|
|
237
|
+
if (!match) return params
|
|
237
238
|
|
|
238
239
|
for (let index = 0; index < route.params.length; index += 1) {
|
|
239
|
-
params[route.params[index]!] = safeDecode(match[index + 1] ?? '')
|
|
240
|
+
params[route.params[index]!] = safeDecode(match[index + 1] ?? '')
|
|
240
241
|
}
|
|
241
242
|
if (route.catchAll) {
|
|
242
243
|
params[route.catchAll] = (match[route.params.length + 1] ?? '')
|
|
243
244
|
.split('/')
|
|
244
245
|
.filter(Boolean)
|
|
245
|
-
.map(safeDecode)
|
|
246
|
+
.map(safeDecode)
|
|
246
247
|
}
|
|
247
|
-
return params
|
|
248
|
+
return params
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
function missingMethodResponse(module: RouteHandlerModule, method: string) {
|
|
251
|
-
const headers = { Allow: allowedMethods(module).join(', ') }
|
|
252
|
-
if (method === 'OPTIONS') return new Response(null, { status: 204, headers })
|
|
253
|
-
return new Response(null, { status: 405, headers })
|
|
252
|
+
const headers = { Allow: allowedMethods(module).join(', ') }
|
|
253
|
+
if (method === 'OPTIONS') return new Response(null, { status: 204, headers })
|
|
254
|
+
return new Response(null, { status: 405, headers })
|
|
254
255
|
}
|
|
255
256
|
|
|
256
257
|
function allowedMethods(module: RouteHandlerModule) {
|
|
257
|
-
const allowed = new Set(routeMethods.filter(method => typeof module[method] === 'function'))
|
|
258
|
+
const allowed = new Set(routeMethods.filter(method => typeof module[method] === 'function'))
|
|
258
259
|
// GET implies auto-HEAD; every route gets auto-OPTIONS. The Allow header is
|
|
259
260
|
// sorted (Next emits e.g. `OPTIONS, POST`).
|
|
260
|
-
if (allowed.has('GET')) allowed.add('HEAD')
|
|
261
|
-
allowed.add('OPTIONS')
|
|
262
|
-
return [...allowed].sort()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function safeDecode(value: string) {
|
|
266
|
-
try {
|
|
267
|
-
return decodeURIComponent(value);
|
|
268
|
-
} catch {
|
|
269
|
-
return value;
|
|
270
|
-
}
|
|
261
|
+
if (allowed.has('GET')) allowed.add('HEAD')
|
|
262
|
+
allowed.add('OPTIONS')
|
|
263
|
+
return [...allowed].sort()
|
|
271
264
|
}
|
package/src/routing/href.ts
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
import type { RouteParamValue } from '../types'
|
|
1
|
+
import type { PrefetchMode, RouteParamValue } from '../types'
|
|
2
2
|
|
|
3
|
-
export type SearchValue = string | number | boolean | null | undefined
|
|
4
|
-
export type SearchInput = URLSearchParams | Record<string, SearchValue | SearchValue[]
|
|
3
|
+
export type SearchValue = string | number | boolean | null | undefined
|
|
4
|
+
export type SearchInput = URLSearchParams | Record<string, SearchValue | SearchValue[]>
|
|
5
5
|
|
|
6
6
|
export interface HrefParts {
|
|
7
|
-
params?: Record<string, RouteParamValue
|
|
8
|
-
search?: SearchInput
|
|
9
|
-
hash?: string
|
|
7
|
+
params?: Record<string, RouteParamValue>
|
|
8
|
+
search?: SearchInput
|
|
9
|
+
hash?: string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function routeHref(route: string, options: HrefParts = {}) {
|
|
13
|
-
const path = applyTrailingSlash(fillParams(route, options.params ?? {}))
|
|
14
|
-
const search = searchString(options.search)
|
|
15
|
-
const hash = hashString(options.hash)
|
|
16
|
-
return `${path}${search}${hash}
|
|
13
|
+
const path = applyTrailingSlash(fillParams(route, options.params ?? {}))
|
|
14
|
+
const search = searchString(options.search)
|
|
15
|
+
const hash = hashString(options.hash)
|
|
16
|
+
return `${path}${search}${hash}`
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
declare global {
|
|
20
20
|
interface Window {
|
|
21
|
-
__PNEXT_TRAILING_SLASH__?: boolean
|
|
21
|
+
__PNEXT_TRAILING_SLASH__?: boolean
|
|
22
|
+
__PNEXT_PREFETCH__?: PrefetchMode
|
|
22
23
|
}
|
|
23
|
-
var __PNEXT_TRAILING_SLASH__: boolean | undefined
|
|
24
|
+
var __PNEXT_TRAILING_SLASH__: boolean | undefined
|
|
25
|
+
var __PNEXT_PREFETCH__: PrefetchMode | undefined
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// With `trailingSlash: true` the server 308s bare page URLs to their slashed
|
|
@@ -31,16 +33,33 @@ declare global {
|
|
|
31
33
|
// Anchored on globalThis, not a module local: this module is dual-copied into
|
|
32
34
|
// the client/prebundled bundles, where setTrailingSlashUrls never runs.
|
|
33
35
|
export function setTrailingSlashUrls(enabled: boolean) {
|
|
34
|
-
globalThis.__PNEXT_TRAILING_SLASH__ = enabled
|
|
36
|
+
globalThis.__PNEXT_TRAILING_SLASH__ = enabled
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
function isTrailingSlashEnabled(): boolean {
|
|
38
|
-
if (process.browser || typeof window !== 'undefined')
|
|
39
|
-
|
|
40
|
+
if (process.browser || typeof window !== 'undefined')
|
|
41
|
+
return window.__PNEXT_TRAILING_SLASH__ === true
|
|
42
|
+
return globalThis.__PNEXT_TRAILING_SLASH__ === true
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function getTrailingSlashUrls() {
|
|
43
|
-
return isTrailingSlashEnabled()
|
|
46
|
+
return isTrailingSlashEnabled()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The app-wide default prefetch mode (config `prefetch`), for links that set
|
|
50
|
+
// none of their own. Isomorphic like the trailing-slash seam: the server sets it
|
|
51
|
+
// from the resolved config, the client reads the injected window global
|
|
52
|
+
// (renderer's prefetchModeScript).
|
|
53
|
+
export function setDefaultPrefetchMode(mode: PrefetchMode | undefined) {
|
|
54
|
+
globalThis.__PNEXT_PREFETCH__ = mode
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getDefaultPrefetchMode(): PrefetchMode {
|
|
58
|
+
const mode =
|
|
59
|
+
process.browser || typeof window !== 'undefined'
|
|
60
|
+
? window.__PNEXT_PREFETCH__
|
|
61
|
+
: globalThis.__PNEXT_PREFETCH__
|
|
62
|
+
return mode === undefined ? 'visible' : mode
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
// The configured basePath ('' when unset). Set once per process by the server
|
|
@@ -48,14 +67,14 @@ export function getTrailingSlashUrls() {
|
|
|
48
67
|
// trailing-slash seam. Core render code (metadata) reads it through
|
|
49
68
|
// withBasePath so file-convention asset hrefs (og-image, manifest) carry the
|
|
50
69
|
// prefix without a compat/next.config import in core.
|
|
51
|
-
let basePathPrefix = ''
|
|
70
|
+
let basePathPrefix = ''
|
|
52
71
|
|
|
53
72
|
export function setBasePathPrefix(value: string) {
|
|
54
|
-
basePathPrefix = value
|
|
73
|
+
basePathPrefix = value
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
export function getBasePathPrefix() {
|
|
58
|
-
return basePathPrefix
|
|
77
|
+
return basePathPrefix
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
/**
|
|
@@ -65,26 +84,26 @@ export function getBasePathPrefix() {
|
|
|
65
84
|
* always a no-op. Any query/hash on the path rides along untouched.
|
|
66
85
|
*/
|
|
67
86
|
export function withBasePath(path: string) {
|
|
68
|
-
const base = basePathPrefix
|
|
69
|
-
if (!base) return path
|
|
70
|
-
if (!path.startsWith('/') || path.startsWith('//')) return path
|
|
71
|
-
if (path === base || path.startsWith(`${base}/`)) return path
|
|
72
|
-
return `${base}${path}
|
|
87
|
+
const base = basePathPrefix
|
|
88
|
+
if (!base) return path
|
|
89
|
+
if (!path.startsWith('/') || path.startsWith('//')) return path
|
|
90
|
+
if (path === base || path.startsWith(`${base}/`)) return path
|
|
91
|
+
return `${base}${path}`
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
export function applyTrailingSlash(pathname: string) {
|
|
76
|
-
const [, path = '', rest = ''] = /^([^?#]*)([\s\S]*)$/.exec(pathname) ?? []
|
|
77
|
-
if (!path.startsWith('/')) return pathname
|
|
95
|
+
const [, path = '', rest = ''] = /^([^?#]*)([\s\S]*)$/.exec(pathname) ?? []
|
|
96
|
+
if (!path.startsWith('/')) return pathname
|
|
78
97
|
if (isTrailingSlashEnabled()) {
|
|
79
98
|
// Add the canonical trailing slash (skip root, already-slashed, and files).
|
|
80
|
-
if (path === '/' || path.endsWith('/') || /\.[^/]+$/.test(path)) return pathname
|
|
81
|
-
return `${path}/${rest}
|
|
99
|
+
if (path === '/' || path.endsWith('/') || /\.[^/]+$/.test(path)) return pathname
|
|
100
|
+
return `${path}/${rest}`
|
|
82
101
|
}
|
|
83
102
|
// trailingSlash:false — Next strips a trailing slash from hrefs so the anchor
|
|
84
103
|
// matches the canonical (unslashed) form and avoids a 308 on click. Root and
|
|
85
104
|
// file paths (`/a.txt/` is not a thing) are left alone.
|
|
86
|
-
if (path === '/' || !path.endsWith('/') || /\.[^/]+\/$/.test(path)) return pathname
|
|
87
|
-
return `${path.replace(/\/+$/, '')}${rest}
|
|
105
|
+
if (path === '/' || !path.endsWith('/') || /\.[^/]+\/$/.test(path)) return pathname
|
|
106
|
+
return `${path.replace(/\/+$/, '')}${rest}`
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
function fillParams(route: string, params: Record<string, RouteParamValue>) {
|
|
@@ -93,47 +112,47 @@ function fillParams(route: string, params: Record<string, RouteParamValue>) {
|
|
|
93
112
|
// catch-all form so its doubled brackets aren't mis-parsed.
|
|
94
113
|
.replace(/\[\[\.\.\.([^\]]+)\]\]/g, (_match, key: string) => encodeParam(params[key], true))
|
|
95
114
|
.replace(/\[\.\.\.([^\]]+)\]/g, (_match, key: string) => encodeParam(params[key], true))
|
|
96
|
-
.replace(/\[([^\]]+)\]/g, (_match, key: string) => encodeParam(params[key], false))
|
|
115
|
+
.replace(/\[([^\]]+)\]/g, (_match, key: string) => encodeParam(params[key], false))
|
|
97
116
|
// An absent optional catch-all (`[[...slug]]`) leaves a dangling `/`; drop it
|
|
98
117
|
// so /docs/:slug* with no params yields /docs, not /docs/.
|
|
99
|
-
return collapseTrailingSlash(filled)
|
|
118
|
+
return collapseTrailingSlash(filled)
|
|
100
119
|
}
|
|
101
120
|
|
|
102
121
|
function collapseTrailingSlash(pathname: string) {
|
|
103
|
-
if (pathname.length <= 1) return pathname
|
|
104
|
-
return pathname.replace(/\/+$/, '') || '/'
|
|
122
|
+
if (pathname.length <= 1) return pathname
|
|
123
|
+
return pathname.replace(/\/+$/, '') || '/'
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
function encodeParam(value: RouteParamValue | undefined, catchAll: boolean) {
|
|
108
127
|
if (Array.isArray(value)) {
|
|
109
|
-
const encoded = value.map(segment => encodeURIComponent(segment))
|
|
110
|
-
return catchAll ? encoded.join('/') : encoded[0] ?? ''
|
|
128
|
+
const encoded = value.map(segment => encodeURIComponent(segment))
|
|
129
|
+
return catchAll ? encoded.join('/') : (encoded[0] ?? '')
|
|
111
130
|
}
|
|
112
|
-
return encodeURIComponent(value ?? '')
|
|
131
|
+
return encodeURIComponent(value ?? '')
|
|
113
132
|
}
|
|
114
133
|
|
|
115
134
|
function searchString(search: SearchInput | undefined) {
|
|
116
|
-
if (!search) return ''
|
|
117
|
-
const params = search instanceof URLSearchParams ? search : objectSearchParams(search)
|
|
118
|
-
const value = params.toString()
|
|
119
|
-
return value ? `?${value}` : ''
|
|
135
|
+
if (!search) return ''
|
|
136
|
+
const params = search instanceof URLSearchParams ? search : objectSearchParams(search)
|
|
137
|
+
const value = params.toString()
|
|
138
|
+
return value ? `?${value}` : ''
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
function objectSearchParams(search: Record<string, SearchValue | SearchValue[]>) {
|
|
123
|
-
const params = new URLSearchParams()
|
|
142
|
+
const params = new URLSearchParams()
|
|
124
143
|
for (const [key, value] of Object.entries(search)) {
|
|
125
|
-
const values = Array.isArray(value) ? value : [value]
|
|
144
|
+
const values = Array.isArray(value) ? value : [value]
|
|
126
145
|
for (const item of values) {
|
|
127
|
-
if (item == null) continue
|
|
128
|
-
params.append(key, String(item))
|
|
146
|
+
if (item == null) continue
|
|
147
|
+
params.append(key, String(item))
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
|
-
return params
|
|
150
|
+
return params
|
|
132
151
|
}
|
|
133
152
|
|
|
134
153
|
function hashString(hash: string | undefined) {
|
|
135
|
-
if (!hash) return ''
|
|
136
|
-
return hash.startsWith('#') ? hash : `#${hash}
|
|
154
|
+
if (!hash) return ''
|
|
155
|
+
return hash.startsWith('#') ? hash : `#${hash}`
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
/**
|
|
@@ -142,18 +161,18 @@ function hashString(hash: string | undefined) {
|
|
|
142
161
|
* Next. Returns the response when the path cannot be decoded, else null.
|
|
143
162
|
*/
|
|
144
163
|
export function malformedUrlResponse(request: Request): Response | null {
|
|
145
|
-
let pathname: string
|
|
164
|
+
let pathname: string
|
|
146
165
|
try {
|
|
147
|
-
pathname = new URL(request.url).pathname
|
|
166
|
+
pathname = new URL(request.url).pathname
|
|
148
167
|
} catch {
|
|
149
|
-
return new Response('Bad Request', { status: 400 })
|
|
168
|
+
return new Response('Bad Request', { status: 400 })
|
|
150
169
|
}
|
|
151
|
-
if (!pathname.includes('%')) return null
|
|
170
|
+
if (!pathname.includes('%')) return null
|
|
152
171
|
try {
|
|
153
|
-
decodeURIComponent(pathname)
|
|
154
|
-
return null
|
|
172
|
+
decodeURIComponent(pathname)
|
|
173
|
+
return null
|
|
155
174
|
} catch {
|
|
156
|
-
return new Response('Bad Request', { status: 400 })
|
|
175
|
+
return new Response('Bad Request', { status: 400 })
|
|
157
176
|
}
|
|
158
177
|
}
|
|
159
178
|
|
|
@@ -165,13 +184,11 @@ export function malformedUrlResponse(request: Request): Response | null {
|
|
|
165
184
|
* already canonical.
|
|
166
185
|
*/
|
|
167
186
|
export function canonicalTrailingSlashPath(pathname: string, trailingSlash: boolean): string {
|
|
168
|
-
if (pathname === '/' || pathname === '') return pathname
|
|
169
|
-
if (/\.[^/]+$/.test(pathname)) return pathname
|
|
170
|
-
const slashed = pathname.endsWith('/')
|
|
171
|
-
if (trailingSlash ? slashed : !slashed) return pathname
|
|
172
|
-
return trailingSlash
|
|
173
|
-
? `${pathname.replace(/\/+$/, '')}/`
|
|
174
|
-
: pathname.replace(/\/+$/, '') || '/';
|
|
187
|
+
if (pathname === '/' || pathname === '') return pathname
|
|
188
|
+
if (/\.[^/]+$/.test(pathname)) return pathname
|
|
189
|
+
const slashed = pathname.endsWith('/')
|
|
190
|
+
if (trailingSlash ? slashed : !slashed) return pathname
|
|
191
|
+
return trailingSlash ? `${pathname.replace(/\/+$/, '')}/` : pathname.replace(/\/+$/, '') || '/'
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
/**
|
|
@@ -188,15 +205,15 @@ export function trailingSlashRedirect(
|
|
|
188
205
|
// skipTrailingSlashRedirect: the app serves both slashed and unslashed URLs
|
|
189
206
|
// as-is, so no canonical redirect is ever emitted (route matching already
|
|
190
207
|
// tolerates a trailing slash via normalizePathname).
|
|
191
|
-
if (config.skipTrailingSlashRedirect) return null
|
|
192
|
-
if (method !== 'GET' && method !== 'HEAD') return null
|
|
193
|
-
const pathname = url.pathname
|
|
194
|
-
if (pathname === '/' || pathname === '') return null
|
|
195
|
-
if (pathname.startsWith('/__pnext') || pathname.startsWith('/assets/')) return null
|
|
196
|
-
const target = canonicalTrailingSlashPath(pathname, Boolean(config.trailingSlash))
|
|
197
|
-
if (target === pathname) return null
|
|
208
|
+
if (config.skipTrailingSlashRedirect) return null
|
|
209
|
+
if (method !== 'GET' && method !== 'HEAD') return null
|
|
210
|
+
const pathname = url.pathname
|
|
211
|
+
if (pathname === '/' || pathname === '') return null
|
|
212
|
+
if (pathname.startsWith('/__pnext') || pathname.startsWith('/assets/')) return null
|
|
213
|
+
const target = canonicalTrailingSlashPath(pathname, Boolean(config.trailingSlash))
|
|
214
|
+
if (target === pathname) return null
|
|
198
215
|
return new Response(null, {
|
|
199
216
|
status: 308,
|
|
200
217
|
headers: { location: `${target}${url.search}` },
|
|
201
|
-
})
|
|
218
|
+
})
|
|
202
219
|
}
|