vinext 0.0.52 → 0.0.53

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 (238) hide show
  1. package/README.md +1 -1
  2. package/dist/build/clean-output.d.ts +14 -0
  3. package/dist/build/clean-output.js +36 -0
  4. package/dist/build/clean-output.js.map +1 -0
  5. package/dist/build/prerender.d.ts +6 -2
  6. package/dist/build/prerender.js +49 -11
  7. package/dist/build/prerender.js.map +1 -1
  8. package/dist/build/run-prerender.js +10 -1
  9. package/dist/build/run-prerender.js.map +1 -1
  10. package/dist/build/static-export.d.ts +5 -0
  11. package/dist/build/static-export.js +8 -3
  12. package/dist/build/static-export.js.map +1 -1
  13. package/dist/cli.js +19 -4
  14. package/dist/cli.js.map +1 -1
  15. package/dist/client/instrumentation-client-inject.d.ts +34 -0
  16. package/dist/client/instrumentation-client-inject.js +57 -0
  17. package/dist/client/instrumentation-client-inject.js.map +1 -0
  18. package/dist/client/navigation-runtime.d.ts +14 -1
  19. package/dist/client/navigation-runtime.js +16 -1
  20. package/dist/client/navigation-runtime.js.map +1 -1
  21. package/dist/client/vinext-next-data.d.ts +2 -1
  22. package/dist/client/vinext-next-data.js.map +1 -1
  23. package/dist/client/window-next.d.ts +10 -2
  24. package/dist/client/window-next.js.map +1 -1
  25. package/dist/cloudflare/tpr.js +1 -1
  26. package/dist/cloudflare/tpr.js.map +1 -1
  27. package/dist/config/config-matchers.js +2 -1
  28. package/dist/config/config-matchers.js.map +1 -1
  29. package/dist/config/next-config.d.ts +12 -3
  30. package/dist/config/next-config.js +44 -14
  31. package/dist/config/next-config.js.map +1 -1
  32. package/dist/deploy.js +29 -7
  33. package/dist/deploy.js.map +1 -1
  34. package/dist/entries/app-rsc-entry.d.ts +4 -2
  35. package/dist/entries/app-rsc-entry.js +23 -3
  36. package/dist/entries/app-rsc-entry.js.map +1 -1
  37. package/dist/entries/pages-client-entry.js +22 -1
  38. package/dist/entries/pages-client-entry.js.map +1 -1
  39. package/dist/entries/pages-server-entry.js +211 -31
  40. package/dist/entries/pages-server-entry.js.map +1 -1
  41. package/dist/index.js +29 -6
  42. package/dist/index.js.map +1 -1
  43. package/dist/plugins/fonts.js +25 -2
  44. package/dist/plugins/fonts.js.map +1 -1
  45. package/dist/routing/route-trie.js +13 -18
  46. package/dist/routing/route-trie.js.map +1 -1
  47. package/dist/routing/utils.d.ts +11 -1
  48. package/dist/routing/utils.js +15 -1
  49. package/dist/routing/utils.js.map +1 -1
  50. package/dist/server/api-handler.js +18 -9
  51. package/dist/server/api-handler.js.map +1 -1
  52. package/dist/server/app-browser-action-result.d.ts +16 -1
  53. package/dist/server/app-browser-action-result.js +15 -1
  54. package/dist/server/app-browser-action-result.js.map +1 -1
  55. package/dist/server/app-browser-entry.js +22 -12
  56. package/dist/server/app-browser-entry.js.map +1 -1
  57. package/dist/server/app-elements.js +1 -1
  58. package/dist/server/app-fallback-renderer.d.ts +12 -3
  59. package/dist/server/app-fallback-renderer.js +10 -5
  60. package/dist/server/app-fallback-renderer.js.map +1 -1
  61. package/dist/server/app-history-state.js +6 -2
  62. package/dist/server/app-history-state.js.map +1 -1
  63. package/dist/server/app-interception-context-header.d.ts +33 -0
  64. package/dist/server/app-interception-context-header.js +44 -0
  65. package/dist/server/app-interception-context-header.js.map +1 -0
  66. package/dist/server/app-mounted-slots-header.d.ts +19 -0
  67. package/dist/server/app-mounted-slots-header.js +40 -1
  68. package/dist/server/app-mounted-slots-header.js.map +1 -1
  69. package/dist/server/app-optimistic-routing.js +26 -18
  70. package/dist/server/app-optimistic-routing.js.map +1 -1
  71. package/dist/server/app-page-boundary-render.d.ts +1 -0
  72. package/dist/server/app-page-boundary-render.js +2 -0
  73. package/dist/server/app-page-boundary-render.js.map +1 -1
  74. package/dist/server/app-page-boundary.d.ts +1 -0
  75. package/dist/server/app-page-boundary.js +2 -0
  76. package/dist/server/app-page-boundary.js.map +1 -1
  77. package/dist/server/app-page-cache.d.ts +2 -0
  78. package/dist/server/app-page-cache.js +7 -1
  79. package/dist/server/app-page-cache.js.map +1 -1
  80. package/dist/server/app-page-dispatch.d.ts +3 -0
  81. package/dist/server/app-page-dispatch.js +11 -4
  82. package/dist/server/app-page-dispatch.js.map +1 -1
  83. package/dist/server/app-page-element-builder.d.ts +2 -1
  84. package/dist/server/app-page-element-builder.js +5 -2
  85. package/dist/server/app-page-element-builder.js.map +1 -1
  86. package/dist/server/app-page-execution.d.ts +1 -0
  87. package/dist/server/app-page-execution.js +2 -0
  88. package/dist/server/app-page-execution.js.map +1 -1
  89. package/dist/server/app-page-head.d.ts +1 -0
  90. package/dist/server/app-page-head.js +8 -0
  91. package/dist/server/app-page-head.js.map +1 -1
  92. package/dist/server/app-page-render-observation.js +1 -1
  93. package/dist/server/app-page-render.d.ts +1 -0
  94. package/dist/server/app-page-render.js +5 -2
  95. package/dist/server/app-page-render.js.map +1 -1
  96. package/dist/server/app-page-response.d.ts +11 -1
  97. package/dist/server/app-page-response.js +14 -2
  98. package/dist/server/app-page-response.js.map +1 -1
  99. package/dist/server/app-page-route-wiring.d.ts +1 -0
  100. package/dist/server/app-page-route-wiring.js +19 -6
  101. package/dist/server/app-page-route-wiring.js.map +1 -1
  102. package/dist/server/app-page-stream.d.ts +1 -0
  103. package/dist/server/app-page-stream.js +2 -0
  104. package/dist/server/app-page-stream.js.map +1 -1
  105. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  106. package/dist/server/app-route-handler-dispatch.js +3 -0
  107. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  108. package/dist/server/app-route-handler-execution.d.ts +1 -0
  109. package/dist/server/app-route-handler-execution.js +1 -0
  110. package/dist/server/app-route-handler-execution.js.map +1 -1
  111. package/dist/server/app-route-handler-response.js +1 -1
  112. package/dist/server/app-rsc-handler.d.ts +2 -0
  113. package/dist/server/app-rsc-handler.js +18 -9
  114. package/dist/server/app-rsc-handler.js.map +1 -1
  115. package/dist/server/app-rsc-request-normalization.js +3 -2
  116. package/dist/server/app-rsc-request-normalization.js.map +1 -1
  117. package/dist/server/app-segment-config.d.ts +4 -1
  118. package/dist/server/app-segment-config.js +6 -1
  119. package/dist/server/app-segment-config.js.map +1 -1
  120. package/dist/server/app-server-action-execution.d.ts +1 -0
  121. package/dist/server/app-server-action-execution.js +4 -0
  122. package/dist/server/app-server-action-execution.js.map +1 -1
  123. package/dist/server/app-ssr-entry.js +39 -3
  124. package/dist/server/app-ssr-entry.js.map +1 -1
  125. package/dist/server/app-ssr-stream.d.ts +24 -1
  126. package/dist/server/app-ssr-stream.js +78 -5
  127. package/dist/server/app-ssr-stream.js.map +1 -1
  128. package/dist/server/app-static-generation.d.ts +1 -0
  129. package/dist/server/app-static-generation.js +2 -1
  130. package/dist/server/app-static-generation.js.map +1 -1
  131. package/dist/server/default-not-found-module.d.ts +20 -0
  132. package/dist/server/default-not-found-module.js +20 -0
  133. package/dist/server/default-not-found-module.js.map +1 -0
  134. package/dist/server/dev-server.d.ts +1 -1
  135. package/dist/server/dev-server.js +23 -7
  136. package/dist/server/dev-server.js.map +1 -1
  137. package/dist/server/headers.d.ts +5 -1
  138. package/dist/server/headers.js +5 -1
  139. package/dist/server/headers.js.map +1 -1
  140. package/dist/server/image-optimization.d.ts +13 -4
  141. package/dist/server/image-optimization.js +15 -4
  142. package/dist/server/image-optimization.js.map +1 -1
  143. package/dist/server/middleware.js +1 -1
  144. package/dist/server/middleware.js.map +1 -1
  145. package/dist/server/pages-api-route.d.ts +18 -0
  146. package/dist/server/pages-api-route.js +3 -1
  147. package/dist/server/pages-api-route.js.map +1 -1
  148. package/dist/server/pages-body-parser-config.d.ts +60 -0
  149. package/dist/server/pages-body-parser-config.js +79 -0
  150. package/dist/server/pages-body-parser-config.js.map +1 -0
  151. package/dist/server/pages-data-route.js +1 -0
  152. package/dist/server/pages-data-route.js.map +1 -1
  153. package/dist/server/pages-default-404.d.ts +31 -0
  154. package/dist/server/pages-default-404.js +40 -0
  155. package/dist/server/pages-default-404.js.map +1 -0
  156. package/dist/server/pages-node-compat.d.ts +10 -0
  157. package/dist/server/pages-node-compat.js +12 -1
  158. package/dist/server/pages-node-compat.js.map +1 -1
  159. package/dist/server/pages-page-data.d.ts +40 -0
  160. package/dist/server/pages-page-data.js +16 -14
  161. package/dist/server/pages-page-data.js.map +1 -1
  162. package/dist/server/pages-page-response.d.ts +2 -0
  163. package/dist/server/pages-page-response.js +11 -8
  164. package/dist/server/pages-page-response.js.map +1 -1
  165. package/dist/server/prerender-route-params.d.ts +14 -0
  166. package/dist/server/prerender-route-params.js +94 -0
  167. package/dist/server/prerender-route-params.js.map +1 -0
  168. package/dist/server/prod-server.d.ts +3 -23
  169. package/dist/server/prod-server.js +40 -57
  170. package/dist/server/prod-server.js.map +1 -1
  171. package/dist/server/proxy-trust.d.ts +41 -0
  172. package/dist/server/proxy-trust.js +70 -0
  173. package/dist/server/proxy-trust.js.map +1 -0
  174. package/dist/server/request-pipeline.d.ts +3 -3
  175. package/dist/server/request-pipeline.js +5 -4
  176. package/dist/server/request-pipeline.js.map +1 -1
  177. package/dist/server/seed-cache.js +12 -6
  178. package/dist/server/seed-cache.js.map +1 -1
  179. package/dist/server/static-file-cache.js +1 -1
  180. package/dist/server/static-file-cache.js.map +1 -1
  181. package/dist/server/streaming-metadata.d.ts +5 -0
  182. package/dist/server/streaming-metadata.js +10 -0
  183. package/dist/server/streaming-metadata.js.map +1 -0
  184. package/dist/shims/app-router-scroll-state.d.ts +12 -0
  185. package/dist/shims/app-router-scroll-state.js +38 -0
  186. package/dist/shims/app-router-scroll-state.js.map +1 -0
  187. package/dist/shims/app-router-scroll.d.ts +14 -0
  188. package/dist/shims/app-router-scroll.js +100 -0
  189. package/dist/shims/app-router-scroll.js.map +1 -0
  190. package/dist/shims/before-interactive-context.d.ts +30 -0
  191. package/dist/shims/before-interactive-context.js +10 -0
  192. package/dist/shims/before-interactive-context.js.map +1 -0
  193. package/dist/shims/cache-runtime.d.ts +1 -1
  194. package/dist/shims/cache-runtime.js +14 -1
  195. package/dist/shims/cache-runtime.js.map +1 -1
  196. package/dist/shims/default-not-found.d.ts +12 -0
  197. package/dist/shims/default-not-found.js +61 -0
  198. package/dist/shims/default-not-found.js.map +1 -0
  199. package/dist/shims/font-local.d.ts +5 -0
  200. package/dist/shims/font-local.js +6 -2
  201. package/dist/shims/font-local.js.map +1 -1
  202. package/dist/shims/head.js +4 -4
  203. package/dist/shims/head.js.map +1 -1
  204. package/dist/shims/headers.d.ts +6 -2
  205. package/dist/shims/headers.js +64 -21
  206. package/dist/shims/headers.js.map +1 -1
  207. package/dist/shims/image.d.ts +1 -1
  208. package/dist/shims/image.js +4 -4
  209. package/dist/shims/image.js.map +1 -1
  210. package/dist/shims/internal/pages-data-target.d.ts +58 -0
  211. package/dist/shims/internal/pages-data-target.js +91 -0
  212. package/dist/shims/internal/pages-data-target.js.map +1 -0
  213. package/dist/shims/internal/pages-data-url.d.ts +42 -0
  214. package/dist/shims/internal/pages-data-url.js +73 -0
  215. package/dist/shims/internal/pages-data-url.js.map +1 -0
  216. package/dist/shims/link.js +59 -9
  217. package/dist/shims/link.js.map +1 -1
  218. package/dist/shims/metadata.d.ts +2 -1
  219. package/dist/shims/metadata.js +61 -2
  220. package/dist/shims/metadata.js.map +1 -1
  221. package/dist/shims/navigation.js +32 -9
  222. package/dist/shims/navigation.js.map +1 -1
  223. package/dist/shims/router.js +376 -77
  224. package/dist/shims/router.js.map +1 -1
  225. package/dist/shims/script.js +86 -12
  226. package/dist/shims/script.js.map +1 -1
  227. package/dist/shims/server.js +1 -0
  228. package/dist/shims/server.js.map +1 -1
  229. package/dist/shims/url-utils.d.ts +2 -1
  230. package/dist/shims/url-utils.js +15 -4
  231. package/dist/shims/url-utils.js.map +1 -1
  232. package/dist/utils/html-limited-bots.d.ts +5 -0
  233. package/dist/utils/html-limited-bots.js +15 -0
  234. package/dist/utils/html-limited-bots.js.map +1 -0
  235. package/dist/utils/query.d.ts +6 -0
  236. package/dist/utils/query.js +10 -1
  237. package/dist/utils/query.js.map +1 -1
  238. package/package.json +1 -1
