vinext 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/build/assets-ignore.d.ts +32 -0
  2. package/dist/build/assets-ignore.js +48 -0
  3. package/dist/build/client-build-config.d.ts +27 -1
  4. package/dist/build/client-build-config.js +58 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/client/navigation-runtime.d.ts +8 -0
  7. package/dist/client/navigation-runtime.js +1 -1
  8. package/dist/client/vinext-next-data.d.ts +2 -1
  9. package/dist/config/config-matchers.d.ts +20 -1
  10. package/dist/config/config-matchers.js +35 -1
  11. package/dist/config/next-config.d.ts +16 -3
  12. package/dist/config/next-config.js +30 -2
  13. package/dist/deploy.js +40 -304
  14. package/dist/entries/app-rsc-entry.d.ts +8 -2
  15. package/dist/entries/app-rsc-entry.js +54 -4
  16. package/dist/entries/app-rsc-manifest.js +20 -2
  17. package/dist/entries/pages-server-entry.js +9 -1
  18. package/dist/index.js +162 -217
  19. package/dist/plugins/postcss.js +18 -14
  20. package/dist/plugins/require-context.d.ts +6 -0
  21. package/dist/plugins/require-context.js +184 -0
  22. package/dist/routing/app-route-graph.d.ts +12 -1
  23. package/dist/routing/app-route-graph.js +137 -5
  24. package/dist/routing/route-pattern.d.ts +2 -1
  25. package/dist/routing/route-pattern.js +16 -1
  26. package/dist/server/api-handler.js +4 -0
  27. package/dist/server/app-browser-entry.js +84 -39
  28. package/dist/server/app-browser-interception-context.d.ts +2 -1
  29. package/dist/server/app-browser-interception-context.js +15 -2
  30. package/dist/server/app-browser-navigation-controller.d.ts +11 -1
  31. package/dist/server/app-browser-navigation-controller.js +77 -1
  32. package/dist/server/app-browser-popstate.d.ts +12 -3
  33. package/dist/server/app-browser-popstate.js +19 -4
  34. package/dist/server/app-browser-state.d.ts +3 -0
  35. package/dist/server/app-browser-state.js +6 -3
  36. package/dist/server/app-browser-visible-commit.js +9 -7
  37. package/dist/server/app-history-state.d.ts +45 -1
  38. package/dist/server/app-history-state.js +109 -1
  39. package/dist/server/app-page-boundary-render.js +41 -19
  40. package/dist/server/app-page-dispatch.d.ts +6 -0
  41. package/dist/server/app-page-dispatch.js +3 -1
  42. package/dist/server/app-page-element-builder.d.ts +1 -0
  43. package/dist/server/app-page-element-builder.js +22 -10
  44. package/dist/server/app-page-render.d.ts +6 -0
  45. package/dist/server/app-page-render.js +5 -3
  46. package/dist/server/app-page-request.d.ts +8 -6
  47. package/dist/server/app-page-request.js +12 -9
  48. package/dist/server/app-page-response.d.ts +2 -2
  49. package/dist/server/app-page-response.js +1 -1
  50. package/dist/server/app-page-route-wiring.js +2 -1
  51. package/dist/server/app-page-stream.d.ts +37 -2
  52. package/dist/server/app-page-stream.js +36 -3
  53. package/dist/server/app-pages-bridge.d.ts +16 -0
  54. package/dist/server/app-pages-bridge.js +23 -3
  55. package/dist/server/app-route-handler-cache.d.ts +1 -0
  56. package/dist/server/app-route-handler-cache.js +1 -0
  57. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  58. package/dist/server/app-route-handler-dispatch.js +2 -0
  59. package/dist/server/app-route-handler-execution.d.ts +1 -0
  60. package/dist/server/app-route-handler-execution.js +1 -0
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  62. package/dist/server/app-route-handler-runtime.js +3 -2
  63. package/dist/server/app-rsc-handler.d.ts +1 -0
  64. package/dist/server/app-rsc-handler.js +4 -3
  65. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  66. package/dist/server/app-rsc-route-matching.js +29 -4
  67. package/dist/server/app-server-action-execution.d.ts +11 -1
  68. package/dist/server/app-server-action-execution.js +68 -10
  69. package/dist/server/app-ssr-entry.d.ts +6 -0
  70. package/dist/server/app-ssr-entry.js +17 -1
  71. package/dist/server/dev-server.d.ts +1 -1
  72. package/dist/server/dev-server.js +54 -31
  73. package/dist/server/isr-cache.d.ts +37 -1
  74. package/dist/server/isr-cache.js +85 -1
  75. package/dist/server/navigation-planner.js +5 -3
  76. package/dist/server/navigation-trace.d.ts +1 -1
  77. package/dist/server/pages-node-compat.d.ts +2 -0
  78. package/dist/server/pages-node-compat.js +4 -0
  79. package/dist/server/pages-page-data.d.ts +10 -7
  80. package/dist/server/pages-page-data.js +4 -2
  81. package/dist/server/pages-page-handler.d.ts +9 -2
  82. package/dist/server/pages-page-handler.js +29 -16
  83. package/dist/server/pages-page-response.d.ts +11 -2
  84. package/dist/server/pages-page-response.js +8 -1
  85. package/dist/server/pages-readiness.d.ts +36 -0
  86. package/dist/server/pages-readiness.js +21 -0
  87. package/dist/server/pages-request-pipeline.d.ts +99 -0
  88. package/dist/server/pages-request-pipeline.js +209 -0
  89. package/dist/server/pages-revalidate.d.ts +15 -0
  90. package/dist/server/pages-revalidate.js +19 -0
  91. package/dist/server/prod-server.d.ts +6 -2
  92. package/dist/server/prod-server.js +101 -217
  93. package/dist/server/socket-error-backstop.d.ts +19 -1
  94. package/dist/server/socket-error-backstop.js +77 -4
  95. package/dist/shims/app-router-scroll.js +22 -4
  96. package/dist/shims/cache-runtime.js +31 -1
  97. package/dist/shims/error-boundary.d.ts +21 -11
  98. package/dist/shims/error-boundary.js +8 -1
  99. package/dist/shims/fetch-cache.d.ts +14 -1
  100. package/dist/shims/fetch-cache.js +18 -1
  101. package/dist/shims/hash-scroll.d.ts +1 -0
  102. package/dist/shims/hash-scroll.js +3 -1
  103. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  104. package/dist/shims/internal/link-status-registry.js +42 -0
  105. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  106. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  107. package/dist/shims/internal/utils.d.ts +1 -0
  108. package/dist/shims/link.js +20 -6
  109. package/dist/shims/navigation.d.ts +2 -2
  110. package/dist/shims/navigation.js +63 -7
  111. package/dist/shims/router-state.d.ts +1 -0
  112. package/dist/shims/router-state.js +2 -0
  113. package/dist/shims/router.d.ts +6 -3
  114. package/dist/shims/router.js +128 -21
  115. package/dist/utils/client-build-manifest.d.ts +8 -1
  116. package/dist/utils/client-build-manifest.js +30 -5
  117. package/dist/utils/client-entry-manifest.d.ts +11 -0
  118. package/dist/utils/client-entry-manifest.js +29 -0
  119. package/package.json +5 -1
