@thomas.f/prnext 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +54 -0
  2. package/build/action-exports.mjs +107 -0
  3. package/build/actions.mjs +214 -0
  4. package/build/app-config.mjs +138 -0
  5. package/build/app-routing.mjs +119 -0
  6. package/build/app-static.mjs +158 -0
  7. package/build/app.mjs +536 -0
  8. package/build/cache-components.mjs +91 -0
  9. package/build/cache-handlers.mjs +54 -0
  10. package/build/check.mjs +40 -0
  11. package/build/compiler.mjs +222 -0
  12. package/build/config.mjs +193 -0
  13. package/build/css-modules.mjs +114 -0
  14. package/build/custom-routes.mjs +363 -0
  15. package/build/dev-worker.mjs +18 -0
  16. package/build/dev.mjs +76 -0
  17. package/build/directory-cache.mjs +29 -0
  18. package/build/dynamic.mjs +125 -0
  19. package/build/edge.mjs +141 -0
  20. package/build/font-data.LICENSE.txt +25 -0
  21. package/build/font-data.json +1 -0
  22. package/build/font-files.mjs +221 -0
  23. package/build/fonts.mjs +131 -0
  24. package/build/framework-imports.mjs +17 -0
  25. package/build/i18n.mjs +39 -0
  26. package/build/images-config.mjs +62 -0
  27. package/build/images.mjs +86 -0
  28. package/build/incremental.mjs +76 -0
  29. package/build/index.mjs +356 -0
  30. package/build/loader-cache.mjs +69 -0
  31. package/build/metadata-images.mjs +72 -0
  32. package/build/metadata-routes.mjs +77 -0
  33. package/build/middleware.mjs +231 -0
  34. package/build/migrate.mjs +289 -0
  35. package/build/module-resolution.mjs +78 -0
  36. package/build/native-discovery.mjs +134 -0
  37. package/build/native-packages.mjs +105 -0
  38. package/build/npm-packages.mjs +110 -0
  39. package/build/og.mjs +14 -0
  40. package/build/page-extensions.mjs +12 -0
  41. package/build/passthrough-loader.cjs +4 -0
  42. package/build/precompress.mjs +122 -0
  43. package/build/scan.mjs +178 -0
  44. package/build/script-workers.mjs +25 -0
  45. package/build/server-actions-config.mjs +27 -0
  46. package/build/source-maps.mjs +34 -0
  47. package/build/standalone-config.mjs +19 -0
  48. package/build/standalone-runner.mjs +21 -0
  49. package/build/standalone-worker.mjs +5 -0
  50. package/build/standalone.mjs +240 -0
  51. package/build/static-export.mjs +93 -0
  52. package/build/styles.mjs +174 -0
  53. package/build/transform-cache.mjs +50 -0
  54. package/build/transform.mjs +155 -0
  55. package/build/turbopack-conditions.mjs +59 -0
  56. package/build/webpack-bridge.cjs +3 -0
  57. package/build/webpack-graph.mjs +256 -0
  58. package/build/webpack-loader.cjs +9 -0
  59. package/cli.mjs +210 -0
  60. package/compat/app-client-page.cjs +32 -0
  61. package/compat/app-context.cjs +40 -0
  62. package/compat/app-context.d.cts +20 -0
  63. package/compat/app-dynamic.cjs +34 -0
  64. package/compat/app-error-boundary.cjs +53 -0
  65. package/compat/app-global-error-boundary.cjs +34 -0
  66. package/compat/app-layout-context.cjs +27 -0
  67. package/compat/app-navigation-boundary.cjs +47 -0
  68. package/compat/app.cjs +33 -0
  69. package/compat/app.d.cts +9 -0
  70. package/compat/cache-handlers.cjs +223 -0
  71. package/compat/cache-life.cjs +34 -0
  72. package/compat/cache.cjs +99 -0
  73. package/compat/cache.d.cts +36 -0
  74. package/compat/compat/router.d.cts +2 -0
  75. package/compat/compat-router.cjs +7 -0
  76. package/compat/constants.cjs +8 -0
  77. package/compat/constants.d.cts +7 -0
  78. package/compat/cookies.cjs +129 -0
  79. package/compat/data-cache.cjs +389 -0
  80. package/compat/dist/shared/lib/app-router-context.shared-runtime.d.cts +3 -0
  81. package/compat/dist/shared/lib/router-context.d.cts +1 -0
  82. package/compat/dist/shared/lib/router-context.shared-runtime.d.cts +3 -0
  83. package/compat/document.cjs +73 -0
  84. package/compat/document.d.cts +41 -0
  85. package/compat/draft.cjs +28 -0
  86. package/compat/dynamic-bailout.cjs +13 -0
  87. package/compat/dynamic.cjs +119 -0
  88. package/compat/dynamic.d.cts +32 -0
  89. package/compat/error.cjs +36 -0
  90. package/compat/error.d.cts +15 -0
  91. package/compat/fetch.d.cts +10 -0
  92. package/compat/font/google.d.cts +1 -0
  93. package/compat/font/local.d.cts +2 -0
  94. package/compat/font-google.cjs +2 -0
  95. package/compat/font-google.d.cts +1944 -0
  96. package/compat/font-local.cjs +5 -0
  97. package/compat/font-local.d.cts +3 -0
  98. package/compat/font-types.d.cts +25 -0
  99. package/compat/head.cjs +180 -0
  100. package/compat/head.d.cts +3 -0
  101. package/compat/headers.cjs +89 -0
  102. package/compat/headers.d.cts +26 -0
  103. package/compat/image-config.cjs +3 -0
  104. package/compat/image-imports.d.ts +12 -0
  105. package/compat/image-shared.cjs +62 -0
  106. package/compat/image.cjs +64 -0
  107. package/compat/image.d.cts +35 -0
  108. package/compat/incremental-cache.cjs +186 -0
  109. package/compat/index.d.ts +159 -0
  110. package/compat/instant-samples.cjs +39 -0
  111. package/compat/instant.cjs +13 -0
  112. package/compat/link.cjs +68 -0
  113. package/compat/link.d.cts +17 -0
  114. package/compat/locale.cjs +30 -0
  115. package/compat/metadata.d.cts +77 -0
  116. package/compat/navigation-controls.cjs +27 -0
  117. package/compat/navigation-server.cjs +25 -0
  118. package/compat/navigation.cjs +52 -0
  119. package/compat/navigation.d.cts +28 -0
  120. package/compat/next-app-router-context.cjs +6 -0
  121. package/compat/next-router-context.cjs +3 -0
  122. package/compat/og.cjs +32 -0
  123. package/compat/og.d.cts +1 -0
  124. package/compat/paths.cjs +33 -0
  125. package/compat/preview.cjs +41 -0
  126. package/compat/rewrite.cjs +41 -0
  127. package/compat/route-request.cjs +64 -0
  128. package/compat/router.cjs +137 -0
  129. package/compat/router.d.cts +58 -0
  130. package/compat/script-context.cjs +5 -0
  131. package/compat/script-loader.cjs +135 -0
  132. package/compat/script.cjs +69 -0
  133. package/compat/script.d.cts +17 -0
  134. package/compat/server-primitives.cjs +109 -0
  135. package/compat/server.cjs +13 -0
  136. package/compat/server.d.cts +37 -0
  137. package/compat/static-generation.cjs +108 -0
  138. package/compat/use-cache.cjs +129 -0
  139. package/index.mjs +1 -0
  140. package/native/platforms.json +6 -0
  141. package/native/resolve.mjs +81 -0
  142. package/package.json +127 -0
  143. package/release/verify.mjs +59 -0
  144. package/runtime/action-client.mjs +19 -0
  145. package/runtime/action-crypto.mjs +138 -0
  146. package/runtime/action-reference.mjs +2 -0
  147. package/runtime/action-server.mjs +161 -0
  148. package/runtime/action-ssr.mjs +5 -0
  149. package/runtime/api.mjs +326 -0
  150. package/runtime/app-client.mjs +539 -0
  151. package/runtime/app-content.mjs +8 -0
  152. package/runtime/app-errors.mjs +12 -0
  153. package/runtime/app-instant.mjs +30 -0
  154. package/runtime/app-metadata.mjs +410 -0
  155. package/runtime/app-partial-model.mjs +228 -0
  156. package/runtime/app-partial.mjs +130 -0
  157. package/runtime/app-prefetch.mjs +34 -0
  158. package/runtime/app-recovery.mjs +87 -0
  159. package/runtime/app-render.mjs +544 -0
  160. package/runtime/app-routing-access.mjs +143 -0
  161. package/runtime/app-routing-context.mjs +35 -0
  162. package/runtime/app-routing-state.mjs +61 -0
  163. package/runtime/app-routing.mjs +323 -0
  164. package/runtime/app-segments.mjs +22 -0
  165. package/runtime/app-static-params.mjs +120 -0
  166. package/runtime/app-static.mjs +86 -0
  167. package/runtime/app-stream-channel.mjs +92 -0
  168. package/runtime/app-stream-html.mjs +279 -0
  169. package/runtime/build-directory.mjs +48 -0
  170. package/runtime/cache-arguments.mjs +33 -0
  171. package/runtime/cache-components-rsc.mjs +64 -0
  172. package/runtime/cache-request.mjs +23 -0
  173. package/runtime/client-navigation.mjs +40 -0
  174. package/runtime/dev-client.mjs +129 -0
  175. package/runtime/dev-file-changes.mjs +110 -0
  176. package/runtime/document.mjs +96 -0
  177. package/runtime/edge.mjs +105 -0
  178. package/runtime/env.mjs +68 -0
  179. package/runtime/fetch-cache.mjs +379 -0
  180. package/runtime/font-preload.mjs +4 -0
  181. package/runtime/http.mjs +190 -0
  182. package/runtime/incremental-cache.mjs +83 -0
  183. package/runtime/metadata-route.mjs +83 -0
  184. package/runtime/middleware.mjs +186 -0
  185. package/runtime/module-loader.mjs +21 -0
  186. package/runtime/navigation.mjs +16 -0
  187. package/runtime/og-loader.mjs +17 -0
  188. package/runtime/pages-client.mjs +197 -0
  189. package/runtime/pages-error-props.mjs +6 -0
  190. package/runtime/pages-errors.mjs +85 -0
  191. package/runtime/pages-initial-props.mjs +43 -0
  192. package/runtime/pages-navigation.mjs +565 -0
  193. package/runtime/pages-paths.mjs +80 -0
  194. package/runtime/pages-revalidate.mjs +33 -0
  195. package/runtime/partial-artifact-cache.mjs +37 -0
  196. package/runtime/partial-template.mjs +174 -0
  197. package/runtime/profile.mjs +9 -0
  198. package/runtime/profiles.json +47 -0
  199. package/runtime/render.mjs +304 -0
  200. package/runtime/request-transport.mjs +41 -0
  201. package/runtime/request-work.mjs +7 -0
  202. package/runtime/route-static.mjs +117 -0
  203. package/runtime/rsc-worker.mjs +426 -0
  204. package/runtime/script-html.mjs +61 -0
  205. package/runtime/stream-select.mjs +25 -0
  206. package/runtime/stream-utils.mjs +127 -0
  207. package/runtime/transport.mjs +108 -0
  208. package/runtime/watch-project.mjs +130 -0
  209. package/runtime/worker-artifacts.mjs +22 -0
  210. package/runtime/worker-metadata.mjs +31 -0
  211. package/runtime/worker.mjs +197 -0