@@ -87,36 +87,109 @@ function fixPreloadAs(html) {
87
87
  return html.replace(/<link(?=[^>]*\srel="preload")[^>]*>/g, (tag) => tag.replace(" as=\"stylesheet\"", " as=\"style\""));
88
88
  }
89
89
  /**
90
+ * Match the `<head ...>` opening tag in a chunk. Matches both bare `<head>`
91
+ * and `<head class="foo">` shapes. Used to splice HTML immediately after the
92
+ * opening tag so injected content runs before any React-emitted resource
93
+ * hints (stylesheets, modulepreloads) that React Float hoists into `<head>`.
94
+ */
95
+ const HEAD_OPEN_RE = /<head\b[^>]*>/;
96
+ /**
90
97
  * Create the tick-buffered HTML transform that injects RSC scripts between
91
98
  * React Fizz flush cycles without corrupting split HTML chunks.
99
+ *
100
+ * Two insertion points are supported in tandem:
101
+ *
102
+ * - `injectHTML` is emitted immediately before `</head>`. This is where the
103
+ * bulk of vinext's head additions live (RSC navigation runtime metadata,
104
+ * bootstrap modulepreload, server-inserted HTML, font preloads, etc.).
105
+ * - `injectAfterHeadOpenHTML` is emitted immediately after the `<head ...>`
106
+ * opening tag so the content runs before any React-emitted resource
107
+ * hints. This is where inline `<Script strategy="beforeInteractive">`
108
+ * captures land so the no-flash dark-mode pattern works.
109
+ *
110
+ * Fallback behaviour differs by insertion point:
111
+ *
112
+ * - `injectHTML` is emitted at end-of-stream by the `flush` handler when no
113
+ * chunk ever contained `</head>` — callers still see the payload on
114
+ * highly fragmented streams (just at the end of the body rather than in
115
+ * the head).
116
+ * - `injectAfterHeadOpenHTML` is silently dropped when `<head ...>` is not
117
+ * found in a discoverable chunk. Emitting it at end-of-stream would put
118
+ * it after the document body, defeating the point — the splice has to
119
+ * happen before resource hints to be useful, so the safer behaviour is
120
+ * to no-op and let the user-rendered Script (in its source-order
121
+ * position) ship as-is.
92
122
  */
