vinext 0.1.5 → 0.1.6

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.
Files changed (69) hide show
  1. package/README.md +2 -2
  2. package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
  3. package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
  4. package/dist/build/report.d.ts +1 -2
  5. package/dist/build/report.js +2 -11
  6. package/dist/build/run-prerender.js +1 -1
  7. package/dist/check.js +3 -3
  8. package/dist/client/pages-router-link-navigation.d.ts +10 -0
  9. package/dist/client/pages-router-link-navigation.js +3 -3
  10. package/dist/deploy.js +3 -3
  11. package/dist/entries/app-rsc-entry.js +1 -0
  12. package/dist/entries/app-rsc-manifest.js +76 -46
  13. package/dist/entries/pages-server-entry.js +1 -0
  14. package/dist/index.js +69 -54
  15. package/dist/init.d.ts +5 -4
  16. package/dist/init.js +27 -8
  17. package/dist/plugins/fonts.js +3 -1
  18. package/dist/plugins/middleware-export-validation.d.ts +5 -0
  19. package/dist/plugins/middleware-export-validation.js +36 -0
  20. package/dist/plugins/og-asset-ownership.d.ts +27 -0
  21. package/dist/plugins/og-asset-ownership.js +285 -0
  22. package/dist/plugins/og-assets.js +22 -4
  23. package/dist/server/accept-encoding.d.ts +29 -0
  24. package/dist/server/accept-encoding.js +95 -0
  25. package/dist/server/app-browser-entry.js +8 -5
  26. package/dist/server/app-browser-navigation-controller.js +1 -1
  27. package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
  28. package/dist/server/app-browser-rsc-redirect.js +8 -1
  29. package/dist/server/app-browser-state.js +1 -1
  30. package/dist/server/app-browser-visible-commit.js +1 -1
  31. package/dist/server/app-middleware.d.ts +1 -0
  32. package/dist/server/app-middleware.js +1 -0
  33. package/dist/server/app-page-dispatch.d.ts +2 -2
  34. package/dist/server/app-page-head.js +2 -2
  35. package/dist/server/app-page-request.d.ts +7 -0
  36. package/dist/server/app-page-request.js +19 -1
  37. package/dist/server/app-prerender-endpoints.d.ts +3 -3
  38. package/dist/server/app-prerender-endpoints.js +3 -4
  39. package/dist/server/app-route-module-loader.d.ts +62 -14
  40. package/dist/server/app-route-module-loader.js +61 -10
  41. package/dist/server/app-rsc-handler.d.ts +7 -6
  42. package/dist/server/app-rsc-handler.js +31 -26
  43. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  44. package/dist/server/app-rsc-route-matching.js +13 -0
  45. package/dist/server/app-ssr-entry.js +2 -2
  46. package/dist/server/headers.d.ts +5 -1
  47. package/dist/server/headers.js +5 -1
  48. package/dist/server/metadata-route-response.d.ts +3 -3
  49. package/dist/server/middleware-runtime.d.ts +2 -1
  50. package/dist/server/middleware-runtime.js +19 -8
  51. package/dist/server/middleware.d.ts +2 -2
  52. package/dist/server/middleware.js +26 -11
  53. package/dist/server/pages-request-pipeline.d.ts +3 -1
  54. package/dist/server/pages-request-pipeline.js +4 -2
  55. package/dist/server/prod-server.d.ts +1 -1
  56. package/dist/server/prod-server.js +85 -72
  57. package/dist/shims/font-google-base.d.ts +1 -0
  58. package/dist/shims/font-google-base.js +6 -21
  59. package/dist/shims/internal/interpolate-as.d.ts +25 -0
  60. package/dist/shims/internal/interpolate-as.js +196 -0
  61. package/dist/shims/link.js +24 -1
  62. package/dist/shims/navigation.js +1 -1
  63. package/dist/shims/navigation.react-server.js +1 -1
  64. package/dist/shims/router.d.ts +1 -3
  65. package/dist/shims/router.js +62 -18
  66. package/dist/typegen.js +2 -10
  67. package/dist/utils/project.d.ts +7 -1
  68. package/dist/utils/project.js +16 -2
  69. package/package.json +3 -3
@@ -16,12 +16,12 @@ import DefaultGlobalError from "../shims/default-global-error.js";
16
16
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
17
17
  import { createSsrErrorMetaRenderer } from "./app-ssr-error-meta.js";
18
18
  import { createNavigationRuntimeRscMetadataScript, createRscEmbedTransform, createTickBufferedTransform } from "./app-ssr-stream.js";
19
+ import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
20
+ import { runWithRootParamsScope } from "../shims/root-params.js";
19
21
  import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-bfcache-identity.js";
20
22
  import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
21
23
  import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
22
24
  import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
23
- import { runWithRootParamsScope } from "../shims/root-params.js";
24
- import { BeforeInteractiveContext } from "../shims/before-interactive-context.js";
25
25
  import { renderBeforeInteractiveInlineScripts } from "./before-interactive-head.js";
