@wular/pnext 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Routing
|
|
2
|
+
|
|
3
|
+
## Files
|
|
4
|
+
|
|
5
|
+
- `app/**/page` defines a UI route.
|
|
6
|
+
- `app/**/layout` exports metadata and can wrap descendant pages.
|
|
7
|
+
- `app/**/route` defines HTTP handlers.
|
|
8
|
+
- `proxy` or `middleware` runs before route matching.
|
|
9
|
+
- `app/**/loading` defines a Suspense fallback for the segment.
|
|
10
|
+
- `app/**/error` defines fallback UI for server render failures in the segment.
|
|
11
|
+
- `app/**/not-found` defines fallback UI.
|
|
12
|
+
|
|
13
|
+
Convention files are matched across `.tsx`, `.ts`, `.jsx`, `.js`, and `.mjs`.
|
|
14
|
+
`.tsx`/`.ts` are tried first, so an all-`.tsx` app resolves on the first
|
|
15
|
+
candidate. Route handlers (`route`) match `.ts`, `.tsx`, `.js`, `.mjs`.
|
|
16
|
+
|
|
17
|
+
## Segments
|
|
18
|
+
|
|
19
|
+
- `app/about/page.tsx` -> `/about`
|
|
20
|
+
- `app/users/[id]/page.tsx` -> `/users/:id`
|
|
21
|
+
- `app/docs/[...slug]/page.tsx` -> `/docs/*`
|
|
22
|
+
- `app/docs/[[...slug]]/page.tsx` -> matches `/docs` AND `/docs/*` (the base path is included)
|
|
23
|
+
- `app/(marketing)/pricing/page.tsx` -> `/pricing`
|
|
24
|
+
- `app/dashboard/@team/page.tsx` -> a `@slot` directory adds no path segment; it is rendered as a slot of `/dashboard`
|
|
25
|
+
|
|
26
|
+
### Intercepting markers
|
|
27
|
+
|
|
28
|
+
A marker prefixes a segment to intercept a route from another level. The marker
|
|
29
|
+
is stripped from the resulting path. See [Intercepting routes](#intercepting-routes).
|
|
30
|
+
|
|
31
|
+
- `(.)segment` -> same level.
|
|
32
|
+
- `(..)segment` -> one level up.
|
|
33
|
+
- `(..)(..)segment` -> two levels up.
|
|
34
|
+
- `(...)segment` -> from the app root.
|
|
35
|
+
|
|
36
|
+
## Page Props
|
|
37
|
+
|
|
38
|
+
Pages receive explicit props:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
type PageProps<P = Record<string, string | string[]>> = {
|
|
42
|
+
params: Promise<P>;
|
|
43
|
+
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
|
44
|
+
request?: NextRequest;
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Request data is read from `props.request`.
|
|
49
|
+
|
|
50
|
+
## Route Handlers
|
|
51
|
+
|
|
52
|
+
Route handlers use the Next-compatible `(request, context)` shape:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import type { RouteContext } from '#gen/app/api/users/[id]/route';
|
|
56
|
+
import type { NextRequest } from '@wular/pnext/server';
|
|
57
|
+
|
|
58
|
+
export async function GET(request: NextRequest, { params }: RouteContext) {
|
|
59
|
+
const { id } = await params;
|
|
60
|
+
return Response.json({
|
|
61
|
+
id,
|
|
62
|
+
q: request.nextUrl.searchParams.get('q'),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`request` is a `NextRequest` compatible object. It extends the standard `Request` with convenience access for cookies and `nextUrl`.
|
|
68
|
+
|
|
69
|
+
If `OPTIONS` is not exported, pnext returns an automatic `OPTIONS` response with an `Allow` header. Unsupported methods return `405 Method Not Allowed` with the same `Allow` header.
|
|
70
|
+
|
|
71
|
+
Static `GET` route handlers are written as files during `pnext build`. Dynamic route handlers can export `params()` to write selected param outputs at build time.
|
|
72
|
+
|
|
73
|
+
`generateStaticParams()` is supported in Next compat mode.
|
|
74
|
+
|
|
75
|
+
## Route Rendering
|
|
76
|
+
|
|
77
|
+
pnext infers route rendering separately from hydration:
|
|
78
|
+
|
|
79
|
+
- `static`: no dynamic params and no request prop usage.
|
|
80
|
+
- `dynamic`: request prop usage or dynamic route segments.
|
|
81
|
+
|
|
82
|
+
Route segment config overrides the inference. Hydration is controlled by `"use client"`.
|
|
83
|
+
|
|
84
|
+
## Route Segment Config
|
|
85
|
+
|
|
86
|
+
Pages, layouts, and route handlers can export segment config. Values must be literals — they are read from the source, not evaluated.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
export const dynamic = 'force-dynamic';
|
|
90
|
+
export const maxDuration = 60;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Recognized: `dynamic` (`auto` | `force-dynamic` | `force-static` | `error`), `revalidate` (a non-negative number or `false`), `fetchCache`, `dynamicParams`, `runtime` (`edge` | `nodejs`), `prefetch`, and `maxDuration`.
|
|
94
|
+
|
|
95
|
+
Config merges across the route's own file and its layout chain, leaf-first: the nearest declaration wins, except `force-dynamic` (any segment forces the whole route) and `revalidate` (the lowest declared number wins).
|
|
96
|
+
|
|
97
|
+
When building with `pnext build --adapter vercel`, `maxDuration` is copied to the generated Vercel Node function metadata.
|
|
98
|
+
|
|
99
|
+
## File Conventions
|
|
100
|
+
|
|
101
|
+
`proxy.ts` and `middleware.ts` run before static files, pages, and route handlers. Use `proxy.ts` for new code. `middleware.ts` is supported for compatibility. If both exist, `proxy.ts` wins.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { NextResponse, type NextRequest } from '@wular/pnext/server';
|
|
105
|
+
|
|
106
|
+
export const config = {
|
|
107
|
+
matcher: ['/admin/:path*'],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export function proxy(request: NextRequest) {
|
|
111
|
+
if (!request.cookies.has('session')) {
|
|
112
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
113
|
+
}
|
|
114
|
+
return NextResponse.next();
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Supported responses:
|
|
119
|
+
|
|
120
|
+
- `NextResponse.next()` continues to the matched static file, page, or route handler.
|
|
121
|
+
- `NextResponse.redirect(url)` returns a redirect response.
|
|
122
|
+
- `NextResponse.rewrite(url)` serves another path without changing the browser URL.
|
|
123
|
+
- `new Response(...)` returns a custom response directly.
|
|
124
|
+
|
|
125
|
+
`config.matcher` takes a string, a string array, or objects with `source` plus `has`/`missing` conditions (`header`, `query`, `cookie`, `host`). Patterns support exact paths, `:param`, `:path*`, and regex. With no `matcher`, every request runs through the proxy.
|
|
126
|
+
|
|
127
|
+
`loading.tsx` is used as the fallback for automatically wrapped server Suspense boundaries. Static builds wait for final HTML; dev and server responses can stream the fallback first.
|
|
128
|
+
|
|
129
|
+
`not-found.tsx` is rendered when a route calls `notFound()` or when no route matches. `pnext build` also writes `404.html` when root `app/not-found.tsx` exists.
|
|
130
|
+
|
|
131
|
+
`global-error.tsx` at the app root replaces the entire document when an unhandled error reaches the root uncaught. It renders its own `<html>` and `<body>`, so pnext skips the normal document shell and serves its markup directly. It only takes over when no nearer `error.tsx` boundary catches first, and a `"use client"` directive opts the file out.
|
|
132
|
+
|
|
133
|
+
`redirect()` and `permanentRedirect()` stop rendering and return redirect responses. They can be called from pages, server components, route handlers, and server helpers used during rendering.
|
|
134
|
+
Validate redirect targets that come from user input.
|
|
135
|
+
|
|
136
|
+
`error.tsx` is rendered when server rendering throws. It receives `error`, `reset`, and `unstable_retry`; `reset()`/`unstable_retry()` refresh the current route in `compat.next` apps.
|
|
137
|
+
|
|
138
|
+
`template.tsx` wraps the segment's children like a layout, but the segment re-renders per request where a layout persists. Nesting order is layout > template > children. A segment without a layout still applies its template.
|
|
139
|
+
|
|
140
|
+
`default.tsx` is the fallback rendered for a parallel `@slot` that has no page match.
|
|
141
|
+
|
|
142
|
+
`forbidden.tsx` and `unauthorized.tsx` render at 403/401 when `forbidden()` / `unauthorized()` is thrown. These are COMPAT-only (require `compat.next`) and experimental (`authInterrupts`); without compat they fall through to the normal error page.
|
|
143
|
+
|
|
144
|
+
### Parallel routes
|
|
145
|
+
|
|
146
|
+
A `@slot` directory (e.g. `@team`) contributes no path segment. Its content is rendered and passed to the owning segment's layout as a prop keyed by the slot name (without the `@`), alongside `children`. A slot uses its `page` when present, else its `default` fallback, else renders nothing. A slot's `loading` wraps its content in a Suspense boundary.
|
|
147
|
+
|
|
148
|
+
### Intercepting routes
|
|
149
|
+
|
|
150
|
+
An interception marker (`(.)`, `(..)`, `(..)(..)`, `(...)`) makes the route respond to soft navigations toward the marked target from the intercepting level, usually rendered into a parallel `@slot`. Hard requests render the non-intercepted target. Intercepting routes are always dynamic and never take static params.
|
|
151
|
+
|
|
152
|
+
## Static Params
|
|
153
|
+
|
|
154
|
+
Use `params()` on a dynamic route to choose params that `pnext build` should write as static HTML.
|
|
155
|
+
Requests outside the returned list keep using dynamic rendering.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import type { PageProps, StaticParams } from '#gen/app/users/[id]/page';
|
|
159
|
+
|
|
160
|
+
export function params(): StaticParams {
|
|
161
|
+
return [{ id: 'ada' }, { id: 'grace' }];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default async function Page({ params }: PageProps) {
|
|
165
|
+
return <p>{(await params).id}</p>;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Type Safety
|
|
2
|
+
|
|
3
|
+
pnext generates route types from the `app/` tree. These types check route params, static params, route handlers, and typed navigation.
|
|
4
|
+
|
|
5
|
+
## Route Types
|
|
6
|
+
|
|
7
|
+
Import generated types for the current route. pnext validates those exports against the route inferred from the file path, so a wrong param name, static param shape, or route handler context fails TypeScript.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// app/users/[id]/page.tsx
|
|
11
|
+
import type { PageProps, StaticParams } from '#gen/app/users/[id]/page';
|
|
12
|
+
|
|
13
|
+
export function params(): StaticParams {
|
|
14
|
+
return [{ id: 'ada' }];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function Page({ params }: PageProps) {
|
|
18
|
+
return <p>{(await params).id}</p>;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Route handlers use generated `RouteContext` for the second argument:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// app/users/[id]/route.ts
|
|
26
|
+
import type { RouteContext } from '#gen/app/users/[id]/route';
|
|
27
|
+
import type { NextRequest } from '@wular/pnext/server';
|
|
28
|
+
|
|
29
|
+
export async function GET(request: NextRequest, { params }: RouteContext) {
|
|
30
|
+
return Response.json({ id: (await params).id });
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
Typed links use typed `href` and `params`:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Link } from '@wular/pnext/link';
|
|
40
|
+
|
|
41
|
+
export function Nav() {
|
|
42
|
+
return <Link href="/users/[id]" params={{ id: 'ada' }}>Ada</Link>;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use a normal `<a>` for external URLs or intentionally untyped links.
|
|
47
|
+
|
|
48
|
+
## Generated Types
|
|
49
|
+
|
|
50
|
+
`pnext dev` and `pnext build` refresh generated types automatically. Use `pnext typegen` to refresh them manually.
|
|
51
|
+
|
|
52
|
+
Generated files live in `.pnext/types` and should not be edited. Include `.pnext/types/**/*.ts` in the app TypeScript project for editor and `tsc` visibility.
|
|
53
|
+
|
|
54
|
+
Use a TypeScript path alias for generated imports:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"compilerOptions": {
|
|
59
|
+
"paths": {
|
|
60
|
+
"#gen/*": ["./.pnext/types/*"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
package/src/api/cache.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { currentCacheScope, type CacheNode } from '../cache/context';
|
|
2
|
+
|
|
3
|
+
export function cache<Args extends unknown[], Result>(
|
|
4
|
+
fn: (...args: Args) => Result,
|
|
5
|
+
): (...args: Args) => Result {
|
|
6
|
+
return (...args: Args) => {
|
|
7
|
+
const scope = currentCacheScope();
|
|
8
|
+
if (!scope) return fn(...args);
|
|
9
|
+
|
|
10
|
+
let root = scope.functions.get(fn);
|
|
11
|
+
if (!root) {
|
|
12
|
+
root = cacheNode();
|
|
13
|
+
scope.functions.set(fn, root);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const node = cacheNodeForArgs(root, args);
|
|
17
|
+
const record = node.record;
|
|
18
|
+
if (record) {
|
|
19
|
+
if (record.status === 'rejected') {
|
|
20
|
+
if (record.async) {
|
|
21
|
+
return Promise.resolve().then(() => {
|
|
22
|
+
throw record.error;
|
|
23
|
+
}) as Result;
|
|
24
|
+
}
|
|
25
|
+
throw record.error;
|
|
26
|
+
}
|
|
27
|
+
if (record.status === 'resolved' && record.async) return Promise.resolve(record.value) as Result;
|
|
28
|
+
return record.value as Result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const value = fn(...args);
|
|
33
|
+
if (isPromiseLike(value)) {
|
|
34
|
+
const promise = Promise.resolve(value).then(
|
|
35
|
+
result => {
|
|
36
|
+
node.record = { status: 'resolved', value: result, async: true };
|
|
37
|
+
return result;
|
|
38
|
+
},
|
|
39
|
+
error => {
|
|
40
|
+
node.record = { status: 'rejected', error, async: true };
|
|
41
|
+
throw error;
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
node.record = { status: 'pending', value: promise };
|
|
45
|
+
return promise as Result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
node.record = { status: 'resolved', value, async: false };
|
|
49
|
+
return value;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
node.record = { status: 'rejected', error, async: false };
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function cacheNode(): CacheNode {
|
|
58
|
+
return { children: new Map() };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function cacheNodeForArgs(root: CacheNode, args: unknown[]) {
|
|
62
|
+
let node = root;
|
|
63
|
+
for (const arg of args) {
|
|
64
|
+
let child = node.children.get(arg);
|
|
65
|
+
if (!child) {
|
|
66
|
+
child = cacheNode();
|
|
67
|
+
node.children.set(arg, child);
|
|
68
|
+
}
|
|
69
|
+
node = child;
|
|
70
|
+
}
|
|
71
|
+
return node;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
75
|
+
return (
|
|
76
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
77
|
+
value !== null &&
|
|
78
|
+
'then' in value
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
+
import { ReadonlyURLSearchParams, type HrefOptions } from './navigation';
|
|
3
|
+
import { blockJavascriptUrl, prefetchRoute, softNavigate } from './router';
|
|
4
|
+
import { seedHistoryEntry } from './router/history';
|
|
5
|
+
import {
|
|
6
|
+
emitLocationChange,
|
|
7
|
+
locationListeners,
|
|
8
|
+
scheduleNavigationScroll,
|
|
9
|
+
} from './router/events';
|
|
10
|
+
import { routeHref } from '../routing/href';
|
|
11
|
+
import type { RouteParams, RouteParamValue, RoutePath } from '../types';
|
|
12
|
+
|
|
13
|
+
export type NavigateOptions<Route extends RoutePath> = HrefOptions<Route> & {
|
|
14
|
+
scroll?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PrefetchOptions<Route extends RoutePath> = HrefOptions<Route> & {
|
|
18
|
+
kind?: 'auto' | 'full';
|
|
19
|
+
onInvalidate?: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface PNextRouter {
|
|
23
|
+
push<Route extends RoutePath>(
|
|
24
|
+
route: Route,
|
|
25
|
+
...options: RouteParams<Route> extends Record<string, never>
|
|
26
|
+
? [options?: NavigateOptions<Route>]
|
|
27
|
+
: [options: NavigateOptions<Route>]
|
|
28
|
+
): void;
|
|
29
|
+
replace<Route extends RoutePath>(
|
|
30
|
+
route: Route,
|
|
31
|
+
...options: RouteParams<Route> extends Record<string, never>
|
|
32
|
+
? [options?: NavigateOptions<Route>]
|
|
33
|
+
: [options: NavigateOptions<Route>]
|
|
34
|
+
): void;
|
|
35
|
+
prefetch<Route extends RoutePath>(
|
|
36
|
+
route: Route,
|
|
37
|
+
...options: RouteParams<Route> extends Record<string, never>
|
|
38
|
+
? [options?: PrefetchOptions<Route>]
|
|
39
|
+
: [options: PrefetchOptions<Route>]
|
|
40
|
+
): void;
|
|
41
|
+
refresh(): void;
|
|
42
|
+
back(): void;
|
|
43
|
+
forward(): void;
|
|
44
|
+
bfcacheId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { ReadonlyURLSearchParams };
|
|
48
|
+
|
|
49
|
+
export interface CurrentRoute<Route extends RoutePath = RoutePath> {
|
|
50
|
+
route: Route | null;
|
|
51
|
+
pathname: string;
|
|
52
|
+
params: RouteParams<Route>;
|
|
53
|
+
searchParams: ReadonlyURLSearchParams;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface RouteState {
|
|
57
|
+
route?: string;
|
|
58
|
+
params?: Record<string, RouteParamValue>;
|
|
59
|
+
catchAllOptional?: boolean;
|
|
60
|
+
staticChildren?: string[];
|
|
61
|
+
bfcacheId?: string;
|
|
62
|
+
/** `prefetch = 'allow-runtime'`: this route's shell is request-sampled. */
|
|
63
|
+
runtimePrefetch?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* `__PNEXT_ROUTE__.route` is stored on the wire in bracket-free colon form
|
|
68
|
+
* (`/:teamSlug/:slug*`) so no `[x]`/`%5Bx%5D` placeholder ever reaches a
|
|
69
|
+
* response body. The public `useRoute()` contract exposes the familiar bracket
|
|
70
|
+
* pattern (`/[teamSlug]/[...slug]`), so convert back at the API boundary.
|
|
71
|
+
*/
|
|
72
|
+
function bracketRoutePattern(state: RouteState): string | null {
|
|
73
|
+
const route = state.route;
|
|
74
|
+
if (route == null) return null;
|
|
75
|
+
return route
|
|
76
|
+
.replace(/:([\w$]+)\*/g, state.catchAllOptional ? '[[...$1]]' : '[...$1]')
|
|
77
|
+
.replace(/:([\w$]+)/g, '[$1]');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare global {
|
|
81
|
+
interface Window {
|
|
82
|
+
__PNEXT_ROUTE__?: RouteState;
|
|
83
|
+
__PNEXT_HISTORY_PATCHED__?: boolean;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// This module is the reason the hard-loaded entry needs its history ids before first render:
|
|
88
|
+
// `bfcacheId` below reads them, and `patchHistory` carries them onto an app-level pushState. Seeding
|
|
89
|
+
// here rather than in the hub's installRouter keeps a page whose client graph never touches router
|
|
90
|
+
// state from paying for them.
|
|
91
|
+
seedHistoryEntry();
|
|
92
|
+
|
|
93
|
+
export function useRouter(): PNextRouter {
|
|
94
|
+
return router;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const router: PNextRouter = {
|
|
98
|
+
push(route, ...[options]) {
|
|
99
|
+
navigate(routerHref(route, options), 'push', options?.scroll);
|
|
100
|
+
},
|
|
101
|
+
replace(route, ...[options]) {
|
|
102
|
+
navigate(routerHref(route, options), 'replace', options?.scroll);
|
|
103
|
+
},
|
|
104
|
+
prefetch(route, ...[options]) {
|
|
105
|
+
void prefetchRoute(routerHref(route, options), {
|
|
106
|
+
strict: true,
|
|
107
|
+
full: options?.kind === 'full',
|
|
108
|
+
onInvalidate: options?.onInvalidate,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
refresh() {
|
|
112
|
+
void softNavigate(window.location.href, { replace: true, scroll: false });
|
|
113
|
+
},
|
|
114
|
+
back() {
|
|
115
|
+
window.history.back();
|
|
116
|
+
},
|
|
117
|
+
forward() {
|
|
118
|
+
window.history.forward();
|
|
119
|
+
},
|
|
120
|
+
get bfcacheId() {
|
|
121
|
+
return routeState().bfcacheId ?? '';
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function routerHref<Route extends RoutePath>(route: Route, options: HrefOptions<Route> | undefined) {
|
|
126
|
+
return hasHrefParts(options) ? routeHref(route, options) : String(route);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function hasHrefParts<Route extends RoutePath>(
|
|
130
|
+
options: HrefOptions<Route> | undefined,
|
|
131
|
+
): options is HrefOptions<Route> {
|
|
132
|
+
return Boolean(options && ('params' in options || 'search' in options || 'hash' in options));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function usePathname() {
|
|
136
|
+
return useLocation().pathname;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function useSearchParams(): ReadonlyURLSearchParams {
|
|
140
|
+
return new ReadonlyURLSearchParams(useLocation().search);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function useParams<Route extends RoutePath = RoutePath>() {
|
|
144
|
+
useLocation();
|
|
145
|
+
return (routeState().params ?? {}) as RouteParams<Route>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function useRoute<Route extends RoutePath = RoutePath>(): CurrentRoute<Route> {
|
|
149
|
+
const location = useLocation();
|
|
150
|
+
return {
|
|
151
|
+
route: bracketRoutePattern(routeState()) as Route | null,
|
|
152
|
+
pathname: location.pathname,
|
|
153
|
+
params: (routeState().params ?? {}) as RouteParams<Route>,
|
|
154
|
+
searchParams: new ReadonlyURLSearchParams(location.search),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function useLinkStatus() {
|
|
159
|
+
return { pending: false };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function useLocation() {
|
|
163
|
+
const [location, setLocation] = useState(() => currentLocation());
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
patchHistory();
|
|
167
|
+
const listener = () =>
|
|
168
|
+
setLocation(previous => {
|
|
169
|
+
const next = currentLocation();
|
|
170
|
+
return previous.pathname === next.pathname &&
|
|
171
|
+
previous.search === next.search &&
|
|
172
|
+
previous.hash === next.hash
|
|
173
|
+
? previous
|
|
174
|
+
: next;
|
|
175
|
+
});
|
|
176
|
+
locationListeners().add(listener);
|
|
177
|
+
window.addEventListener('popstate', listener);
|
|
178
|
+
// Re-read once the listener is attached: the URL can move between the
|
|
179
|
+
// render that captured it and this effect (a history traversal landing
|
|
180
|
+
// while a swapped-in tree mounts), and nothing was listening for that one.
|
|
181
|
+
listener();
|
|
182
|
+
return () => {
|
|
183
|
+
locationListeners().delete(listener);
|
|
184
|
+
window.removeEventListener('popstate', listener);
|
|
185
|
+
};
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
return location;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function currentLocation() {
|
|
192
|
+
if (!process.browser && typeof window === 'undefined') {
|
|
193
|
+
return {
|
|
194
|
+
pathname: '/',
|
|
195
|
+
search: '',
|
|
196
|
+
hash: '',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
pathname: window.location.pathname,
|
|
201
|
+
search: window.location.search,
|
|
202
|
+
hash: window.location.hash,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function routeState() {
|
|
207
|
+
if (!process.browser && typeof window === 'undefined') return {};
|
|
208
|
+
const state = (history.state as Record<string, unknown> | null) ?? {};
|
|
209
|
+
const routeState = window.__PNEXT_ROUTE__ ?? {};
|
|
210
|
+
const bfcacheId = typeof state.__pnextBfcacheId === 'string' ? state.__pnextBfcacheId : undefined;
|
|
211
|
+
return bfcacheId ? ({ ...routeState, bfcacheId } as RouteState) : routeState;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function navigate(url: string, mode: 'push' | 'replace', scroll = true) {
|
|
215
|
+
if (blockJavascriptUrl(url)) return;
|
|
216
|
+
const next = new URL(url, window.location.href);
|
|
217
|
+
if (next.origin !== window.location.origin) {
|
|
218
|
+
const holder = globalThis as { __pnextMpaNavigation?: number };
|
|
219
|
+
holder.__pnextMpaNavigation = (holder.__pnextMpaNavigation ?? 0) + 1;
|
|
220
|
+
requestAnimationFrame(() =>
|
|
221
|
+
requestAnimationFrame(() => {
|
|
222
|
+
if (mode === 'replace') window.location.replace(next.href);
|
|
223
|
+
else window.location.assign(next.href);
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (next.pathname !== window.location.pathname || next.search !== window.location.search) {
|
|
230
|
+
void softNavigate(next.href, { replace: mode === 'replace', scroll });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (mode === 'replace') window.history.replaceState(history.state, '', next);
|
|
235
|
+
else window.history.pushState(history.state, '', next);
|
|
236
|
+
scheduleNavigationScroll(next, { scroll });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Exported so compat's link-status can guarantee raw history.pushState /
|
|
240
|
+
// replaceState calls broadcast a location change (ending a link's pending
|
|
241
|
+
// state) even when no useLocation-based hook has mounted yet.
|
|
242
|
+
export function patchHistory() {
|
|
243
|
+
if ((!process.browser && typeof window === 'undefined') || window.__PNEXT_HISTORY_PATCHED__) return;
|
|
244
|
+
window.__PNEXT_HISTORY_PATCHED__ = true;
|
|
245
|
+
const pushState = window.history.pushState.bind(window.history);
|
|
246
|
+
const replaceState = window.history.replaceState.bind(window.history);
|
|
247
|
+
|
|
248
|
+
// Broadcast only when the URL actually changes. The router itself calls replaceState with the
|
|
249
|
+
// CURRENT href for bookkeeping - including at the start of every soft navigation - and emitting for
|
|
250
|
+
// those would clear a just-clicked link's pending state.
|
|
251
|
+
//
|
|
252
|
+
// An app-level pushState/replaceState creates an entry that still shows THIS document (shallow
|
|
253
|
+
// routing). Carrying pnext's own entry state over marks the new entry as pointing at the rendered
|
|
254
|
+
// document, so a later traversal is a location broadcast rather than a fetch-and-swap of a URL that
|
|
255
|
+
// may not even exist on the server. Router-initiated calls already carry the keys.
|
|
256
|
+
function copyInternalState(data: unknown): unknown {
|
|
257
|
+
const current = (window.history.state as Record<string, unknown> | null) ?? {};
|
|
258
|
+
const carried: Record<string, unknown> = {};
|
|
259
|
+
for (const key of Object.keys(current)) {
|
|
260
|
+
if (key.startsWith('__pnext')) carried[key] = current[key];
|
|
261
|
+
}
|
|
262
|
+
if (Object.keys(carried).length === 0) return data;
|
|
263
|
+
const own = (data as Record<string, unknown> | null) ?? {};
|
|
264
|
+
if (Object.keys(own).some(key => key.startsWith('__pnext'))) return data;
|
|
265
|
+
return { ...own, ...carried };
|
|
266
|
+
}
|
|
267
|
+
window.history.pushState = function patchedPushState(data, unused, url) {
|
|
268
|
+
const before = window.location.href;
|
|
269
|
+
const result = pushState(copyInternalState(data), unused, url);
|
|
270
|
+
if (window.location.href !== before) emitLocationChange();
|
|
271
|
+
return result;
|
|
272
|
+
};
|
|
273
|
+
window.history.replaceState = function patchedReplaceState(data, unused, url) {
|
|
274
|
+
const before = window.location.href;
|
|
275
|
+
const result = replaceState(copyInternalState(data), unused, url);
|
|
276
|
+
if (window.location.href !== before) emitLocationChange();
|
|
277
|
+
return result;
|
|
278
|
+
};
|
|
279
|
+
}
|