93
- function createTickBufferedTransform(rscEmbed, injectHTML = "") {
123
+ function createTickBufferedTransform(rscEmbed, injectHTML = "", injectAfterHeadOpenHTML = "") {
94
124
  const decoder = new TextDecoder();
95
125
  const encoder = new TextEncoder();
96
126
  const insertsPerFlush = typeof injectHTML === "function";
97
127
  let injected = false;
128
+ let preHeadInjected = false;
98
129
  let buffered = [];
99
130
  let timeoutId = null;
100
131
  const readInsertion = () => typeof injectHTML === "function" ? injectHTML() : injectHTML;
132
+ const readPreHeadInsertion = () => typeof injectAfterHeadOpenHTML === "function" ? injectAfterHeadOpenHTML() : injectAfterHeadOpenHTML;
101
133
  const emitInsertion = (controller) => {
102
134
  const insertion = readInsertion();
103
135
  if (insertion) controller.enqueue(encoder.encode(insertion));
104
136
  };
137
+ /**
138
+ * Splice the pre-head insertion (typically captured beforeInteractive inline
139
+ * scripts) immediately after the `<head ...>` opening tag. Returns the
140
+ * rewritten chunk and a flag indicating whether the splice happened, so the
141
+ * caller can mark `preHeadInjected` and stop scanning further chunks.
142
+ *
143
+ * NOTE: This is called only when `<head ...>` lies fully inside `chunk` —
144
+ * we deliberately avoid stitching across chunk boundaries because doing so
145
+ * would force the transform to hold output until it had seen `<head ...>`,
146
+ * which both delays TTFB and complicates the existing `</head>` injection
147
+ * path. In practice React Fizz emits the opening shell as a single chunk.
148
+ */
149
+ const spliceAfterHeadOpen = (chunk) => {
150
+ if (preHeadInjected) return {
151
+ chunk,
152
+ spliced: false
153
+ };
154
+ const insertion = readPreHeadInsertion();
155
+ if (!insertion) return {
156
+ chunk,
157
+ spliced: false
158
+ };
159
+ const match = HEAD_OPEN_RE.exec(chunk);
160
+ if (!match) return {
161
+ chunk,
162
+ spliced: false
163
+ };
164
+ const insertAt = match.index + match[0].length;
165
+ return {
166
+ chunk: chunk.slice(0, insertAt) + insertion + chunk.slice(insertAt),
167
+ spliced: true
168
+ };
169
+ };
105
170
  const flushBuffered = (controller) => {
106
171
  if (buffered.length === 0) return;
107
172
  if (injected && insertsPerFlush) emitInsertion(controller);
108
173
  for (const chunk of buffered) {
174
+ let working = chunk;
175
+ if (!preHeadInjected) {
176
+ const result = spliceAfterHeadOpen(working);
177
+ if (result.spliced) {
178
+ working = result.chunk;
179
+ preHeadInjected = true;
180
+ }
181
+ }
109
182
  if (!injected) {
110
- const headEnd = chunk.indexOf("</head>");
183
+ const headEnd = working.indexOf("</head>");
111
184
  if (headEnd !== -1) {
112
- const before = chunk.slice(0, headEnd);
113
- const after = chunk.slice(headEnd);
185
+ const before = working.slice(0, headEnd);
186
+ const after = working.slice(headEnd);
114
187
  controller.enqueue(encoder.encode(before + readInsertion() + after));
115
188
  injected = true;
116
189
  continue;
117
190
  }
118
191
  }
119
- controller.enqueue(encoder.encode(chunk));
192
+ controller.enqueue(encoder.encode(working));
120
193
  }
121
194
  buffered = [];
122
195
  };
@@ -1 +1 @@
1
- {"version":3,"file":"app-ssr-stream.js","names":[],"sources":["../../src/server/app-ssr-stream.ts"],"sourcesContent":["import { createInlineScriptTag, safeJsonStringify } from \"./html.js\";\nimport {\n bytesToBase64,\n concatUint8Arrays,\n RSC_EMBEDDED_BINARY_CHUNK,\n type RscEmbeddedChunk,\n} from \"./app-rsc-embedded-chunks.js\";\nimport { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from \"../client/navigation-runtime.js\";\n\ntype RscEmbedTransform = {\n flush(): string;\n finalize(): Promise<string>;\n /** Resolves when all raw bytes from the embed stream have been read. */\n getRawBuffer(): Promise<ArrayBuffer>;\n};\n\ntype HtmlInsertion = string | (() => string);\n\nconst NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(\n NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION,\n)})]`;\n\nexport function navigationRuntimeRscBootstrapExpression(): string {\n return `((${NAVIGATION_RUNTIME_REFERENCE}??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]})`;\n}\n\nexport function createNavigationRuntimeRscMetadataScript(\n params: Record<string, string | string[]>,\n nav: { pathname: string; searchParams: [string, string][] },\n): string {\n return (\n \"Object.assign(\" +\n navigationRuntimeRscBootstrapExpression() +\n \",{params:\" +\n safeJsonStringify(params) +\n \",nav:\" +\n safeJsonStringify(nav) +\n \"})\"\n );\n}\n\nfunction createNavigationRuntimeRscChunkScript(chunk: RscEmbeddedChunk): string {\n return navigationRuntimeRscBootstrapExpression() + \".rsc.push(\" + safeJsonStringify(chunk) + \")\";\n}\n\nfunction createNavigationRuntimeRscDoneScript(): string {\n return navigationRuntimeRscBootstrapExpression() + \".done=true\";\n}\n\n/**\n * Fix invalid preload \"as\" values in RSC Flight hint lines before they reach\n * the client. React Flight emits HL hints with as=\"stylesheet\" for CSS, but\n * the HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixFlightHints(text: string): string {\n return text.replace(/(\\d*:HL\\[.*?),\"stylesheet\"(\\]|,)/g, '$1,\"style\"$2');\n}\n\n/**\n * Create a helper that progressively embeds RSC chunks as inline <script> tags.\n * The browser entry turns the embedded chunks back into Uint8Array data.\n */\nexport function createRscEmbedTransform(\n embedStream: ReadableStream<Uint8Array>,\n scriptNonce?: string,\n): RscEmbedTransform {\n const reader = embedStream.getReader();\n let pendingChunks: RscEmbeddedChunk[] = [];\n const rawChunks: Uint8Array[] = [];\n let reading = false;\n\n async function pumpReader(): Promise<void> {\n if (reading) return;\n reading = true;\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n // Accumulate raw bytes BEFORE fixFlightHints so the cache stores\n // unmodified RSC data. The embed script path below applies fixes.\n rawChunks.push(result.value);\n try {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const text = decoder.decode(result.value);\n // The RSC entry already fixes HL hints at the source. Keep this second\n // pass as defense in depth for any embed stream that bypasses that\n // wrapper; the rewrite is idempotent, so double-application is safe.\n pendingChunks.push(fixFlightHints(text));\n } catch {\n pendingChunks.push([RSC_EMBEDDED_BINARY_CHUNK, bytesToBase64(result.value)]);\n }\n }\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\"[vinext] RSC embed stream read error:\", error);\n }\n throw error;\n } finally {\n reading = false;\n }\n }\n\n const pumpPromise = pumpReader();\n\n return {\n flush(): string {\n if (pendingChunks.length === 0) return \"\";\n\n const chunks = pendingChunks;\n pendingChunks = [];\n\n let scripts = \"\";\n for (const chunk of chunks) {\n scripts += createInlineScriptTag(createNavigationRuntimeRscChunkScript(chunk), scriptNonce);\n }\n return scripts;\n },\n\n async finalize(): Promise<string> {\n await pumpPromise;\n let scripts = this.flush();\n scripts += createInlineScriptTag(createNavigationRuntimeRscDoneScript(), scriptNonce);\n return scripts;\n },\n\n async getRawBuffer(): Promise<ArrayBuffer> {\n await pumpPromise;\n const buffer = concatUint8Arrays(rawChunks);\n rawChunks.length = 0;\n return buffer.buffer;\n },\n };\n}\n\n/**\n * Fix invalid preload \"as\" values in server-rendered HTML.\n * React Fizz emits <link rel=\"preload\" as=\"stylesheet\"> for CSS, but the\n * HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixPreloadAs(html: string): string {\n return html.replace(/<link(?=[^>]*\\srel=\"preload\")[^>]*>/g, (tag) =>\n tag.replace(' as=\"stylesheet\"', ' as=\"style\"'),\n );\n}\n\n/**\n * Create the tick-buffered HTML transform that injects RSC scripts between\n * React Fizz flush cycles without corrupting split HTML chunks.\n */\nexport function createTickBufferedTransform(\n rscEmbed: RscEmbedTransform,\n injectHTML: HtmlInsertion = \"\",\n): TransformStream<Uint8Array, Uint8Array> {\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n const insertsPerFlush = typeof injectHTML === \"function\";\n let injected = false;\n let buffered: string[] = [];\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n const readInsertion = (): string =>\n typeof injectHTML === \"function\" ? injectHTML() : injectHTML;\n const emitInsertion = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n const insertion = readInsertion();\n if (insertion) {\n controller.enqueue(encoder.encode(insertion));\n }\n };\n\n const flushBuffered = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n if (buffered.length === 0) return;\n\n if (injected && insertsPerFlush) {\n // Emit newly collected server-inserted HTML before the next Fizz HTML\n // batch so CSS-in-JS styles precede the elements they style.\n emitInsertion(controller);\n }\n\n for (const chunk of buffered) {\n if (!injected) {\n const headEnd = chunk.indexOf(\"</head>\");\n if (headEnd !== -1) {\n const before = chunk.slice(0, headEnd);\n const after = chunk.slice(headEnd);\n controller.enqueue(encoder.encode(before + readInsertion() + after));\n injected = true;\n continue;\n }\n }\n controller.enqueue(encoder.encode(chunk));\n }\n buffered = [];\n };\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n buffered.push(fixPreloadAs(decoder.decode(chunk, { stream: true })));\n\n if (timeoutId !== null) return;\n\n timeoutId = setTimeout(() => {\n try {\n flushBuffered(controller);\n\n const rscScripts = rscEmbed.flush();\n if (rscScripts) {\n controller.enqueue(encoder.encode(rscScripts));\n }\n } catch {\n // Stream was cancelled between when the timeout was registered and\n // when it fired (e.g. client disconnected, health-check cancelled\n // the response body). Ignore — the stream is already closed.\n }\n\n timeoutId = null;\n }, 0);\n },\n\n async flush(controller) {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n flushBuffered(controller);\n\n if (!injected) {\n emitInsertion(controller);\n injected = true;\n } else if (insertsPerFlush) {\n emitInsertion(controller);\n }\n\n const finalScripts = await rscEmbed.finalize();\n if (finalScripts) {\n controller.enqueue(encoder.encode(finalScripts));\n }\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"app-ssr-stream.js","names":[],"sources":["../../src/server/app-ssr-stream.ts"],"sourcesContent":["import { createInlineScriptTag, safeJsonStringify } from \"./html.js\";\nimport {\n bytesToBase64,\n concatUint8Arrays,\n RSC_EMBEDDED_BINARY_CHUNK,\n type RscEmbeddedChunk,\n} from \"./app-rsc-embedded-chunks.js\";\nimport { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from \"../client/navigation-runtime.js\";\n\ntype RscEmbedTransform = {\n flush(): string;\n finalize(): Promise<string>;\n /** Resolves when all raw bytes from the embed stream have been read. */\n getRawBuffer(): Promise<ArrayBuffer>;\n};\n\ntype HtmlInsertion = string | (() => string);\n\nconst NAVIGATION_RUNTIME_REFERENCE = `self[Symbol.for(${safeJsonStringify(\n NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION,\n)})]`;\n\nexport function navigationRuntimeRscBootstrapExpression(): string {\n return `((${NAVIGATION_RUNTIME_REFERENCE}??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]})`;\n}\n\nexport function createNavigationRuntimeRscMetadataScript(\n params: Record<string, string | string[]>,\n nav: { pathname: string; searchParams: [string, string][] },\n): string {\n return (\n \"Object.assign(\" +\n navigationRuntimeRscBootstrapExpression() +\n \",{params:\" +\n safeJsonStringify(params) +\n \",nav:\" +\n safeJsonStringify(nav) +\n \"})\"\n );\n}\n\nfunction createNavigationRuntimeRscChunkScript(chunk: RscEmbeddedChunk): string {\n return navigationRuntimeRscBootstrapExpression() + \".rsc.push(\" + safeJsonStringify(chunk) + \")\";\n}\n\nfunction createNavigationRuntimeRscDoneScript(): string {\n return navigationRuntimeRscBootstrapExpression() + \".done=true\";\n}\n\n/**\n * Fix invalid preload \"as\" values in RSC Flight hint lines before they reach\n * the client. React Flight emits HL hints with as=\"stylesheet\" for CSS, but\n * the HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixFlightHints(text: string): string {\n return text.replace(/(\\d*:HL\\[.*?),\"stylesheet\"(\\]|,)/g, '$1,\"style\"$2');\n}\n\n/**\n * Create a helper that progressively embeds RSC chunks as inline <script> tags.\n * The browser entry turns the embedded chunks back into Uint8Array data.\n */\nexport function createRscEmbedTransform(\n embedStream: ReadableStream<Uint8Array>,\n scriptNonce?: string,\n): RscEmbedTransform {\n const reader = embedStream.getReader();\n let pendingChunks: RscEmbeddedChunk[] = [];\n const rawChunks: Uint8Array[] = [];\n let reading = false;\n\n async function pumpReader(): Promise<void> {\n if (reading) return;\n reading = true;\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n // Accumulate raw bytes BEFORE fixFlightHints so the cache stores\n // unmodified RSC data. The embed script path below applies fixes.\n rawChunks.push(result.value);\n try {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const text = decoder.decode(result.value);\n // The RSC entry already fixes HL hints at the source. Keep this second\n // pass as defense in depth for any embed stream that bypasses that\n // wrapper; the rewrite is idempotent, so double-application is safe.\n pendingChunks.push(fixFlightHints(text));\n } catch {\n pendingChunks.push([RSC_EMBEDDED_BINARY_CHUNK, bytesToBase64(result.value)]);\n }\n }\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\"[vinext] RSC embed stream read error:\", error);\n }\n throw error;\n } finally {\n reading = false;\n }\n }\n\n const pumpPromise = pumpReader();\n\n return {\n flush(): string {\n if (pendingChunks.length === 0) return \"\";\n\n const chunks = pendingChunks;\n pendingChunks = [];\n\n let scripts = \"\";\n for (const chunk of chunks) {\n scripts += createInlineScriptTag(createNavigationRuntimeRscChunkScript(chunk), scriptNonce);\n }\n return scripts;\n },\n\n async finalize(): Promise<string> {\n await pumpPromise;\n let scripts = this.flush();\n scripts += createInlineScriptTag(createNavigationRuntimeRscDoneScript(), scriptNonce);\n return scripts;\n },\n\n async getRawBuffer(): Promise<ArrayBuffer> {\n await pumpPromise;\n const buffer = concatUint8Arrays(rawChunks);\n rawChunks.length = 0;\n return buffer.buffer;\n },\n };\n}\n\n/**\n * Fix invalid preload \"as\" values in server-rendered HTML.\n * React Fizz emits <link rel=\"preload\" as=\"stylesheet\"> for CSS, but the\n * HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixPreloadAs(html: string): string {\n return html.replace(/<link(?=[^>]*\\srel=\"preload\")[^>]*>/g, (tag) =>\n tag.replace(' as=\"stylesheet\"', ' as=\"style\"'),\n );\n}\n\n/**\n * Match the `<head ...>` opening tag in a chunk. Matches both bare `<head>`\n * and `<head class=\"foo\">` shapes. Used to splice HTML immediately after the\n * opening tag so injected content runs before any React-emitted resource\n * hints (stylesheets, modulepreloads) that React Float hoists into `<head>`.\n */\nconst HEAD_OPEN_RE = /<head\\b[^>]*>/;\n\n/**\n * Create the tick-buffered HTML transform that injects RSC scripts between\n * React Fizz flush cycles without corrupting split HTML chunks.\n *\n * Two insertion points are supported in tandem:\n *\n * - `injectHTML` is emitted immediately before `</head>`. This is where the\n * bulk of vinext's head additions live (RSC navigation runtime metadata,\n * bootstrap modulepreload, server-inserted HTML, font preloads, etc.).\n * - `injectAfterHeadOpenHTML` is emitted immediately after the `<head ...>`\n * opening tag so the content runs before any React-emitted resource\n * hints. This is where inline `<Script strategy=\"beforeInteractive\">`\n * captures land so the no-flash dark-mode pattern works.\n *\n * Fallback behaviour differs by insertion point:\n *\n * - `injectHTML` is emitted at end-of-stream by the `flush` handler when no\n * chunk ever contained `</head>` — callers still see the payload on\n * highly fragmented streams (just at the end of the body rather than in\n * the head).\n * - `injectAfterHeadOpenHTML` is silently dropped when `<head ...>` is not\n * found in a discoverable chunk. Emitting it at end-of-stream would put\n * it after the document body, defeating the point — the splice has to\n * happen before resource hints to be useful, so the safer behaviour is\n * to no-op and let the user-rendered Script (in its source-order\n * position) ship as-is.\n */\nexport function createTickBufferedTransform(\n rscEmbed: RscEmbedTransform,\n injectHTML: HtmlInsertion = \"\",\n injectAfterHeadOpenHTML: HtmlInsertion = \"\",\n): TransformStream<Uint8Array, Uint8Array> {\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n const insertsPerFlush = typeof injectHTML === \"function\";\n let injected = false;\n let preHeadInjected = false;\n let buffered: string[] = [];\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n const readInsertion = (): string =>\n typeof injectHTML === \"function\" ? injectHTML() : injectHTML;\n const readPreHeadInsertion = (): string =>\n typeof injectAfterHeadOpenHTML === \"function\"\n ? injectAfterHeadOpenHTML()\n : injectAfterHeadOpenHTML;\n const emitInsertion = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n const insertion = readInsertion();\n if (insertion) {\n controller.enqueue(encoder.encode(insertion));\n }\n };\n\n /**\n * Splice the pre-head insertion (typically captured beforeInteractive inline\n * scripts) immediately after the `<head ...>` opening tag. Returns the\n * rewritten chunk and a flag indicating whether the splice happened, so the\n * caller can mark `preHeadInjected` and stop scanning further chunks.\n *\n * NOTE: This is called only when `<head ...>` lies fully inside `chunk` —\n * we deliberately avoid stitching across chunk boundaries because doing so\n * would force the transform to hold output until it had seen `<head ...>`,\n * which both delays TTFB and complicates the existing `</head>` injection\n * path. In practice React Fizz emits the opening shell as a single chunk.\n */\n const spliceAfterHeadOpen = (chunk: string): { chunk: string; spliced: boolean } => {\n if (preHeadInjected) return { chunk, spliced: false };\n const insertion = readPreHeadInsertion();\n if (!insertion) return { chunk, spliced: false };\n const match = HEAD_OPEN_RE.exec(chunk);\n if (!match) return { chunk, spliced: false };\n const insertAt = match.index + match[0].length;\n return {\n chunk: chunk.slice(0, insertAt) + insertion + chunk.slice(insertAt),\n spliced: true,\n };\n };\n\n const flushBuffered = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n if (buffered.length === 0) return;\n\n if (injected && insertsPerFlush) {\n // Emit newly collected server-inserted HTML before the next Fizz HTML\n // batch so CSS-in-JS styles precede the elements they style.\n emitInsertion(controller);\n }\n\n for (const chunk of buffered) {\n let working = chunk;\n if (!preHeadInjected) {\n const result = spliceAfterHeadOpen(working);\n if (result.spliced) {\n working = result.chunk;\n preHeadInjected = true;\n }\n }\n if (!injected) {\n const headEnd = working.indexOf(\"</head>\");\n if (headEnd !== -1) {\n const before = working.slice(0, headEnd);\n const after = working.slice(headEnd);\n controller.enqueue(encoder.encode(before + readInsertion() + after));\n injected = true;\n continue;\n }\n }\n controller.enqueue(encoder.encode(working));\n }\n buffered = [];\n };\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n buffered.push(fixPreloadAs(decoder.decode(chunk, { stream: true })));\n\n if (timeoutId !== null) return;\n\n timeoutId = setTimeout(() => {\n try {\n flushBuffered(controller);\n\n const rscScripts = rscEmbed.flush();\n if (rscScripts) {\n controller.enqueue(encoder.encode(rscScripts));\n }\n } catch {\n // Stream was cancelled between when the timeout was registered and\n // when it fired (e.g. client disconnected, health-check cancelled\n // the response body). Ignore — the stream is already closed.\n }\n\n timeoutId = null;\n }, 0);\n },\n\n async flush(controller) {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n flushBuffered(controller);\n\n if (!injected) {\n emitInsertion(controller);\n injected = true;\n } else if (insertsPerFlush) {\n emitInsertion(controller);\n }\n\n const finalScripts = await rscEmbed.finalize();\n if (finalScripts) {\n controller.enqueue(encoder.encode(finalScripts));\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { HeadersContext } from "../shims/headers.js";
3
3
  //#region src/server/app-static-generation.d.ts
4
4
  type AppStaticGenerationRouteKind = "page" | "route";
5
5
  type CreateStaticGenerationHeadersContextOptions = {
6
+ draftModeSecret?: string;
6
7
  dynamicConfig?: string;
7
8
  routeKind: AppStaticGenerationRouteKind;
8
9
  routePattern?: string;
@@ -8,7 +8,8 @@ function getAppRouteStaticGenerationErrorMessage(routePattern, expression) {
8
8
  function createStaticGenerationHeadersContext(options) {
9
9
  const context = {
10
10
  headers: new Headers(),
11
- cookies: /* @__PURE__ */ new Map()
11
+ cookies: /* @__PURE__ */ new Map(),
12
+ draftModeSecret: options.draftModeSecret
12
13
  };
13
14
  if (options.dynamicConfig === "force-static") context.forceStatic = true;
14
15
  if (options.dynamicConfig === "error") context.accessError = new Error(options.routeKind === "route" ? getAppRouteStaticGenerationErrorMessage(options.routePattern) : getAppPageStaticGenerationErrorMessage());
@@ -1 +1 @@
1
- {"version":3,"file":"app-static-generation.js","names":[],"sources":["../../src/server/app-static-generation.ts"],"sourcesContent":["import type { HeadersContext } from \"vinext/shims/headers\";\n\ntype AppStaticGenerationRouteKind = \"page\" | \"route\";\n\ntype CreateStaticGenerationHeadersContextOptions = {\n dynamicConfig?: string;\n routeKind: AppStaticGenerationRouteKind;\n routePattern?: string;\n};\n\nexport function getAppPageStaticGenerationErrorMessage(): string {\n return (\n 'Page with `dynamic = \"error\"` used a dynamic API. ' +\n \"This page was expected to be fully static, but headers(), cookies(), \" +\n \"or searchParams was accessed. Remove the dynamic API usage or change \" +\n 'the dynamic config to \"auto\" or \"force-dynamic\".'\n );\n}\n\nexport function getAppRouteStaticGenerationErrorMessage(\n routePattern?: string,\n expression?: string,\n): string {\n const route = routePattern ?? \"unknown route\";\n const dynamicExpression = expression ?? \"a dynamic request API\";\n return (\n `Route ${route} with \\`dynamic = \"error\"\\` couldn't be rendered statically ` +\n `because it used ${dynamicExpression}. ` +\n \"See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering\"\n );\n}\n\nexport function createStaticGenerationHeadersContext(\n options: CreateStaticGenerationHeadersContextOptions,\n): HeadersContext {\n const context: HeadersContext = {\n headers: new Headers(),\n cookies: new Map(),\n };\n\n if (options.dynamicConfig === \"force-static\") {\n context.forceStatic = true;\n }\n\n if (options.dynamicConfig === \"error\") {\n context.accessError = new Error(\n options.routeKind === \"route\"\n ? getAppRouteStaticGenerationErrorMessage(options.routePattern)\n : getAppPageStaticGenerationErrorMessage(),\n );\n }\n\n return context;\n}\n"],"mappings":";AAUA,SAAgB,yCAAiD;CAC/D,OACE;;AAOJ,SAAgB,wCACd,cACA,YACQ;CAGR,OACE,SAHY,gBAAgB,gBAGb,8EAFS,cAAc,wBAGD;;AAKzC,SAAgB,qCACd,SACgB;CAChB,MAAM,UAA0B;EAC9B,SAAS,IAAI,SAAS;EACtB,yBAAS,IAAI,KAAK;EACnB;CAED,IAAI,QAAQ,kBAAkB,gBAC5B,QAAQ,cAAc;CAGxB,IAAI,QAAQ,kBAAkB,SAC5B,QAAQ,cAAc,IAAI,MACxB,QAAQ,cAAc,UAClB,wCAAwC,QAAQ,aAAa,GAC7D,wCAAwC,CAC7C;CAGH,OAAO"}
1
+ {"version":3,"file":"app-static-generation.js","names":[],"sources":["../../src/server/app-static-generation.ts"],"sourcesContent":["import type { HeadersContext } from \"vinext/shims/headers\";\n\ntype AppStaticGenerationRouteKind = \"page\" | \"route\";\n\ntype CreateStaticGenerationHeadersContextOptions = {\n draftModeSecret?: string;\n dynamicConfig?: string;\n routeKind: AppStaticGenerationRouteKind;\n routePattern?: string;\n};\n\nexport function getAppPageStaticGenerationErrorMessage(): string {\n return (\n 'Page with `dynamic = \"error\"` used a dynamic API. ' +\n \"This page was expected to be fully static, but headers(), cookies(), \" +\n \"or searchParams was accessed. Remove the dynamic API usage or change \" +\n 'the dynamic config to \"auto\" or \"force-dynamic\".'\n );\n}\n\nexport function getAppRouteStaticGenerationErrorMessage(\n routePattern?: string,\n expression?: string,\n): string {\n const route = routePattern ?? \"unknown route\";\n const dynamicExpression = expression ?? \"a dynamic request API\";\n return (\n `Route ${route} with \\`dynamic = \"error\"\\` couldn't be rendered statically ` +\n `because it used ${dynamicExpression}. ` +\n \"See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering\"\n );\n}\n\nexport function createStaticGenerationHeadersContext(\n options: CreateStaticGenerationHeadersContextOptions,\n): HeadersContext {\n const context: HeadersContext = {\n headers: new Headers(),\n cookies: new Map(),\n draftModeSecret: options.draftModeSecret,\n };\n\n if (options.dynamicConfig === \"force-static\") {\n context.forceStatic = true;\n }\n\n if (options.dynamicConfig === \"error\") {\n context.accessError = new Error(\n options.routeKind === \"route\"\n ? getAppRouteStaticGenerationErrorMessage(options.routePattern)\n : getAppPageStaticGenerationErrorMessage(),\n );\n }\n\n return context;\n}\n"],"mappings":";AAWA,SAAgB,yCAAiD;CAC/D,OACE;;AAOJ,SAAgB,wCACd,cACA,YACQ;CAGR,OACE,SAHY,gBAAgB,gBAGb,8EAFS,cAAc,wBAGD;;AAKzC,SAAgB,qCACd,SACgB;CAChB,MAAM,UAA0B;EAC9B,SAAS,IAAI,SAAS;EACtB,yBAAS,IAAI,KAAK;EAClB,iBAAiB,QAAQ;EAC1B;CAED,IAAI,QAAQ,kBAAkB,gBAC5B,QAAQ,cAAc;CAGxB,IAAI,QAAQ,kBAAkB,SAC5B,QAAQ,cAAc,IAAI,MACxB,QAAQ,cAAc,UAClB,wCAAwC,QAAQ,aAAa,GAC7D,wCAAwC,CAC7C;CAGH,OAAO"}
@@ -0,0 +1,20 @@
1
+ //#region src/server/default-not-found-module.d.ts
2
+ /**
3
+ * Module-shaped wrapper around vinext's built-in default not-found component.
4
+ * Used as the fallback when an app does not define its own `app/not-found.tsx`
5
+ * (and has not opted into `app/global-not-found.tsx`). The runtime treats any
6
+ * `{ default: Component }` record as a "not-found module", so wrapping the
7
+ * component this way lets us thread the default through the existing
8
+ * `rootNotFoundModule` plumbing without introducing a parallel code path.
9
+ *
10
+ * Mirrors Next.js's `defaultNotFoundPath`
11
+ * (`next/dist/client/components/builtin/not-found.js`), which is selected
12
+ * automatically when the user has not supplied a custom not-found file:
13
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/loaders/next-app-loader/index.ts
14
+ */
15
+ declare const DEFAULT_NOT_FOUND_MODULE: {
16
+ readonly default: any;
17
+ };
18
+ //#endregion
19
+ export { DEFAULT_NOT_FOUND_MODULE };
20
+ //# sourceMappingURL=default-not-found-module.d.ts.map
@@ -0,0 +1,20 @@
1
+ import DefaultNotFound from "../shims/default-not-found.js";
2
+ //#region src/server/default-not-found-module.ts
3
+ /**
4
+ * Module-shaped wrapper around vinext's built-in default not-found component.
5
+ * Used as the fallback when an app does not define its own `app/not-found.tsx`
6
+ * (and has not opted into `app/global-not-found.tsx`). The runtime treats any
7
+ * `{ default: Component }` record as a "not-found module", so wrapping the
8
+ * component this way lets us thread the default through the existing
9
+ * `rootNotFoundModule` plumbing without introducing a parallel code path.
10
+ *
11
+ * Mirrors Next.js's `defaultNotFoundPath`
12
+ * (`next/dist/client/components/builtin/not-found.js`), which is selected
13
+ * automatically when the user has not supplied a custom not-found file:
14
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/loaders/next-app-loader/index.ts
15
+ */
16
+ const DEFAULT_NOT_FOUND_MODULE = { default: DefaultNotFound };
17
+ //#endregion
18
+ export { DEFAULT_NOT_FOUND_MODULE };
19
+
20
+ //# sourceMappingURL=default-not-found-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-not-found-module.js","names":[],"sources":["../../src/server/default-not-found-module.ts"],"sourcesContent":["import DefaultNotFound from \"vinext/shims/default-not-found\";\n\n/**\n * Module-shaped wrapper around vinext's built-in default not-found component.\n * Used as the fallback when an app does not define its own `app/not-found.tsx`\n * (and has not opted into `app/global-not-found.tsx`). The runtime treats any\n * `{ default: Component }` record as a \"not-found module\", so wrapping the\n * component this way lets us thread the default through the existing\n * `rootNotFoundModule` plumbing without introducing a parallel code path.\n *\n * Mirrors Next.js's `defaultNotFoundPath`\n * (`next/dist/client/components/builtin/not-found.js`), which is selected\n * automatically when the user has not supplied a custom not-found file:\n * https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/loaders/next-app-loader/index.ts\n */\nexport const DEFAULT_NOT_FOUND_MODULE = {\n default: DefaultNotFound,\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,2BAA2B,EACtC,SAAS,iBACV"}
@@ -36,7 +36,7 @@ declare function parseCookieLocale(req: IncomingMessage, i18nConfig: NextI18nCon
36
36
  * 4. Render the component to HTML
37
37
  * 5. Wrap in _document shell and send response
38
38
  */
39
- declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
39
+ declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
40
40
 
41
41
  statusCode?: number,
42
42
  /**
@@ -18,6 +18,7 @@ import { createInlineScriptTag, createNonceAttribute, safeJsonStringify } from "
18
18
  import { getScriptNonceFromNodeHeaderSources } from "./csp.js";
19
19
  import { logRequest, now } from "./request-log.js";
20
20
  import { detectLocaleFromAcceptLanguage, extractLocaleFromUrl as extractLocaleFromUrl$1, parseCookieLocaleFromHeader, resolvePagesI18nRequest } from "./pages-i18n.js";
21
+ import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
21
22
  import path from "node:path";
22
23
  import React from "react";
23
24
  import { renderToReadableStream } from "react-dom/server.edge";
@@ -132,7 +133,7 @@ function parseCookieLocale(req, i18nConfig) {
132
133
  * 4. Render the component to HTML
133
134
  * 5. Wrap in _document shell and send response
134
135
  */
135
- function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false) {
136
+ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false) {
136
137
  const matcher = fileMatcher ?? createValidFileMatcher();
137
138
  const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
138
139
  _alsRegistration.catch(() => {});
@@ -274,7 +275,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
274
275
  };
275
276
  const result = await pageModule.getServerSideProps(context);
276
277
  if (res.writableEnded) return;
277
- if (result && "props" in result) pageProps = result.props;
278
+ if (result && "props" in result) pageProps = await Promise.resolve(result.props);
278
279
  if (result && "redirect" in result) {
279
280
  const { redirect } = result;
280
281
  const status = redirect.statusCode ?? (redirect.permanent ? 308 : 307);
@@ -301,6 +302,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
301
302
  if (Array.isArray(val)) gsspExtraHeaders[key] = val.map(String);
302
303
  else gsspExtraHeaders[key] = String(val);
303
304
  }
305
+ if (!Object.keys(gsspExtraHeaders).some((k) => k.toLowerCase() === "cache-control")) gsspExtraHeaders["Cache-Control"] = "private, no-cache, no-store, max-age=0, must-revalidate";
304
306
  }
305
307
  const responseHeaders = typeof res.getHeaders === "function" ? res.getHeaders() : void 0;
306
308
  const scriptNonce = getScriptNonceFromNodeHeaderSources(req.headers, responseHeaders);
@@ -340,7 +342,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
340
342
  params: userFacingParams,
341
343
  locale: locale ?? currentDefaultLocale,
342
344
  locales: i18nConfig?.locales,
343
- defaultLocale: currentDefaultLocale
345
+ defaultLocale: currentDefaultLocale,
346
+ revalidateReason: "stale"
344
347
  });
345
348
  if (freshResult && "props" in freshResult) {
346
349
  const revalidate = typeof freshResult.revalidate === "number" ? freshResult.revalidate : 0;
@@ -392,7 +395,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
392
395
  domainLocales,
393
396
  __vinext: {
394
397
  pageModuleUrl: regenPageUrl,
395
- appModuleUrl: regenAppUrl
398
+ appModuleUrl: regenAppUrl,
399
+ hasMiddleware
396
400
  }
397
401
  })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}<\/script>`}\n ${cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/)?.[0] ?? ""}</body></html>`, freshProps), revalidate);
