vinext 1.0.0-beta.1 → 1.0.0-beta.2

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 (175) hide show
  1. package/README.md +4 -1
  2. package/dist/build/client-build-config.js +1 -0
  3. package/dist/build/report.d.ts +8 -1
  4. package/dist/build/report.js +12 -2
  5. package/dist/check.js +15 -2
  6. package/dist/cli.js +3 -2
  7. package/dist/config/config-matchers.d.ts +13 -37
  8. package/dist/config/config-matchers.js +31 -121
  9. package/dist/config/next-config.d.ts +3 -2
  10. package/dist/config/next-config.js +3 -1
  11. package/dist/config/request-context.d.ts +14 -0
  12. package/dist/config/request-context.js +26 -0
  13. package/dist/entries/app-rsc-entry.js +27 -9
  14. package/dist/entries/app-rsc-manifest.js +17 -0
  15. package/dist/entries/pages-server-entry.js +3 -2
  16. package/dist/index.js +82 -58
  17. package/dist/init.d.ts +6 -4
  18. package/dist/init.js +20 -22
  19. package/dist/plugins/og-assets.js +1 -1
  20. package/dist/routing/app-route-graph.d.ts +13 -5
  21. package/dist/routing/app-route-graph.js +59 -13
  22. package/dist/routing/app-router.js +12 -6
  23. package/dist/routing/file-matcher.d.ts +11 -22
  24. package/dist/routing/file-matcher.js +21 -33
  25. package/dist/routing/pages-router.js +23 -29
  26. package/dist/routing/route-matching.d.ts +14 -1
  27. package/dist/routing/route-matching.js +13 -1
  28. package/dist/routing/route-pattern.d.ts +2 -1
  29. package/dist/routing/route-pattern.js +7 -4
  30. package/dist/routing/route-trie.d.ts +5 -1
  31. package/dist/routing/route-trie.js +5 -2
  32. package/dist/server/app-browser-entry.js +7 -6
  33. package/dist/server/app-browser-history-controller.d.ts +1 -1
  34. package/dist/server/app-browser-history-controller.js +3 -3
  35. package/dist/server/app-browser-navigation-controller.js +1 -1
  36. package/dist/server/app-browser-state.js +1 -1
  37. package/dist/server/app-browser-visible-commit.js +1 -1
  38. package/dist/server/app-fallback-renderer.d.ts +3 -0
  39. package/dist/server/app-fallback-renderer.js +4 -2
  40. package/dist/server/app-middleware.js +2 -1
  41. package/dist/server/app-page-boundary-render.d.ts +12 -1
  42. package/dist/server/app-page-boundary-render.js +84 -2
  43. package/dist/server/app-page-cache-finalizer.d.ts +1 -1
  44. package/dist/server/app-page-cache.d.ts +1 -1
  45. package/dist/server/app-page-dispatch.d.ts +11 -2
  46. package/dist/server/app-page-dispatch.js +14 -17
  47. package/dist/server/app-page-element-builder.d.ts +7 -2
  48. package/dist/server/app-page-element-builder.js +112 -27
  49. package/dist/server/app-page-head.d.ts +54 -2
  50. package/dist/server/app-page-head.js +164 -78
  51. package/dist/server/app-page-http-access-fallback-metadata.d.ts +29 -0
  52. package/dist/server/app-page-http-access-fallback-metadata.js +106 -0
  53. package/dist/server/app-page-render.d.ts +2 -2
  54. package/dist/server/app-page-render.js +1 -1
  55. package/dist/server/app-page-request.d.ts +12 -0
  56. package/dist/server/app-page-request.js +133 -13
  57. package/dist/server/app-page-route-wiring.d.ts +9 -0
  58. package/dist/server/app-page-route-wiring.js +43 -3
  59. package/dist/server/app-post-middleware-context.d.ts +1 -1
  60. package/dist/server/app-post-middleware-context.js +1 -1
  61. package/dist/server/app-route-handler-dispatch.js +31 -17
  62. package/dist/server/app-route-handler-execution.d.ts +6 -1
  63. package/dist/server/app-route-handler-execution.js +42 -16
  64. package/dist/server/app-route-handler-policy.d.ts +2 -0
  65. package/dist/server/app-route-handler-policy.js +3 -3
  66. package/dist/server/app-route-module-loader.d.ts +3 -1
  67. package/dist/server/app-route-module-loader.js +1 -0
  68. package/dist/server/app-rsc-cache-busting.d.ts +1 -1
  69. package/dist/server/app-rsc-errors.d.ts +1 -1
  70. package/dist/server/app-rsc-errors.js +12 -2
  71. package/dist/server/app-rsc-handler.d.ts +10 -5
  72. package/dist/server/app-rsc-handler.js +52 -24
  73. package/dist/server/app-rsc-request-normalization.d.ts +3 -2
  74. package/dist/server/app-rsc-request-normalization.js +8 -2
  75. package/dist/server/app-rsc-response-finalizer.d.ts +2 -2
  76. package/dist/server/app-rsc-response-finalizer.js +5 -11
  77. package/dist/server/app-rsc-route-matching.d.ts +20 -0
  78. package/dist/server/app-rsc-route-matching.js +87 -11
  79. package/dist/server/app-segment-config.js +13 -3
  80. package/dist/server/app-server-action-execution.d.ts +3 -1
  81. package/dist/server/app-server-action-execution.js +9 -7
  82. package/dist/server/app-ssr-entry.js +2 -2
  83. package/dist/server/app-ssr-stream.d.ts +1 -7
  84. package/dist/server/app-ssr-stream.js +2 -10
  85. package/dist/server/config-headers.d.ts +24 -0
  86. package/dist/server/config-headers.js +52 -0
  87. package/dist/server/dev-initial-server-error.d.ts +1 -1
  88. package/dist/server/dev-server.js +29 -13
  89. package/dist/server/isr-cache.d.ts +1 -1
  90. package/dist/server/middleware-matcher-pattern.d.ts +22 -0
  91. package/dist/server/middleware-matcher-pattern.js +219 -0
  92. package/dist/server/middleware-matcher.d.ts +2 -7
  93. package/dist/server/middleware-matcher.js +13 -60
  94. package/dist/server/middleware-path-to-regexp.d.ts +14 -0
  95. package/dist/server/middleware-path-to-regexp.js +228 -0
  96. package/dist/server/middleware-runtime.js +4 -3
  97. package/dist/server/middleware.d.ts +1 -1
  98. package/dist/server/middleware.js +2 -1
  99. package/dist/server/pages-data-route.d.ts +11 -1
  100. package/dist/server/pages-data-route.js +16 -1
  101. package/dist/server/pages-get-initial-props.d.ts +15 -2
  102. package/dist/server/pages-get-initial-props.js +16 -6
  103. package/dist/server/pages-page-data.d.ts +3 -1
  104. package/dist/server/pages-page-data.js +16 -7
  105. package/dist/server/pages-page-handler.d.ts +4 -2
  106. package/dist/server/pages-page-handler.js +16 -17
  107. package/dist/server/pages-page-response.js +1 -1
  108. package/dist/server/pages-readiness.d.ts +3 -3
  109. package/dist/server/pages-request-pipeline.d.ts +1 -0
  110. package/dist/server/pages-request-pipeline.js +25 -11
  111. package/dist/server/pages-router-entry.js +6 -0
  112. package/dist/server/prerender-route-params.js +2 -2
  113. package/dist/server/prod-server.d.ts +1 -4
  114. package/dist/server/prod-server.js +36 -23
  115. package/dist/server/request-pipeline.d.ts +15 -29
  116. package/dist/server/request-pipeline.js +24 -59
  117. package/dist/server/rsc-stream-hints.js +122 -18
  118. package/dist/shims/app.d.ts +1 -1
  119. package/dist/shims/cache-request-state.d.ts +27 -1
  120. package/dist/shims/cache-request-state.js +60 -1
  121. package/dist/shims/cache-runtime.d.ts +12 -1
  122. package/dist/shims/cache-runtime.js +22 -7
  123. package/dist/shims/cache.d.ts +5 -5
  124. package/dist/shims/cache.js +26 -10
  125. package/dist/shims/document.d.ts +42 -83
  126. package/dist/shims/document.js +62 -53
  127. package/dist/shims/dynamic.d.ts +5 -25
  128. package/dist/shims/dynamic.js +29 -8
  129. package/dist/shims/error.d.ts +1 -1
  130. package/dist/shims/error.js +1 -1
  131. package/dist/shims/fetch-cache.js +12 -3
  132. package/dist/shims/font-google-base.d.ts +11 -4
  133. package/dist/shims/font-local.d.ts +10 -3
  134. package/dist/shims/head.d.ts +13 -2
  135. package/dist/shims/head.js +3 -3
  136. package/dist/shims/headers.d.ts +28 -31
  137. package/dist/shims/headers.js +109 -49
  138. package/dist/shims/image.d.ts +27 -37
  139. package/dist/shims/image.js +21 -14
  140. package/dist/shims/internal/cookie-serialize.d.ts +3 -1
  141. package/dist/shims/internal/cookie-serialize.js +3 -1
  142. package/dist/shims/internal/hybrid-client-route-owner.js +3 -1
  143. package/dist/shims/internal/utils.d.ts +3 -3
  144. package/dist/shims/legacy-image.d.ts +3 -37
  145. package/dist/shims/legacy-image.js +6 -16
  146. package/dist/shims/link.d.ts +15 -22
  147. package/dist/shims/link.js +20 -11
  148. package/dist/shims/navigation-context-state.d.ts +1 -1
  149. package/dist/shims/navigation.d.ts +7 -3
  150. package/dist/shims/navigation.js +24 -13
  151. package/dist/shims/navigation.react-server.js +1 -1
  152. package/dist/shims/next-shims-augmentations.d.ts +1 -0
  153. package/dist/shims/next-shims-public.d.ts +1 -0
  154. package/dist/shims/public-shim-map.json.js +103 -0
  155. package/dist/shims/readonly-url-search-params.d.ts +3 -3
  156. package/dist/shims/readonly-url-search-params.js +3 -3
  157. package/dist/shims/request-state-types.d.ts +1 -1
  158. package/dist/shims/router.d.ts +55 -21
  159. package/dist/shims/router.js +162 -37
  160. package/dist/shims/script.d.ts +6 -10
  161. package/dist/shims/script.js +6 -3
  162. package/dist/shims/server.d.ts +45 -22
  163. package/dist/shims/server.js +44 -5
  164. package/dist/shims/unified-request-context.d.ts +1 -1
  165. package/dist/shims/unified-request-context.js +2 -0
  166. package/dist/shims/url-safety.d.ts +1 -1
  167. package/dist/shims/url-safety.js +2 -2
  168. package/dist/typegen.d.ts +8 -2
  169. package/dist/typegen.js +76 -15
  170. package/dist/utils/domain-locale.d.ts +2 -2
  171. package/dist/utils/external-url.d.ts +5 -0
  172. package/dist/utils/external-url.js +7 -0
  173. package/dist/utils/regex-safety.d.ts +8 -0
  174. package/dist/utils/regex-safety.js +737 -0
  175. package/package.json +13 -2