26
26
  import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
27
27
  import { ssrAppRouterInstance } from "./app-ssr-router-instance.js";
@@ -23,6 +23,10 @@ declare const VINEXT_TIMING_HEADER = "x-vinext-timing";
23
23
  declare const VINEXT_PRERENDER_SECRET_HEADER = "x-vinext-prerender-secret";
24
24
  /** URL-encoded JSON route params for build-time prerender renders. */
25
25
  declare const VINEXT_PRERENDER_ROUTE_PARAMS_HEADER = "x-vinext-prerender-route-params";
26
+ /** Internal endpoint used to evaluate App Router generateStaticParams exports. */
27
+ declare const VINEXT_PRERENDER_STATIC_PARAMS_PATH = "/__vinext/prerender/static-params";
28
+ /** Internal endpoint used to evaluate Pages Router getStaticPaths exports. */
29
+ declare const VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH = "/__vinext/prerender/pages-static-paths";
26
30
  /** TPR (Tailored Per-Request) revalidation interval in seconds. */
27
31
  declare const VINEXT_REVALIDATE_HEADER = "x-vinext-revalidate";
28
32
  /** Marker on cached ISR entries indicating RSC payload (value "1"). */
@@ -107,4 +111,4 @@ declare const INTERNAL_HEADERS: string[];
107
111
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
108
112
  declare const VINEXT_INTERNAL_HEADERS: string[];
109
113
  //#endregion
110
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
114
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -23,6 +23,10 @@ const VINEXT_TIMING_HEADER = "x-vinext-timing";
23
23
  const VINEXT_PRERENDER_SECRET_HEADER = "x-vinext-prerender-secret";
24
24
  /** URL-encoded JSON route params for build-time prerender renders. */
25
25
  const VINEXT_PRERENDER_ROUTE_PARAMS_HEADER = "x-vinext-prerender-route-params";
26
+ /** Internal endpoint used to evaluate App Router generateStaticParams exports. */
27
+ const VINEXT_PRERENDER_STATIC_PARAMS_PATH = "/__vinext/prerender/static-params";
28
+ /** Internal endpoint used to evaluate Pages Router getStaticPaths exports. */
29
+ const VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH = "/__vinext/prerender/pages-static-paths";
26
30
  /** TPR (Tailored Per-Request) revalidation interval in seconds. */
27
31
  const VINEXT_REVALIDATE_HEADER = "x-vinext-revalidate";
28
32
  /** Marker on cached ISR entries indicating RSC payload (value "1"). */