398
402
  setRevalidateDuration(cacheKey, revalidate);
@@ -419,7 +423,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
419
423
  params: userFacingParams,
420
424
  locale: locale ?? currentDefaultLocale,
421
425
  locales: i18nConfig?.locales,
422
- defaultLocale: currentDefaultLocale
426
+ defaultLocale: currentDefaultLocale,
427
+ revalidateReason: "stale"
423
428
  };
424
429
  const result = await pageModule.getStaticProps(context);
425
430
  if (result && "props" in result) pageProps = result.props;
@@ -546,7 +551,8 @@ hydrate();
546
551
  domainLocales,
547
552
  __vinext: {
548
553
  pageModuleUrl,
549
- appModuleUrl
554
+ appModuleUrl,
555
+ hasMiddleware
550
556
  }
551
557
  })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}`, scriptNonce);
552
558
  const docPath = path.join(pagesDir, "_document");
@@ -669,8 +675,18 @@ async function renderErrorPage(server, runner, _req, res, url, pagesDir, statusC
669
675
  } catch {
670
676
  continue;
671
677
  }
678
+ if (statusCode === 404) {
679
+ const defaultResponse = buildDefaultPagesNotFoundResponse();
680
+ const headers = {};
681
+ defaultResponse.headers.forEach((value, key) => {
682
+ headers[key] = value;
683
+ });
684
+ res.writeHead(defaultResponse.status, headers);
685
+ res.end(await defaultResponse.text());
686
+ return;
687
+ }
672
688
  res.writeHead(statusCode, { "Content-Type": "text/plain" });
673
- res.end(`${statusCode} - ${statusCode === 404 ? "Page not found" : "Internal Server Error"}`);
689
+ res.end(`${statusCode} - Internal Server Error`);
674
690
  }
675
691
  //#endregion
676
692
  export { createSSRHandler, detectLocaleFromHeaders, extractLocaleFromUrl, parseCookieLocale };