@@ -0,0 +1,228 @@
1
+ //#region src/server/middleware-path-to-regexp.ts
2
+ function lexer(value) {
3
+ const tokens = [];
4
+ let index = 0;
5
+ while (index < value.length) {
6
+ const character = value[index];
7
+ if (character === "*" || character === "+" || character === "?") {
8
+ tokens.push({
9
+ type: "MODIFIER",
10
+ index,
11
+ value: value[index++]
12
+ });
13
+ continue;
14
+ }
15
+ if (character === "\\") {
16
+ tokens.push({
17
+ type: "ESCAPED_CHAR",
18
+ index: index++,
19
+ value: value[index++]
20
+ });
21
+ continue;
22
+ }
23
+ if (character === "{") {
24
+ tokens.push({
25
+ type: "OPEN",
26
+ index,
27
+ value: value[index++]
28
+ });
29
+ continue;
30
+ }
31
+ if (character === "}") {
32
+ tokens.push({
33
+ type: "CLOSE",
34
+ index,
35
+ value: value[index++]
36
+ });
37
+ continue;
38
+ }
39
+ if (character === ":") {
40
+ let name = "";
41
+ let nameEnd = index + 1;
42
+ while (nameEnd < value.length) {
43
+ const code = value.charCodeAt(nameEnd);
44
+ if (code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95) {
45
+ name += value[nameEnd++];
46
+ continue;
47
+ }
48
+ break;
49
+ }
50
+ if (!name) throw new TypeError(`Missing parameter name at ${index}`);
51
+ tokens.push({
52
+ type: "NAME",
53
+ index,
54
+ value: name
55
+ });
56
+ index = nameEnd;
57
+ continue;
58
+ }
59
+ if (character === "(") {
60
+ let depth = 1;
61
+ let pattern = "";
62
+ let patternEnd = index + 1;
63
+ if (value[patternEnd] === "?") throw new TypeError(`Pattern cannot start with "?" at ${patternEnd}`);
64
+ while (patternEnd < value.length) {
65
+ if (value[patternEnd] === "\\") {
66
+ pattern += value[patternEnd++] + value[patternEnd++];
67
+ continue;
68
+ }
69
+ if (value[patternEnd] === ")") {
70
+ depth--;
71
+ if (depth === 0) {
72
+ patternEnd++;
73
+ break;
74
+ }
75
+ } else if (value[patternEnd] === "(") {
76
+ depth++;
77
+ if (value[patternEnd + 1] !== "?") throw new TypeError(`Capturing groups are not allowed at ${patternEnd}`);
78
+ }
79
+ pattern += value[patternEnd++];
80
+ }
81
+ if (depth) throw new TypeError(`Unbalanced pattern at ${index}`);
82
+ if (!pattern) throw new TypeError(`Missing pattern at ${index}`);
83
+ tokens.push({
84
+ type: "PATTERN",
85
+ index,
86
+ value: pattern
87
+ });
88
+ index = patternEnd;
89
+ continue;
90
+ }
91
+ tokens.push({
92
+ type: "CHAR",
93
+ index,
94
+ value: value[index++]
95
+ });
96
+ }
97
+ tokens.push({
98
+ type: "END",
99
+ index,
100
+ value: ""
101
+ });
102
+ return tokens;
103
+ }
104
+ function parseMiddlewarePath(value) {
105
+ const tokens = lexer(value);
106
+ const result = [];
107
+ const prefixes = "./";
108
+ const delimiter = "/#?";
109
+ let key = 0;
110
+ let index = 0;
111
+ let path = "";
112
+ const tryConsume = (type) => {
113
+ if (index < tokens.length && tokens[index].type === type) return tokens[index++].value;
114
+ };
115
+ const mustConsume = (type) => {
116
+ const consumed = tryConsume(type);
117
+ if (consumed !== void 0) return consumed;
118
+ const next = tokens[index];
119
+ throw new TypeError(`Unexpected ${next.type} at ${next.index}, expected ${type}`);
120
+ };
121
+ const consumeText = () => {
122
+ let text = "";
123
+ let consumed;
124
+ while ((consumed = tryConsume("CHAR") ?? tryConsume("ESCAPED_CHAR")) !== void 0) text += consumed;
125
+ return text;
126
+ };
127
+ const containsDelimiter = (text) => {
128
+ for (const character of delimiter) if (text.includes(character)) return true;
129
+ return false;
130
+ };
131
+ const defaultPattern = (prefix) => {
132
+ const previous = result[result.length - 1];
133
+ const previousText = prefix || (typeof previous === "string" ? previous : "");
134
+ if (previous && !previousText) {
135
+ const name = typeof previous === "string" ? previous : previous.name;
136
+ throw new TypeError(`Must have text between two parameters, missing text after "${name}"`);
137
+ }
138
+ if (!previousText || containsDelimiter(previousText)) return "[^\\/#\\?]+?";
139
+ return `(?:(?!${escapeRegex(previousText)})[^\\/#\\?])+?`;
140
+ };
141
+ while (index < tokens.length) {
142
+ const character = tryConsume("CHAR");
143
+ const name = tryConsume("NAME");
144
+ const pattern = tryConsume("PATTERN");
145
+ if (name !== void 0 || pattern !== void 0) {
146
+ let prefix = character ?? "";
147
+ if (!prefixes.includes(prefix)) {
148
+ path += prefix;
149
+ prefix = "";
150
+ }
151
+ if (path) {
152
+ result.push(path);
153
+ path = "";
154
+ }
155
+ result.push({
156
+ name: name ?? key++,
157
+ prefix,
158
+ suffix: "",
159
+ pattern: pattern ?? defaultPattern(prefix),
160
+ modifier: tryConsume("MODIFIER") ?? ""
161
+ });
162
+ continue;
163
+ }
164
+ const text = character ?? tryConsume("ESCAPED_CHAR");
165
+ if (text !== void 0) {
166
+ path += text;
167
+ continue;
168
+ }
169
+ if (path) {
170
+ result.push(path);
171
+ path = "";
172
+ }
173
+ if (tryConsume("OPEN") !== void 0) {
174
+ const prefix = consumeText();
175
+ const groupName = tryConsume("NAME") ?? "";
176
+ const groupPattern = tryConsume("PATTERN") ?? "";
177
+ const suffix = consumeText();
178
+ mustConsume("CLOSE");
179
+ result.push({
180
+ name: groupName || (groupPattern ? key++ : ""),
181
+ pattern: groupName && !groupPattern ? defaultPattern(prefix) : groupPattern,
182
+ prefix,
183
+ suffix,
184
+ modifier: tryConsume("MODIFIER") ?? ""
185
+ });
186
+ continue;
187
+ }
188
+ mustConsume("END");
189
+ }
190
+ return result;
191
+ }
192
+ function escapeRegex(value) {
193
+ return value.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
194
+ }
195
+ function normalizeMiddlewarePathTokens(tokens) {
196
+ return tokens.map((token) => {
197
+ if (typeof token === "object" && (token.modifier === "*" || token.modifier === "+") && token.prefix === "" && token.suffix === "") return {
198
+ ...token,
199
+ prefix: "/"
200
+ };
201
+ return token;
202
+ });
203
+ }
204
+ function middlewarePathTokensToRegExp(tokens) {
205
+ const delimiterRegex = `[${escapeRegex("/#?")}]`;
206
+ let route = "^";
207
+ for (const token of tokens) {
208
+ if (typeof token === "string") {
209
+ route += escapeRegex(token);
210
+ continue;
211
+ }
212
+ const prefix = escapeRegex(token.prefix);
213
+ const suffix = escapeRegex(token.suffix);
214
+ if (token.pattern) if (prefix || suffix) if (token.modifier === "+" || token.modifier === "*") {
215
+ const optional = token.modifier === "*" ? "?" : "";
216
+ route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${optional}`;
217
+ } else route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;
218
+ else {
219
+ if (token.modifier === "+" || token.modifier === "*") throw new TypeError(`Can not repeat "${token.name}" without a prefix and suffix`);
220
+ route += `(${token.pattern})${token.modifier}`;
221
+ }
222
+ else route += `(?:${prefix}${suffix})${token.modifier}`;
223
+ }
224
+ route += `${delimiterRegex}?$`;
225
+ return new RegExp(route, "i");
226
+ }
227
+ //#endregion
228
+ export { middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath };
@@ -112,10 +112,11 @@ function resolveMiddlewarePathname(request) {
112
112
  return badRequestResponse();
113
113
  }
114
114
  }
115
- function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash, hadBasePath) {
115
+ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath) {
116
116
  const url = new URL(request.url);
117
117
  let mwRequest = request.body && !request.bodyUsed ? request.clone() : request;
118
- const mwPathname = basePath && hadBasePath ? addBasePathToPathname(normalizedPathname, basePath) : normalizedPathname;
118
+ const requestPathname = url.pathname;
119
+ const mwPathname = basePath && hadBasePath && !hasBasePath(requestPathname, basePath) ? addBasePathToPathname(requestPathname, basePath) : requestPathname;
119
120
  if (mwPathname !== url.pathname) {
120
121
  const mwUrl = new URL(url);
121
122
  mwUrl.pathname = mwPathname;
@@ -141,7 +142,7 @@ async function executeMiddleware(options) {
141
142
  const hadBasePath = options.hadBasePath ?? (!options.basePath || hasBasePath(new URL(options.request.url).pathname, options.basePath));
142
143
  const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
143
144
  if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
144
- const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
145
+ const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
145
146
  if (options.isDataRequest) Object.defineProperty(nextRequest, "__isData", {
146
147
  enumerable: false,
147
148
  value: true
@@ -58,6 +58,6 @@ declare function findMiddlewareFile(root: string, fileMatcher: ValidFileMatcher,
58
58
  * @param request - The incoming Request object
59
59
  * @returns Middleware result describing what action to take
60
60
  */
61
- declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean): Promise<MiddlewareResult>;
61
+ declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string): Promise<MiddlewareResult>;
62
62
  //#endregion
63
63
  export { findMiddlewareFile, isProxyFile, matchPattern, matchesMiddleware, resolveMiddlewareHandler, runMiddleware };
@@ -94,7 +94,7 @@ function isMiddlewareModule(value) {
94
94
  * @param request - The incoming Request object
95
95
  * @returns Middleware result describing what action to take
96
96
  */
97
- async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest) {
97
+ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname) {
98
98
  const mod = await runner.import(middlewarePath);
99
99
  if (!isMiddlewareModule(mod)) throw new Error(`Middleware module "${middlewarePath}" did not evaluate to an object.`);
100
100
  return runGeneratedMiddleware({
@@ -106,6 +106,7 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
106
106
  isDataRequest,
107
107
  isProxy: isProxyFile(middlewarePath),
108
108
  module: mod,
109
+ normalizedPathname,
109
110
  request,
110
111
  trailingSlash
111
112
  });
@@ -34,6 +34,16 @@ type NextDataMatch = {
34
34
  * even when the buildId is wrong (so we can still return a 404 JSON response).
35
35
  */
36
36
  declare function isNextDataPathname(pathname: string): boolean;
37
+ /**
38
+ * WHATWG URL parsing removes TAB, LF, and CR characters. Reject paths where
39
+ * that normalization would manufacture the internal Pages data namespace.
40
+ */
41
+ declare function urlParserCreatesPagesDataPath(pathname: string): boolean;
42
+ /**
43
+ * Keep URL-parser-ignored characters encoded until route matching decodes the
44
+ * captured parameter. Passing them literally to `new URL()` would remove them.
45
+ */
46
+ declare function encodeUrlParserIgnoredCharacters(pathname: string): string;
37
47
  /**
38
48
  * Parse `/_next/data/<buildId>/<...page>.json` and return the normalized page
39
49
  * pathname. Returns `null` if the pathname does not match the pattern or if
@@ -120,4 +130,4 @@ type NormalizePagesDataRequestResult = {
120
130
  */
121
131
  declare function normalizePagesDataRequest(request: Request, buildId: string | null, basePath?: string, trailingSlash?: boolean): NormalizePagesDataRequestResult;
122
132
  //#endregion
123
- export { buildMiddlewarePrefetchSkipResponse, buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname };
133
+ export { buildMiddlewarePrefetchSkipResponse, buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, encodeUrlParserIgnoredCharacters, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname, urlParserCreatesPagesDataPath };
@@ -32,6 +32,21 @@ function isNextDataPathname(pathname) {
32
32
  return pathname.startsWith(NEXT_DATA_PREFIX) && pathname.endsWith(NEXT_DATA_SUFFIX);
33
33
  }
34
34
  /**
35
+ * WHATWG URL parsing removes TAB, LF, and CR characters. Reject paths where
36
+ * that normalization would manufacture the internal Pages data namespace.
37
+ */
38
+ function urlParserCreatesPagesDataPath(pathname) {
39
+ const parsedPathname = pathname.replaceAll(" ", "").replaceAll("\n", "").replaceAll("\r", "");
40
+ return pathname !== parsedPathname && !isNextDataPathname(pathname) && isNextDataPathname(parsedPathname);
41
+ }
42
+ /**
43
+ * Keep URL-parser-ignored characters encoded until route matching decodes the
44
+ * captured parameter. Passing them literally to `new URL()` would remove them.
45
+ */
46
+ function encodeUrlParserIgnoredCharacters(pathname) {
47
+ return pathname.replaceAll(" ", "%09").replaceAll("\n", "%0A").replaceAll("\r", "%0D");
48
+ }
49
+ /**
35
50
  * Parse `/_next/data/<buildId>/<...page>.json` and return the normalized page
36
51
  * pathname. Returns `null` if the pathname does not match the pattern or if
37
52
  * the buildId segment does not match the server's buildId.
@@ -168,4 +183,4 @@ function normalizePagesDataRequest(request, buildId, basePath = "", trailingSlas
168
183
  };
169
184
  }
170
185
  //#endregion
171
- export { buildMiddlewarePrefetchSkipResponse, buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname };
186
+ export { buildMiddlewarePrefetchSkipResponse, buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, encodeUrlParserIgnoredCharacters, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname, urlParserCreatesPagesDataPath };
@@ -1,3 +1,5 @@
1
+ import { NextRouter } from "../shims/router.js";
2
+
1
3
  //#region src/server/pages-get-initial-props.d.ts
2
4
  type PagesGetInitialPropsContext = {
3
5
  req?: unknown;
@@ -10,6 +12,16 @@ type PagesGetInitialPropsContext = {
10
12
  locales?: string[];
11
13
  defaultLocale?: string;
12
14
  } & Record<string, unknown>;
15
+ type PagesGetInitialPropsRouter = Omit<Pick<NextRouter, "route" | "pathname" | "query" | "asPath">, "query"> & {
16
+ query: Record<string, unknown>;
17
+ };
18
+ /**
19
+ * Build the URL-state subset of Next.js's ServerRouter used by isolated
20
+ * initial-props helpers. Real request handlers pass their full `next/router`
21
+ * instance; this fallback keeps direct helper callers and tests aligned with
22
+ * the same required fields.
23
+ */
24
+ declare function createPagesGetInitialPropsRouter(pathname: string, query: Record<string, unknown>, asPath: string): PagesGetInitialPropsRouter;
13
25
  declare function hasPagesGetInitialProps(component: unknown): boolean;
14
26
  declare function isResponseSent(res: unknown): boolean;
15
27
  declare function loadPagesGetInitialProps(component: unknown, context: PagesGetInitialPropsContext): Promise<Record<string, unknown> | null>;
@@ -47,7 +59,8 @@ type DevAppInitialPropsContext = {
47
59
  res: unknown;
48
60
  pathname: string;
49
61
  query: Record<string, unknown>;
50
- asPath: string;
62
+ asPath: string; /** The request-scoped `next/router` server instance when available. */
63
+ router?: PagesGetInitialPropsRouter;
51
64
  locale?: string;
52
65
  locales?: string[];
53
66
  defaultLocale?: string;
@@ -64,4 +77,4 @@ type DevAppInitialPropsContext = {
64
77
  */
65
78
  declare function loadDevAppInitialProps(ctx: DevAppInitialPropsContext): Promise<DevAppInitialPropsResult>;
66
79
  //#endregion
67
- export { DevAppInitialPropsContext, DevAppInitialPropsResult, hasPagesGetInitialProps, isResponseSent, loadDevAppInitialProps, loadPagesGetInitialProps };
80
+ export { DevAppInitialPropsContext, DevAppInitialPropsResult, PagesGetInitialPropsRouter, createPagesGetInitialPropsRouter, hasPagesGetInitialProps, isResponseSent, loadDevAppInitialProps, loadPagesGetInitialProps };
@@ -1,4 +1,18 @@
1
1
  //#region src/server/pages-get-initial-props.ts
2
+ /**
3
+ * Build the URL-state subset of Next.js's ServerRouter used by isolated
4
+ * initial-props helpers. Real request handlers pass their full `next/router`
5
+ * instance; this fallback keeps direct helper callers and tests aligned with
6
+ * the same required fields.
7
+ */
8
+ function createPagesGetInitialPropsRouter(pathname, query, asPath) {
9
+ return {
10
+ route: pathname.replace(/\/$/, "") || "/",
11
+ pathname,
12
+ query,
13
+ asPath
14
+ };
15
+ }
2
16
  function isObjectLike(value) {
3
17
  return typeof value === "object" && value !== null || typeof value === "function";
4
18
  }
@@ -61,11 +75,7 @@ async function loadDevAppInitialProps(ctx) {
61
75
  const initialProps = await loadPagesGetInitialProps(ctx.appComponent, {
62
76
  AppTree: ctx.appTree,
63
77
  Component: ctx.component,
64
- router: {
65
- pathname: ctx.pathname,
66
- query: ctx.query,
67
- asPath: ctx.asPath
68
- },
78
+ router: ctx.router ?? createPagesGetInitialPropsRouter(ctx.pathname, ctx.query, ctx.asPath),
69
79
  ctx: {
70
80
  req: ctx.req,
71
81
  res: ctx.res,
@@ -89,4 +99,4 @@ async function loadDevAppInitialProps(ctx) {
89
99
  };
90
100
  }
91
101
  //#endregion
92
- export { hasPagesGetInitialProps, isResponseSent, loadDevAppInitialProps, loadPagesGetInitialProps };
102
+ export { createPagesGetInitialPropsRouter, hasPagesGetInitialProps, isResponseSent, loadDevAppInitialProps, loadPagesGetInitialProps };
@@ -3,6 +3,7 @@ import { VinextNextData } from "../client/vinext-next-data.js";
3
3
  import { CachedPagesValue } from "../shims/cache-handler.js";
4
4
  import { ISRCacheEntry } from "./isr-cache.js";
5
5
  import { PagesPreviewData } from "./pages-preview.js";
6
+ import { PagesGetInitialPropsRouter } from "./pages-get-initial-props.js";
6
7
  import { PagesGsspResponse, PagesI18nRenderContext, PagesNextDataExtras } from "./pages-page-response.js";
7
8
  import { ReactNode } from "react";
8
9
 
@@ -161,7 +162,8 @@ type ResolvePagesPageDataOptions = {
161
162
  deploymentId?: string;
162
163
  htmlLimitedBots?: string;
163
164
  pageModule: PagesPageModule;
164
- AppComponent?: unknown;
165
+ AppComponent?: unknown; /** The request-scoped `next/router` server instance when available. */
166
+ router?: PagesGetInitialPropsRouter;
165
167
  params: Record<string, unknown>;
166
168
  query: Record<string, unknown>;
167
169
  asPath?: string;
@@ -1,4 +1,6 @@
1
+ import { normalizePathnameForRouteMatch } from "../routing/utils.js";
1
2
  import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
3
+ import { isDangerousScheme } from "../shims/url-safety.js";
2
4
  import { buildCacheStateHeaders } from "./cache-headers.js";
3
5
  import { isUnknownRecord } from "../utils/record.js";
4
6
  import { applyCdnResponseHeaders } from "./cache-control.js";
@@ -6,7 +8,7 @@ import { decideIsr } from "./isr-decision.js";
6
8
  import { buildPagesCacheValue } from "./isr-cache.js";
7
9
  import { normalizeStaticPathname } from "../routing/route-pattern.js";
8
10
  import { buildPagesNextDataScript, etagMatches, generatePagesETag, isPagesStreamingBot, requestsNoCache } from "./pages-page-response.js";
9
- import { hasPagesGetInitialProps, isResponseSent, loadPagesGetInitialProps } from "./pages-get-initial-props.js";
11
+ import { createPagesGetInitialPropsRouter, hasPagesGetInitialProps, isResponseSent, loadPagesGetInitialProps } from "./pages-get-initial-props.js";
10
12
  import { buildNextDataPropsJsonResponse } from "./pages-data-route.js";
11
13
  import { isSerializableProps } from "./pages-serializable-props.js";
12
14
  import { isBotUserAgent } from "../utils/html-limited-bots.js";
@@ -57,11 +59,7 @@ async function loadPagesAppInitialRenderProps(options, getSharedReqRes) {
57
59
  const initialProps = await loadPagesGetInitialProps(options.AppComponent, {
58
60
  AppTree: options.createAppTree ?? options.createPageElement,
59
61
  Component: options.pageModule.default,
60
- router: {
61
- pathname: options.routePattern,
62
- query: options.query,
63
- asPath: options.asPath ?? options.routeUrl
64
- },
62
+ router: options.router ?? createPagesGetInitialPropsRouter(options.routePattern, options.query, options.asPath ?? options.routeUrl),
65
63
  ctx: {
66
64
  req,
67
65
  res,
@@ -111,6 +109,17 @@ async function loadPagesAppInitialRenderProps(options, getSharedReqRes) {
111
109
  */
112
110
  function buildPagesRedirectResponse(redirect, options, props = { pageProps: {} }) {
113
111
  const destination = options.sanitizeDestination(redirect.destination);
112
+ if (isDangerousScheme(destination)) {
113
+ const headers = new Headers({
114
+ "Cache-Control": "private, no-cache, no-store, max-age=0, must-revalidate",
115
+ "Content-Type": "text/plain; charset=utf-8"
116
+ });
117
+ if (options.deploymentId) headers.set(NEXTJS_DEPLOYMENT_ID_HEADER, options.deploymentId);
118
+ return new Response("Invalid redirect destination", {
119
+ status: 500,
120
+ headers
121
+ });
122
+ }
114
123
  if (options.isDataReq) {
115
124
  const init = { headers: {} };
116
125
  if (options.deploymentId) init.headers[NEXTJS_DEPLOYMENT_ID_HEADER] = options.deploymentId;
@@ -152,7 +161,7 @@ function getPagesRouteParams(routePattern) {
152
161
  }).filter((param) => param !== null);
153
162
  }
154
163
  function matchesPagesStaticPath(pathEntry, params, routeParams, routeUrl) {
155
- if (typeof pathEntry === "string") return normalizeStaticPathname(pathEntry) === normalizeStaticPathname(routeUrl);
164
+ if (typeof pathEntry === "string") return normalizePathnameForRouteMatch(normalizeStaticPathname(pathEntry)) === normalizePathnameForRouteMatch(normalizeStaticPathname(routeUrl));
156
165
  const entryParams = pathEntry.params;
157
166
  if (entryParams === void 0 || entryParams === null) return false;
158
167
  return routeParams.every(({ key, repeat, optional }) => {
@@ -1,4 +1,5 @@
1
1
  import { RenderPageEnhancers } from "./pages-document-initial-props.js";
2
+ import { PagesGetInitialPropsRouter } from "./pages-get-initial-props.js";
2
3
  import { PagesPageModule } from "./pages-page-data.js";
3
4
  import { ComponentType, ReactNode } from "react";
4
5
 
@@ -23,7 +24,7 @@ type I18nConfig = {
23
24
  domain: string;
24
25
  defaultLocale: string;
25
26
  locales?: string[];
26
- http?: boolean;
27
+ http?: true;
27
28
  }>;
28
29
  } | null;
29
30
  type VinextConfigSubset = {
@@ -60,7 +61,8 @@ type CreatePagesPageHandlerOptions = {
60
61
  */
61
62
  getPagesNavigationIsReadyFromSerializedState: ((routePattern: string | undefined, searchString: string, nextData?: Record<string, unknown>) => boolean) | null; /** `setI18nContext` from `vinext/i18n-context`. */
62
63
  setI18nContext: ((ctx: Record<string, unknown>) => void) | null; /** `wrapWithRouterContext` from `next/router`. */
63
- wrapWithRouterContext: ((element: ReactNode) => ReactNode) | null; /** `resetSSRHead` from `next/head`. */
64
+ wrapWithRouterContext: ((element: ReactNode) => ReactNode) | null; /** Request-scoped `next/router` server instance. */
65
+ router?: PagesGetInitialPropsRouter; /** `resetSSRHead` from `next/head`. */
64
66
  resetSSRHead: (() => void) | undefined; /** `getSSRHeadHTML` from `next/head`. */
65
67
  getSSRHeadHTML: (() => string) | undefined; /** `setDocumentInitialHead` from `next/head`. */
66
68
  setDocumentInitialHead: ((head: ReactNode[]) => void) | undefined; /** `flushPreloads` from `next/dynamic`. */
@@ -53,7 +53,7 @@ function buildI18nRenderContext(i18nConfig, locale, currentDefaultLocale, domain
53
53
  * accepts the same options shape the generated entry always passed inline.
54
54
  */
55
55
  function createPagesPageHandler(opts) {
56
- const { pageRoutes, errorPageRoute, matchRoute, i18nConfig, vinextConfig, buildId, hasMiddleware, appAssetPath, hasRewrites, setSSRContext, getPagesNavigationIsReadyFromSerializedState, setI18nContext, wrapWithRouterContext, resetSSRHead, getSSRHeadHTML, setDocumentInitialHead, flushPreloads, getFontLinks, getFontStyles, getFontPreloads, renderToReadableStream, renderIsrPassToStringAsync, safeJsonStringify, sanitizeDestination, createPageElement, enhancePageElement, AppComponent, DocumentComponent } = opts;
56
+ const { pageRoutes, errorPageRoute, matchRoute, i18nConfig, vinextConfig, buildId, hasMiddleware, appAssetPath, hasRewrites, setSSRContext, getPagesNavigationIsReadyFromSerializedState, setI18nContext, wrapWithRouterContext, router, resetSSRHead, getSSRHeadHTML, setDocumentInitialHead, flushPreloads, getFontLinks, getFontStyles, getFontPreloads, renderToReadableStream, renderIsrPassToStringAsync, safeJsonStringify, sanitizeDestination, createPageElement, enhancePageElement, AppComponent, DocumentComponent } = opts;
57
57
  function renderToStringAsync(element) {
58
58
  return renderToReadableStream(element).then((stream) => new Response(stream).text());
59
59
  }
@@ -138,15 +138,18 @@ function createPagesPageHandler(opts) {
138
138
  } else return buildDefaultPagesNotFoundResponse();
139
139
  }
140
140
  const { route, params } = match;
141
- const routerAsPath = i18nConfig ? extractLocaleFromUrl(renderAsPath ?? routeUrl, i18nConfig, locale).url : renderAsPath ?? routeUrl;
141
+ const pageModule = route.module;
142
+ const isStaticPropsRoute = typeof pageModule.getStaticProps === "function";
143
+ const isStaticPropsRender = isStaticPropsRoute && typeof pageModule.getServerSideProps !== "function";
144
+ const renderRouteUrl = isStaticPropsRender ? routeUrl.split("?")[0] : routeUrl;
145
+ const routerAsPathSource = isStaticPropsRender ? renderRouteUrl : renderAsPath ?? renderRouteUrl;
146
+ const routerAsPath = i18nConfig ? extractLocaleFromUrl(routerAsPathSource, i18nConfig, locale).url : routerAsPathSource;
142
147
  return runWithRequestContext(createRequestContext({ executionContext: getRequestExecutionContext() }), async () => {
143
148
  ensureFetchPatch();
144
149
  try {
145
150
  const routePattern = patternToNextFormat(route.pattern);
146
151
  const renderStatusCode = renderStatusCodeOverride ?? (routePattern === "/404" ? 404 : void 0);
147
- const query = mergeRouteParamsIntoQuery(parseQueryString(routeUrl), params);
148
- const pageModule = route.module;
149
- const isStaticPropsRoute = typeof pageModule.getStaticProps === "function";
152
+ const query = mergeRouteParamsIntoQuery(parseQueryString(renderRouteUrl), params);
150
153
  const isOnDemandRevalidate = isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER));
151
154
  const preview = isStaticPropsRoute || typeof pageModule.getServerSideProps === "function" ? getPagesPreviewState(request.headers.get("cookie"), { isOnDemandRevalidate }) : {
152
155
  data: false,
@@ -161,7 +164,7 @@ function createPagesPageHandler(opts) {
161
164
  }),
162
165
  ...previewData === false ? {} : { isPreview: true }
163
166
  };
164
- const navigationIsReady = typeof getPagesNavigationIsReadyFromSerializedState === "function" ? getPagesNavigationIsReadyFromSerializedState(routePattern, new URL(routerAsPath, "http://_").search, pagesNextData) : true;
167
+ const navigationIsReady = isStaticPropsRender ? false : typeof getPagesNavigationIsReadyFromSerializedState === "function" ? getPagesNavigationIsReadyFromSerializedState(routePattern, originalRequestUrl.search, pagesNextData) : true;
165
168
  function applySSRContext(extra) {
166
169
  if (typeof setSSRContext === "function") setSSRContext({
167
170
  pathname: routePattern,
@@ -204,7 +207,7 @@ function createPagesPageHandler(opts) {
204
207
  pageModuleUrl,
205
208
  appModuleUrl,
206
209
  hasMiddleware,
207
- routeUrl
210
+ routeUrl: renderRouteUrl
208
211
  }
209
212
  };
210
213
  const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareHeaders);
@@ -253,6 +256,7 @@ function createPagesPageHandler(opts) {
253
256
  previewData,
254
257
  pageModule,
255
258
  AppComponent,
259
+ router,
256
260
  params,
257
261
  query,
258
262
  asPath: routerAsPath,
@@ -260,7 +264,7 @@ function createPagesPageHandler(opts) {
260
264
  renderIsrPassToStringAsync,
261
265
  route: { isDynamic: route.isDynamic },
262
266
  routePattern,
263
- routeUrl,
267
+ routeUrl: renderRouteUrl,
264
268
  runInFreshUnifiedContext(callback) {
265
269
  return runWithRequestContext(createRequestContext({ executionContext: null }), async () => {
266
270
  ensureFetchPatch();
@@ -309,15 +313,10 @@ function createPagesPageHandler(opts) {
309
313
  const documentReqRes = serializedPagesNextData.autoExport === true ? null : pageDataResult.documentReqRes ?? createPageReqRes();
310
314
  const isrRevalidateSeconds = pageDataResult.isrRevalidateSeconds;
311
315
  const isFallbackRender = pageDataResult.isFallback === true;
312
- if (isFallbackRender && typeof setSSRContext === "function") setSSRContext({
313
- pathname: routePattern,
314
- query,
315
- asPath: routerAsPath,
316
+ if (isFallbackRender) applySSRContext({
317
+ query: {},
318
+ asPath: routePattern,
316
319
  navigationIsReady: false,
317
- locale,
318
- locales: i18nConfig ? i18nConfig.locales : void 0,
319
- defaultLocale: currentDefaultLocale,
320
- domainLocales,
321
320
  isFallback: true
322
321
  });
323
322
  if (isDataReq) {
@@ -404,7 +403,7 @@ function createPagesPageHandler(opts) {
404
403
  resetSSRHead: typeof resetSSRHead === "function" ? resetSSRHead : void 0,
405
404
  setDocumentInitialHead: typeof setDocumentInitialHead === "function" ? setDocumentInitialHead : void 0,
406
405
  routePattern,
407
- routeUrl,
406
+ routeUrl: renderRouteUrl,
408
407
  safeJsonStringify,
409
408
  scriptNonce,
410
409
  statusCode: renderStatusCode,
@@ -72,7 +72,7 @@ function buildPagesNextDataScript(options) {
72
72
  const nextDataPayload = {
73
73
  props: options.props ?? { pageProps: options.pageProps },
74
74
  page: options.routePattern,
75
- query: options.params,
75
+ query: options.isFallback === true ? {} : options.params,
76
76
  buildId: options.buildId,
77
77
  isFallback: options.isFallback === true
78
78
  };
@@ -7,9 +7,9 @@ import { PagesPageModule } from "./pages-page-data.js";
7
7
  *
8
8
  * The initial `router.isReady` value for the `next/navigation` compat hooks is
9
9
  * derived from the page/_app data-fetching exports plus the configured-rewrites
10
- * flag, serialized into `__NEXT_DATA__`. The dev SSR handler and the production
11
- * Pages page handler must compute this identically so server HTML and client
12
- * hydration agree see `getPagesNavigationIsReadyFromSerializedState` in
10
+ * flag. The client recomputes readiness from those flags and its live URL,
11
+ * matching the Pages Router constructor; server-only readiness remains in the
12
+ * request context. See `getPagesNavigationIsReadyFromSerializedState` in
13
13
  * `shims/router.ts`.
14
14
  */
15
15
  /**
@@ -44,6 +44,7 @@ type PagesPipelineDeps = {
44
44
  hasMiddleware: boolean;
45
45
  ctx?: unknown;
46
46
  rawSearch?: string;
47
+ configMatchPathname?: string;
47
48
  matchPageRoute?: ((pathname: string, request: Request) => PageRouteMatch | null) | null;
48
49
  runMiddleware?: ((request: Request, ctx: unknown, opts: {
49
50
  isDataRequest: boolean;