vinext 0.0.51 → 0.0.52
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/dist/build/precompress.d.ts +7 -7
- package/dist/build/precompress.js +18 -17
- package/dist/build/precompress.js.map +1 -1
- package/dist/build/prerender.d.ts +3 -14
- package/dist/build/prerender.js +40 -40
- package/dist/build/prerender.js.map +1 -1
- package/dist/check.js +4 -0
- package/dist/check.js.map +1 -1
- package/dist/cli-args.d.ts +1 -0
- package/dist/cli-args.js +5 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +47 -0
- package/dist/client/navigation-runtime.js +156 -0
- package/dist/client/navigation-runtime.js.map +1 -0
- package/dist/client/pages-router-link-navigation.d.ts +26 -0
- package/dist/client/pages-router-link-navigation.js +14 -0
- package/dist/client/pages-router-link-navigation.js.map +1 -0
- package/dist/client/vinext-next-data.d.ts +12 -2
- package/dist/client/vinext-next-data.js +50 -1
- package/dist/client/vinext-next-data.js.map +1 -0
- package/dist/cloudflare/kv-cache-handler.js +2 -1
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/config/config-matchers.d.ts +63 -16
- package/dist/config/config-matchers.js +143 -8
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +20 -2
- package/dist/config/next-config.js +11 -1
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +101 -39
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.js +9 -3
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +53 -13
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +1 -0
- package/dist/entries/app-rsc-manifest.js +53 -6
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/app-ssr-entry.d.ts +3 -3
- package/dist/entries/app-ssr-entry.js +4 -4
- package/dist/entries/app-ssr-entry.js.map +1 -1
- package/dist/entries/pages-client-entry.js +18 -2
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +58 -8
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +2 -1
- package/dist/entries/runtime-entry-module.js +9 -3
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +132 -40
- package/dist/index.js.map +1 -1
- package/dist/plugins/css-data-url.d.ts +7 -0
- package/dist/plugins/css-data-url.js +81 -0
- package/dist/plugins/css-data-url.js.map +1 -0
- package/dist/plugins/fonts.js +5 -3
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/middleware-server-only.d.ts +54 -0
- package/dist/plugins/middleware-server-only.js +91 -0
- package/dist/plugins/middleware-server-only.js.map +1 -0
- package/dist/plugins/optimize-imports.js +4 -4
- package/dist/plugins/optimize-imports.js.map +1 -1
- package/dist/plugins/strip-server-exports.js +5 -8
- package/dist/plugins/strip-server-exports.js.map +1 -1
- package/dist/routing/app-route-graph.d.ts +20 -1
- package/dist/routing/app-route-graph.js +58 -6
- package/dist/routing/app-route-graph.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -2
- package/dist/routing/app-router.js +2 -2
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/utils.d.ts +2 -1
- package/dist/routing/utils.js +4 -1
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.js +139 -37
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-entry.js +293 -149
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-interception-context.d.ts +24 -0
- package/dist/server/app-browser-interception-context.js +32 -0
- package/dist/server/app-browser-interception-context.js.map +1 -0
- package/dist/server/app-browser-navigation-controller.d.ts +3 -1
- package/dist/server/app-browser-navigation-controller.js +5 -1
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
- package/dist/server/app-browser-rsc-redirect.js +2 -2
- package/dist/server/app-browser-rsc-redirect.js.map +1 -1
- package/dist/server/app-browser-state.d.ts +18 -1
- package/dist/server/app-browser-state.js +19 -1
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-browser-stream.d.ts +5 -14
- package/dist/server/app-browser-stream.js +13 -7
- package/dist/server/app-browser-stream.js.map +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +2 -1
- package/dist/server/app-browser-visible-commit.js +1 -0
- package/dist/server/app-browser-visible-commit.js.map +1 -1
- package/dist/server/app-elements-wire.d.ts +10 -5
- package/dist/server/app-elements-wire.js +84 -2
- package/dist/server/app-elements-wire.js.map +1 -1
- package/dist/server/app-elements.d.ts +3 -2
- package/dist/server/app-elements.js +3 -2
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.js +5 -3
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-middleware.d.ts +13 -0
- package/dist/server/app-middleware.js +3 -1
- package/dist/server/app-middleware.js.map +1 -1
- package/dist/server/app-optimistic-routing.d.ts +54 -0
- package/dist/server/app-optimistic-routing.js +200 -0
- package/dist/server/app-optimistic-routing.js.map +1 -0
- package/dist/server/app-page-cache.d.ts +13 -1
- package/dist/server/app-page-cache.js +61 -6
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +2 -0
- package/dist/server/app-page-dispatch.js +28 -1
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.js +2 -1
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +28 -1
- package/dist/server/app-page-execution.js +89 -4
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.js +21 -2
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-probe.js +1 -1
- package/dist/server/app-page-render.d.ts +2 -0
- package/dist/server/app-page-render.js +2 -1
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.js +17 -10
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +3 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-prerender-static-params.d.ts +2 -1
- package/dist/server/app-prerender-static-params.js +44 -8
- package/dist/server/app-prerender-static-params.js.map +1 -1
- package/dist/server/app-route-handler-cache.d.ts +2 -2
- package/dist/server/app-route-handler-cache.js +3 -2
- package/dist/server/app-route-handler-cache.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.d.ts +6 -1
- package/dist/server/app-route-handler-dispatch.js +1 -1
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +17 -2
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-response.js +5 -4
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-router-entry.js +6 -2
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/app-rsc-handler.d.ts +9 -1
- package/dist/server/app-rsc-handler.js +32 -14
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-render-mode.d.ts +4 -3
- package/dist/server/app-rsc-render-mode.js +7 -1
- package/dist/server/app-rsc-render-mode.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.d.ts +4 -1
- package/dist/server/app-rsc-request-normalization.js +4 -1
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-rsc-response-finalizer.d.ts +8 -1
- package/dist/server/app-rsc-response-finalizer.js +10 -3
- package/dist/server/app-rsc-response-finalizer.js.map +1 -1
- package/dist/server/app-rsc-route-matching.js +2 -2
- package/dist/server/app-rsc-route-matching.js.map +1 -1
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +2 -0
- package/dist/server/app-ssr-entry.js +56 -55
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +17 -3
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +1 -1
- package/dist/server/artifact-compatibility.js.map +1 -1
- package/dist/server/cache-headers.d.ts +7 -0
- package/dist/server/cache-headers.js +19 -0
- package/dist/server/cache-headers.js.map +1 -0
- package/dist/server/cache-proof.d.ts +49 -3
- package/dist/server/cache-proof.js +78 -22
- package/dist/server/cache-proof.js.map +1 -1
- package/dist/server/client-reuse-manifest.d.ts +99 -0
- package/dist/server/client-reuse-manifest.js +212 -0
- package/dist/server/client-reuse-manifest.js.map +1 -0
- package/dist/server/default-global-error-module.d.ts +20 -0
- package/dist/server/default-global-error-module.js +20 -0
- package/dist/server/default-global-error-module.js.map +1 -0
- package/dist/server/dev-server.d.ts +9 -1
- package/dist/server/dev-server.js +76 -29
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/edge-api-runtime.d.ts +5 -0
- package/dist/server/edge-api-runtime.js +8 -0
- package/dist/server/edge-api-runtime.js.map +1 -0
- package/dist/server/headers.d.ts +18 -1
- package/dist/server/headers.js +18 -1
- package/dist/server/headers.js.map +1 -1
- package/dist/server/http-error-responses.d.ts +16 -1
- package/dist/server/http-error-responses.js +21 -1
- package/dist/server/http-error-responses.js.map +1 -1
- package/dist/server/isr-cache.d.ts +6 -2
- package/dist/server/isr-cache.js +20 -4
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/middleware-runtime.d.ts +15 -0
- package/dist/server/middleware-runtime.js +59 -7
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +4 -2
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/navigation-planner.d.ts +9 -3
- package/dist/server/navigation-planner.js +98 -25
- package/dist/server/navigation-planner.js.map +1 -1
- package/dist/server/navigation-trace.d.ts +2 -1
- package/dist/server/navigation-trace.js +1 -0
- package/dist/server/navigation-trace.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +27 -1
- package/dist/server/pages-api-route.js +24 -3
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-data-route.d.ts +77 -0
- package/dist/server/pages-data-route.js +97 -0
- package/dist/server/pages-data-route.js.map +1 -0
- package/dist/server/pages-i18n.d.ts +51 -1
- package/dist/server/pages-i18n.js +61 -1
- package/dist/server/pages-i18n.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +29 -2
- package/dist/server/pages-page-data.js +31 -17
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +11 -1
- package/dist/server/pages-page-response.js +5 -3
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.d.ts +13 -15
- package/dist/server/prod-server.js +109 -56
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-pipeline.d.ts +11 -2
- package/dist/server/request-pipeline.js +28 -11
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/seed-cache.d.ts +12 -31
- package/dist/server/seed-cache.js +22 -35
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/server-action-not-found.js +8 -3
- package/dist/server/server-action-not-found.js.map +1 -1
- package/dist/server/skip-cache-proof.d.ts +41 -0
- package/dist/server/skip-cache-proof.js +101 -0
- package/dist/server/skip-cache-proof.js.map +1 -0
- package/dist/server/static-file-cache.d.ts +1 -1
- package/dist/server/static-file-cache.js +7 -6
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/shims/client-locale.d.ts +15 -0
- package/dist/shims/client-locale.js +13 -0
- package/dist/shims/client-locale.js.map +1 -0
- package/dist/shims/default-global-error.d.ts +32 -0
- package/dist/shims/default-global-error.js +181 -0
- package/dist/shims/default-global-error.js.map +1 -0
- package/dist/shims/document.d.ts +59 -3
- package/dist/shims/document.js +36 -5
- package/dist/shims/document.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +2 -2
- package/dist/shims/form.js +13 -6
- package/dist/shims/form.js.map +1 -1
- package/dist/shims/link.d.ts +21 -3
- package/dist/shims/link.js +131 -22
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.js +4 -4
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +8 -2
- package/dist/shims/navigation.js +36 -15
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/og.d.ts +18 -2
- package/dist/shims/og.js +49 -1
- package/dist/shims/og.js.map +1 -0
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/root-params.d.ts +3 -1
- package/dist/shims/root-params.js +11 -3
- package/dist/shims/root-params.js.map +1 -1
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/router.d.ts +12 -5
- package/dist/shims/router.js +172 -22
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/server.d.ts +21 -4
- package/dist/shims/server.js +29 -9
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.js +5 -1
- package/dist/shims/slot.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/dist/shims/url-safety.d.ts +23 -1
- package/dist/shims/url-safety.js +29 -2
- package/dist/shims/url-safety.js.map +1 -1
- package/dist/typegen.d.ts +10 -0
- package/dist/typegen.js +242 -0
- package/dist/typegen.js.map +1 -0
- package/dist/utils/asset-prefix.d.ts +33 -5
- package/dist/utils/asset-prefix.js +39 -6
- package/dist/utils/asset-prefix.js.map +1 -1
- package/dist/utils/cache-control-metadata.d.ts +2 -1
- package/dist/utils/cache-control-metadata.js +1 -3
- package/dist/utils/cache-control-metadata.js.map +1 -1
- package/dist/utils/domain-locale.d.ts +2 -1
- package/dist/utils/domain-locale.js +9 -1
- package/dist/utils/domain-locale.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +1 -1
- package/dist/utils/lazy-chunks.js +1 -1
- package/dist/utils/lazy-chunks.js.map +1 -1
- package/dist/utils/prerender-output-paths.d.ts +15 -0
- package/dist/utils/prerender-output-paths.js +24 -0
- package/dist/utils/prerender-output-paths.js.map +1 -0
- package/dist/utils/query.d.ts +17 -1
- package/dist/utils/query.js +36 -1
- package/dist/utils/query.js.map +1 -1
- package/dist/utils/record.d.ts +5 -0
- package/dist/utils/record.js +8 -0
- package/dist/utils/record.js.map +1 -0
- package/package.json +11 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-fallback-renderer.js","names":[],"sources":["../../src/server/app-fallback-renderer.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\nimport {\n renderAppPageErrorBoundary,\n renderAppPageHttpAccessFallback,\n type AppPageBoundaryRoute,\n} from \"./app-page-boundary-render.js\";\nimport type { AppPageFontPreload } from \"./app-page-execution.js\";\nimport type { AppPageMiddlewareContext } from \"./app-page-response.js\";\nimport type { AppPageSsrHandler } from \"./app-page-stream.js\";\nimport type { MetadataFileRoute } from \"./metadata-routes.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype AppPageComponent = import(\"react\").ComponentType<any>;\ntype AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\ntype AppPageBoundaryOnError = (\n error: unknown,\n requestInfo: unknown,\n errorContext: unknown,\n) => unknown;\n\ntype AppFallbackRendererRootBoundaries<TModule extends AppPageModule = AppPageModule> = {\n rootForbiddenModule?: TModule | null;\n rootLayouts: readonly (TModule | null | undefined)[];\n rootNotFoundModule?: TModule | null;\n rootUnauthorizedModule?: TModule | null;\n};\n\ntype AppFallbackRendererFontProviders = {\n buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;\n getFontLinks: () => string[];\n getFontPreloads: () => AppPageFontPreload[];\n getFontStyles: () => string[];\n};\n\ntype AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> = {\n clearRequestContext: () => void;\n createRscOnErrorHandler: (\n request: Request,\n pathname: string,\n routePath: string,\n ) => AppPageBoundaryOnError;\n fontProviders: AppFallbackRendererFontProviders;\n getNavigationContext: () => unknown;\n globalErrorModule?: TModule | null;\n /**\n * Optional `app/global-not-found.tsx` module. When provided, route-miss 404s\n * render this module as a standalone document (skipping the root layout)\n * because it ships its own `<html>` and `<body>`. Page-triggered `notFound()`\n * calls continue to use the regular `not-found.tsx` boundary inside layouts.\n * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx\n */\n globalNotFoundModule?: TModule | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n metadataRoutes: MetadataFileRoute[];\n /** Configured next.config `basePath`, threaded into file-based metadata href emission. */\n basePath?: string;\n resolveChildSegments: (\n routeSegments: readonly string[],\n treePosition: number,\n params: AppPageParams,\n ) => string[];\n rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;\n rscRenderer: (\n element: ReactNode | AppElements,\n options: { onError: AppPageBoundaryOnError },\n ) => ReadableStream<Uint8Array>;\n sanitizer: (error: Error) => Error;\n ssrLoader: () => Promise<AppPageSsrHandler>;\n};\n\ntype AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {\n renderErrorBoundary: (\n route: AppPageBoundaryRoute<TModule> | null,\n error: unknown,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderHttpAccessFallback: (\n route: AppPageBoundaryRoute<TModule> | null,\n statusCode: number,\n isRscRequest: boolean,\n request: Request,\n opts: {\n boundaryComponent?: AppPageComponent | null;\n layouts?: readonly (TModule | null | undefined)[] | null;\n matchedParams?: AppPageParams;\n },\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderNotFound: (\n route: AppPageBoundaryRoute<TModule> | null,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n};\n\nconst EMPTY_MW_CTX: AppPageMiddlewareContext = { headers: null, status: null };\n\nexport function createAppFallbackRenderer<TModule extends AppPageModule>(\n options: AppFallbackRendererOptions<TModule>,\n): AppFallbackRenderer<TModule> {\n const {\n basePath = \"\",\n clearRequestContext,\n createRscOnErrorHandler: buildRscOnErrorHandler,\n fontProviders,\n getNavigationContext,\n globalErrorModule,\n globalNotFoundModule,\n makeThenableParams,\n metadataRoutes,\n resolveChildSegments,\n rootBoundaries,\n rscRenderer,\n sanitizer,\n ssrLoader,\n } = options;\n\n const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } =\n rootBoundaries;\n\n return {\n renderHttpAccessFallback(\n route,\n statusCode,\n isRscRequest,\n request,\n opts,\n scriptNonce,\n middlewareContext,\n ) {\n // global-not-found.tsx replaces the root layout for route-miss 404s.\n // Only applies when:\n // - The user defined app/global-not-found.tsx\n // - The 404 originates from a route miss (no matched route)\n // - The caller did not already pick a specific boundary component\n // Page-triggered notFound() calls (route is non-null) keep using the\n // regular not-found.tsx boundary inside the route's layouts.\n // See https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx#L495-L520\n const useGlobalNotFound =\n statusCode === 404 && !!globalNotFoundModule && !route && !opts?.boundaryComponent;\n\n if (useGlobalNotFound) {\n const globalNotFoundComponent = globalNotFoundModule?.default ?? null;\n if (globalNotFoundComponent) {\n return renderAppPageHttpAccessFallback({\n boundaryComponent: globalNotFoundComponent,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule,\n isRscRequest,\n layoutModules: [],\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule: null,\n rootLayouts: [],\n rootNotFoundModule: null,\n rootUnauthorizedModule: null,\n route: null,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n skipLayoutWrapping: true,\n statusCode,\n });\n }\n }\n\n return renderAppPageHttpAccessFallback({\n basePath,\n boundaryComponent: opts?.boundaryComponent ?? null,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule,\n isRscRequest,\n layoutModules: opts?.layouts ?? null,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule,\n rootLayouts,\n rootNotFoundModule,\n rootUnauthorizedModule,\n route,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n statusCode,\n });\n },\n\n renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext) {\n return this.renderHttpAccessFallback(\n route,\n 404,\n isRscRequest,\n request,\n { matchedParams },\n scriptNonce,\n middlewareContext,\n );\n },\n\n renderErrorBoundary(\n route,\n error,\n isRscRequest,\n request,\n matchedParams,\n scriptNonce,\n middlewareContext,\n ) {\n return renderAppPageErrorBoundary({\n basePath,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n error,\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule,\n isRscRequest,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootLayouts,\n route,\n renderToReadableStream: rscRenderer,\n sanitizeErrorForClient: sanitizer,\n scriptNonce,\n });\n },\n };\n}\n"],"mappings":";;AA2GA,MAAM,eAAyC;CAAE,SAAS;CAAM,QAAQ;CAAM;AAE9E,SAAgB,0BACd,SAC8B;CAC9B,MAAM,EACJ,WAAW,IACX,qBACA,yBAAyB,wBACzB,eACA,sBACA,mBACA,sBACA,oBACA,gBACA,sBACA,gBACA,aACA,WACA,cACE;CAEJ,MAAM,EAAE,qBAAqB,aAAa,oBAAoB,2BAC5D;CAEF,OAAO;EACL,yBACE,OACA,YACA,cACA,SACA,MACA,aACA,mBACA;GAYA,IAFE,eAAe,OAAO,CAAC,CAAC,wBAAwB,CAAC,SAAS,CAAC,MAAM,mBAE5C;IACrB,MAAM,0BAA0B,sBAAsB,WAAW;IACjE,IAAI,yBACF,OAAO,gCAAgC;KACrC,mBAAmB;KACnB,qBAAqB,cAAc;KACnC;KACA,wBAAwB,UAAU,WAAW;MAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;KAE7D,cAAc,cAAc;KAC5B,iBAAiB,cAAc;KAC/B,eAAe,cAAc;KAC7B;KACA;KACA;KACA,eAAe,EAAE;KACjB,gBAAgB;KAChB;KACA,eAAe,MAAM,iBAAiB,EAAE;KACxC,mBAAmB,qBAAqB;KACxC;KACA,YAAY,QAAQ;KACpB;KACA,qBAAqB;KACrB,aAAa,EAAE;KACf,oBAAoB;KACpB,wBAAwB;KACxB,OAAO;KACP,wBAAwB;KACxB;KACA,oBAAoB;KACpB;KACD,CAAC;;GAIN,OAAO,gCAAgC;IACrC;IACA,mBAAmB,MAAM,qBAAqB;IAC9C,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA;IACA;IACA,eAAe,MAAM,WAAW;IAChC,gBAAgB;IAChB;IACA,eAAe,MAAM,iBAAiB,OAAO,UAAU,EAAE;IACzD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA;IACA;IACA;IACA,wBAAwB;IACxB;IACA;IACD,CAAC;;EAGJ,eAAe,OAAO,cAAc,SAAS,eAAe,aAAa,mBAAmB;GAC1F,OAAO,KAAK,yBACV,OACA,KACA,cACA,SACA,EAAE,eAAe,EACjB,aACA,kBACD;;EAGH,oBACE,OACA,OACA,cACA,SACA,eACA,aACA,mBACA;GACA,OAAO,2BAA2B;IAChC;IACA,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D;IACA,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA;IACA;IACA,gBAAgB;IAChB;IACA,eAAe,iBAAiB,OAAO,UAAU,EAAE;IACnD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA,wBAAwB;IACxB,wBAAwB;IACxB;IACD,CAAC;;EAEL"}
|
|
1
|
+
{"version":3,"file":"app-fallback-renderer.js","names":[],"sources":["../../src/server/app-fallback-renderer.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\nimport {\n renderAppPageErrorBoundary,\n renderAppPageHttpAccessFallback,\n type AppPageBoundaryRoute,\n} from \"./app-page-boundary-render.js\";\nimport { DEFAULT_GLOBAL_ERROR_MODULE } from \"./default-global-error-module.js\";\nimport type { AppPageFontPreload } from \"./app-page-execution.js\";\nimport type { AppPageMiddlewareContext } from \"./app-page-response.js\";\nimport type { AppPageSsrHandler } from \"./app-page-stream.js\";\nimport type { MetadataFileRoute } from \"./metadata-routes.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype AppPageComponent = import(\"react\").ComponentType<any>;\ntype AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\ntype AppPageBoundaryOnError = (\n error: unknown,\n requestInfo: unknown,\n errorContext: unknown,\n) => unknown;\n\ntype AppFallbackRendererRootBoundaries<TModule extends AppPageModule = AppPageModule> = {\n rootForbiddenModule?: TModule | null;\n rootLayouts: readonly (TModule | null | undefined)[];\n rootNotFoundModule?: TModule | null;\n rootUnauthorizedModule?: TModule | null;\n};\n\ntype AppFallbackRendererFontProviders = {\n buildFontLinkHeader: (preloads: readonly AppPageFontPreload[] | null | undefined) => string;\n getFontLinks: () => string[];\n getFontPreloads: () => AppPageFontPreload[];\n getFontStyles: () => string[];\n};\n\ntype AppFallbackRendererOptions<TModule extends AppPageModule = AppPageModule> = {\n clearRequestContext: () => void;\n createRscOnErrorHandler: (\n request: Request,\n pathname: string,\n routePath: string,\n ) => AppPageBoundaryOnError;\n fontProviders: AppFallbackRendererFontProviders;\n getNavigationContext: () => unknown;\n globalErrorModule?: TModule | null;\n /**\n * Optional `app/global-not-found.tsx` module. When provided, route-miss 404s\n * render this module as a standalone document (skipping the root layout)\n * because it ships its own `<html>` and `<body>`. Page-triggered `notFound()`\n * calls continue to use the regular `not-found.tsx` boundary inside layouts.\n * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx\n */\n globalNotFoundModule?: TModule | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n metadataRoutes: MetadataFileRoute[];\n /** Configured next.config `basePath`, threaded into file-based metadata href emission. */\n basePath?: string;\n resolveChildSegments: (\n routeSegments: readonly string[],\n treePosition: number,\n params: AppPageParams,\n ) => string[];\n rootBoundaries: AppFallbackRendererRootBoundaries<TModule>;\n rscRenderer: (\n element: ReactNode | AppElements,\n options: { onError: AppPageBoundaryOnError },\n ) => ReadableStream<Uint8Array>;\n sanitizer: (error: Error) => Error;\n ssrLoader: () => Promise<AppPageSsrHandler>;\n};\n\ntype AppFallbackRenderer<TModule extends AppPageModule = AppPageModule> = {\n renderErrorBoundary: (\n route: AppPageBoundaryRoute<TModule> | null,\n error: unknown,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderHttpAccessFallback: (\n route: AppPageBoundaryRoute<TModule> | null,\n statusCode: number,\n isRscRequest: boolean,\n request: Request,\n opts: {\n boundaryComponent?: AppPageComponent | null;\n layouts?: readonly (TModule | null | undefined)[] | null;\n matchedParams?: AppPageParams;\n },\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n renderNotFound: (\n route: AppPageBoundaryRoute<TModule> | null,\n isRscRequest: boolean,\n request: Request,\n matchedParams: AppPageParams | undefined,\n scriptNonce: string | undefined,\n middlewareContext: AppPageMiddlewareContext,\n ) => Promise<Response | null>;\n};\n\nconst EMPTY_MW_CTX: AppPageMiddlewareContext = { headers: null, status: null };\n\nexport function createAppFallbackRenderer<TModule extends AppPageModule>(\n options: AppFallbackRendererOptions<TModule>,\n): AppFallbackRenderer<TModule> {\n const {\n basePath = \"\",\n clearRequestContext,\n createRscOnErrorHandler: buildRscOnErrorHandler,\n fontProviders,\n getNavigationContext,\n globalErrorModule,\n globalNotFoundModule,\n makeThenableParams,\n metadataRoutes,\n resolveChildSegments,\n rootBoundaries,\n rscRenderer,\n sanitizer,\n ssrLoader,\n } = options;\n\n const { rootForbiddenModule, rootLayouts, rootNotFoundModule, rootUnauthorizedModule } =\n rootBoundaries;\n\n // When the app does not define `app/global-error.tsx`, fall back to vinext's\n // built-in default global error component so that uncaught render errors\n // produce the same UI Next.js ships out of the box (matching markup, inline\n // styles, theme CSS, and the \"ERROR <digest>\" footer for server errors).\n // See packages/vinext/src/shims/default-global-error.tsx and\n // packages/vinext/src/server/default-global-error-module.ts.\n const effectiveGlobalErrorModule: TModule | null =\n globalErrorModule ?? (DEFAULT_GLOBAL_ERROR_MODULE as unknown as TModule);\n\n return {\n renderHttpAccessFallback(\n route,\n statusCode,\n isRscRequest,\n request,\n opts,\n scriptNonce,\n middlewareContext,\n ) {\n // global-not-found.tsx replaces the root layout for route-miss 404s.\n // Only applies when:\n // - The user defined app/global-not-found.tsx\n // - The 404 originates from a route miss (no matched route)\n // - The caller did not already pick a specific boundary component\n // Page-triggered notFound() calls (route is non-null) keep using the\n // regular not-found.tsx boundary inside the route's layouts.\n // See https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx#L495-L520\n const useGlobalNotFound =\n statusCode === 404 && !!globalNotFoundModule && !route && !opts?.boundaryComponent;\n\n if (useGlobalNotFound) {\n const globalNotFoundComponent = globalNotFoundModule?.default ?? null;\n if (globalNotFoundComponent) {\n return renderAppPageHttpAccessFallback({\n boundaryComponent: globalNotFoundComponent,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isRscRequest,\n layoutModules: [],\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule: null,\n rootLayouts: [],\n rootNotFoundModule: null,\n rootUnauthorizedModule: null,\n route: null,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n skipLayoutWrapping: true,\n statusCode,\n });\n }\n }\n\n return renderAppPageHttpAccessFallback({\n basePath,\n boundaryComponent: opts?.boundaryComponent ?? null,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isRscRequest,\n layoutModules: opts?.layouts ?? null,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: opts?.matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootForbiddenModule,\n rootLayouts,\n rootNotFoundModule,\n rootUnauthorizedModule,\n route,\n renderToReadableStream: rscRenderer,\n scriptNonce,\n statusCode,\n });\n },\n\n renderNotFound(route, isRscRequest, request, matchedParams, scriptNonce, middlewareContext) {\n return this.renderHttpAccessFallback(\n route,\n 404,\n isRscRequest,\n request,\n { matchedParams },\n scriptNonce,\n middlewareContext,\n );\n },\n\n renderErrorBoundary(\n route,\n error,\n isRscRequest,\n request,\n matchedParams,\n scriptNonce,\n middlewareContext,\n ) {\n return renderAppPageErrorBoundary({\n basePath,\n buildFontLinkHeader: fontProviders.buildFontLinkHeader,\n clearRequestContext,\n createRscOnErrorHandler(pathname, routePath) {\n return buildRscOnErrorHandler(request, pathname, routePath);\n },\n error,\n getFontLinks: fontProviders.getFontLinks,\n getFontPreloads: fontProviders.getFontPreloads,\n getFontStyles: fontProviders.getFontStyles,\n getNavigationContext,\n globalErrorModule: effectiveGlobalErrorModule,\n isRscRequest,\n loadSsrHandler: ssrLoader,\n makeThenableParams,\n matchedParams: matchedParams ?? route?.params ?? {},\n middlewareContext: middlewareContext ?? EMPTY_MW_CTX,\n metadataRoutes,\n requestUrl: request.url,\n resolveChildSegments,\n rootLayouts,\n route,\n renderToReadableStream: rscRenderer,\n sanitizeErrorForClient: sanitizer,\n scriptNonce,\n });\n },\n };\n}\n"],"mappings":";;;AA4GA,MAAM,eAAyC;CAAE,SAAS;CAAM,QAAQ;CAAM;AAE9E,SAAgB,0BACd,SAC8B;CAC9B,MAAM,EACJ,WAAW,IACX,qBACA,yBAAyB,wBACzB,eACA,sBACA,mBACA,sBACA,oBACA,gBACA,sBACA,gBACA,aACA,WACA,cACE;CAEJ,MAAM,EAAE,qBAAqB,aAAa,oBAAoB,2BAC5D;CAQF,MAAM,6BACJ,qBAAsB;CAExB,OAAO;EACL,yBACE,OACA,YACA,cACA,SACA,MACA,aACA,mBACA;GAYA,IAFE,eAAe,OAAO,CAAC,CAAC,wBAAwB,CAAC,SAAS,CAAC,MAAM,mBAE5C;IACrB,MAAM,0BAA0B,sBAAsB,WAAW;IACjE,IAAI,yBACF,OAAO,gCAAgC;KACrC,mBAAmB;KACnB,qBAAqB,cAAc;KACnC;KACA,wBAAwB,UAAU,WAAW;MAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;KAE7D,cAAc,cAAc;KAC5B,iBAAiB,cAAc;KAC/B,eAAe,cAAc;KAC7B;KACA,mBAAmB;KACnB;KACA,eAAe,EAAE;KACjB,gBAAgB;KAChB;KACA,eAAe,MAAM,iBAAiB,EAAE;KACxC,mBAAmB,qBAAqB;KACxC;KACA,YAAY,QAAQ;KACpB;KACA,qBAAqB;KACrB,aAAa,EAAE;KACf,oBAAoB;KACpB,wBAAwB;KACxB,OAAO;KACP,wBAAwB;KACxB;KACA,oBAAoB;KACpB;KACD,CAAC;;GAIN,OAAO,gCAAgC;IACrC;IACA,mBAAmB,MAAM,qBAAqB;IAC9C,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA,mBAAmB;IACnB;IACA,eAAe,MAAM,WAAW;IAChC,gBAAgB;IAChB;IACA,eAAe,MAAM,iBAAiB,OAAO,UAAU,EAAE;IACzD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA;IACA;IACA;IACA,wBAAwB;IACxB;IACA;IACD,CAAC;;EAGJ,eAAe,OAAO,cAAc,SAAS,eAAe,aAAa,mBAAmB;GAC1F,OAAO,KAAK,yBACV,OACA,KACA,cACA,SACA,EAAE,eAAe,EACjB,aACA,kBACD;;EAGH,oBACE,OACA,OACA,cACA,SACA,eACA,aACA,mBACA;GACA,OAAO,2BAA2B;IAChC;IACA,qBAAqB,cAAc;IACnC;IACA,wBAAwB,UAAU,WAAW;KAC3C,OAAO,uBAAuB,SAAS,UAAU,UAAU;;IAE7D;IACA,cAAc,cAAc;IAC5B,iBAAiB,cAAc;IAC/B,eAAe,cAAc;IAC7B;IACA,mBAAmB;IACnB;IACA,gBAAgB;IAChB;IACA,eAAe,iBAAiB,OAAO,UAAU,EAAE;IACnD,mBAAmB,qBAAqB;IACxC;IACA,YAAY,QAAQ;IACpB;IACA;IACA;IACA,wBAAwB;IACxB,wBAAwB;IACxB;IACD,CAAC;;EAEL"}
|
|
@@ -13,9 +13,22 @@ type ApplyAppMiddlewareOptions = {
|
|
|
13
13
|
cleanPathname: string;
|
|
14
14
|
context: AppMiddlewareContext;
|
|
15
15
|
i18nConfig?: NextI18nConfig | null;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the inbound request was a `_next/data` fetch. Captured from the
|
|
18
|
+
* raw incoming headers by the caller, because `x-nextjs-data` is in
|
|
19
|
+
* INTERNAL_HEADERS and is stripped before this function runs.
|
|
20
|
+
*/
|
|
21
|
+
isDataRequest?: boolean;
|
|
16
22
|
isProxy: boolean;
|
|
17
23
|
module: MiddlewareModule;
|
|
18
24
|
request: Request;
|
|
25
|
+
/**
|
|
26
|
+
* Forwarded to `executeMiddleware` so the NextRequest exposes a NextURL with
|
|
27
|
+
* the configured trailingSlash policy. This is what makes
|
|
28
|
+
* `NextResponse.redirect(request.nextUrl)` emit a Location that honours
|
|
29
|
+
* `trailingSlash`.
|
|
30
|
+
*/
|
|
31
|
+
trailingSlash?: boolean;
|
|
19
32
|
};
|
|
20
33
|
type ApplyAppMiddlewareResult = {
|
|
21
34
|
kind: "continue";
|
|
@@ -116,10 +116,12 @@ async function applyAppMiddleware(options) {
|
|
|
116
116
|
const result = await executeMiddleware({
|
|
117
117
|
basePath: options.basePath,
|
|
118
118
|
i18nConfig: options.i18nConfig,
|
|
119
|
+
isDataRequest: options.isDataRequest,
|
|
119
120
|
isProxy: options.isProxy,
|
|
120
121
|
module: options.module,
|
|
121
122
|
normalizedPathname: cleanPathname,
|
|
122
|
-
request: middlewareRequest
|
|
123
|
+
request: middlewareRequest,
|
|
124
|
+
trailingSlash: options.trailingSlash
|
|
123
125
|
});
|
|
124
126
|
if (!result.continue) {
|
|
125
127
|
if (result.redirectUrl) return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-middleware.js","names":[],"sources":["../../src/server/app-middleware.ts"],"sourcesContent":["import type { NextI18nConfig } from \"../config/next-config.js\";\nimport { isExternalUrl, proxyExternalRequest } from \"../config/config-matchers.js\";\nimport { applyMiddlewareRequestHeaders, setHeadersContext } from \"vinext/shims/headers\";\nimport { setNavigationContext } from \"vinext/shims/navigation\";\nimport { FLIGHT_HEADERS, VINEXT_MW_CTX_HEADER } from \"./headers.js\";\nimport { buildRequestHeadersFromMiddlewareResponse } from \"./middleware-request-headers.js\";\nimport { mergeMiddlewareResponseHeaders } from \"./middleware-response-headers.js\";\nimport { executeMiddleware, type MiddlewareModule } from \"./middleware-runtime.js\";\nimport { cloneRequestWithHeaders, processMiddlewareHeaders } from \"./request-pipeline.js\";\nimport { internalServerErrorResponse } from \"./http-error-responses.js\";\n\nexport type AppMiddlewareContext = {\n headers: Headers | null;\n requestHeaders: Headers | null;\n status: number | null;\n};\n\nexport type ApplyAppMiddlewareOptions = {\n basePath?: string;\n cleanPathname: string;\n context: AppMiddlewareContext;\n i18nConfig?: NextI18nConfig | null;\n isProxy: boolean;\n module: MiddlewareModule;\n request: Request;\n};\n\nexport type ApplyAppMiddlewareResult =\n | {\n kind: \"continue\";\n cleanPathname: string;\n search: string | null;\n }\n | {\n kind: \"response\";\n response: Response;\n };\n\ntype ForwardedMiddlewareContext = {\n h?: unknown;\n r?: unknown;\n s?: unknown;\n};\n\n// Re-exported from headers.ts for backward compatibility.\nexport { FLIGHT_HEADERS } from \"./headers.js\";\n\nconst FLIGHT_HEADER_SET = new Set(FLIGHT_HEADERS);\n\nfunction isForwardedMiddlewareContext(value: unknown): value is ForwardedMiddlewareContext {\n return !!value && typeof value === \"object\";\n}\n\nfunction requestWithoutFlightHeaders(request: Request): Request {\n let hasFlightHeader = false;\n const headers = new Headers();\n\n for (const [key, value] of request.headers) {\n if (FLIGHT_HEADER_SET.has(key.toLowerCase())) {\n hasFlightHeader = true;\n } else {\n headers.append(key, value);\n }\n }\n\n if (!hasFlightHeader) return request;\n const source = request.body ? request.clone() : request;\n return cloneRequestWithHeaders(source, headers);\n}\n\nfunction appendForwardedHeader(headers: Headers, value: unknown): void {\n if (!Array.isArray(value) || value.length < 2) return;\n const key = value[0];\n const headerValue = value[1];\n if (typeof key === \"string\" && typeof headerValue === \"string\") {\n headers.append(key, headerValue);\n }\n}\n\nfunction responseFromMiddlewareRedirect(result: {\n redirectStatus?: number;\n redirectUrl?: string;\n response?: Response;\n responseHeaders?: Headers;\n}): Response {\n if (result.response) return result.response;\n\n const headers = new Headers(result.responseHeaders);\n if (result.redirectUrl) {\n headers.set(\"Location\", result.redirectUrl);\n }\n return new Response(null, {\n status: result.redirectStatus ?? 307,\n headers,\n });\n}\n\nexport function isExternalMiddlewareRewrite(rewriteUrl: string, request: Request): boolean {\n const rewriteParsed = new URL(rewriteUrl, request.url);\n return rewriteParsed.origin !== new URL(request.url).origin;\n}\n\nfunction requestWithMiddlewareRequestHeaders(\n request: Request,\n middlewareHeaders: Headers | null,\n): Request {\n const nextHeaders = middlewareHeaders\n ? buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders, {\n preserveCredentialHeaders: true,\n })\n : null;\n if (!nextHeaders) return request;\n\n const init: RequestInit = {\n method: request.method,\n headers: nextHeaders,\n body: request.body,\n };\n if (request.body) {\n Object.defineProperty(init, \"duplex\", { value: \"half\", enumerable: true });\n }\n\n return new Request(request.url, init);\n}\n\nexport async function proxyExternalMiddlewareRewrite(\n request: Request,\n rewriteUrl: string,\n context: AppMiddlewareContext,\n): Promise<Response> {\n const proxyRequest = requestWithMiddlewareRequestHeaders(\n request,\n context.requestHeaders ?? context.headers,\n );\n setHeadersContext(null);\n setNavigationContext(null);\n\n const proxyResponse = await proxyExternalRequest(proxyRequest, rewriteUrl);\n const headers = new Headers(proxyResponse.headers);\n processMiddlewareHeaders(headers);\n\n if (!context.headers) {\n return new Response(proxyResponse.body, {\n status: proxyResponse.status,\n statusText: proxyResponse.statusText,\n headers,\n });\n }\n\n const middlewareHeaders = new Headers(context.headers);\n processMiddlewareHeaders(middlewareHeaders);\n mergeMiddlewareResponseHeaders(headers, middlewareHeaders);\n return new Response(proxyResponse.body, {\n status: proxyResponse.status,\n statusText: proxyResponse.statusText,\n headers,\n });\n}\n\nfunction applyForwardedMiddlewareContext(\n request: Request,\n context: AppMiddlewareContext,\n): { applied: boolean; rewriteUrl?: string } {\n if (process.env.NODE_ENV === \"production\") {\n return { applied: false };\n }\n\n const header = request.headers.get(VINEXT_MW_CTX_HEADER);\n if (!header) return { applied: false };\n\n try {\n const data = JSON.parse(header);\n if (!isForwardedMiddlewareContext(data)) return { applied: false };\n\n if (Array.isArray(data.h) && data.h.length > 0) {\n context.headers = new Headers();\n for (const entry of data.h) {\n appendForwardedHeader(context.headers, entry);\n }\n }\n if (typeof data.s === \"number\") {\n context.status = data.s;\n }\n if (typeof data.r === \"string\" && data.r.length > 0) {\n return { applied: true, rewriteUrl: data.r };\n }\n return { applied: true };\n } catch (e) {\n console.error(\"[vinext] Failed to parse forwarded middleware context:\", e);\n return { applied: false };\n }\n}\n\nexport async function applyAppMiddleware(\n options: ApplyAppMiddlewareOptions,\n): Promise<ApplyAppMiddlewareResult> {\n const forwarded = applyForwardedMiddlewareContext(options.request, options.context);\n const middlewareRequest = requestWithoutFlightHeaders(options.request);\n let cleanPathname = options.cleanPathname;\n let search: string | null = null;\n\n if (forwarded.rewriteUrl) {\n try {\n if (isExternalMiddlewareRewrite(forwarded.rewriteUrl, middlewareRequest)) {\n return {\n kind: \"response\",\n response: await proxyExternalMiddlewareRewrite(\n middlewareRequest,\n forwarded.rewriteUrl,\n options.context,\n ),\n };\n }\n const rewriteParsed = new URL(forwarded.rewriteUrl, middlewareRequest.url);\n cleanPathname = rewriteParsed.pathname;\n search = rewriteParsed.search;\n } catch (e) {\n console.error(\"[vinext] Failed to apply forwarded middleware rewrite:\", e);\n forwarded.applied = false;\n }\n }\n\n if (!forwarded.applied) {\n const result = await executeMiddleware({\n basePath: options.basePath,\n i18nConfig: options.i18nConfig,\n isProxy: options.isProxy,\n module: options.module,\n normalizedPathname: cleanPathname,\n request: middlewareRequest,\n });\n\n if (!result.continue) {\n if (result.redirectUrl) {\n return { kind: \"response\", response: responseFromMiddlewareRedirect(result) };\n }\n if (result.response) {\n return { kind: \"response\", response: result.response };\n }\n return { kind: \"response\", response: internalServerErrorResponse() };\n }\n\n if (result.responseHeaders) {\n options.context.headers = new Headers(result.responseHeaders);\n }\n\n if (result.status !== undefined) {\n options.context.status = result.status;\n }\n\n if (result.rewriteUrl) {\n if (result.rewriteStatus !== undefined) {\n options.context.status = result.rewriteStatus;\n }\n if (isExternalUrl(result.rewriteUrl)) {\n return {\n kind: \"response\",\n response: await proxyExternalMiddlewareRewrite(\n middlewareRequest,\n result.rewriteUrl,\n options.context,\n ),\n };\n }\n const rewriteParsed = new URL(result.rewriteUrl, middlewareRequest.url);\n cleanPathname = rewriteParsed.pathname;\n search = rewriteParsed.search;\n }\n }\n\n if (options.context.headers) {\n options.context.requestHeaders = new Headers(options.context.headers);\n applyMiddlewareRequestHeaders(options.context.headers);\n processMiddlewareHeaders(options.context.headers);\n }\n\n return { kind: \"continue\", cleanPathname, search };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-middleware.js","names":[],"sources":["../../src/server/app-middleware.ts"],"sourcesContent":["import type { NextI18nConfig } from \"../config/next-config.js\";\nimport { isExternalUrl, proxyExternalRequest } from \"../config/config-matchers.js\";\nimport { applyMiddlewareRequestHeaders, setHeadersContext } from \"vinext/shims/headers\";\nimport { setNavigationContext } from \"vinext/shims/navigation\";\nimport { FLIGHT_HEADERS, VINEXT_MW_CTX_HEADER } from \"./headers.js\";\nimport { buildRequestHeadersFromMiddlewareResponse } from \"./middleware-request-headers.js\";\nimport { mergeMiddlewareResponseHeaders } from \"./middleware-response-headers.js\";\nimport { executeMiddleware, type MiddlewareModule } from \"./middleware-runtime.js\";\nimport { cloneRequestWithHeaders, processMiddlewareHeaders } from \"./request-pipeline.js\";\nimport { internalServerErrorResponse } from \"./http-error-responses.js\";\n\nexport type AppMiddlewareContext = {\n headers: Headers | null;\n requestHeaders: Headers | null;\n status: number | null;\n};\n\nexport type ApplyAppMiddlewareOptions = {\n basePath?: string;\n cleanPathname: string;\n context: AppMiddlewareContext;\n i18nConfig?: NextI18nConfig | null;\n /**\n * Whether the inbound request was a `_next/data` fetch. Captured from the\n * raw incoming headers by the caller, because `x-nextjs-data` is in\n * INTERNAL_HEADERS and is stripped before this function runs.\n */\n isDataRequest?: boolean;\n isProxy: boolean;\n module: MiddlewareModule;\n request: Request;\n /**\n * Forwarded to `executeMiddleware` so the NextRequest exposes a NextURL with\n * the configured trailingSlash policy. This is what makes\n * `NextResponse.redirect(request.nextUrl)` emit a Location that honours\n * `trailingSlash`.\n */\n trailingSlash?: boolean;\n};\n\nexport type ApplyAppMiddlewareResult =\n | {\n kind: \"continue\";\n cleanPathname: string;\n search: string | null;\n }\n | {\n kind: \"response\";\n response: Response;\n };\n\ntype ForwardedMiddlewareContext = {\n h?: unknown;\n r?: unknown;\n s?: unknown;\n};\n\n// Re-exported from headers.ts for backward compatibility.\nexport { FLIGHT_HEADERS } from \"./headers.js\";\n\nconst FLIGHT_HEADER_SET = new Set(FLIGHT_HEADERS);\n\nfunction isForwardedMiddlewareContext(value: unknown): value is ForwardedMiddlewareContext {\n return !!value && typeof value === \"object\";\n}\n\nfunction requestWithoutFlightHeaders(request: Request): Request {\n let hasFlightHeader = false;\n const headers = new Headers();\n\n for (const [key, value] of request.headers) {\n if (FLIGHT_HEADER_SET.has(key.toLowerCase())) {\n hasFlightHeader = true;\n } else {\n headers.append(key, value);\n }\n }\n\n if (!hasFlightHeader) return request;\n const source = request.body ? request.clone() : request;\n return cloneRequestWithHeaders(source, headers);\n}\n\nfunction appendForwardedHeader(headers: Headers, value: unknown): void {\n if (!Array.isArray(value) || value.length < 2) return;\n const key = value[0];\n const headerValue = value[1];\n if (typeof key === \"string\" && typeof headerValue === \"string\") {\n headers.append(key, headerValue);\n }\n}\n\nfunction responseFromMiddlewareRedirect(result: {\n redirectStatus?: number;\n redirectUrl?: string;\n response?: Response;\n responseHeaders?: Headers;\n}): Response {\n if (result.response) return result.response;\n\n const headers = new Headers(result.responseHeaders);\n if (result.redirectUrl) {\n headers.set(\"Location\", result.redirectUrl);\n }\n return new Response(null, {\n status: result.redirectStatus ?? 307,\n headers,\n });\n}\n\nexport function isExternalMiddlewareRewrite(rewriteUrl: string, request: Request): boolean {\n const rewriteParsed = new URL(rewriteUrl, request.url);\n return rewriteParsed.origin !== new URL(request.url).origin;\n}\n\nfunction requestWithMiddlewareRequestHeaders(\n request: Request,\n middlewareHeaders: Headers | null,\n): Request {\n const nextHeaders = middlewareHeaders\n ? buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders, {\n preserveCredentialHeaders: true,\n })\n : null;\n if (!nextHeaders) return request;\n\n const init: RequestInit = {\n method: request.method,\n headers: nextHeaders,\n body: request.body,\n };\n if (request.body) {\n Object.defineProperty(init, \"duplex\", { value: \"half\", enumerable: true });\n }\n\n return new Request(request.url, init);\n}\n\nexport async function proxyExternalMiddlewareRewrite(\n request: Request,\n rewriteUrl: string,\n context: AppMiddlewareContext,\n): Promise<Response> {\n const proxyRequest = requestWithMiddlewareRequestHeaders(\n request,\n context.requestHeaders ?? context.headers,\n );\n setHeadersContext(null);\n setNavigationContext(null);\n\n const proxyResponse = await proxyExternalRequest(proxyRequest, rewriteUrl);\n const headers = new Headers(proxyResponse.headers);\n processMiddlewareHeaders(headers);\n\n if (!context.headers) {\n return new Response(proxyResponse.body, {\n status: proxyResponse.status,\n statusText: proxyResponse.statusText,\n headers,\n });\n }\n\n const middlewareHeaders = new Headers(context.headers);\n processMiddlewareHeaders(middlewareHeaders);\n mergeMiddlewareResponseHeaders(headers, middlewareHeaders);\n return new Response(proxyResponse.body, {\n status: proxyResponse.status,\n statusText: proxyResponse.statusText,\n headers,\n });\n}\n\nfunction applyForwardedMiddlewareContext(\n request: Request,\n context: AppMiddlewareContext,\n): { applied: boolean; rewriteUrl?: string } {\n if (process.env.NODE_ENV === \"production\") {\n return { applied: false };\n }\n\n const header = request.headers.get(VINEXT_MW_CTX_HEADER);\n if (!header) return { applied: false };\n\n try {\n const data = JSON.parse(header);\n if (!isForwardedMiddlewareContext(data)) return { applied: false };\n\n if (Array.isArray(data.h) && data.h.length > 0) {\n context.headers = new Headers();\n for (const entry of data.h) {\n appendForwardedHeader(context.headers, entry);\n }\n }\n if (typeof data.s === \"number\") {\n context.status = data.s;\n }\n if (typeof data.r === \"string\" && data.r.length > 0) {\n return { applied: true, rewriteUrl: data.r };\n }\n return { applied: true };\n } catch (e) {\n console.error(\"[vinext] Failed to parse forwarded middleware context:\", e);\n return { applied: false };\n }\n}\n\nexport async function applyAppMiddleware(\n options: ApplyAppMiddlewareOptions,\n): Promise<ApplyAppMiddlewareResult> {\n const forwarded = applyForwardedMiddlewareContext(options.request, options.context);\n const middlewareRequest = requestWithoutFlightHeaders(options.request);\n let cleanPathname = options.cleanPathname;\n let search: string | null = null;\n\n if (forwarded.rewriteUrl) {\n try {\n if (isExternalMiddlewareRewrite(forwarded.rewriteUrl, middlewareRequest)) {\n return {\n kind: \"response\",\n response: await proxyExternalMiddlewareRewrite(\n middlewareRequest,\n forwarded.rewriteUrl,\n options.context,\n ),\n };\n }\n const rewriteParsed = new URL(forwarded.rewriteUrl, middlewareRequest.url);\n cleanPathname = rewriteParsed.pathname;\n search = rewriteParsed.search;\n } catch (e) {\n console.error(\"[vinext] Failed to apply forwarded middleware rewrite:\", e);\n forwarded.applied = false;\n }\n }\n\n if (!forwarded.applied) {\n const result = await executeMiddleware({\n basePath: options.basePath,\n i18nConfig: options.i18nConfig,\n isDataRequest: options.isDataRequest,\n isProxy: options.isProxy,\n module: options.module,\n normalizedPathname: cleanPathname,\n request: middlewareRequest,\n trailingSlash: options.trailingSlash,\n });\n\n if (!result.continue) {\n if (result.redirectUrl) {\n return { kind: \"response\", response: responseFromMiddlewareRedirect(result) };\n }\n if (result.response) {\n return { kind: \"response\", response: result.response };\n }\n return { kind: \"response\", response: internalServerErrorResponse() };\n }\n\n if (result.responseHeaders) {\n options.context.headers = new Headers(result.responseHeaders);\n }\n\n if (result.status !== undefined) {\n options.context.status = result.status;\n }\n\n if (result.rewriteUrl) {\n if (result.rewriteStatus !== undefined) {\n options.context.status = result.rewriteStatus;\n }\n if (isExternalUrl(result.rewriteUrl)) {\n return {\n kind: \"response\",\n response: await proxyExternalMiddlewareRewrite(\n middlewareRequest,\n result.rewriteUrl,\n options.context,\n ),\n };\n }\n const rewriteParsed = new URL(result.rewriteUrl, middlewareRequest.url);\n cleanPathname = rewriteParsed.pathname;\n search = rewriteParsed.search;\n }\n }\n\n if (options.context.headers) {\n options.context.requestHeaders = new Headers(options.context.headers);\n applyMiddlewareRequestHeaders(options.context.headers);\n processMiddlewareHeaders(options.context.headers);\n }\n\n return { kind: \"continue\", cleanPathname, search };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { RouteManifest, RouteManifestRoute } from "../routing/app-route-graph.js";
|
|
2
|
+
import { AppElements } from "./app-elements-wire.js";
|
|
3
|
+
//#region src/server/app-optimistic-routing.d.ts
|
|
4
|
+
type OptimisticRouteMatch = {
|
|
5
|
+
params: Record<string, string | string[]>;
|
|
6
|
+
route: RouteManifestRoute;
|
|
7
|
+
};
|
|
8
|
+
type OptimisticRouteTemplate = {
|
|
9
|
+
elements: AppElements;
|
|
10
|
+
mountedSlotsHeader: string | null;
|
|
11
|
+
pageElementIds: readonly string[];
|
|
12
|
+
routeId: string;
|
|
13
|
+
};
|
|
14
|
+
type OptimisticNavigationPayload = {
|
|
15
|
+
elements: AppElements;
|
|
16
|
+
params: Record<string, string | string[]>;
|
|
17
|
+
template: OptimisticRouteTemplate;
|
|
18
|
+
};
|
|
19
|
+
declare function getOptimisticRouteTemplateKey(options: {
|
|
20
|
+
interceptionContext: string | null;
|
|
21
|
+
mountedSlotsHeader: string | null;
|
|
22
|
+
routeId: string;
|
|
23
|
+
}): string;
|
|
24
|
+
declare function getOptimisticPrefetchSourceKey(options: {
|
|
25
|
+
cacheKey: string;
|
|
26
|
+
interceptionContext: string | null;
|
|
27
|
+
mountedSlotsHeader: string | null;
|
|
28
|
+
}): string;
|
|
29
|
+
declare function matchOptimisticRouteManifestRoute(options: {
|
|
30
|
+
basePath: string;
|
|
31
|
+
href: string;
|
|
32
|
+
routeManifest: RouteManifest;
|
|
33
|
+
}): OptimisticRouteMatch | null;
|
|
34
|
+
declare function createOptimisticRouteTemplate(options: {
|
|
35
|
+
allowLoadingShell?: boolean;
|
|
36
|
+
basePath: string;
|
|
37
|
+
elements: AppElements;
|
|
38
|
+
href: string;
|
|
39
|
+
interceptionContext: string | null;
|
|
40
|
+
mountedSlotsHeader: string | null;
|
|
41
|
+
routeManifest: RouteManifest;
|
|
42
|
+
}): OptimisticRouteTemplate | null;
|
|
43
|
+
declare function createOptimisticRouteElements(template: OptimisticRouteTemplate): AppElements;
|
|
44
|
+
declare function resolveOptimisticNavigationPayload(options: {
|
|
45
|
+
basePath: string;
|
|
46
|
+
href: string;
|
|
47
|
+
interceptionContext: string | null;
|
|
48
|
+
mountedSlotsHeader: string | null;
|
|
49
|
+
routeManifest: RouteManifest;
|
|
50
|
+
templates: ReadonlyMap<string, OptimisticRouteTemplate>;
|
|
51
|
+
}): OptimisticNavigationPayload | null;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { OptimisticRouteTemplate, createOptimisticRouteElements, createOptimisticRouteTemplate, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, matchOptimisticRouteManifestRoute, resolveOptimisticNavigationPayload };
|
|
54
|
+
//# sourceMappingURL=app-optimistic-routing.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { decodeMatchedParams, splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
|
+
import { stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { isUnknownRecord } from "../utils/record.js";
|
|
4
|
+
import { AppElementsWire } from "./app-elements-wire.js";
|
|
5
|
+
import "./app-elements.js";
|
|
6
|
+
import { stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
|
|
7
|
+
import { Suspense, createElement, isValidElement } from "react";
|
|
8
|
+
//#region src/server/app-optimistic-routing.ts
|
|
9
|
+
const routeTrieCache = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
const OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER = new Promise(() => {});
|
|
11
|
+
function getOptimisticRouteTemplateKey(options) {
|
|
12
|
+
return `${options.routeId}\0${options.interceptionContext ?? ""}\0${options.mountedSlotsHeader ?? ""}`;
|
|
13
|
+
}
|
|
14
|
+
function getOptimisticPrefetchSourceKey(options) {
|
|
15
|
+
return `${options.cacheKey}\0${options.interceptionContext ?? ""}\0${options.mountedSlotsHeader ?? ""}`;
|
|
16
|
+
}
|
|
17
|
+
function createNode() {
|
|
18
|
+
return {
|
|
19
|
+
catchAllChild: null,
|
|
20
|
+
dynamicChild: null,
|
|
21
|
+
optionalCatchAllChild: null,
|
|
22
|
+
route: null,
|
|
23
|
+
staticChildren: /* @__PURE__ */ new Map()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function buildRouteTrie(routeManifest) {
|
|
27
|
+
const root = createNode();
|
|
28
|
+
for (const route of routeManifest.segmentGraph.routes.values()) {
|
|
29
|
+
let node = root;
|
|
30
|
+
const parts = route.patternParts;
|
|
31
|
+
if (parts.length === 0) {
|
|
32
|
+
node.route ??= route;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
for (const [index, part] of parts.entries()) {
|
|
36
|
+
const isTerminal = index === parts.length - 1;
|
|
37
|
+
if (part.startsWith(":") && part.endsWith("+")) {
|
|
38
|
+
if (isTerminal && node.catchAllChild === null) node.catchAllChild = {
|
|
39
|
+
paramName: part.slice(1, -1),
|
|
40
|
+
route
|
|
41
|
+
};
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (part.startsWith(":") && part.endsWith("*")) {
|
|
45
|
+
if (isTerminal && node.optionalCatchAllChild === null) node.optionalCatchAllChild = {
|
|
46
|
+
paramName: part.slice(1, -1),
|
|
47
|
+
route
|
|
48
|
+
};
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (part.startsWith(":")) {
|
|
52
|
+
const paramName = part.slice(1);
|
|
53
|
+
if (node.dynamicChild === null) node.dynamicChild = {
|
|
54
|
+
node: createNode(),
|
|
55
|
+
paramName
|
|
56
|
+
};
|
|
57
|
+
else if (node.dynamicChild.paramName !== paramName && import.meta.env.DEV) console.warn(`[vinext] Optimistic route trie found conflicting dynamic segments at the same level: :${node.dynamicChild.paramName} vs ${part}`);
|
|
58
|
+
node = node.dynamicChild.node;
|
|
59
|
+
if (isTerminal) node.route ??= route;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
let staticChild = node.staticChildren.get(part);
|
|
63
|
+
if (staticChild === void 0) {
|
|
64
|
+
staticChild = createNode();
|
|
65
|
+
node.staticChildren.set(part, staticChild);
|
|
66
|
+
}
|
|
67
|
+
node = staticChild;
|
|
68
|
+
if (isTerminal) node.route ??= route;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return root;
|
|
72
|
+
}
|
|
73
|
+
function getRouteTrie(routeManifest) {
|
|
74
|
+
const existing = routeTrieCache.get(routeManifest);
|
|
75
|
+
if (existing) return existing;
|
|
76
|
+
const trie = buildRouteTrie(routeManifest);
|
|
77
|
+
routeTrieCache.set(routeManifest, trie);
|
|
78
|
+
return trie;
|
|
79
|
+
}
|
|
80
|
+
function matchNode(node, urlParts, index) {
|
|
81
|
+
if (index === urlParts.length) {
|
|
82
|
+
if (node.route !== null) return {
|
|
83
|
+
route: node.route,
|
|
84
|
+
params: Object.create(null)
|
|
85
|
+
};
|
|
86
|
+
if (node.optionalCatchAllChild !== null) return {
|
|
87
|
+
route: node.optionalCatchAllChild.route,
|
|
88
|
+
params: Object.create(null)
|
|
89
|
+
};
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const segment = urlParts[index];
|
|
93
|
+
const staticChild = node.staticChildren.get(segment);
|
|
94
|
+
if (staticChild !== void 0) return matchNode(staticChild, urlParts, index + 1);
|
|
95
|
+
if (node.dynamicChild !== null) {
|
|
96
|
+
const match = matchNode(node.dynamicChild.node, urlParts, index + 1);
|
|
97
|
+
if (match === null) return null;
|
|
98
|
+
match.params[node.dynamicChild.paramName] = segment;
|
|
99
|
+
return match;
|
|
100
|
+
}
|
|
101
|
+
if (node.catchAllChild !== null) return {
|
|
102
|
+
route: node.catchAllChild.route,
|
|
103
|
+
params: { [node.catchAllChild.paramName]: urlParts.slice(index) }
|
|
104
|
+
};
|
|
105
|
+
if (node.optionalCatchAllChild !== null) return {
|
|
106
|
+
route: node.optionalCatchAllChild.route,
|
|
107
|
+
params: { [node.optionalCatchAllChild.paramName]: urlParts.slice(index) }
|
|
108
|
+
};
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function hrefToRouteParts(href, basePath) {
|
|
112
|
+
let url;
|
|
113
|
+
try {
|
|
114
|
+
url = new URL(href, "https://vinext.local");
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
stripRscCacheBustingSearchParam(url);
|
|
119
|
+
const appPathname = stripBasePath(stripRscSuffix(url.pathname), basePath);
|
|
120
|
+
return splitPathnameForRouteMatch(appPathname === "" ? "/" : appPathname);
|
|
121
|
+
}
|
|
122
|
+
function matchOptimisticRouteManifestRoute(options) {
|
|
123
|
+
const urlParts = hrefToRouteParts(options.href, options.basePath);
|
|
124
|
+
if (urlParts === null) return null;
|
|
125
|
+
const match = matchNode(getRouteTrie(options.routeManifest), urlParts, 0);
|
|
126
|
+
if (match === null) return null;
|
|
127
|
+
decodeMatchedParams(match.params);
|
|
128
|
+
return match;
|
|
129
|
+
}
|
|
130
|
+
function elementHasSuspenseFallback(value, depth = 0) {
|
|
131
|
+
if (depth > 100) return false;
|
|
132
|
+
if (Array.isArray(value)) return value.some((entry) => elementHasSuspenseFallback(entry, depth + 1));
|
|
133
|
+
if (!isValidElement(value)) return false;
|
|
134
|
+
const props = Reflect.get(value, "props");
|
|
135
|
+
if (value.type === Suspense && isUnknownRecord(props)) {
|
|
136
|
+
const fallback = Reflect.get(props, "fallback");
|
|
137
|
+
if (fallback !== null && fallback !== void 0) return true;
|
|
138
|
+
}
|
|
139
|
+
if (!isUnknownRecord(props)) return false;
|
|
140
|
+
return elementHasSuspenseFallback(Reflect.get(props, "children"), depth + 1);
|
|
141
|
+
}
|
|
142
|
+
function getPageElementIds(elements) {
|
|
143
|
+
return Object.keys(elements).filter((key) => AppElementsWire.parseElementKey(key)?.kind === "page").sort();
|
|
144
|
+
}
|
|
145
|
+
function OptimisticRouteSegment() {
|
|
146
|
+
throw OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER;
|
|
147
|
+
}
|
|
148
|
+
function createOptimisticRouteTemplate(options) {
|
|
149
|
+
const match = matchOptimisticRouteManifestRoute({
|
|
150
|
+
basePath: options.basePath,
|
|
151
|
+
href: options.href,
|
|
152
|
+
routeManifest: options.routeManifest
|
|
153
|
+
});
|
|
154
|
+
if (match === null || !match.route.isDynamic) return null;
|
|
155
|
+
if (options.interceptionContext !== null) return null;
|
|
156
|
+
const metadata = AppElementsWire.readMetadata(options.elements);
|
|
157
|
+
if (metadata.interception !== null || metadata.interceptionContext !== null) return null;
|
|
158
|
+
const routeElement = options.elements[metadata.routeId];
|
|
159
|
+
if (!options.allowLoadingShell && !elementHasSuspenseFallback(routeElement)) return null;
|
|
160
|
+
if (options.allowLoadingShell && options.elements["__prefetchLoadingShell"] !== "LoadingBoundary") return null;
|
|
161
|
+
if (options.allowLoadingShell && (routeElement === void 0 || routeElement === null)) return null;
|
|
162
|
+
const pageElementIds = getPageElementIds(options.elements);
|
|
163
|
+
if (pageElementIds.length === 0) return null;
|
|
164
|
+
return {
|
|
165
|
+
elements: options.elements,
|
|
166
|
+
mountedSlotsHeader: options.mountedSlotsHeader,
|
|
167
|
+
pageElementIds,
|
|
168
|
+
routeId: match.route.id
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function createOptimisticRouteElements(template) {
|
|
172
|
+
const elements = { ...template.elements };
|
|
173
|
+
for (const pageElementId of template.pageElementIds) elements[pageElementId] = createElement(OptimisticRouteSegment);
|
|
174
|
+
return elements;
|
|
175
|
+
}
|
|
176
|
+
function resolveOptimisticNavigationPayload(options) {
|
|
177
|
+
if (options.interceptionContext !== null) return null;
|
|
178
|
+
const match = matchOptimisticRouteManifestRoute({
|
|
179
|
+
basePath: options.basePath,
|
|
180
|
+
href: options.href,
|
|
181
|
+
routeManifest: options.routeManifest
|
|
182
|
+
});
|
|
183
|
+
if (match === null || !match.route.isDynamic) return null;
|
|
184
|
+
const template = options.templates.get(getOptimisticRouteTemplateKey({
|
|
185
|
+
interceptionContext: options.interceptionContext,
|
|
186
|
+
mountedSlotsHeader: options.mountedSlotsHeader,
|
|
187
|
+
routeId: match.route.id
|
|
188
|
+
}));
|
|
189
|
+
if (template === void 0) return null;
|
|
190
|
+
if (template.mountedSlotsHeader !== options.mountedSlotsHeader) return null;
|
|
191
|
+
return {
|
|
192
|
+
elements: createOptimisticRouteElements(template),
|
|
193
|
+
params: match.params,
|
|
194
|
+
template
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { createOptimisticRouteElements, createOptimisticRouteTemplate, getOptimisticPrefetchSourceKey, getOptimisticRouteTemplateKey, matchOptimisticRouteManifestRoute, resolveOptimisticNavigationPayload };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=app-optimistic-routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-optimistic-routing.js","names":[],"sources":["../../src/server/app-optimistic-routing.ts"],"sourcesContent":["import { createElement, isValidElement, Suspense } from \"react\";\nimport { isUnknownRecord } from \"../utils/record.js\";\nimport { stripBasePath } from \"../utils/base-path.js\";\nimport { decodeMatchedParams, splitPathnameForRouteMatch } from \"../routing/utils.js\";\nimport type { RouteManifest, RouteManifestRoute } from \"../routing/app-route-graph.js\";\nimport { stripRscCacheBustingSearchParam, stripRscSuffix } from \"./app-rsc-cache-busting.js\";\nimport {\n AppElementsWire,\n APP_PREFETCH_LOADING_SHELL_MARKER_KEY,\n type AppElementValue,\n type AppElements,\n} from \"./app-elements.js\";\n\ntype OptimisticRouteTrieNode = {\n catchAllChild: { paramName: string; route: RouteManifestRoute } | null;\n dynamicChild: { node: OptimisticRouteTrieNode; paramName: string } | null;\n optionalCatchAllChild: { paramName: string; route: RouteManifestRoute } | null;\n route: RouteManifestRoute | null;\n staticChildren: Map<string, OptimisticRouteTrieNode>;\n};\n\ntype OptimisticRouteMatch = {\n params: Record<string, string | string[]>;\n route: RouteManifestRoute;\n};\n\nexport type OptimisticRouteTemplate = {\n elements: AppElements;\n mountedSlotsHeader: string | null;\n pageElementIds: readonly string[];\n routeId: string;\n};\n\ntype OptimisticNavigationPayload = {\n elements: AppElements;\n params: Record<string, string | string[]>;\n template: OptimisticRouteTemplate;\n};\n\nconst routeTrieCache = new WeakMap<RouteManifest, OptimisticRouteTrieNode>();\n// Shared never-settling thenable used to suspend optimistic page segments until\n// the real RSC payload replaces them.\nconst OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER = new Promise<never>(() => {});\n\nexport function getOptimisticRouteTemplateKey(options: {\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeId: string;\n}): string {\n return `${options.routeId}\\0${options.interceptionContext ?? \"\"}\\0${options.mountedSlotsHeader ?? \"\"}`;\n}\n\nexport function getOptimisticPrefetchSourceKey(options: {\n cacheKey: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n}): string {\n return `${options.cacheKey}\\0${options.interceptionContext ?? \"\"}\\0${options.mountedSlotsHeader ?? \"\"}`;\n}\n\nfunction createNode(): OptimisticRouteTrieNode {\n return {\n catchAllChild: null,\n dynamicChild: null,\n optionalCatchAllChild: null,\n route: null,\n staticChildren: new Map(),\n };\n}\n\nfunction buildRouteTrie(routeManifest: RouteManifest): OptimisticRouteTrieNode {\n const root = createNode();\n\n for (const route of routeManifest.segmentGraph.routes.values()) {\n let node = root;\n const parts = route.patternParts;\n\n if (parts.length === 0) {\n node.route ??= route;\n continue;\n }\n\n for (const [index, part] of parts.entries()) {\n const isTerminal = index === parts.length - 1;\n if (part.startsWith(\":\") && part.endsWith(\"+\")) {\n if (isTerminal && node.catchAllChild === null) {\n node.catchAllChild = { paramName: part.slice(1, -1), route };\n }\n break;\n }\n\n if (part.startsWith(\":\") && part.endsWith(\"*\")) {\n if (isTerminal && node.optionalCatchAllChild === null) {\n node.optionalCatchAllChild = { paramName: part.slice(1, -1), route };\n }\n break;\n }\n\n if (part.startsWith(\":\")) {\n const paramName = part.slice(1);\n if (node.dynamicChild === null) {\n node.dynamicChild = { node: createNode(), paramName };\n } else if (node.dynamicChild.paramName !== paramName && import.meta.env.DEV) {\n console.warn(\n `[vinext] Optimistic route trie found conflicting dynamic segments at the same level: :${node.dynamicChild.paramName} vs ${part}`,\n );\n }\n node = node.dynamicChild.node;\n if (isTerminal) node.route ??= route;\n continue;\n }\n\n let staticChild = node.staticChildren.get(part);\n if (staticChild === undefined) {\n staticChild = createNode();\n node.staticChildren.set(part, staticChild);\n }\n node = staticChild;\n if (isTerminal) node.route ??= route;\n }\n }\n\n return root;\n}\n\nfunction getRouteTrie(routeManifest: RouteManifest): OptimisticRouteTrieNode {\n const existing = routeTrieCache.get(routeManifest);\n if (existing) return existing;\n\n const trie = buildRouteTrie(routeManifest);\n routeTrieCache.set(routeManifest, trie);\n return trie;\n}\n\nfunction matchNode(\n node: OptimisticRouteTrieNode,\n urlParts: readonly string[],\n index: number,\n): OptimisticRouteMatch | null {\n if (index === urlParts.length) {\n if (node.route !== null) {\n return { route: node.route, params: Object.create(null) };\n }\n if (node.optionalCatchAllChild !== null) {\n return {\n route: node.optionalCatchAllChild.route,\n params: Object.create(null),\n };\n }\n return null;\n }\n\n const segment = urlParts[index];\n const staticChild = node.staticChildren.get(segment);\n if (staticChild !== undefined) {\n // Static children are authoritative for optimistic routing. If a known\n // static subtree does not contain the remaining URL, do not fall through to\n // a catch-all sibling and render the wrong loading boundary.\n return matchNode(staticChild, urlParts, index + 1);\n }\n\n if (node.dynamicChild !== null) {\n const match = matchNode(node.dynamicChild.node, urlParts, index + 1);\n if (match === null) return null;\n match.params[node.dynamicChild.paramName] = segment;\n return match;\n }\n\n if (node.catchAllChild !== null) {\n return {\n route: node.catchAllChild.route,\n params: {\n [node.catchAllChild.paramName]: urlParts.slice(index),\n },\n };\n }\n\n if (node.optionalCatchAllChild !== null) {\n return {\n route: node.optionalCatchAllChild.route,\n params: {\n [node.optionalCatchAllChild.paramName]: urlParts.slice(index),\n },\n };\n }\n\n return null;\n}\n\nfunction hrefToRouteParts(href: string, basePath: string): string[] | null {\n let url: URL;\n try {\n url = new URL(href, \"https://vinext.local\");\n } catch {\n return null;\n }\n\n stripRscCacheBustingSearchParam(url);\n const withoutRscSuffix = stripRscSuffix(url.pathname);\n const appPathname = stripBasePath(withoutRscSuffix, basePath);\n return splitPathnameForRouteMatch(appPathname === \"\" ? \"/\" : appPathname);\n}\n\nexport function matchOptimisticRouteManifestRoute(options: {\n basePath: string;\n href: string;\n routeManifest: RouteManifest;\n}): OptimisticRouteMatch | null {\n const urlParts = hrefToRouteParts(options.href, options.basePath);\n if (urlParts === null) return null;\n\n const match = matchNode(getRouteTrie(options.routeManifest), urlParts, 0);\n if (match === null) return null;\n\n decodeMatchedParams(match.params);\n return match;\n}\n\nfunction elementHasSuspenseFallback(value: unknown, depth = 0): boolean {\n if (depth > 100) return false;\n if (Array.isArray(value)) {\n return value.some((entry) => elementHasSuspenseFallback(entry, depth + 1));\n }\n if (!isValidElement(value)) return false;\n\n const props = Reflect.get(value, \"props\");\n if (value.type === Suspense && isUnknownRecord(props)) {\n const fallback = Reflect.get(props, \"fallback\");\n if (fallback !== null && fallback !== undefined) return true;\n }\n\n if (!isUnknownRecord(props)) return false;\n return elementHasSuspenseFallback(Reflect.get(props, \"children\"), depth + 1);\n}\n\nfunction getPageElementIds(elements: AppElements): string[] {\n return Object.keys(elements)\n .filter((key) => AppElementsWire.parseElementKey(key)?.kind === \"page\")\n .sort();\n}\n\nfunction OptimisticRouteSegment(): null {\n throw OPTIMISTIC_ROUTE_SEGMENT_SUSPENSE_TRIGGER;\n}\n\nexport function createOptimisticRouteTemplate(options: {\n allowLoadingShell?: boolean;\n basePath: string;\n elements: AppElements;\n href: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeManifest: RouteManifest;\n}): OptimisticRouteTemplate | null {\n const match = matchOptimisticRouteManifestRoute({\n basePath: options.basePath,\n href: options.href,\n routeManifest: options.routeManifest,\n });\n if (match === null || !match.route.isDynamic) return null;\n if (options.interceptionContext !== null) return null;\n\n const metadata = AppElementsWire.readMetadata(options.elements);\n if (metadata.interception !== null || metadata.interceptionContext !== null) return null;\n\n const routeElement = options.elements[metadata.routeId];\n // Full-prefetch learning is intentionally heuristic: legacy full prefetches\n // are accepted only when the serialized route subtree still contains a\n // Suspense fallback. Authoritative loading-shell prefetches use the marker\n // check below instead.\n if (!options.allowLoadingShell && !elementHasSuspenseFallback(routeElement)) return null;\n if (\n options.allowLoadingShell &&\n options.elements[APP_PREFETCH_LOADING_SHELL_MARKER_KEY] !== \"LoadingBoundary\"\n ) {\n return null;\n }\n // Shell prefetches must include the eagerly-rendered loading component. A\n // null route element means the server had no route loading boundary.\n if (options.allowLoadingShell && (routeElement === undefined || routeElement === null))\n return null;\n\n const pageElementIds = getPageElementIds(options.elements);\n if (pageElementIds.length === 0) return null;\n\n return {\n elements: options.elements,\n mountedSlotsHeader: options.mountedSlotsHeader,\n pageElementIds,\n routeId: match.route.id,\n };\n}\n\nexport function createOptimisticRouteElements(template: OptimisticRouteTemplate): AppElements {\n const elements: Record<string, AppElementValue> = { ...template.elements };\n for (const pageElementId of template.pageElementIds) {\n elements[pageElementId] = createElement(OptimisticRouteSegment);\n }\n return elements;\n}\n\nexport function resolveOptimisticNavigationPayload(options: {\n basePath: string;\n href: string;\n interceptionContext: string | null;\n mountedSlotsHeader: string | null;\n routeManifest: RouteManifest;\n templates: ReadonlyMap<string, OptimisticRouteTemplate>;\n}): OptimisticNavigationPayload | null {\n if (options.interceptionContext !== null) return null;\n\n const match = matchOptimisticRouteManifestRoute({\n basePath: options.basePath,\n href: options.href,\n routeManifest: options.routeManifest,\n });\n if (match === null || !match.route.isDynamic) return null;\n\n const template = options.templates.get(\n getOptimisticRouteTemplateKey({\n interceptionContext: options.interceptionContext,\n mountedSlotsHeader: options.mountedSlotsHeader,\n routeId: match.route.id,\n }),\n );\n if (template === undefined) return null;\n if (template.mountedSlotsHeader !== options.mountedSlotsHeader) return null;\n\n return {\n elements: createOptimisticRouteElements(template),\n params: match.params,\n template,\n };\n}\n"],"mappings":"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"}
|
|
@@ -13,6 +13,17 @@ type AppPageRequestCacheLife = {
|
|
|
13
13
|
revalidate?: number;
|
|
14
14
|
expire?: number;
|
|
15
15
|
};
|
|
16
|
+
type AppPageCacheOutcomeMetric = Readonly<{
|
|
17
|
+
artifact: "html" | "rsc";
|
|
18
|
+
/**
|
|
19
|
+
* Internal cache lookup key for debugging and tests. Runtime telemetry sinks should hash or
|
|
20
|
+
* redact this value before export to avoid high-cardinality or user-derived labels.
|
|
21
|
+
*/
|
|
22
|
+
cacheKey: string;
|
|
23
|
+
outcome: "hit" | "miss" | "stale";
|
|
24
|
+
reason: "empty-entry" | "no-entry" | "non-app-page-entry" | "read-error" | "served" | "stale-empty-entry";
|
|
25
|
+
}>;
|
|
26
|
+
type AppPageCacheOutcomeRecorder = (metric: AppPageCacheOutcomeMetric) => void;
|
|
16
27
|
type BuildAppPageCacheRenderObservation = (input: {
|
|
17
28
|
cacheTags: readonly string[];
|
|
18
29
|
state: AppPageRenderObservationState;
|
|
@@ -47,6 +58,7 @@ type ReadAppPageCacheResponseOptions = {
|
|
|
47
58
|
middlewareHeaders?: Headers | null;
|
|
48
59
|
middlewareStatus?: number | null;
|
|
49
60
|
mountedSlotsHeader?: string | null;
|
|
61
|
+
recordCacheOutcome?: AppPageCacheOutcomeRecorder;
|
|
50
62
|
renderMode?: AppRscRenderMode;
|
|
51
63
|
expireSeconds?: number;
|
|
52
64
|
revalidateSeconds: number;
|
|
@@ -98,5 +110,5 @@ declare function finalizeAppPageHtmlCacheResponse(response: Response, options: F
|
|
|
98
110
|
declare function finalizeAppPageRscCacheResponse(response: Response, options: ScheduleAppPageRscCacheWriteOptions): Response;
|
|
99
111
|
declare function scheduleAppPageRscCacheWrite(options: ScheduleAppPageRscCacheWriteOptions): boolean;
|
|
100
112
|
//#endregion
|
|
101
|
-
export { buildAppPageCacheTags, buildAppPageCachedResponse, finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse, readAppPageCacheResponse, scheduleAppPageRscCacheWrite };
|
|
113
|
+
export { AppPageCacheOutcomeMetric, buildAppPageCacheTags, buildAppPageCachedResponse, finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse, readAppPageCacheResponse, scheduleAppPageRscCacheWrite };
|
|
102
114
|
//# sourceMappingURL=app-page-cache.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { VINEXT_MOUNTED_SLOTS_HEADER } from "./headers.js";
|
|
2
|
+
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
2
3
|
import { encodeCacheTag } from "../utils/encode-cache-tag.js";
|
|
3
4
|
import { buildAppPageCacheValue } from "./isr-cache.js";
|
|
4
5
|
import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader } from "./app-rsc-cache-busting.js";
|
|
@@ -8,6 +9,11 @@ import { readStreamAsText } from "../utils/text-stream.js";
|
|
|
8
9
|
import { createEmptyAppPageRenderObservationState } from "./app-page-render-observation.js";
|
|
9
10
|
//#region src/server/app-page-cache.ts
|
|
10
11
|
const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
|
|
12
|
+
function recordAppPageCacheOutcome(recordCacheOutcome, input) {
|
|
13
|
+
try {
|
|
14
|
+
recordCacheOutcome?.(input);
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
11
17
|
function buildAppPageCacheTags(pathname, extraTags) {
|
|
12
18
|
const tags = [
|
|
13
19
|
pathname,
|
|
@@ -34,9 +40,9 @@ function buildAppPageCachedHeaders(options) {
|
|
|
34
40
|
const headers = new Headers({
|
|
35
41
|
"Cache-Control": options.cacheControl,
|
|
36
42
|
"Content-Type": options.contentType,
|
|
37
|
-
Vary: VINEXT_RSC_VARY_HEADER
|
|
38
|
-
[VINEXT_CACHE_HEADER]: options.cacheState
|
|
43
|
+
Vary: VINEXT_RSC_VARY_HEADER
|
|
39
44
|
});
|
|
45
|
+
setCacheStateHeaders(headers, options.cacheState);
|
|
40
46
|
if (options.mountedSlotsHeader) headers.set(VINEXT_MOUNTED_SLOTS_HEADER, options.mountedSlotsHeader);
|
|
41
47
|
mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders ?? null);
|
|
42
48
|
return headers;
|
|
@@ -90,9 +96,20 @@ function buildAppPageCachedResponse(cachedValue, options) {
|
|
|
90
96
|
}
|
|
91
97
|
async function readAppPageCacheResponse(options) {
|
|
92
98
|
const isrKey = options.isRscRequest ? options.isrRscKey(options.cleanPathname, options.mountedSlotsHeader, options.renderMode) : options.isrHtmlKey(options.cleanPathname);
|
|
99
|
+
const artifact = options.isRscRequest ? "rsc" : "html";
|
|
93
100
|
try {
|
|
94
101
|
const cached = await options.isrGet(isrKey);
|
|
95
102
|
const cachedValue = getCachedAppPageValue(cached);
|
|
103
|
+
if (cached && !cachedValue) {
|
|
104
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
105
|
+
artifact,
|
|
106
|
+
cacheKey: isrKey,
|
|
107
|
+
outcome: "miss",
|
|
108
|
+
reason: "non-app-page-entry"
|
|
109
|
+
});
|
|
110
|
+
options.isrDebug?.("MISS (non app-page cache entry)", options.cleanPathname);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
96
113
|
if (cachedValue && !cached?.isStale) {
|
|
97
114
|
const hitResponse = buildAppPageCachedResponse(cachedValue, {
|
|
98
115
|
cacheState: "HIT",
|
|
@@ -105,10 +122,22 @@ async function readAppPageCacheResponse(options) {
|
|
|
105
122
|
revalidateSeconds: options.revalidateSeconds
|
|
106
123
|
});
|
|
107
124
|
if (hitResponse) {
|
|
125
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
126
|
+
artifact,
|
|
127
|
+
cacheKey: isrKey,
|
|
128
|
+
outcome: "hit",
|
|
129
|
+
reason: "served"
|
|
130
|
+
});
|
|
108
131
|
options.isrDebug?.(options.isRscRequest ? "HIT (RSC)" : "HIT (HTML)", options.cleanPathname);
|
|
109
132
|
options.clearRequestContext();
|
|
110
133
|
return hitResponse;
|
|
111
134
|
}
|
|
135
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
136
|
+
artifact,
|
|
137
|
+
cacheKey: isrKey,
|
|
138
|
+
outcome: "miss",
|
|
139
|
+
reason: "empty-entry"
|
|
140
|
+
});
|
|
112
141
|
options.isrDebug?.("MISS (empty cached entry)", options.cleanPathname);
|
|
113
142
|
}
|
|
114
143
|
if (cached?.isStale && cachedValue) {
|
|
@@ -133,14 +162,40 @@ async function readAppPageCacheResponse(options) {
|
|
|
133
162
|
revalidateSeconds: options.revalidateSeconds
|
|
134
163
|
});
|
|
135
164
|
if (staleResponse) {
|
|
165
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
166
|
+
artifact,
|
|
167
|
+
cacheKey: isrKey,
|
|
168
|
+
outcome: "stale",
|
|
169
|
+
reason: "served"
|
|
170
|
+
});
|
|
136
171
|
options.isrDebug?.(options.isRscRequest ? "STALE (RSC)" : "STALE (HTML)", options.cleanPathname);
|
|
137
172
|
options.clearRequestContext();
|
|
138
173
|
return staleResponse;
|
|
139
174
|
}
|
|
175
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
176
|
+
artifact,
|
|
177
|
+
cacheKey: isrKey,
|
|
178
|
+
outcome: "miss",
|
|
179
|
+
reason: "stale-empty-entry"
|
|
180
|
+
});
|
|
140
181
|
options.isrDebug?.("STALE MISS (empty stale entry)", options.cleanPathname);
|
|
141
182
|
}
|
|
142
|
-
if (!cached)
|
|
183
|
+
if (!cached) {
|
|
184
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
185
|
+
artifact,
|
|
186
|
+
cacheKey: isrKey,
|
|
187
|
+
outcome: "miss",
|
|
188
|
+
reason: "no-entry"
|
|
189
|
+
});
|
|
190
|
+
options.isrDebug?.("MISS (no cache entry)", options.cleanPathname);
|
|
191
|
+
}
|
|
143
192
|
} catch (isrReadError) {
|
|
193
|
+
recordAppPageCacheOutcome(options.recordCacheOutcome, {
|
|
194
|
+
artifact,
|
|
195
|
+
cacheKey: isrKey,
|
|
196
|
+
outcome: "miss",
|
|
197
|
+
reason: "read-error"
|
|
198
|
+
});
|
|
144
199
|
console.error("[vinext] ISR cache read error:", isrReadError);
|
|
145
200
|
}
|
|
146
201
|
return null;
|
|
@@ -153,7 +208,7 @@ function finalizeAppPageHtmlCacheResponse(response, options) {
|
|
|
153
208
|
const clientHeaders = new Headers(response.headers);
|
|
154
209
|
if (options.preserveClientResponseHeaders !== true) {
|
|
155
210
|
clientHeaders.set("Cache-Control", NO_STORE_CACHE_CONTROL);
|
|
156
|
-
clientHeaders
|
|
211
|
+
setCacheStateHeaders(clientHeaders, "MISS");
|
|
157
212
|
}
|
|
158
213
|
const cachePromise = (async () => {
|
|
159
214
|
try {
|
|
@@ -201,7 +256,7 @@ function finalizeAppPageRscCacheResponse(response, options) {
|
|
|
201
256
|
if (options.preserveClientResponseHeaders === true) return response;
|
|
202
257
|
const clientHeaders = new Headers(response.headers);
|
|
203
258
|
clientHeaders.set("Cache-Control", NO_STORE_CACHE_CONTROL);
|
|
204
|
-
clientHeaders
|
|
259
|
+
setCacheStateHeaders(clientHeaders, "MISS");
|
|
205
260
|
return new Response(response.body, {
|
|
206
261
|
status: response.status,
|
|
207
262
|
statusText: response.statusText,
|