@@ -0,0 +1,32 @@
1
+ //#region src/build/assets-ignore.d.ts
2
+ /**
3
+ * Build metadata that must never be served by the Cloudflare ASSETS binding.
4
+ *
5
+ * On Cloudflare Workers the ASSETS binding serves any uploaded file whose path
6
+ * matches the request BEFORE the Worker runs, so anything written into the
7
+ * client output directory (`dist/client`) is publicly fetchable unless excluded.
8
+ * Vite writes its build/SSR manifests under `dist/client/.vite/` (enabled for
9
+ * Cloudflare builds so the worker entry can compute lazy chunks), which would
10
+ * otherwise expose the full source-file → chunk mapping — including the paths of
11
+ * routes that are never linked from the UI.
12
+ *
13
+ * The Node production server already blocks `/.vite/` explicitly
14
+ * (see `server/static-file-cache.ts`); `.assetsignore` is the equivalent guard
15
+ * for the Cloudflare deployment target. wrangler matches `.assetsignore`
16
+ * patterns with `.gitignore` semantics (via the `ignore` package), so the bare
17
+ * `.vite` entry excludes the directory and everything beneath it.
18
+ */
19
+ declare const DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS: readonly string[];
20
+ /**
21
+ * Ensure a `.assetsignore` in `clientDir` contains every pattern in `patterns`.
22
+ *
23
+ * Any pre-existing (user-authored) content is preserved verbatim and only the
24
+ * missing patterns are appended, so the result is idempotent across rebuilds
25
+ * and never clobbers a `.assetsignore` the developer wrote themselves.
26
+ *
27
+ * @returns `true` when the file was created or modified, `false` when it already
28
+ * covered every requested pattern.
29
+ */
30
+ declare function ensureAssetsIgnore(clientDir: string, patterns?: readonly string[]): boolean;
31
+ //#endregion
32
+ export { DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS, ensureAssetsIgnore };
@@ -0,0 +1,48 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ //#region src/build/assets-ignore.ts
4
+ /**
5
+ * Build metadata that must never be served by the Cloudflare ASSETS binding.
6
+ *
7
+ * On Cloudflare Workers the ASSETS binding serves any uploaded file whose path
8
+ * matches the request BEFORE the Worker runs, so anything written into the
9
+ * client output directory (`dist/client`) is publicly fetchable unless excluded.
10
+ * Vite writes its build/SSR manifests under `dist/client/.vite/` (enabled for
11
+ * Cloudflare builds so the worker entry can compute lazy chunks), which would
12
+ * otherwise expose the full source-file → chunk mapping — including the paths of
13
+ * routes that are never linked from the UI.
14
+ *
15
+ * The Node production server already blocks `/.vite/` explicitly
16
+ * (see `server/static-file-cache.ts`); `.assetsignore` is the equivalent guard
17
+ * for the Cloudflare deployment target. wrangler matches `.assetsignore`
18
+ * patterns with `.gitignore` semantics (via the `ignore` package), so the bare
19
+ * `.vite` entry excludes the directory and everything beneath it.
20
+ */
21
+ const DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS = [".vite"];
22
+ /** wrangler reads asset-exclusion patterns from this file in the assets dir. */
23
+ const ASSETS_IGNORE_FILENAME = ".assetsignore";
24
+ const GENERATED_HEADER = "# Keep build metadata out of the deployed asset bundle (generated by vinext)";
25
+ /**
26
+ * Ensure a `.assetsignore` in `clientDir` contains every pattern in `patterns`.
27
+ *
28
+ * Any pre-existing (user-authored) content is preserved verbatim and only the
29
+ * missing patterns are appended, so the result is idempotent across rebuilds
30
+ * and never clobbers a `.assetsignore` the developer wrote themselves.
31
+ *
32
+ * @returns `true` when the file was created or modified, `false` when it already
33
+ * covered every requested pattern.
34
+ */
35
+ function ensureAssetsIgnore(clientDir, patterns = DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS) {
36
+ const assetsIgnorePath = path.join(clientDir, ASSETS_IGNORE_FILENAME);
37
+ const existing = fs.existsSync(assetsIgnorePath) ? fs.readFileSync(assetsIgnorePath, "utf-8") : null;
38
+ const present = new Set((existing ?? "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
39
+ const missing = patterns.filter((pattern) => !present.has(pattern));
40
+ if (missing.length === 0) return false;
41
+ const lines = existing && existing.trim().length > 0 ? [existing.replace(/\s*$/, "")] : [GENERATED_HEADER];
42
+ lines.push(...missing, "");
43
+ fs.mkdirSync(clientDir, { recursive: true });
44
+ fs.writeFileSync(assetsIgnorePath, lines.join("\n"));
45
+ return true;
46
+ }
47
+ //#endregion
48
+ export { DEFAULT_VINEXT_ASSET_IGNORE_PATTERNS, ensureAssetsIgnore };
@@ -62,6 +62,32 @@ declare function createClientCodeSplittingConfig(clientManualChunks: (id: string
62
62
  name(moduleId: string): string | null;
63
63
  }[];
64
64
  };
65
+ /**
66
+ * Regex matching any {@link FRAMEWORK_PACKAGES} package inside `node_modules`.
67
+ * Derived from the package list so the regex and {@link isRscFrameworkModule}
68
+ * predicate can't drift.
69
+ */
70
+ declare const RSC_FRAMEWORK_CHUNK_TEST: RegExp;
71
+ declare function isRscFrameworkModule(id: string): boolean;
72
+ /**
73
+ * Output config that isolates React (and the RSC flight runtime) into a
74
+ * dedicated "framework" chunk in the RSC server build. Returns the bundler-
75
+ * appropriate shape: rolldown's `codeSplitting` for Vite 8+, Rollup's
76
+ * `manualChunks` for Vite 7. See {@link RSC_FRAMEWORK_CHUNK_TEST} for the
77
+ * motivation (issue #1549).
78
+ */
79
+ declare function createRscFrameworkChunkOutputConfig(viteMajorVersion: number): {
80
+ codeSplitting: {
81
+ groups: {
82
+ name: string;
83
+ test: RegExp;
84
+ }[];
85
+ };
86
+ manualChunks?: undefined;
87
+ } | {
88
+ manualChunks(id: string): string | undefined;
89
+ codeSplitting?: undefined;
90
+ };
65
91
  /**
66
92
  * Rollup treeshake configuration for production client builds.
67
93
  *
@@ -129,4 +155,4 @@ type VinextBuildConfigWithLegacy = VinextBuildConfig & {
129
155
  declare function getBuildBundlerOptions(build: UserConfig["build"] | undefined): VinextBuildBundlerOptions | undefined;
130
156
  declare function withBuildBundlerOptions(viteMajorVersion: number, bundlerOptions: VinextBuildBundlerOptions): Partial<VinextBuildConfigWithLegacy>;
131
157
  //#endregion
132
- export { clientTreeshakeConfig, createClientAssetFileNames, createClientCodeSplittingConfig, createClientManualChunks, createClientOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfigForVite, withBuildBundlerOptions };
158
+ export { RSC_FRAMEWORK_CHUNK_TEST, clientTreeshakeConfig, createClientAssetFileNames, createClientCodeSplittingConfig, createClientManualChunks, createClientOutputConfig, createRscFrameworkChunkOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfigForVite, isRscFrameworkModule, withBuildBundlerOptions };
@@ -105,6 +105,63 @@ function createClientCodeSplittingConfig(clientManualChunks) {
105
105
  };
106
106
  }
107
107
  /**
108
+ * Matches React framework packages (and the RSC flight runtime) inside
109
+ * `node_modules`. Used to split them into a dedicated "framework" chunk in the
110
+ * RSC server build.
111
+ *
112
+ * Why the RSC build needs this: without an explicit framework chunk, the
113
+ * bundler colocates React into whichever chunk first reaches it — typically the
114
+ * RSC entry chunk, which also carries the root layout's CSS. Modules that only
115
+ * import React for runtime helpers (notably `app/global-not-found.tsx`, which
116
+ * replaces the root layout for route-miss 404s) then import that entry chunk
117
+ * and inherit the root layout's CSS in their `serverResources` metadata. The
118
+ * 404 document ends up linking the layout's stylesheet last, so the layout's
119
+ * rules win the cascade over global-not-found's — the bug tracked in
120
+ * https://github.com/cloudflare/vinext/issues/1549.
121
+ *
122
+ * Splitting React into its own (CSS-free) chunk means global-not-found imports
123
+ * the framework chunk instead of the layout-bearing entry chunk, so it no
124
+ * longer inherits the root layout's CSS. The match list mirrors the client
125
+ * build's `framework` chunk, plus `react-server-dom-webpack` for the RSC flight
126
+ * runtime that the server environment bundles.
127
+ *
128
+ * Uses `[\\/]` rather than `/` for the path separator so it matches on Windows
129
+ * too, per the rolldown `codeSplitting` docs.
130
+ */
131
+ const FRAMEWORK_PACKAGES = [
132
+ "react",
133
+ "react-dom",
134
+ "scheduler",
135
+ "react-server-dom-webpack"
136
+ ];
137
+ /**
138
+ * Regex matching any {@link FRAMEWORK_PACKAGES} package inside `node_modules`.
139
+ * Derived from the package list so the regex and {@link isRscFrameworkModule}
140
+ * predicate can't drift.
141
+ */
142
+ const RSC_FRAMEWORK_CHUNK_TEST = new RegExp(`[\\\\/]node_modules[\\\\/](${FRAMEWORK_PACKAGES.join("|")})[\\\\/]`);
143
+ function isRscFrameworkModule(id) {
144
+ if (!id.includes("node_modules")) return false;
145
+ const pkg = getPackageName(id);
146
+ return pkg !== null && FRAMEWORK_PACKAGES.includes(pkg);
147
+ }
148
+ /**
149
+ * Output config that isolates React (and the RSC flight runtime) into a
150
+ * dedicated "framework" chunk in the RSC server build. Returns the bundler-
151
+ * appropriate shape: rolldown's `codeSplitting` for Vite 8+, Rollup's
152
+ * `manualChunks` for Vite 7. See {@link RSC_FRAMEWORK_CHUNK_TEST} for the
153
+ * motivation (issue #1549).
154
+ */
155
+ function createRscFrameworkChunkOutputConfig(viteMajorVersion) {
156
+ if (viteMajorVersion >= 8) return { codeSplitting: { groups: [{
157
+ name: "framework",
158
+ test: RSC_FRAMEWORK_CHUNK_TEST
159
+ }] } };
160
+ return { manualChunks(id) {
161
+ return isRscFrameworkModule(id) ? "framework" : void 0;
162
+ } };
163
+ }
164
+ /**
108
165
  * Rollup treeshake configuration for production client builds.
109
166
  *
110
167
  * Uses the 'recommended' preset as a safe base, then overrides
@@ -171,4 +228,4 @@ function withBuildBundlerOptions(viteMajorVersion, bundlerOptions) {
171
228
  return viteMajorVersion >= 8 ? { rolldownOptions: bundlerOptions } : { rollupOptions: bundlerOptions };
172
229
  }
173
230
  //#endregion
174
- export { clientTreeshakeConfig, createClientAssetFileNames, createClientCodeSplittingConfig, createClientManualChunks, createClientOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfigForVite, withBuildBundlerOptions };
231
+ export { RSC_FRAMEWORK_CHUNK_TEST, clientTreeshakeConfig, createClientAssetFileNames, createClientCodeSplittingConfig, createClientManualChunks, createClientOutputConfig, createRscFrameworkChunkOutputConfig, getBuildBundlerOptions, getClientTreeshakeConfigForVite, isRscFrameworkModule, withBuildBundlerOptions };
package/dist/cli.js CHANGED
@@ -18,6 +18,7 @@ import { createRequire } from "node:module";
18
18
  import fs from "node:fs";
19
19
  import path from "node:path";
20
20
  import { pathToFileURL } from "node:url";
21
+ import { randomBytes } from "node:crypto";
21
22
  import { execFileSync } from "node:child_process";
22
23
  //#region src/cli.ts
23
24
  /**
@@ -263,6 +264,7 @@ async function buildApp() {
263
264
  const resolvedNextConfig = await resolveNextConfig(await loadNextConfig(root, PHASE_PRODUCTION_BUILD), root);
264
265
  process.env.__VINEXT_SHARED_BUILD_ID = resolvedNextConfig.buildId;
265
266
  process.env.__VINEXT_SHARED_RSC_COMPATIBILITY_ID = createRscCompatibilityId(resolvedNextConfig);
267
+ if (!process.env.__VINEXT_SHARED_REVALIDATE_SECRET) process.env.__VINEXT_SHARED_REVALIDATE_SECRET = randomBytes(32).toString("hex");
266
268
  const outputMode = resolvedNextConfig.output;
267
269
  const distDir = path.resolve(root, "dist");
268
270
  if (outputMode === "standalone") {
@@ -26,6 +26,14 @@ type NavigationRuntimeFunctions = {
26
26
  commitHashNavigation?: (href: string, historyUpdateMode: NavigationRuntimeHistoryUpdateMode, scroll: boolean) => void;
27
27
  navigateExternal?: (href: string, historyUpdateMode: NavigationRuntimeHistoryUpdateMode) => Promise<void>;
28
28
  navigate?: NavigationRuntimeNavigate;
29
+ /**
30
+ * Called at the start of every App Router navigation so the <Link> shim can
31
+ * reset any link that is still showing a `useLinkStatus()` pending state but
32
+ * is not the one driving this navigation (e.g. a programmatic router.push or
33
+ * a shallow-routing transition). Registered by shims/link.tsx; decoupled
34
+ * through the runtime to avoid a circular import with shims/navigation.ts.
35
+ */
36
+ notifyLinkNavigationStart?: () => void;
29
37
  pingVisibleLinks?: () => void;
30
38
  };
31
39
  type NavigationRuntimeBootstrap = {
@@ -31,7 +31,7 @@ function readRuntimeWindow() {
31
31
  }
32
32
  function isNavigationRuntimeFunctions(value) {
33
33
  if (!isUnknownRecord(value)) return false;
34
- return isOptionalRuntimeFunction(Reflect.get(value, "clearNavigationCaches")) && isOptionalRuntimeFunction(Reflect.get(value, "commitHashNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "navigateExternal")) && isOptionalRuntimeFunction(Reflect.get(value, "navigate")) && isOptionalRuntimeFunction(Reflect.get(value, "pingVisibleLinks"));
34
+ return isOptionalRuntimeFunction(Reflect.get(value, "clearNavigationCaches")) && isOptionalRuntimeFunction(Reflect.get(value, "commitHashNavigation")) && isOptionalRuntimeFunction(Reflect.get(value, "navigateExternal")) && isOptionalRuntimeFunction(Reflect.get(value, "navigate")) && isOptionalRuntimeFunction(Reflect.get(value, "notifyLinkNavigationStart")) && isOptionalRuntimeFunction(Reflect.get(value, "pingVisibleLinks"));
35
35
  }
36
36
  function isNavigationRuntimeRscChunk(value) {
37
37
  if (typeof value === "string") return true;
@@ -10,7 +10,8 @@ type VinextNextData = {
10
10
  /** vinext-specific additions (not part of Next.js upstream). */__vinext?: {
11
11
  /** Absolute URL of the page module for dynamic import. */pageModuleUrl?: string; /** Absolute URL of the `_app` module for dynamic import. */
12
12
  appModuleUrl?: string; /** True when the Pages Router server has middleware/proxy configured. */
13
- hasMiddleware?: boolean;
13
+ hasMiddleware?: boolean; /** True when build-time rewrites can affect the initial Pages Router ready state. */
14
+ hasRewrites?: boolean;
14
15
  };
15
16
  } & NEXT_DATA;
16
17
  type BrowserVinextNextData = NonNullable<Window["__NEXT_DATA__"]> & VinextNextData;
@@ -159,6 +159,25 @@ declare function sanitizeDestination(dest: string): string;
159
159
  * per RFC 3986, plus protocol-relative URLs (//).
160
160
  */
161
161
  declare function isExternalUrl(url: string): boolean;
162
+ /**
163
+ * Merge the original request's query params into a config-redirect
164
+ * destination, preserving them on the resulting `Location`.
165
+ *
166
+ * Next.js carries the original request query across config redirects
167
+ * (`prepareDestination({ query: parsedUrl.query })` →
168
+ * `stringifyQuery(...)` in resolve-routes.ts). This matters for App Router
169
+ * RSC client navigations: the cache-busting `_rsc` query must survive the
170
+ * redirect so the browser's auto-followed request to the destination is
171
+ * still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
172
+ * (issue #1529).
173
+ *
174
+ * Destination query params win — a request param is only carried over when
175
+ * the destination does not already specify that key. Mirrors the merge
176
+ * semantics in `proxyExternalRequest`. External destinations are returned
177
+ * untouched (a config redirect to another origin should not leak the
178
+ * original request's query).
179
+ */
180
+ declare function preserveRedirectDestinationQuery(destination: string, requestSearch: string): string;
162
181
  /**
163
182
  * Proxy an incoming request to an external URL and return the upstream response.
164
183
  *
@@ -218,4 +237,4 @@ declare function applyLocaleToRoutes<T extends NextRedirect | NextRewrite>(route
218
237
  trailingSlash?: boolean;
219
238
  }): T[];
220
239
  //#endregion
221
- export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
240
+ export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
@@ -764,6 +764,40 @@ function isExternalUrl(url) {
764
764
  return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
765
765
  }
766
766
  /**
767
+ * Merge the original request's query params into a config-redirect
768
+ * destination, preserving them on the resulting `Location`.
769
+ *
770
+ * Next.js carries the original request query across config redirects
771
+ * (`prepareDestination({ query: parsedUrl.query })` →
772
+ * `stringifyQuery(...)` in resolve-routes.ts). This matters for App Router
773
+ * RSC client navigations: the cache-busting `_rsc` query must survive the
774
+ * redirect so the browser's auto-followed request to the destination is
775
+ * still treated as an RSC fetch. Dropping it breaks RSC fetch semantics
776
+ * (issue #1529).
777
+ *
778
+ * Destination query params win — a request param is only carried over when
779
+ * the destination does not already specify that key. Mirrors the merge
780
+ * semantics in `proxyExternalRequest`. External destinations are returned
781
+ * untouched (a config redirect to another origin should not leak the
782
+ * original request's query).
783
+ */
784
+ function preserveRedirectDestinationQuery(destination, requestSearch) {
785
+ if (requestSearch === "" || requestSearch === "?" || isExternalUrl(destination)) return destination;
786
+ const requestParams = new URLSearchParams(requestSearch);
787
+ if ([...requestParams.keys()].length === 0) return destination;
788
+ const hashIndex = destination.indexOf("#");
789
+ const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
790
+ const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
791
+ const queryIndex = beforeHash.indexOf("?");
792
+ const pathPart = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
793
+ const destQuery = queryIndex === -1 ? "" : beforeHash.slice(queryIndex + 1);
794
+ const merged = new URLSearchParams(destQuery);
795
+ const destKeys = new Set(merged.keys());
796
+ for (const [key, value] of requestParams) if (!destKeys.has(key)) merged.append(key, value);
797
+ const mergedQuery = merged.toString();
798
+ return mergedQuery === "" ? `${pathPart}${hash}` : `${pathPart}?${mergedQuery}${hash}`;
799
+ }
800
+ /**
767
801
  * Proxy an incoming request to an external URL and return the upstream response.
768
802
  *
769
803
  * Used for external rewrites (e.g. `/ph/:path*` → `https://us.i.posthog.com/:path*`).
@@ -925,4 +959,4 @@ function applyLocaleToRoutes(routes, i18n, type, options = {}) {
925
959
  return out;
926
960
  }
927
961
  //#endregion
928
- export { applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
962
+ export { applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
@@ -132,7 +132,14 @@ type NextConfig = {
132
132
  */
133
133
  instrumentationClientInject?: string[]; /** Extra origins allowed to access the dev server. */
134
134
  allowedDevOrigins?: string[]; /** Maximum age in seconds for stale ISR entries before blocking regeneration. */
135
- expireTime?: number; /** User agents that require blocking metadata in the initial head. */
135
+ expireTime?: number;
136
+ /**
137
+ * Maximum total length (in characters) of the preload `Link` header emitted
138
+ * during App Router SSR. React drops whole entries once the limit is
139
+ * exceeded; `0` disables emission entirely. Defaults to 6000.
140
+ * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/reactMaxHeadersLength
141
+ */
142
+ reactMaxHeadersLength?: number; /** User agents that require blocking metadata in the initial head. */
136
143
  htmlLimitedBots?: RegExp | string;
137
144
  /**
138
145
  * Enable Cache Components (Next.js 16).
@@ -244,8 +251,14 @@ type ResolvedNextConfig = {
244
251
  serverActionsAllowedOrigins: string[]; /** Packages whose barrel imports should be optimized (from experimental.optimizePackageImports). */
245
252
  optimizePackageImports: string[]; /** Inline app CSS into production HTML (from experimental.inlineCss). */
246
253
  inlineCss: boolean; /** Parsed body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). Defaults to 1MB. */
247
- serverActionsBodySizeLimit: number; /** Route-level expire fallback in seconds for ISR entries with numeric revalidate. */
248
- expireTime: number; /** Serialized htmlLimitedBots regexp source from next.config. */
254
+ serverActionsBodySizeLimit: number; /** Verbatim body size limit config value (e.g. "2mb") for the "Body exceeded {limit} limit" error. Defaults to "1 MB". */
255
+ serverActionsBodySizeLimitLabel: string; /** Route-level expire fallback in seconds for ISR entries with numeric revalidate. */
256
+ expireTime: number;
257
+ /**
258
+ * Maximum total length (in characters) of the preload `Link` header emitted
259
+ * during App Router SSR. `0` disables emission. Defaults to 6000.
260
+ */
261
+ reactMaxHeadersLength: number; /** Serialized htmlLimitedBots regexp source from next.config. */
249
262
  htmlLimitedBots: string | undefined;
250
263
  /**
251
264
  * Packages that should be treated as server-external (not bundled by Vite).
@@ -72,6 +72,12 @@ const CONFIG_FILES = [
72
72
  ];
73
73
  const DEFAULT_EXPIRE_TIME = 31536e3;
74
74
  /**
75
+ * Default cap for the App Router preload `Link` header length, matching the
76
+ * Next.js `defaultConfig.reactMaxHeadersLength`.
77
+ * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/reactMaxHeadersLength
78
+ */
79
+ const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6e3;
80
+ /**
75
81
  * Check whether an error indicates a CJS module was loaded in an ESM context
76
82
  * (i.e. the file uses `require()` which is not available in ESM).
77
83
  */
@@ -523,7 +529,9 @@ async function resolveNextConfig(config, root = process.cwd()) {
523
529
  optimizePackageImports: [],
524
530
  inlineCss: false,
525
531
  serverActionsBodySizeLimit: 1 * 1024 * 1024,
532
+ serverActionsBodySizeLimitLabel: "1 MB",
526
533
  expireTime: DEFAULT_EXPIRE_TIME,
534
+ reactMaxHeadersLength: DEFAULT_REACT_MAX_HEADERS_LENGTH,
527
535
  htmlLimitedBots: void 0,
528
536
  serverExternalPackages: [],
529
537
  cacheHandler: void 0,
@@ -588,7 +596,9 @@ async function resolveNextConfig(config, root = process.cwd()) {
588
596
  const experimental = readOptionalRecord(config.experimental);
589
597
  const serverActionsConfig = readOptionalRecord(experimental?.serverActions);
590
598
  const serverActionsAllowedOrigins = readStringArray(serverActionsConfig?.allowedOrigins);
591
- const serverActionsBodySizeLimit = parseBodySizeLimit(readOptionalBodySizeLimit(serverActionsConfig?.bodySizeLimit));
599
+ const serverActionsBodySizeLimitConfig = readOptionalBodySizeLimit(serverActionsConfig?.bodySizeLimit);
600
+ const serverActionsBodySizeLimit = parseBodySizeLimit(serverActionsBodySizeLimitConfig);
601
+ const serverActionsBodySizeLimitLabel = serverActionsBodySizeLimitConfig === void 0 ? "1 MB" : String(serverActionsBodySizeLimitConfig);
592
602
  const hashSalt = (readOptionalString(experimental?.outputHashSalt) ?? "") + (process.env.NEXT_HASH_SALT ?? "");
593
603
  const htmlLimitedBots = resolveHtmlLimitedBots(config.htmlLimitedBots);
594
604
  const rawOptimize = experimental?.optimizePackageImports;
@@ -658,7 +668,9 @@ async function resolveNextConfig(config, root = process.cwd()) {
658
668
  optimizePackageImports,
659
669
  inlineCss,
660
670
  serverActionsBodySizeLimit,
671
+ serverActionsBodySizeLimitLabel,
661
672
  expireTime: typeof config.expireTime === "number" ? config.expireTime : DEFAULT_EXPIRE_TIME,
673
+ reactMaxHeadersLength: typeof config.reactMaxHeadersLength === "number" ? config.reactMaxHeadersLength : DEFAULT_REACT_MAX_HEADERS_LENGTH,
662
674
  htmlLimitedBots,
663
675
  serverExternalPackages,
664
676
  cacheHandler,
@@ -680,12 +692,28 @@ async function resolveNextConfig(config, root = process.cwd()) {
680
692
  if (resolved.basePath !== "" && resolved.basePath !== "/" && resolved.assetPrefix === "") resolved.assetPrefix = resolved.basePath;
681
693
  return resolved;
682
694
  }
695
+ /**
696
+ * Whether an alias target is a relative filesystem path (`./foo`, `../foo`,
697
+ * or a bare `.`/`..`) that should be resolved against the project root.
698
+ *
699
+ * Both Next.js Turbopack `resolveAlias` and webpack `resolve.alias` accept two
700
+ * kinds of values: relative/absolute file paths AND bare package specifiers
701
+ * (e.g. `react`, `preact/compat`, `@scope/pkg`). Bare specifiers must be left
702
+ * verbatim so Vite/Rolldown re-resolves them through node_modules — resolving
703
+ * them against `root` mangles them into bogus `<root>/react` paths and breaks
704
+ * the build with "No such file or directory". See cloudflare/vinext#1507.
705
+ */
706
+ function isRelativeAliasTarget(value) {
707
+ return value === "." || value === ".." || value.startsWith("./") || value.startsWith("../");
708
+ }
683
709
  function normalizeAliasEntries(aliases, root) {
684
710
  if (!aliases) return {};
685
711
  const normalized = {};
686
712
  for (const [key, value] of Object.entries(aliases)) {
687
713
  if (typeof value !== "string") continue;
688
- normalized[key] = path.isAbsolute(value) ? value : path.resolve(root, value);
714
+ if (path.isAbsolute(value)) normalized[key] = value;
715
+ else if (isRelativeAliasTarget(value)) normalized[key] = path.resolve(root, value);
716
+ else normalized[key] = value;
689
717
  }
690
718
  return normalized;
691
719
  }