@@ -0,0 +1,304 @@
1
+ import { localePath } from '../compat/locale.cjs';
2
+ import React from 'react';
3
+ import DefaultApp, { loadGetInitialProps } from '../compat/app.cjs';
4
+ import { RouterProvider, makeRouter } from '../compat/router.cjs';
5
+ import { HeadProvider } from '../compat/head.cjs';
6
+ import { addBasePath, removeBasePath, normalizeTrailingSlash } from '../compat/paths.cjs';
7
+ import { withCacheRequest } from './cache-request.mjs';
8
+ import { installFetchCache } from './fetch-cache.mjs';
9
+ import { staticPath, validateStaticPaths, localizedStaticPaths } from './pages-paths.mjs';
10
+ import { loadModule } from './module-loader.mjs';
11
+ import { renderPageError, summarizePageFailure, restorePageFailure } from './pages-errors.mjs';
12
+ export { renderPageError } from './pages-errors.mjs';
13
+ import { CapturedResponse, createRequest, queryFromUrl, MAX_RESPONSE_BYTES, plainResponse } from './http.mjs';
14
+ import { currentRequest } from '../compat/headers.cjs';
15
+
16
+ // Preserve the original build/test API while request workers import the small
17
+ // HTTP and API modules directly and load this renderer only for Pages HTML.
18
+ export { loadModule } from './module-loader.mjs';
19
+ export { CapturedResponse, createRequest, queryFromUrl, escapeHtml, serializeData, MAX_RESPONSE_BYTES, errorResponse } from './http.mjs';
20
+ export { runApi } from './api.mjs';
21
+
22
+ installFetchCache();
23
+
24
+ const HTML_HEADERS = { 'content-type': 'text/html; charset=utf-8' };
25
+
26
+ function assertSerializable(value, path = 'props', parents = new Set()) {
27
+ if (value === null || ['string', 'boolean'].includes(typeof value)) return;
28
+ if (typeof value === 'number' && Number.isFinite(value)) return;
29
+ if (typeof value !== 'object' || parents.has(value)) throw new Error(`${path} is not JSON serializable`);
30
+ if (!Array.isArray(value) && ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new Error(`${path} must contain only plain JSON values`);
31
+ parents.add(value);
32
+ for (const [key, child] of Object.entries(value)) assertSerializable(child, `${path}.${key}`, parents);
33
+ parents.delete(value);
34
+ }
35
+
36
+ function revalidateValue(value) {
37
+ if (value === undefined || value === false) return false;
38
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError('getStaticProps revalidate must be false or a nonnegative integer number of seconds');
39
+ return value;
40
+ }
41
+
42
+ function dataResult(result, response, isStatic) {
43
+ if (!result || typeof result !== 'object' || Array.isArray(result)) throw new Error('Data functions must return { props }, { notFound: true }, or { redirect }');
44
+ if (isStatic) revalidateValue(result.revalidate);
45
+ else if (result.revalidate !== undefined && result.revalidate !== false) throw new Error('getServerSideProps cannot specify revalidate');
46
+ if (result.notFound !== undefined && typeof result.notFound !== 'boolean') throw new Error('notFound must be a boolean');
47
+ const choices = Number(Object.hasOwn(result, 'props')) + Number(result.notFound === true) + Number(Object.hasOwn(result, 'redirect'));
48
+ if (choices !== 1) throw new Error('Data functions must return exactly one of props, notFound, or redirect');
49
+ if (result.notFound === true) return plainResponse(404, 'Not Found');
50
+ if (Object.hasOwn(result, 'redirect')) {
51
+ if (!result.redirect || typeof result.redirect !== 'object' || Array.isArray(result.redirect)) throw new Error('redirect must be an object');
52
+ const { destination, permanent, statusCode } = result.redirect;
53
+ if (typeof destination !== 'string' || /[\r\n]/.test(destination)) throw new Error('Invalid redirect destination');
54
+ if ((permanent === undefined) === (statusCode === undefined) || (permanent !== undefined && typeof permanent !== 'boolean')) {
55
+ throw new Error('redirect must specify either a boolean permanent or a valid statusCode');
56
+ }
57
+ if (result.redirect.basePath !== undefined && result.redirect.basePath !== false) throw new Error('redirect.basePath must be false when provided');
58
+ const status = statusCode ?? (permanent ? 308 : 307);
59
+ if (![301, 302, 303, 307, 308].includes(status)) throw new Error('Invalid redirect status');
60
+ return { status, headers: { ...response.getHeaders(), location: destination }, body: Buffer.alloc(0) };
61
+ }
62
+ return null;
63
+ }
64
+
65
+ function canonicalPageUrl(value = '/') {
66
+ const canonical = new URL('http://prnext.local');
67
+ canonical.pathname = new URL(value, canonical).pathname;
68
+ return canonical.href;
69
+ }
70
+
71
+ function visiblePageUrl(value, dataRequest, basePath) {
72
+ const visible = new URL(value);
73
+ visible.pathname = removeBasePath(visible.pathname, basePath);
74
+ if (dataRequest && /^\/(?:_prnext|_next)\/data\//.test(visible.pathname)) {
75
+ const parts = visible.pathname.split('/').slice(4);
76
+ if (parts.at(-1)?.endsWith('.json')) {
77
+ parts[parts.length - 1] = parts.at(-1).slice(0, -5);
78
+ if (parts[0] === 'index') parts.shift();
79
+ visible.pathname = `/${parts.join('/')}`;
80
+ }
81
+ }
82
+ return visible;
83
+ }
84
+
85
+ export function renderPageData(options) {
86
+ return renderPage({ ...options, renderMode: 'data' });
87
+ }
88
+
89
+ export async function renderPageRequest(options) {
90
+ if (options.route?.errorStatus === 500 || options.renderMode === 'error500') return renderPageError({ ...options, nativeErrors: false }, { statusCode: 500, error: restorePageFailure(options.pageFailure) });
91
+ if (options.renderMode === 'error404') return renderPageError({ ...options, nativeErrors: false }, { statusCode: 404 });
92
+ const capturedResponse = new CapturedResponse();
93
+ let result;
94
+ try { result = await renderPage({ ...options, capturedResponse }); }
95
+ catch (error) {
96
+ if ([400, 504].includes(error?.statusCode) || options.signal?.aborted) throw error;
97
+ console.error('[prnext]', error?.stack || error);
98
+ return renderPageError(options, { statusCode: 500, error, responseHeaders: capturedResponse.getHeaders() });
99
+ }
100
+ if (result.pageError && options.renderMode !== 'data') {
101
+ await result.finalizeCache?.();
102
+ return renderPageError(options, { statusCode: result.pageError, responseHeaders: { ...result.headers, ...capturedResponse.getHeaders() } });
103
+ }
104
+ return result;
105
+ }
106
+
107
+ export function renderPage(options) {
108
+ const input = options.renderMode === 'isr' || options.isFallback ? {
109
+ ...options, url: canonicalPageUrl(options.url), originalUrl: undefined,
110
+ method: 'GET', headers: {}, body: '',
111
+ } : options;
112
+ return withCacheRequest(input, 'pages', () => renderPageInner(input));
113
+ }
114
+
115
+ async function renderPageInner({ modulePath, route = {}, url = 'http://localhost/', originalUrl, params = {}, method = 'GET', headers = {}, body = '', staticProps, production = process.env.NODE_ENV === 'production', renderMode, isFallback = false, revalidateReason = 'stale', buildId, manifest, basePath = manifest?.config?.basePath || '', errorPage = false, errorStatus = route.errorStatus, error, errorDataRequest = false, capturedResponse, responseHeaders = {}, documentRequest, middlewareMatched = false }) {
116
+ const mod = await loadModule(modulePath);
117
+ const Page = mod.default;
118
+ if (typeof Page !== 'function' && typeof Page !== 'object') throw new Error('Page must export a React component as default');
119
+ const App = mod.App || DefaultApp;
120
+ const hasPageInitialProps = typeof Page.getInitialProps === 'function';
121
+ const hasAppInitialProps = App.getInitialProps !== App.origGetInitialProps;
122
+ if (hasPageInitialProps && (mod.getStaticProps || mod.getServerSideProps)) throw new Error('getInitialProps cannot be combined with getStaticProps or getServerSideProps on the same page');
123
+ if (mod.getServerSideProps && mod.getStaticProps) throw new Error('A page cannot define both getServerSideProps and getStaticProps');
124
+ const isStatic = typeof mod.getStaticProps === 'function';
125
+ if (renderMode === 'isr' && !isStatic) throw new Error('ISR rendering requires a getStaticProps page');
126
+ if (isFallback && !isStatic) throw new Error('Fallback rendering requires a getStaticProps page');
127
+ if (!['build', 'stale', 'on-demand'].includes(revalidateReason)) throw new TypeError('Invalid getStaticProps revalidateReason');
128
+ let devNotFound = false;
129
+ if (renderMode === 'isr' && manifest?.dev && route.pattern?.includes('[')) {
130
+ if (typeof mod.getStaticPaths !== 'function') throw new Error(`Dynamic page ${route.pattern} with getStaticProps must export getStaticPaths.`);
131
+ const listing = localizedStaticPaths(route, await mod.getStaticPaths(manifest.config?.i18n ? {locales:manifest.config.i18n.locales,defaultLocale:manifest.config.i18n.defaultLocale} : {}), manifest.config?.i18n);
132
+ const requested = staticPath(route.pattern, new URL(url).pathname).path;
133
+ devNotFound = listing.fallback === false && !listing.paths.some(entry => entry.path === requested);
134
+ }
135
+ const requestOriginal = originalUrl ? new URL(originalUrl, url) : undefined;
136
+ if (requestOriginal) requestOriginal.pathname = removeBasePath(requestOriginal.pathname, basePath);
137
+ const { request, url: parsed } = createRequest({ url, originalUrl: requestOriginal?.href, method, headers, body, params });
138
+ const previewData = currentRequest().previewData;
139
+ const preview = previewData !== false;
140
+ request.draftMode = currentRequest().draftMode;
141
+ request.preview = preview;
142
+ request.previewData = previewData;
143
+ const dataRequest = renderMode === 'data';
144
+ const visible = visiblePageUrl(originalUrl ? new URL(originalUrl, parsed) : parsed, dataRequest || errorDataRequest || errorPage, originalUrl ? basePath : '');
145
+ const response = capturedResponse || new CapturedResponse();
146
+ for (const [name, value] of Object.entries(responseHeaders)) response.setHeader(name, value);
147
+ if (errorStatus) response.statusCode = errorStatus;
148
+ let result = devNotFound ? { notFound: true, revalidate: 0 } : isFallback ? { props: {} } : staticProps;
149
+ const errorQuery = queryFromUrl(visible);
150
+ const i18n = manifest?.config?.i18n;
151
+ const locale = route.locale || (i18n ? localePath(visible.pathname, i18n).locale : undefined);
152
+ const domainLocale = i18n?.domains?.find(value => value.domain.toLowerCase() === new URL(url).host.toLowerCase());
153
+ const language = i18n ? {locale,locales:i18n.locales,defaultLocale:domainLocale?.defaultLocale || i18n.defaultLocale} : {};
154
+ const snapshot = { ...language, ...(i18n ? {i18n,domain:new URL(url).host,domainLocales:i18n.domains,isLocaleDomain:!!domainLocale} : {}), pathname: route.internal && errorStatus ? '/_error' : route.originalPattern || route.pattern || parsed.pathname, query: errorPage && route.pattern === '/_error' ? errorQuery : isStatic || isFallback ? { ...params } : request.query,
155
+ asPath: isStatic || isFallback ? normalizeTrailingSlash(parsed.pathname, manifest?.config) : `${errorPage && route.errorStatus ? route.pattern : visible.pathname}${visible.search}`, isFallback, isReady: true, basePath, trailingSlash: manifest?.config?.trailingSlash || false, skipTrailingSlashRedirect: manifest?.config?.skipTrailingSlashRedirect || false, isPreview: preview,
156
+ ...(originalUrl && !isStatic && !isFallback && visible.href !== parsed.href ? { rewrite: { url: `${parsed.pathname}${parsed.search}`, params } } : {}) };
157
+ let documentRequestObject = request;
158
+ if (route.internal && route.errorStatus && !errorPage) {
159
+ documentRequestObject = createRequest({ url: new URL(`/${errorStatus}`, url).href, method, headers }).request;
160
+ } else if (renderMode === 'isr' && documentRequest) {
161
+ const documentUrl = new URL(documentRequest.url.startsWith('/') ? new URL(url).origin + documentRequest.url : documentRequest.url, url);
162
+ const original = documentRequest.originalUrl ? new URL(documentRequest.originalUrl, documentUrl) : undefined;
163
+ if (original) original.pathname = removeBasePath(original.pathname, basePath);
164
+ documentRequestObject = createRequest({ url: documentUrl.href, originalUrl: original?.href,
165
+ method: documentRequest.method, headers: documentRequest.headers, params }).request;
166
+ }
167
+ const autoStatic = !route.errorStatus && !errorPage && !hasPageInitialProps && !hasAppInitialProps && !isStatic && !mod.getServerSideProps;
168
+ if (i18n) snapshot.asPath = localePath(snapshot.asPath, i18n).pathname;
169
+ const router = makeRouter(snapshot);
170
+ const AppTree = ({ pageProps = {}, ...props }) => React.createElement(RouterProvider, { router: snapshot },
171
+ React.createElement(HeadProvider, { collector: [] }, React.createElement(App, { ...props, Component: Page, pageProps, router })));
172
+ const context = { err: error, req: autoStatic ? undefined : documentRequestObject, res: autoStatic ? undefined : response,
173
+ ...language, pathname: snapshot.pathname, query: autoStatic ? {} : snapshot.query,
174
+ asPath: autoStatic ? snapshot.pathname : snapshot.asPath, AppTree };
175
+ // Browser transitions resolve rewrites here, then run legacy hooks in the
176
+ // browser. SSG/SSP still fetch their complete, server-produced App props.
177
+ const navigationRequest = dataRequest && request.headers['x-prnext-navigation'] === '1' && !isStatic && !mod.getServerSideProps;
178
+ const routingOnly = navigationRequest && !middlewareMatched;
179
+ const legacyNavigation = navigationRequest && middlewareMatched && (hasPageInitialProps || hasAppInitialProps);
180
+ const navigationResponse = result => {
181
+ if (!legacyNavigation) return result;
182
+ const headers = { ...result.headers, 'x-prnext-legacy-navigation': '1' };
183
+ const location = Array.isArray(headers.location) ? headers.location[0] : headers.location;
184
+ if ([301, 302, 303, 307, 308].includes(result.status) && typeof location === 'string') {
185
+ // Fetch must follow this legacy hook response, then continue the original
186
+ // page's client hooks. Keep it distinct from a router redirect, which
187
+ // changes the selected page and history URL.
188
+ delete headers.location;
189
+ headers['x-prnext-legacy-location'] = location;
190
+ return { ...result, status: 200, headers, body: Buffer.alloc(0) };
191
+ }
192
+ return { ...result, headers };
193
+ };
194
+ const initial = routingOnly ? { pageProps: {} } : await loadGetInitialProps(App, { AppTree, Component: Page, router, ctx: context });
195
+ if (response.writableEnded) return navigationResponse(response.result());
196
+ const { pageProps: initialPageProps = {}, ...appProps } = initial;
197
+ if (result === undefined && mod.getServerSideProps) {
198
+ result = await mod.getServerSideProps({ req: request, res: response, params, query: request.query,
199
+ ...language, resolvedUrl: `${localePath(parsed.pathname, i18n).pathname}${visible.search}`, preview, previewData, draftMode: currentRequest().draftMode });
200
+ if (result === undefined && !response.writableEnded) throw new Error('getServerSideProps must return a result');
201
+ } else if (result === undefined && mod.getStaticProps) {
202
+ result = await mod.getStaticProps({ ...language, params, preview, previewData, draftMode: currentRequest().draftMode, revalidateReason });
203
+ if (result === undefined) throw new Error('getStaticProps must return a result');
204
+ }
205
+ if (response.writableEnded) return response.result();
206
+ result ??= { props: initialPageProps };
207
+ if (errorStatus && (result.notFound || result.redirect)) throw new Error(`The ${errorStatus} page cannot return notFound or redirect`);
208
+ const early = dataResult(result, response, isStatic);
209
+ const revalidate = isStatic ? revalidateValue(result.revalidate) : false;
210
+ const pageData = props => ({ ...appProps, pageProps: props, ...(isStatic ? { __N_SSG: true } : mod.getServerSideProps ? { __N_SSP: true } : {}), __PRNEXT_ROUTER__: snapshot });
211
+ const dataResponse = (data, status = response.statusCode, redirect = false) => {
212
+ const dataJSON = JSON.stringify(data);
213
+ if (Buffer.byteLength(dataJSON) > MAX_RESPONSE_BYTES) throw new Error('Page data exceeds the 16 MiB PRNext limit');
214
+ const responseHeaders = { 'cache-control': 'private, no-cache, no-store, max-age=0, must-revalidate',
215
+ 'content-type': 'application/json; charset=utf-8', ...response.result().headers };
216
+ if (redirect) responseHeaders['content-type'] = 'application/json; charset=utf-8';
217
+ return { status, headers: responseHeaders, body: [204, 205, 304].includes(status) ? Buffer.alloc(0) : Buffer.from(dataJSON) };
218
+ };
219
+ const withData = (rendered, data) => {
220
+ if (!isStatic && !isFallback) return rendered;
221
+ const dataJSON = JSON.stringify(data);
222
+ if (Buffer.byteLength(dataJSON) > MAX_RESPONSE_BYTES) throw new Error('Page data exceeds the 16 MiB PRNext limit');
223
+ return { ...rendered, dataJSON, revalidate, generatedAt: Date.now() };
224
+ };
225
+ if (early) {
226
+ const data = result.notFound ? { notFound: true } : pageData({
227
+ __N_REDIRECT: result.redirect.destination,
228
+ __N_REDIRECT_STATUS: early.status,
229
+ ...(result.redirect.basePath === undefined ? {} : { __N_REDIRECT_BASE_PATH: result.redirect.basePath }),
230
+ });
231
+ if (dataRequest) return dataResponse(data, result.notFound ? 404 : response.statusCode, !!result.redirect);
232
+ if (result.redirect) {
233
+ if (result.redirect.basePath !== false) early.headers.location = addBasePath(early.headers.location, basePath);
234
+ if (early.headers.location.startsWith('/')) {
235
+ const [pathname, ...query] = early.headers.location.split('?');
236
+ early.headers.location = pathname.replace(/\\/g, '/').replace(/\/{2,}/g, '/') + (query[0] ? `?${query.join('?')}` : '');
237
+ }
238
+ }
239
+ return withData({ ...early, ...(result.notFound ? { pageError: 404 } : {}) }, data);
240
+ }
241
+ const dataProps = await result.props;
242
+ if (isStatic || mod.getServerSideProps) {
243
+ if (dataProps === null || typeof dataProps !== 'object' || Array.isArray(dataProps)) throw new Error('props must be a plain object');
244
+ assertSerializable(dataProps);
245
+ }
246
+ const props = isFallback ? {} : isStatic || mod.getServerSideProps ? { ...initialPageProps, ...dataProps } : dataProps;
247
+ // Client transitions execute data functions only. In particular, neither
248
+ // React rendering nor dynamic() SSR preloading belongs to a data request.
249
+ const legacyDataHtml = dataRequest && !isStatic && !mod.getServerSideProps && (hasPageInitialProps || hasAppInitialProps);
250
+ if (dataRequest && (!legacyDataHtml || routingOnly)) return dataResponse(pageData(props), errorStatus === 404 ? 200 : response.statusCode);
251
+ const { renderDocument } = await import('./document.mjs');
252
+ const document = await renderDocument({ Document: mod.Document, App, Page, props, appProps, snapshot, route, request: documentRequestObject,
253
+ response, error, production, buildId: buildId ?? manifest?.buildId, manifest, isStatic,
254
+ hasServerProps: typeof mod.getServerSideProps === 'function', hasPageInitialProps, hasAppInitialProps });
255
+ if (document.ended) return navigationResponse(response.result());
256
+ // A middleware data probe performs the observable server render before the
257
+ // browser executes its own hooks. Only routing metadata is needed on wire.
258
+ if (navigationRequest) return navigationResponse(dataResponse({ pageProps: {}, __PRNEXT_ROUTER__: snapshot }));
259
+ const html = document.html;
260
+ return withData({ status: response.statusCode, headers: { ...HTML_HEADERS, ...response.result().headers }, body: Buffer.from(html) }, pageData(props));
261
+ }
262
+
263
+ export async function renderIsrPage(options) {
264
+ let response;
265
+ try { response = await renderPage({ ...options, renderMode: 'isr' }); }
266
+ catch (error) {
267
+ if (!options.capturePageFailure) throw error;
268
+ const pageFailure = summarizePageFailure(error);
269
+ console.error('[prnext] Page generation failed:', pageFailure.stack || pageFailure.message);
270
+ return { status: 500, headers: {}, body: Buffer.alloc(0), pageFailure };
271
+ }
272
+ const html = response.body;
273
+ const data = Buffer.from(response.dataJSON);
274
+ return { status: response.status, headers: response.headers, finalizeCache: response.finalizeCache,
275
+ body: [html, data], isr: { revalidate: response.revalidate, htmlLength: html.byteLength, dataLength: data.byteLength } };
276
+ }
277
+
278
+ export async function renderFallback(options) {
279
+ const { path = options.route?.pattern || options.pattern || '/', pattern, client, css, ...input } = options;
280
+ const response = await renderPage({ ...input, url: canonicalPageUrl(path),
281
+ route: options.route || { pattern, client, css }, params: {}, isFallback: true });
282
+ await response.finalizeCache?.();
283
+ return { ...response, body: response.body.toString('utf8') };
284
+ }
285
+
286
+ export async function prerenderRoute({ modulePath, path = '/', params = {}, client, css = [], pattern, production = true, buildId, revalidateReason = 'build', basePath = '', manifest, distDir, route, errorStatus = route?.errorStatus }) {
287
+ const mod = await loadModule(modulePath);
288
+ if (mod.getServerSideProps) throw new Error('getServerSideProps pages cannot be prerendered');
289
+ const options = { modulePath, url: canonicalPageUrl(path), params,
290
+ route: route || { pattern, client, css }, buildId, revalidateReason, production, basePath, manifest, distDir, errorStatus };
291
+ let result = await renderPage(options);
292
+ await result.finalizeCache?.();
293
+ if (result.pageError && manifest) {
294
+ const replacement = await renderPageError(options, { statusCode: result.pageError });
295
+ await replacement.finalizeCache?.();
296
+ result = { ...result, body: replacement.body, headers: replacement.headers };
297
+ }
298
+ return { ...result, body: result.body.toString('utf8') };
299
+ }
300
+
301
+ export async function getStaticPaths({ modulePath }) {
302
+ const mod = await loadModule(modulePath);
303
+ return mod.getStaticPaths ? mod.getStaticPaths({}) : null;
304
+ }
@@ -0,0 +1,41 @@
1
+ const MAX_HEADER = 256 * 1024;
2
+ const MAX_BODY = 8 * 1024 * 1024;
3
+ const EMPTY_BODY = Buffer.alloc(0);
4
+
5
+ // Awaiting the consumer preserves socket backpressure. At most one body is
6
+ // assembled, and binary bytes never pass through JSON strings or base64.
7
+ export async function* requestFrames(source, { authenticate } = {}) {
8
+ let fragments = [], size = 0, input, body, offset = 0;
9
+ for await (const chunk of source) {
10
+ let cursor = 0;
11
+ while (cursor < chunk.length) {
12
+ if (body) {
13
+ const length = Math.min(body.length - offset, chunk.length - cursor);
14
+ chunk.copy(body, offset, cursor, cursor + length);
15
+ cursor += length; offset += length;
16
+ if (offset !== body.length) continue;
17
+ input.body = body; body = undefined; offset = 0;
18
+ yield input; input = undefined;
19
+ continue;
20
+ }
21
+ const end = chunk.indexOf(10, cursor);
22
+ const part = chunk.subarray(cursor, end < 0 ? chunk.length : end);
23
+ size += part.length;
24
+ if (size > (authenticate ? 256 : MAX_HEADER)) throw new Error('Request transport header exceeds limit');
25
+ fragments.push(part);
26
+ cursor = end < 0 ? chunk.length : end + 1;
27
+ if (end < 0) continue;
28
+ const line = fragments.length === 1 ? part.toString() : Buffer.concat(fragments, size).toString();
29
+ fragments = []; size = 0;
30
+ if (authenticate) { authenticate(line); authenticate = undefined; continue; }
31
+ if (!line.trim()) continue;
32
+ input = JSON.parse(line);
33
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Invalid request frame');
34
+ if (input.bodyLength === undefined) { yield input; input = undefined; continue; }
35
+ if (!Number.isSafeInteger(input.bodyLength) || input.bodyLength < 0 || input.bodyLength > MAX_BODY) throw new Error('Invalid request body length');
36
+ if (input.bodyLength === 0) { input.body = EMPTY_BODY; yield input; input = undefined; }
37
+ else body = Buffer.allocUnsafe(input.bodyLength);
38
+ }
39
+ }
40
+ if (size || body || authenticate) throw new Error('Truncated request transport frame');
41
+ }
@@ -0,0 +1,7 @@
1
+ // Track the actual application promise as well as the HTTP wrapper. A wrapper
2
+ // can reject on abort while npm code keeps running and retaining its context.
3
+ export const requestWork = Symbol.for('prnext.request-work');
4
+ export function trackRequestWork(promise, signal) {
5
+ signal?.[requestWork]?.(promise);
6
+ return promise;
7
+ }
@@ -0,0 +1,117 @@
1
+ import { loadModule } from './module-loader.mjs';
2
+ import { runApi } from './api.mjs';
3
+ import { MAX_RESPONSE_BYTES } from './http.mjs';
4
+ import { collectStaticParams, validateRuntimeStaticConfig } from './app-static-params.mjs';
5
+ import { runRequestContext, currentRequest } from '../compat/headers.cjs';
6
+ import { flushCacheWork, getCachePaths } from '../compat/data-cache.cjs';
7
+ import { trackStaticDependency, staticMetadata } from '../compat/static-generation.cjs';
8
+ import { apiTimeoutError, withSignal } from './stream-utils.mjs';
9
+
10
+ const METHODS = ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'];
11
+ const NON_STATIC = ['OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'];
12
+
13
+ function descriptor(module, route) {
14
+ const entry = { page: module, pageConfig: route.handlerConfig, segments: [] };
15
+ validateRuntimeStaticConfig(entry);
16
+ const methods = METHODS.filter(method => typeof module[method] === 'function');
17
+ const optedIn = module.dynamic === 'force-static' || module.dynamic === 'error' ||
18
+ module.revalidate === false || module.revalidate > 0 || typeof module.generateStaticParams === 'function';
19
+ return { entry, methods, hasExplicitHead: methods.includes('HEAD'),
20
+ staticEligible: module.runtime !== 'edge' && methods.includes('GET') && optedIn && module.dynamic !== 'force-dynamic' && !NON_STATIC.some(method => methods.includes(method)) };
21
+ }
22
+
23
+ function canonicalOptions(options) {
24
+ const url = new URL('http://prnext.local');
25
+ url.pathname = new URL(options.path || options.url || '/', url).pathname;
26
+ return { ...options, url: url.href, originalUrl: undefined, params: options.params || {},
27
+ method: options.renderMode === 'isr' && options.method === 'HEAD' ? 'HEAD' : 'GET',
28
+ headers: {}, body: '', production: options.production ?? true };
29
+ }
30
+
31
+ function deadline(options, phase) {
32
+ const controller = new AbortController();
33
+ const timeoutMs = options.timeoutMs ?? 25_000;
34
+ // Keep the timer alive while awaiting an otherwise handle-free import or
35
+ // generator so Node reports the timeout rather than exiting on pending TLA.
36
+ const timer = setTimeout(() => controller.abort(apiTimeoutError(phase, timeoutMs)), timeoutMs);
37
+ return { signal: options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal,
38
+ close: () => clearTimeout(timer) };
39
+ }
40
+
41
+ export async function inspectAppRouteHandler(options) {
42
+ const request = canonicalOptions(options);
43
+ const work = deadline(options, 'Route Handler static parameters');
44
+ try {
45
+ return await runRequestContext({ ...request, signal: work.signal, phase: 'route', cacheConfig: options.route?.cacheConfig }, async () => {
46
+ const context = currentRequest();
47
+ try {
48
+ work.signal.throwIfAborted();
49
+ const { entry, ...info } = descriptor(await withSignal(loadModule(options.modulePath), work.signal), options.route || {});
50
+ return { ...info, ...await withSignal(collectStaticParams(entry, options.route?.pattern), work.signal) };
51
+ } finally { await flushCacheWork(context); }
52
+ });
53
+ } finally { work.close(); }
54
+ }
55
+
56
+ export async function prerenderAppRouteHandler(options) {
57
+ const request = canonicalOptions(options);
58
+ const config = options.route?.cacheConfig || {};
59
+ const mode = config.dynamic || 'auto';
60
+ const work = deadline(options, 'static Route Handler');
61
+ request.signal = work.signal;
62
+ try {
63
+ work.signal.throwIfAborted();
64
+ const { staticEligible } = descriptor(await withSignal(loadModule(options.modulePath), work.signal), options.route || {});
65
+ if (!staticEligible) return { dynamic: true, reason: 'Route Handler has no static GET opt-in or exports non-static methods' };
66
+ if (config.revalidate === 0 && mode !== 'force-static') return { dynamic: true, reason: 'revalidate: 0' };
67
+ return await runRequestContext({ ...request, phase: 'route', cacheConfig: config,
68
+ routePattern: options.route?.pattern, staticGeneration: { mode } }, async () => {
69
+ const context = currentRequest();
70
+ let response;
71
+ try {
72
+ trackStaticDependency({ paths: getCachePaths(context) }, context);
73
+ response = await runApi({ ...request, stream: true, preserveHeadBody: true,
74
+ staticState: context.staticState, cacheState: context.cacheState });
75
+ staticMetadata(context);
76
+ // Build exports keep successful responses, redirects and not-found. A
77
+ // request-time regeneration preserves the handler's actual status.
78
+ if (options.renderMode !== 'isr' && response.status >= 400 && response.status !== 404) {
79
+ return { dynamic: true, reason: `Route Handler returned status ${response.status}` };
80
+ }
81
+ const chunks = [];
82
+ let length = 0;
83
+ const body = response.body;
84
+ const source = Buffer.isBuffer(body) || typeof body === 'string' ? [body] : body;
85
+ for await (const chunk of source || []) {
86
+ const bytes = Buffer.from(chunk);
87
+ length += bytes.byteLength;
88
+ if (length > MAX_RESPONSE_BYTES) throw new Error('Static Route Handler response exceeds the 16 MiB PRNext limit');
89
+ chunks.push(bytes);
90
+ }
91
+ await response.finalizeCache?.();
92
+ const metadata = staticMetadata(context);
93
+ if (metadata.externalCache) return { dynamic: true, reason: 'External cache handlers must be consulted at request time' };
94
+ if (Buffer.byteLength(JSON.stringify({ ...metadata, headers: response.headers })) > 60 * 1024) {
95
+ throw new Error('Static Route Handler response headers and dependencies exceed the PRNext metadata limit');
96
+ }
97
+ return { body: Buffer.concat(chunks, length), status: response.status, headers: response.headers,
98
+ ...metadata, generatedAt: Date.now() };
99
+ } finally {
100
+ await response?.cancel?.();
101
+ await flushCacheWork(context);
102
+ }
103
+ });
104
+ } catch (error) {
105
+ if (error.code === 'PRNEXT_DYNAMIC_SERVER_USAGE' && mode !== 'error') return { dynamic: true, reason: error.dynamicReason || error.message };
106
+ throw error;
107
+ } finally { work.close(); }
108
+ }
109
+
110
+ export async function renderRouteHandlerIsr(options) {
111
+ const rendered = await prerenderAppRouteHandler({ ...options, renderMode: 'isr' });
112
+ if (rendered.dynamic) return { status: 200, headers: {}, body: [],
113
+ isr: { kind: 'route', dynamic: true, revalidate: 0, htmlLength: 0, dataLength: 0, tags: [], paths: [] } };
114
+ return { status: rendered.status, headers: rendered.headers, body: [rendered.body],
115
+ isr: { kind: 'route', revalidate: rendered.revalidate, htmlLength: rendered.body.byteLength,
116
+ dataLength: 0, tags: rendered.tags, paths: rendered.paths } };
117
+ }