@@ -129,4 +133,4 @@ const INTERNAL_HEADERS = [
129
133
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
130
134
  const VINEXT_INTERNAL_HEADERS = [VINEXT_PRERENDER_ROUTE_PARAMS_HEADER];
131
135
  //#endregion
132
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
136
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_REQUEST_HEADER_PREFIX, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_REVALIDATE_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -2,15 +2,15 @@ import { MetadataFileRoute } from "./metadata-routes.js";
2
2
 
3
3
  //#region src/server/metadata-route-response.d.ts
4
4
  type AppPageParams = Record<string, string | string[]>;
5
- type MakeThenableParams = (params: AppPageParams) => unknown;
5
+ type MetadataRouteMakeThenableParams = (params: AppPageParams) => unknown;
6
6
  type MetadataRuntimeRoute = MetadataFileRoute & {
7
7
  fileDataBase64?: string;
8
8
  };
9
9
  type MetadataRouteRequestOptions = {
10
10
  metadataRoutes: readonly MetadataRuntimeRoute[];
11
11
  cleanPathname: string;
12
- makeThenableParams: MakeThenableParams;
12
+ makeThenableParams: MetadataRouteMakeThenableParams;
13
13
  };
14
14
  declare function handleMetadataRouteRequest(options: MetadataRouteRequestOptions): Promise<Response | null>;
15
15
  //#endregion
16
- export { handleMetadataRouteRequest };
16
+ export { MetadataRouteMakeThenableParams, MetadataRuntimeRoute, handleMetadataRouteRequest };
@@ -56,6 +56,7 @@ type ExecuteMiddlewareOptions = {
56
56
  type RunGeneratedMiddlewareOptions = ExecuteMiddlewareOptions & {
57
57
  ctx?: ExecutionContextLike;
58
58
  };
59
+ declare function createMiddlewareMissingExportError(filePath: string | undefined, isProxy: boolean): Error;
59
60
  declare function resolveMiddlewareModuleHandler(mod: MiddlewareModule, options: {
60
61
  filePath?: string;
61
62
  isProxy: boolean;
@@ -63,4 +64,4 @@ declare function resolveMiddlewareModuleHandler(mod: MiddlewareModule, options:
63
64
  declare function executeMiddleware(options: ExecuteMiddlewareOptions): Promise<MiddlewareResult>;
64
65
  declare function runGeneratedMiddleware(options: RunGeneratedMiddlewareOptions): Promise<MiddlewareResult>;
65
66
  //#endregion
66
- export { MiddlewareModule, MiddlewareResult, executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
67
+ export { MiddlewareModule, MiddlewareResult, createMiddlewareMissingExportError, executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
@@ -9,6 +9,7 @@ import { normalizePath } from "./normalize-path.js";
9
9
  import { matchesMiddleware } from "./middleware-matcher.js";
10
10
  import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
11
11
  import { processMiddlewareHeaders } from "./request-pipeline.js";
12
+ import path from "node:path";
12
13
  //#region src/server/middleware-runtime.ts
13
14
  function isMiddlewareHandler(value) {
14
15
  return typeof value === "function";
@@ -16,19 +17,29 @@ function isMiddlewareHandler(value) {
16
17
  function isMiddlewareConfigExport(value) {
17
18
  return !!value && typeof value === "object";
18
19
  }
19
- function middlewareFileLabel(isProxy) {
20
- return isProxy ? "Proxy" : "Middleware";
21
- }
22
20
  function middlewareExpectedExport(isProxy) {
23
21
  return isProxy ? "proxy" : "middleware";
24
22
  }
23
+ function middlewareDisplayPath(filePath) {
24
+ const fileName = path.basename(filePath);
25
+ return path.basename(path.dirname(filePath)) === "src" ? `./src/${fileName}` : `./${fileName}`;
26
+ }
27
+ function createMiddlewareMissingExportError(filePath, isProxy) {
28
+ const expectedExport = middlewareExpectedExport(isProxy);
29
+ const displayPath = filePath ? middlewareDisplayPath(filePath) : void 0;
30
+ const resolvedPath = displayPath ? ` "${displayPath}"` : "";
31
+ const migrationReason = isProxy ? "- You are migrating from `middleware` to `proxy`, but haven't updated the exported function.\n" : "";
32
+ return /* @__PURE__ */ new Error(`The file${resolvedPath} must export a function, either as a default export or as a named "${expectedExport}" export.\nThis function is what Next.js runs for every request handled by this ${isProxy ? "proxy (previously called middleware)" : "middleware"}.\n\nWhy this happens:\n` + migrationReason + `- The file exists but doesn't export a function.
33
+ - The export is not a function (e.g., an object or constant).
34
+ - There's a syntax error preventing the export from being recognized.
35
+
36
+ To fix it:
37
+ - Ensure this file has either a default or "${expectedExport}" function export.\n\nLearn more: https://nextjs.org/docs/messages/middleware-to-proxy`);
38
+ }
25
39
  function resolveMiddlewareModuleHandler(mod, options) {
26
40
  const handler = options.isProxy ? mod.proxy ?? mod.default : mod.middleware ?? mod.default;
27
41
  if (isMiddlewareHandler(handler)) return handler;
28
- const fileLabel = middlewareFileLabel(options.isProxy);
29
- const expectedExport = middlewareExpectedExport(options.isProxy);
30
- const fileSuffix = options.filePath ? ` "${options.filePath}"` : "";
31
- throw new Error(`The ${fileLabel} file${fileSuffix} must export a function named \`${expectedExport}\` or a \`default\` function.`);
42
+ throw createMiddlewareMissingExportError(options.filePath, options.isProxy);
32
43
  }
33
44
  function middlewareMatcher(mod) {
34
45
  const config = mod.config;
@@ -226,4 +237,4 @@ async function runGeneratedMiddleware(options) {
226
237
  return options.ctx ? runWithExecutionContext(options.ctx, run) : run();
227
238
  }
228
239
  //#endregion
229
- export { executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
240
+ export { createMiddlewareMissingExportError, executeMiddleware, resolveMiddlewareModuleHandler, runGeneratedMiddleware };
@@ -28,7 +28,7 @@ declare function isProxyFile(filePath: string): boolean;
28
28
  */
29
29
  declare function resolveMiddlewareHandler(mod: MiddlewareModule, filePath: string): (request: NextRequest, event: NextFetchEvent) => Response | undefined | void | Promise<Response | undefined | void>;
30
30
  /**
31
- * Find the proxy or middleware file in the project root.
31
+ * Find the proxy or middleware file at the selected app/pages convention level.
32
32
  * Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.
33
33
  * If middleware.ts is found, logs a deprecation warning.
34
34
  *
@@ -44,7 +44,7 @@ declare function resolveMiddlewareHandler(mod: MiddlewareModule, filePath: strin
44
44
  * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts
45
45
  * (search for "MIDDLEWARE_FILENAME" + "file convention is deprecated")
46
46
  */
47
- declare function findMiddlewareFile(root: string, fileMatcher: ValidFileMatcher): string | null;
47
+ declare function findMiddlewareFile(root: string, fileMatcher: ValidFileMatcher, conventionDir?: string): string | null;
48
48
  /**
49
49
  * Load and execute middleware for a given request.
50
50
  *
@@ -13,7 +13,8 @@ import path from "node:path";
13
13
  * https://github.com/vercel/next.js/blob/canary/packages/next/src/build/templates/middleware.ts
14
14
  */
15
15
  function isProxyFile(filePath) {
16
- return path.basename(filePath).replace(/\.\w+$/, "") === "proxy";
16
+ const base = path.basename(filePath);
17
+ return base === "proxy" || base.startsWith("proxy.");
17
18
  }
18
19
  /**
19
20
  * Resolve the middleware/proxy handler function from a module's exports.
@@ -32,9 +33,8 @@ function resolveMiddlewareHandler(mod, filePath) {
32
33
  isProxy: isProxyFile(filePath)
33
34
  });
34
35
  }
35
- const MIDDLEWARE_LOCATIONS = ["", "src/"];
36
36
  /**
37
- * Find the proxy or middleware file in the project root.
37
+ * Find the proxy or middleware file at the selected app/pages convention level.
38
38
  * Checks for proxy.ts (Next.js 16) first, then falls back to middleware.ts.
39
39
  * If middleware.ts is found, logs a deprecation warning.
40
40
  *
@@ -50,18 +50,33 @@ const MIDDLEWARE_LOCATIONS = ["", "src/"];
50
50
  * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts
51
51
  * (search for "MIDDLEWARE_FILENAME" + "file convention is deprecated")
52
52
  */
53
- function findMiddlewareFile(root, fileMatcher) {
54
- for (const dir of MIDDLEWARE_LOCATIONS) for (const ext of fileMatcher.dottedExtensions) {
55
- const fullPath = path.join(root, dir, `proxy${ext}`);
56
- if (fs.existsSync(fullPath)) return fullPath;
53
+ function findMiddlewareFile(root, fileMatcher, conventionDir = root) {
54
+ let proxyPath = null;
55
+ for (const ext of fileMatcher.dottedExtensions) {
56
+ const fullPath = path.join(conventionDir, `proxy${ext}`);
57
+ if (fs.existsSync(fullPath)) {
58
+ proxyPath = fullPath;
59
+ break;
60
+ }
57
61
  }
58
- for (const dir of MIDDLEWARE_LOCATIONS) for (const ext of fileMatcher.dottedExtensions) {
59
- const fullPath = path.join(root, dir, `middleware${ext}`);
62
+ let middlewarePath = null;
63
+ for (const ext of fileMatcher.dottedExtensions) {
64
+ const fullPath = path.join(conventionDir, `middleware${ext}`);
60
65
  if (fs.existsSync(fullPath)) {
61
- console.warn("The \"middleware\" file convention is deprecated. Please use \"proxy\" instead.\n\n To migrate automatically, run:\n npx @next/codemod@canary middleware-to-proxy .\n\n Learn more: https://nextjs.org/docs/messages/middleware-to-proxy");
62
- return fullPath;
66
+ middlewarePath = fullPath;
67
+ break;
63
68
  }
64
69
  }
70
+ if (proxyPath && middlewarePath) {
71
+ const relativeProxyPath = `./${path.relative(root, proxyPath)}`;
72
+ const relativeMiddlewarePath = `./${path.relative(root, middlewarePath)}`;
73
+ throw new Error(`Both middleware file "${relativeMiddlewarePath}" and proxy file "${relativeProxyPath}" are detected. Please use "${relativeProxyPath}" only. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`);
74
+ }
75
+ if (proxyPath) return proxyPath;
76
+ if (middlewarePath) {
77
+ console.warn("The \"middleware\" file convention is deprecated. Please use \"proxy\" instead.\n\n To migrate automatically, run:\n npx @next/codemod@canary middleware-to-proxy .\n\n Learn more: https://nextjs.org/docs/messages/middleware-to-proxy");
78
+ return middlewarePath;
79
+ }
65
80
  return null;
66
81
  }
67
82
  function isMiddlewareModule(value) {
@@ -106,7 +106,9 @@ type PagesPipelineResult = {
106
106
  * ASSUMPTION: request already has internal headers filtered and basePath stripped.
107
107
  * The adapter is responsible for that pre-processing before calling runPagesRequest.
108
108
  * The adapter also handles: open-redirect guard, _next/static 404, image optimization,
109
- * _next/data normalization, Node decode/normalize/400, public-file serving.
109
+ * _next/data normalization and classification: adapters must rewrite the data
110
+ * URL to its page pathname and set `isDataReq` (the source of truth here), Node
111
+ * decode/normalize/400, public-file serving.
110
112
  * runPagesRequest receives a "clean" request with basePath-stripped URL.
111
113
  */
112
114
  declare function runPagesRequest(request: Request, deps: PagesPipelineDeps): Promise<PagesPipelineResult>;
@@ -40,7 +40,9 @@ function wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) {
40
40
  * ASSUMPTION: request already has internal headers filtered and basePath stripped.
41
41
  * The adapter is responsible for that pre-processing before calling runPagesRequest.
42
42
  * The adapter also handles: open-redirect guard, _next/static 404, image optimization,
43
- * _next/data normalization, Node decode/normalize/400, public-file serving.
43
+ * _next/data normalization and classification: adapters must rewrite the data
44
+ * URL to its page pathname and set `isDataReq` (the source of truth here), Node
45
+ * decode/normalize/400, public-file serving.
44
46
  * runPagesRequest receives a "clean" request with basePath-stripped URL.
45
47
  */
46
48
  async function runPagesRequest(request, deps) {
@@ -54,7 +56,7 @@ async function runPagesRequest(request, deps) {
54
56
  hadBasePath
55
57
  };
56
58
  {
57
- const trailingSlashRedirect = normalizeTrailingSlash(pathname, basePath, trailingSlash, search);
59
+ const trailingSlashRedirect = isDataReq ? null : normalizeTrailingSlash(pathname, basePath, trailingSlash, search);
58
60
  if (trailingSlashRedirect) return {
59
61
  type: "response",
60
62
  response: trailingSlashRedirect
@@ -1,3 +1,4 @@
1
+ import { negotiateEncoding } from "./accept-encoding.js";
1
2
  import { StaticFileCache } from "./static-file-cache.js";
2
3
  import { resolveRequestHost, trustProxy, trustedHosts } from "./proxy-trust.js";
3
4
  import { IncomingMessage, ServerResponse } from "node:http";
@@ -59,7 +60,6 @@ type ProdServerOptions = {
59
60
  declare const COMPRESSIBLE_TYPES: Set<string>;
60
61
  /** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
61
62
  declare const COMPRESS_THRESHOLD = 1024;
62
- declare function negotiateEncoding(req: IncomingMessage): "zstd" | "br" | "gzip" | "deflate" | null;
63
63
  /**
64
64
  * Merge middleware headers and a Web Response's headers into a single
65
65
  * record suitable for Node.js `res.writeHead()`. Uses `getSetCookie()`
@@ -19,6 +19,7 @@ import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRoutePara
19
19
  import { computeClientRuntimeMetadata } from "../utils/client-runtime-metadata.js";
20
20
  import { readPrerenderSecret } from "../build/server-manifest.js";
21
21
  import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
22
+ import { negotiateEncoding, parseAcceptedEncodings, selectContentEncoding } from "./accept-encoding.js";
22
23
  import fs from "node:fs";
23
24
  import path from "node:path";
24
25
  import fs$1 from "node:fs/promises";
@@ -167,25 +168,6 @@ const COMPRESSIBLE_TYPES = new Set([
167
168
  /** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
168
169
  const COMPRESS_THRESHOLD = 1024;
169
170
  /**
170
- * Parse the Accept-Encoding header and return the best supported encoding.
171
- * Preference order: zstd > br > gzip > deflate > identity.
172
- *
173
- * zstd decompresses ~3-5x faster than brotli at similar compression ratios.
174
- * Supported in Chrome 123+, Firefox 126+. Safari can decompress but doesn't
175
- * send zstd in Accept-Encoding, so it transparently falls back to br/gzip.
176
- */
177
- const HAS_ZSTD = typeof zlib.createZstdCompress === "function";
178
- function negotiateEncoding(req) {
179
- const accept = req.headers["accept-encoding"];
180
- if (!accept || typeof accept !== "string") return null;
181
- const lower = accept.toLowerCase();
182
- if (HAS_ZSTD && lower.includes("zstd")) return "zstd";
183
- if (lower.includes("br")) return "br";
184
- if (lower.includes("gzip")) return "gzip";
185
- if (lower.includes("deflate")) return "deflate";
186
- return null;
187
- }
188
- /**
189
171
  * Create a compression stream for the given encoding.
190
172
  */
191
173
  function createCompressor(encoding, mode = "default") {
@@ -265,6 +247,23 @@ function omitHeadersCaseInsensitive(headersRecord, targets) {
265
247
  }
266
248
  return filtered;
267
249
  }
250
+ function mergeVaryHeader(headers, value) {
251
+ const merged = { ...headers };
252
+ const existingKey = Object.keys(merged).find((key) => key.toLowerCase() === "vary");
253
+ if (!existingKey) {
254
+ merged.Vary = value;
255
+ return merged;
256
+ }
257
+ const rawVary = merged[existingKey];
258
+ const existingVary = Array.isArray(rawVary) ? rawVary.join(", ") : rawVary;
259
+ if (existingVary.trim().length === 0) {
260
+ merged[existingKey] = value;
261
+ return merged;
262
+ }
263
+ const values = existingVary.split(",").map((entry) => entry.trim().toLowerCase());
264
+ if (!values.includes("*") && !values.includes(value.toLowerCase())) merged[existingKey] = `${existingVary}, ${value}`;
265
+ return merged;
266
+ }
268
267
  function matchesIfNoneMatchHeader(ifNoneMatch, etag) {
269
268
  if (!ifNoneMatch) return false;
270
269
  if (ifNoneMatch === "*") return true;
@@ -318,33 +317,37 @@ function mergeWebResponse(middlewareHeaders, response, statusOverride) {
318
317
  function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true, statusText) {
319
318
  const buf = typeof body === "string" ? Buffer.from(body) : body;
320
319
  const baseType = contentType.split(";")[0].trim();
321
- const encoding = compress ? negotiateEncoding(req) : null;
320
+ const varyByEncoding = compress && COMPRESSIBLE_TYPES.has(baseType);
321
+ const encoding = compress ? negotiateEncoding(req) : "identity";
322
322
  const headersWithoutBodyHeaders = omitHeadersCaseInsensitive(extraHeaders, OMIT_BODY_HEADERS);
323
- const writeHead = (headers) => {
324
- if (statusText) res.writeHead(statusCode, statusText, headers);
325
- else res.writeHead(statusCode, headers);
323
+ const writeHead = (headers, responseStatus = statusCode, responseStatusText = statusText) => {
324
+ if (responseStatusText) res.writeHead(responseStatus, responseStatusText, headers);
325
+ else res.writeHead(responseStatus, headers);
326
326
  };
327
- if (encoding && COMPRESSIBLE_TYPES.has(baseType) && buf.length >= 1024) {
328
- const compressor = createCompressor(encoding);
329
- const rawVary = extraHeaders["Vary"] ?? extraHeaders["vary"];
330
- const existingVary = Array.isArray(rawVary) ? rawVary.join(", ") : rawVary;
331
- let varyValue;
332
- if (existingVary) varyValue = existingVary.toLowerCase().includes("accept-encoding") ? existingVary : existingVary + ", Accept-Encoding";
333
- else varyValue = "Accept-Encoding";
334
- writeHead({
327
+ if (encoding !== "identity" && varyByEncoding && buf.length >= 1024) {
328
+ writeHead(mergeVaryHeader({
335
329
  ...headersWithoutBodyHeaders,
336
330
  "Content-Type": contentType,
337
- "Content-Encoding": encoding,
338
- Vary: varyValue
339
- });
331
+ "Content-Encoding": encoding
332
+ }, "Accept-Encoding"));
333
+ if (req.method === "HEAD") {
334
+ res.end();
335
+ return;
336
+ }
337
+ const compressor = createCompressor(encoding);
340
338
  compressor.end(buf);
341
339
  pipeline(compressor, res, () => {});
342
340
  } else {
343
- writeHead({
341
+ const identityHeaders = {
344
342
  ...headersWithoutBodyHeaders,
345
343
  "Content-Type": contentType,
346
344
  "Content-Length": String(buf.length)
347
- });
345
+ };
346
+ writeHead(varyByEncoding ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
347
+ if (req.method === "HEAD") {
348
+ res.end();
349
+ return;
350
+ }
348
351
  res.end(buf);
349
352
  }
350
353
  }
@@ -378,24 +381,35 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
378
381
  }
379
382
  const entry = cache.lookup(lookupPath);
380
383
  if (!entry) return false;
384
+ const rawAe = compress ? req.headers["accept-encoding"] : void 0;
385
+ const parsed = typeof rawAe === "string" ? parseAcceptedEncodings(rawAe) : void 0;
386
+ const availableVariants = [
387
+ ...entry.zst ? ["zstd"] : [],
388
+ ...entry.br ? ["br"] : [],
389
+ ...entry.gz ? ["gzip"] : []
390
+ ];
391
+ const variesByEncoding = compress && availableVariants.length > 0;
392
+ const selected = parsed ? selectContentEncoding(parsed, availableVariants) : "identity";
393
+ const variant = selected === "zstd" ? entry.zst : selected === "br" ? entry.br : selected === "gzip" ? entry.gz : entry.original;
381
394
  const ifNoneMatch = req.headers["if-none-match"];
382
395
  if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
383
- if (extraHeaders) res.writeHead(304, {
396
+ const notModifiedHeaders = variesByEncoding ? mergeVaryHeader({
384
397
  ...entry.notModifiedHeaders,
385
398
  ...extraHeaders
386
- });
387
- else res.writeHead(304, entry.notModifiedHeaders);
399
+ }, "Accept-Encoding") : {
400
+ ...entry.notModifiedHeaders,
401
+ ...extraHeaders
402
+ };
403
+ if (selected !== "identity") notModifiedHeaders["Content-Encoding"] = selected;
404
+ res.writeHead(304, notModifiedHeaders);
388
405
  res.end();
389
406
  return true;
390
407
  }
391
- const rawAe = compress ? req.headers["accept-encoding"] : void 0;
392
- const ae = typeof rawAe === "string" ? rawAe.toLowerCase() : void 0;
393
- const variant = ae ? ae.includes("zstd") && entry.zst || ae.includes("br") && entry.br || ae.includes("gzip") && entry.gz || entry.original : entry.original;
394
- if (extraHeaders) res.writeHead(responseStatus, {
408
+ const responseHeaders = {
395
409
  ...variant.headers,
396
410
  ...extraHeaders
397
- });
398
- else res.writeHead(responseStatus, variant.headers);
411
+ };
412
+ res.writeHead(responseStatus, variesByEncoding ? mergeVaryHeader(responseHeaders, "Accept-Encoding") : responseHeaders);
399
413
  if (omitBody || req.method === "HEAD") {
400
414
  res.end();
401
415
  return true;
@@ -428,18 +442,6 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
428
442
  const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
429
443
  const baseType = ct.split(";")[0].trim();
430
444
  const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
431
- const ifNoneMatch = req.headers["if-none-match"];
432
- if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
433
- const notModifiedHeaders = {
434
- ETag: etag,
435
- "Cache-Control": cacheControl,
436
- ...isCompressible ? { Vary: "Accept-Encoding" } : void 0,
437
- ...extraHeaders
438
- };
439
- res.writeHead(304, notModifiedHeaders);
440
- res.end();
441
- return true;
442
- }
443
445
  const baseHeaders = {
444
446
  "Content-Type": ct,
445
447
  "Cache-Control": cacheControl,
@@ -448,12 +450,19 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
448
450
  };
449
451
  if (isCompressible) {
450
452
  const encoding = negotiateEncoding(req);
451
- if (encoding) {
452
- res.writeHead(responseStatus, {
453
+ const ifNoneMatch = req.headers["if-none-match"];
454
+ if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
455
+ const notModifiedHeaders = mergeVaryHeader(baseHeaders, "Accept-Encoding");
456
+ if (encoding !== "identity") notModifiedHeaders["Content-Encoding"] = encoding;
457
+ res.writeHead(304, notModifiedHeaders);
458
+ res.end();
459
+ return true;
460
+ }
461
+ if (encoding !== "identity") {
462
+ res.writeHead(responseStatus, mergeVaryHeader({
453
463
  ...baseHeaders,
454
- "Content-Encoding": encoding,
455
- Vary: "Accept-Encoding"
456
- });
464
+ "Content-Encoding": encoding
465
+ }, "Accept-Encoding"));
457
466
  if (omitBody || req.method === "HEAD") {
458
467
  res.end();
459
468
  return true;
@@ -468,10 +477,17 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
468
477
  return true;
469
478
  }
470
479
  }
471
- res.writeHead(responseStatus, {
480
+ const ifNoneMatch = req.headers["if-none-match"];
481
+ if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
482
+ res.writeHead(304, isCompressible ? mergeVaryHeader(baseHeaders, "Accept-Encoding") : baseHeaders);
483
+ res.end();
484
+ return true;
485
+ }
486
+ const identityHeaders = {
472
487
  ...baseHeaders,
473
488
  "Content-Length": String(resolved.size)
474
- });
489
+ };
490
+ res.writeHead(responseStatus, isCompressible ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
475
491
  if (omitBody || req.method === "HEAD") {
476
492
  res.end();
477
493
  return true;
@@ -568,25 +584,22 @@ async function sendWebResponse(webResponse, req, res, compress) {
568
584
  if (existing !== void 0) nodeHeaders[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
569
585
  else nodeHeaders[key] = value;
570
586
  });
587
+ const alreadyEncoded = webResponse.headers.get("content-encoding") !== null;
571
588
  if (!webResponse.body) {
572
589
  writeHead(nodeHeaders);
573
590
  res.end();
574
591
  return;
575
592
  }
576
- const alreadyEncoded = webResponse.headers.has("content-encoding");
577
593
  const baseType = (webResponse.headers.get("content-type") ?? "").split(";")[0].trim();
578
- const encoding = compress && !alreadyEncoded ? negotiateEncoding(req) : null;
579
- const shouldCompress = !!(encoding && COMPRESSIBLE_TYPES.has(baseType));
594
+ const varyByEncoding = compress && !alreadyEncoded && COMPRESSIBLE_TYPES.has(baseType);
595
+ const encoding = compress && !alreadyEncoded ? negotiateEncoding(req) : "identity";
596
+ const shouldCompress = encoding !== "identity" && COMPRESSIBLE_TYPES.has(baseType);
580
597
  if (shouldCompress) {
581
598
  delete nodeHeaders["content-length"];
582
599
  delete nodeHeaders["Content-Length"];
583
600
  nodeHeaders["Content-Encoding"] = encoding;
584
- const existingVary = nodeHeaders["Vary"] ?? nodeHeaders["vary"];
585
- if (existingVary) {
586
- if (!String(existingVary).toLowerCase().includes("accept-encoding")) nodeHeaders["Vary"] = existingVary + ", Accept-Encoding";
587
- } else nodeHeaders["Vary"] = "Accept-Encoding";
588
601
  }
589
- writeHead(nodeHeaders);
602
+ writeHead(varyByEncoding ? mergeVaryHeader(nodeHeaders, "Accept-Encoding") : nodeHeaders);
590
603
  if (req.method === "HEAD") {
591
604
  cancelResponseBody(webResponse);
592
605
  res.end();
@@ -19,6 +19,7 @@ type FontResult = {
19
19
  };
20
20
  type InternalGoogleFontRuntimeOptions = {
21
21
  selfHostedCSS?: string;
22
+ preloadUrls?: string[];
22
23
  adjustedFallbackCSS?: string;
23
24
  fontWeight?: number;
24
25
  fontStyle?: "normal" | "italic";
@@ -206,27 +206,12 @@ function getSSRFontPreloads() {
206
206
  return [...ssrFontPreloads];
207
207
  }
208
208
  /**
209
- * Extract font file URLs from @font-face CSS rules.
210
- * Parses url('...') references from the CSS text.
211
- */
212
- function extractFontUrlsFromCSS(css) {
213
- const urls = [];
214
- const urlRegex = /url\(['"]?([^'")]+)['"]?\)/g;
215
- let match;
216
- while ((match = urlRegex.exec(css)) !== null) {
217
- const url = match[1];
218
- if (url && url.startsWith("/")) urls.push(url);
219
- }
220
- return urls;
221
- }
222
- /**
223
- * Collect font file URLs from self-hosted CSS for preload link generation.
209
+ * Collect build-selected font file URLs for preload link generation.
224
210
  * Only collects on the server (SSR). Deduplicates by href using a Set for O(1) lookups.
225
211
  */
226
- function collectFontPreloadsFromCSS(css) {
212
+ function collectFontPreloads(urls) {
227
213
  if (typeof document !== "undefined") return;
228
- const urls = extractFontUrlsFromCSS(css);
229
- for (const href of urls) if (!ssrFontPreloadHrefs.has(href)) {
214
+ for (const href of urls) if (href.startsWith("/") && !ssrFontPreloadHrefs.has(href)) {
230
215
  ssrFontPreloadHrefs.add(href);
231
216
  ssrFontPreloads.push({
232
217
  href,
@@ -240,10 +225,10 @@ const injectedSelfHosted = /* @__PURE__ */ new Set();
240
225
  * Inject self-hosted @font-face CSS (from the build plugin).
241
226
  * This replaces the CDN <link> tag with inline CSS.
242
227
  */
243
- function injectSelfHostedCSS(css) {
228
+ function injectSelfHostedCSS(css, preloadUrls = []) {
229
+ collectFontPreloads(preloadUrls);
244
230
  if (injectedSelfHosted.has(css)) return;
245
231
  injectedSelfHosted.add(css);
246
- collectFontPreloadsFromCSS(css);
247
232
  if (typeof document === "undefined") {
248
233
  ssrFontStyles.push(css);
249
234
  return;
@@ -277,7 +262,7 @@ function createFontLoader(family) {
277
262
  internalStyle: internal?.fontStyle,
278
263
  google: true
279
264
  });
280
- if (internal?.selfHostedCSS) injectSelfHostedCSS(internal.selfHostedCSS);
265
+ if (internal?.selfHostedCSS) injectSelfHostedCSS(internal.selfHostedCSS, internal.preloadUrls);
281
266
  else {
282
267
  const url = buildGoogleFontsUrl(family, options);
283
268
  injectFontStylesheet(url);
@@ -0,0 +1,25 @@
1
+ import { UrlQuery } from "../../utils/query.js";
2
+
3
+ //#region src/shims/internal/interpolate-as.d.ts
4
+ /**
5
+ * Wire-compatible alias for Node's `querystring.ParsedUrlQuery`. Inlined here
6
+ * so this module has no dependency on the `querystring` types package.
7
+ */
8
+ type ParsedUrlQuery = {
9
+ [key: string]: string | string[] | undefined;
10
+ };
11
+ type DynamicRouteHrefProjection = {
12
+ href: string;
13
+ params: string[];
14
+ query: ParsedUrlQuery;
15
+ routePathname: string;
16
+ };
17
+ /**
18
+ * Resolve a bracket-pattern route href against its displayed href. Query
19
+ * values can be supplied directly (object-form hrefs) or parsed from the route
20
+ * href (string-form hrefs). A `?` after `#` is part of the fragment, not a
21
+ * query delimiter.
22
+ */
23
+ declare function interpolateDynamicRouteHref(routeHref: string, asHref: string, queryInput?: UrlQuery): DynamicRouteHrefProjection | null;
24
+ //#endregion
25
+ export { DynamicRouteHrefProjection, interpolateDynamicRouteHref };