vinext 0.2.0 → 0.2.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.
- package/dist/build/css-url-assets.d.ts +3 -1
- package/dist/build/css-url-assets.js +18 -1
- package/dist/build/inject-pregenerated-paths.d.ts +3 -0
- package/dist/build/inject-pregenerated-paths.js +4 -1
- package/dist/build/prerender-server-entry.d.ts +1 -0
- package/dist/build/prerender-server-entry.js +49 -0
- package/dist/build/prerender-server-pool.d.ts +44 -0
- package/dist/build/prerender-server-pool.js +194 -0
- package/dist/build/prerender.js +86 -22
- package/dist/build/run-prerender.js +2 -1
- package/dist/check.d.ts +4 -6
- package/dist/check.js +8 -9
- package/dist/cli.js +1 -1
- package/dist/client/vinext-next-data.d.ts +4 -1
- package/dist/config/config-matchers.js +33 -11
- package/dist/config/next-config.d.ts +36 -6
- package/dist/config/next-config.js +16 -2
- package/dist/entries/app-browser-entry.js +5 -1
- package/dist/entries/app-rsc-entry.d.ts +3 -2
- package/dist/entries/app-rsc-entry.js +4 -0
- package/dist/entries/pages-client-entry.js +14 -3
- package/dist/entries/pages-server-entry.js +18 -4
- package/dist/index.js +320 -25
- package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +8 -1
- package/dist/packages/cloudflare/src/deploy.js +1 -5
- package/dist/packages/cloudflare/src/tpr.js +26 -5
- package/dist/plugins/fonts.js +1 -1
- package/dist/plugins/ignore-dynamic-requests.js +1 -1
- package/dist/plugins/og-assets.js +2 -1
- package/dist/plugins/optimize-imports.js +1 -1
- package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
- package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
- package/dist/plugins/sass.d.ts +17 -1
- package/dist/plugins/sass.js +74 -1
- package/dist/plugins/styled-jsx.d.ts +16 -0
- package/dist/plugins/styled-jsx.js +149 -0
- package/dist/routing/app-route-graph.js +22 -16
- package/dist/routing/file-matcher.d.ts +8 -1
- package/dist/routing/file-matcher.js +15 -3
- package/dist/server/app-browser-entry.js +46 -14
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-server-action-client.js +5 -3
- package/dist/server/app-browser-state.d.ts +1 -0
- package/dist/server/app-browser-state.js +2 -2
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-page-boundary.js +2 -1
- package/dist/server/app-page-cache-finalizer.d.ts +1 -0
- package/dist/server/app-page-cache-finalizer.js +3 -3
- package/dist/server/app-page-cache.js +23 -6
- package/dist/server/app-page-dispatch.d.ts +1 -0
- package/dist/server/app-page-dispatch.js +25 -8
- package/dist/server/app-page-element-builder.js +1 -0
- package/dist/server/app-page-execution.js +2 -1
- package/dist/server/app-page-render-identity.d.ts +1 -0
- package/dist/server/app-page-render-identity.js +2 -1
- package/dist/server/app-page-render.d.ts +2 -0
- package/dist/server/app-page-render.js +67 -16
- package/dist/server/app-page-response.d.ts +7 -0
- package/dist/server/app-page-response.js +16 -4
- package/dist/server/app-page-stream.d.ts +2 -1
- package/dist/server/app-page-stream.js +1 -1
- package/dist/server/app-route-handler-execution.js +1 -1
- package/dist/server/app-route-tree-prefetch.d.ts +43 -0
- package/dist/server/app-route-tree-prefetch.js +187 -0
- package/dist/server/app-router-entry.js +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +2 -1
- package/dist/server/app-rsc-cache-busting.js +9 -4
- package/dist/server/app-rsc-handler.d.ts +6 -0
- package/dist/server/app-rsc-handler.js +89 -11
- package/dist/server/app-rsc-render-mode.d.ts +3 -2
- package/dist/server/app-rsc-render-mode.js +4 -1
- package/dist/server/app-rsc-request-normalization.d.ts +3 -4
- package/dist/server/app-rsc-request-normalization.js +4 -5
- package/dist/server/app-server-action-execution.js +4 -3
- package/dist/server/app-ssr-entry.js +3 -3
- package/dist/server/cache-control.d.ts +3 -1
- package/dist/server/cache-control.js +13 -1
- package/dist/server/dev-server.d.ts +11 -1
- package/dist/server/dev-server.js +78 -20
- package/dist/server/headers.d.ts +6 -2
- package/dist/server/headers.js +11 -5
- package/dist/server/isr-cache.d.ts +2 -1
- package/dist/server/isr-cache.js +7 -3
- package/dist/server/pages-data-route.d.ts +4 -2
- package/dist/server/pages-data-route.js +18 -4
- package/dist/server/pages-dev-module-url.d.ts +2 -1
- package/dist/server/pages-dev-module-url.js +6 -3
- package/dist/server/pages-node-compat.d.ts +12 -1
- package/dist/server/pages-node-compat.js +50 -1
- package/dist/server/pages-page-data.d.ts +9 -0
- package/dist/server/pages-page-data.js +17 -9
- package/dist/server/pages-page-handler.js +19 -7
- package/dist/server/pages-page-response.d.ts +1 -0
- package/dist/server/pages-page-response.js +3 -2
- package/dist/server/pages-request-pipeline.d.ts +8 -6
- package/dist/server/pages-request-pipeline.js +30 -6
- package/dist/server/pages-router-entry.js +1 -1
- package/dist/server/prod-server.d.ts +4 -2
- package/dist/server/prod-server.js +38 -22
- package/dist/server/request-pipeline.js +1 -1
- package/dist/shims/cache-handler.js +8 -1
- package/dist/shims/cache.js +3 -0
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +77 -52
- package/dist/shims/form.d.ts +1 -1
- package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
- package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
- package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
- package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
- package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
- package/dist/shims/internal/pages-data-target.d.ts +8 -3
- package/dist/shims/internal/pages-data-target.js +9 -4
- package/dist/shims/link.d.ts +1 -1
- package/dist/shims/link.js +179 -42
- package/dist/shims/navigation.d.ts +15 -3
- package/dist/shims/navigation.js +202 -39
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/request-context.js +18 -0
- package/dist/shims/router.js +31 -15
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -1
- package/dist/utils/middleware-request-headers.js +1 -1
- package/dist/utils/protocol-headers.d.ts +7 -1
- package/dist/utils/protocol-headers.js +7 -1
- package/dist/utils/vite-version.d.ts +2 -0
- package/dist/utils/vite-version.js +11 -1
- package/package.json +2 -2
|
@@ -13,6 +13,8 @@ type EmitRestoredCssUrlAsset = (asset: {
|
|
|
13
13
|
* safe to run on every build-environment stylesheet, vendored CSS included.
|
|
14
14
|
*/
|
|
15
15
|
declare function markCssUrlAssetReferences(code: string, id: string): string | null;
|
|
16
|
+
/** Rebase relative asset URLs when Sass inlines a partial into an entry file. */
|
|
17
|
+
declare function rebaseCssUrlAssetReferences(code: string, sourceDirectory: string, targetDirectory: string): string | null;
|
|
16
18
|
/**
|
|
17
19
|
* Mutates emitted CSS assets in place so byte-identical `url()` dependencies
|
|
18
20
|
* keep their distinct Next-compatible filenames. Sibling files are produced via
|
|
@@ -26,4 +28,4 @@ declare function markCssUrlAssetReferences(code: string, id: string): string | n
|
|
|
26
28
|
*/
|
|
27
29
|
declare function restoreDedupedCssAssetReferences(bundle: CssUrlAssetBundle, emitRestoredAsset: EmitRestoredCssUrlAsset): void;
|
|
28
30
|
//#endregion
|
|
29
|
-
export { markCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
31
|
+
export { markCssUrlAssetReferences, rebaseCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
//#region src/build/css-url-assets.ts
|
|
2
3
|
/**
|
|
3
4
|
* Next-parity for CSS `url()` asset dependencies.
|
|
@@ -128,6 +129,22 @@ function markCssUrlAssetReferences(code, id) {
|
|
|
128
129
|
});
|
|
129
130
|
});
|
|
130
131
|
}
|
|
132
|
+
/** Rebase relative asset URLs when Sass inlines a partial into an entry file. */
|
|
133
|
+
function rebaseCssUrlAssetReferences(code, sourceDirectory, targetDirectory) {
|
|
134
|
+
return rewriteCssUrls(code, (rawUrl) => {
|
|
135
|
+
const parts = splitUrl(rawUrl.trim());
|
|
136
|
+
if (!isRelativeAssetUrl(parts.path)) return null;
|
|
137
|
+
const lower = parts.path.toLowerCase();
|
|
138
|
+
if (!CSS_ASSET_EXT_RE.test(lower) || lower.endsWith(".css")) return null;
|
|
139
|
+
const absolutePath = path.resolve(sourceDirectory, parts.path);
|
|
140
|
+
let rebasedPath = path.relative(targetDirectory, absolutePath).replaceAll("\\", "/");
|
|
141
|
+
if (!rebasedPath.startsWith(".")) rebasedPath = `./${rebasedPath}`;
|
|
142
|
+
return joinUrl({
|
|
143
|
+
...parts,
|
|
144
|
+
path: rebasedPath
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
131
148
|
function isCssFileName(fileName) {
|
|
132
149
|
return fileName.toLowerCase().endsWith(".css");
|
|
133
150
|
}
|
|
@@ -210,4 +227,4 @@ function stripChunkMarkers(code) {
|
|
|
210
227
|
return code.replace(CSS_URL_MARKER_PARAM_RE, (_m, leadingQuestion) => leadingQuestion ? leadingQuestion : "");
|
|
211
228
|
}
|
|
212
229
|
//#endregion
|
|
213
|
-
export { markCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
230
|
+
export { markCssUrlAssetReferences, rebaseCssUrlAssetReferences, restoreDedupedCssAssetReferences };
|
|
@@ -11,7 +11,10 @@ function injectPregeneratedConcretePaths(root) {
|
|
|
11
11
|
if (!fs.existsSync(workerEntry)) return;
|
|
12
12
|
let code = fs.readFileSync(workerEntry, "utf-8").replace(VINEXT_PREGEN_RE, "");
|
|
13
13
|
const table = readPrerenderManifest(path.join(root, "dist", "server", "vinext-prerender.json"))?.pregeneratedConcretePaths ?? [];
|
|
14
|
-
if (table.length > 0)
|
|
14
|
+
if (table.length > 0) {
|
|
15
|
+
globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = table;
|
|
16
|
+
code = `${VINEXT_PREGEN_START}\nglobalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS = ${JSON.stringify(table)};\n${VINEXT_PREGEN_END}\n` + code;
|
|
17
|
+
} else delete globalThis.__VINEXT_PREGENERATED_CONCRETE_PATHS;
|
|
15
18
|
fs.writeFileSync(workerEntry, code);
|
|
16
19
|
}
|
|
17
20
|
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NoOpCacheHandler, setCacheHandler } from "../shims/cache-handler.js";
|
|
2
|
+
import { startProdServer } from "../server/prod-server.js";
|
|
3
|
+
//#region src/build/prerender-server-entry.ts
|
|
4
|
+
/**
|
|
5
|
+
* Child-process entry for a parallel prerender render server.
|
|
6
|
+
*
|
|
7
|
+
* Forked by `prerender-server-pool.ts`. React SSR/RSC rendering is CPU-bound
|
|
8
|
+
* JS, so a single-process promise pool serializes every render on one core.
|
|
9
|
+
* This entry lets the prerender phase fan rendering out across N OS processes
|
|
10
|
+
* (one per core, like Next.js's static worker pool): each child starts a normal
|
|
11
|
+
* vinext production server on an ephemeral port and reports it to the parent
|
|
12
|
+
* over IPC. The parent then load-balances per-route fetches across the pool.
|
|
13
|
+
*
|
|
14
|
+
* `VINEXT_PRERENDER` / `VINEXT_PRERENDER_OUTDIR` are passed via the fork env so
|
|
15
|
+
* they are set before any module loads (some server modules read the flag at
|
|
16
|
+
* import time).
|
|
17
|
+
*/
|
|
18
|
+
async function main() {
|
|
19
|
+
const outDir = process.env.VINEXT_PRERENDER_OUTDIR;
|
|
20
|
+
if (!outDir) throw new Error("[vinext] prerender server worker: VINEXT_PRERENDER_OUTDIR not set");
|
|
21
|
+
setCacheHandler(new NoOpCacheHandler());
|
|
22
|
+
const { port } = await startProdServer({
|
|
23
|
+
port: 0,
|
|
24
|
+
host: "127.0.0.1",
|
|
25
|
+
outDir,
|
|
26
|
+
noCompression: true,
|
|
27
|
+
purpose: "prerender",
|
|
28
|
+
silent: true
|
|
29
|
+
});
|
|
30
|
+
if (typeof process.send === "function") process.send({
|
|
31
|
+
type: "ready",
|
|
32
|
+
port
|
|
33
|
+
});
|
|
34
|
+
else throw new Error("[vinext] prerender server worker: no IPC channel to parent");
|
|
35
|
+
}
|
|
36
|
+
process.on("disconnect", () => process.exit(0));
|
|
37
|
+
main().catch((err) => {
|
|
38
|
+
const message = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
39
|
+
if (typeof process.send === "function") {
|
|
40
|
+
process.send({
|
|
41
|
+
type: "error",
|
|
42
|
+
error: message
|
|
43
|
+
}, () => process.exit(1));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/build/prerender-server-pool.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether the forkable worker entry exists as a runnable `.js` sibling. False
|
|
4
|
+
* when vinext runs from source (e.g. the test suite transpiles `.ts` on the
|
|
5
|
+
* fly and a forked plain-node child can't load `.ts`); callers fall back to the
|
|
6
|
+
* single in-process server. True in the published/built package.
|
|
7
|
+
*/
|
|
8
|
+
declare function prerenderPoolAvailable(): boolean;
|
|
9
|
+
type PrerenderServerPool = {
|
|
10
|
+
/** Ports of the forked render servers, for round-robin per-route fetches. */ports: number[];
|
|
11
|
+
/**
|
|
12
|
+
* Throw if any worker exited unexpectedly (i.e. not via `close()`). The
|
|
13
|
+
* caller MUST call this after the render loop: a crashed worker makes every
|
|
14
|
+
* route routed to it fail with a connection error, which the per-route
|
|
15
|
+
* handler records as a (non-fatal) error — without this check the build
|
|
16
|
+
* would emit partial output and still exit 0. Mirrors the single-process
|
|
17
|
+
* design, where a server crash takes the whole build down loudly.
|
|
18
|
+
*/
|
|
19
|
+
assertHealthy: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Record a failed fetch to a render worker. This closes the tiny ordering gap
|
|
22
|
+
* where the request can fail before Node has delivered the child `exit` event.
|
|
23
|
+
*/
|
|
24
|
+
recordRenderError: (err: unknown) => void; /** Kill every forked server. Safe to call more than once. */
|
|
25
|
+
close: () => Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Choose how many render processes to fork for `routeCount` routes.
|
|
29
|
+
*
|
|
30
|
+
* Returns 1 to mean "don't fork — render in-process as before" (small apps,
|
|
31
|
+
* single/dual-core, low memory, or an explicit `--prerender-concurrency 1`).
|
|
32
|
+
* `maxOverride` comes from `--prerender-concurrency`, which caps both the
|
|
33
|
+
* number of in-flight route fetches and the worker count (the default is capped
|
|
34
|
+
* at `min(cores, 8)`).
|
|
35
|
+
*/
|
|
36
|
+
declare function resolvePrerenderPoolSize(routeCount: number, maxOverride?: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* Fork `size` production servers against `outDir` and resolve once all are
|
|
39
|
+
* listening. Each child reports its port over IPC. Rejects (and tears down any
|
|
40
|
+
* already-started children) if any child fails to come up.
|
|
41
|
+
*/
|
|
42
|
+
declare function startPrerenderServerPool(outDir: string, size: number, entry?: string): Promise<PrerenderServerPool>;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { PrerenderServerPool, prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { fork } from "node:child_process";
|
|
6
|
+
//#region src/build/prerender-server-pool.ts
|
|
7
|
+
/**
|
|
8
|
+
* Parallel prerender server pool.
|
|
9
|
+
*
|
|
10
|
+
* React SSR/RSC rendering during prerender is CPU-bound JS. vinext renders
|
|
11
|
+
* every route by fetching it from a single in-process production server, so the
|
|
12
|
+
* promise pool in `prerender.ts` only overlaps I/O — the actual rendering
|
|
13
|
+
* serializes on one core. This module forks a pool of OS processes (one vinext
|
|
14
|
+
* production server each, on its own ephemeral port) so the prerender phase can
|
|
15
|
+
* spread rendering across cores, mirroring Next.js's static worker pool. The
|
|
16
|
+
* caller load-balances per-route fetches across `ports`.
|
|
17
|
+
*
|
|
18
|
+
* child_process (not worker_threads) is deliberate: worker threads share the
|
|
19
|
+
* process and contend badly for CPU on this workload (measured ~2× slower
|
|
20
|
+
* per route and non-scaling), which is also why Next.js uses processes.
|
|
21
|
+
*/
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const WORKER_ENTRY = path.join(__dirname, "prerender-server-entry.js");
|
|
24
|
+
/**
|
|
25
|
+
* Whether the forkable worker entry exists as a runnable `.js` sibling. False
|
|
26
|
+
* when vinext runs from source (e.g. the test suite transpiles `.ts` on the
|
|
27
|
+
* fly and a forked plain-node child can't load `.ts`); callers fall back to the
|
|
28
|
+
* single in-process server. True in the published/built package.
|
|
29
|
+
*/
|
|
30
|
+
function prerenderPoolAvailable() {
|
|
31
|
+
return fs.existsSync(WORKER_ENTRY);
|
|
32
|
+
}
|
|
33
|
+
/** Hard cap on render processes. Beyond this the main thread (which collects
|
|
34
|
+
* HTML and writes files) becomes the bottleneck, so more workers don't help. */
|
|
35
|
+
const MAX_POOL_SIZE = 8;
|
|
36
|
+
/** Don't fork a dedicated process unless a worker gets at least this many
|
|
37
|
+
* routes — below this the fork + bundle-import startup cost outweighs the
|
|
38
|
+
* parallel-render saving (measured: a 2-worker pool only reliably beats the
|
|
39
|
+
* single server from ~50 routes/worker up). */
|
|
40
|
+
const MIN_ROUTES_PER_WORKER = 48;
|
|
41
|
+
/** Rough memory budget reserved for the main build process plus its in-process
|
|
42
|
+
* prerender server. */
|
|
43
|
+
const APPROX_MAIN_PROCESS_BYTES = 768 * 1024 * 1024;
|
|
44
|
+
/** Rough memory budget per worker (a worker loads the whole server bundle).
|
|
45
|
+
* Used with total system memory as a coarse upper bound on constrained machines
|
|
46
|
+
* so K render processes don't OOM. Deliberately under-forks rather than risking
|
|
47
|
+
* OOM; measured mid-size apps are usually lower (~260-380 MB/worker). */
|
|
48
|
+
const APPROX_BYTES_PER_WORKER = 768 * 1024 * 1024;
|
|
49
|
+
/** Per-worker readiness timeout. */
|
|
50
|
+
const WORKER_READY_TIMEOUT_MS = 6e4;
|
|
51
|
+
/** Best-effort close timeout so build cleanup is not blocked forever by a wedged child. */
|
|
52
|
+
const WORKER_CLOSE_TIMEOUT_MS = 5e3;
|
|
53
|
+
const PRERENDER_WORKER_TRANSPORT_ERROR_CODES = new Set([
|
|
54
|
+
"ECONNREFUSED",
|
|
55
|
+
"ECONNRESET",
|
|
56
|
+
"EPIPE",
|
|
57
|
+
"UND_ERR_SOCKET"
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Choose how many render processes to fork for `routeCount` routes.
|
|
61
|
+
*
|
|
62
|
+
* Returns 1 to mean "don't fork — render in-process as before" (small apps,
|
|
63
|
+
* single/dual-core, low memory, or an explicit `--prerender-concurrency 1`).
|
|
64
|
+
* `maxOverride` comes from `--prerender-concurrency`, which caps both the
|
|
65
|
+
* number of in-flight route fetches and the worker count (the default is capped
|
|
66
|
+
* at `min(cores, 8)`).
|
|
67
|
+
*/
|
|
68
|
+
function resolvePrerenderPoolSize(routeCount, maxOverride) {
|
|
69
|
+
const cores = Math.max(1, os.availableParallelism());
|
|
70
|
+
const byCores = Math.max(1, Math.min(cores - 1, MAX_POOL_SIZE));
|
|
71
|
+
const memoryForWorkers = Math.max(0, os.totalmem() - APPROX_MAIN_PROCESS_BYTES);
|
|
72
|
+
const byMemory = Math.max(1, Math.floor(memoryForWorkers / APPROX_BYTES_PER_WORKER));
|
|
73
|
+
const cap = maxOverride && maxOverride > 0 ? Math.min(maxOverride, byCores) : byCores;
|
|
74
|
+
const byRoutes = Math.floor(routeCount / MIN_ROUTES_PER_WORKER);
|
|
75
|
+
return Math.max(1, Math.min(cap, byMemory, byRoutes));
|
|
76
|
+
}
|
|
77
|
+
function getErrorCauseCode(err) {
|
|
78
|
+
if (!(err instanceof Error)) return void 0;
|
|
79
|
+
const cause = err.cause;
|
|
80
|
+
return typeof cause?.code === "string" ? cause.code : void 0;
|
|
81
|
+
}
|
|
82
|
+
function isWorkerTransportError(err) {
|
|
83
|
+
const code = getErrorCauseCode(err);
|
|
84
|
+
if (code && PRERENDER_WORKER_TRANSPORT_ERROR_CODES.has(code)) return true;
|
|
85
|
+
return err instanceof TypeError && err.message === "fetch failed";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Fork `size` production servers against `outDir` and resolve once all are
|
|
89
|
+
* listening. Each child reports its port over IPC. Rejects (and tears down any
|
|
90
|
+
* already-started children) if any child fails to come up.
|
|
91
|
+
*/
|
|
92
|
+
async function startPrerenderServerPool(outDir, size, entry = WORKER_ENTRY) {
|
|
93
|
+
const children = [];
|
|
94
|
+
let shuttingDown = false;
|
|
95
|
+
let crash = null;
|
|
96
|
+
let renderTransportError = null;
|
|
97
|
+
const close = async () => {
|
|
98
|
+
shuttingDown = true;
|
|
99
|
+
await Promise.all(children.map(closeChild));
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
const readies = Array.from({ length: size }, () => {
|
|
103
|
+
const child = fork(entry, [], {
|
|
104
|
+
cwd: process.cwd(),
|
|
105
|
+
env: {
|
|
106
|
+
...process.env,
|
|
107
|
+
VINEXT_PRERENDER: "1",
|
|
108
|
+
VINEXT_PRERENDER_OUTDIR: outDir
|
|
109
|
+
},
|
|
110
|
+
stdio: [
|
|
111
|
+
"ignore",
|
|
112
|
+
"inherit",
|
|
113
|
+
"inherit",
|
|
114
|
+
"ipc"
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
children.push(child);
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
reject(/* @__PURE__ */ new Error("[vinext] prerender render server did not start within 60s"));
|
|
121
|
+
}, WORKER_READY_TIMEOUT_MS);
|
|
122
|
+
const cleanupStartupListeners = () => {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
child.off("message", onMessage);
|
|
125
|
+
child.off("error", onError);
|
|
126
|
+
child.off("exit", onStartupExit);
|
|
127
|
+
};
|
|
128
|
+
const onMessage = (msg) => {
|
|
129
|
+
cleanupStartupListeners();
|
|
130
|
+
if (msg?.type === "ready" && typeof msg.port === "number") {
|
|
131
|
+
child.once("exit", (code, signal) => {
|
|
132
|
+
if (!shuttingDown && !crash) crash = {
|
|
133
|
+
port: msg.port,
|
|
134
|
+
code,
|
|
135
|
+
signal
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
resolve(msg.port);
|
|
139
|
+
} else reject(/* @__PURE__ */ new Error(`[vinext] prerender render server failed: ${msg?.error ?? "unknown"}`));
|
|
140
|
+
};
|
|
141
|
+
const onError = (err) => {
|
|
142
|
+
cleanupStartupListeners();
|
|
143
|
+
reject(err);
|
|
144
|
+
};
|
|
145
|
+
const onStartupExit = (code, signal) => {
|
|
146
|
+
if (code !== null || signal !== null) {
|
|
147
|
+
cleanupStartupListeners();
|
|
148
|
+
reject(/* @__PURE__ */ new Error(`[vinext] prerender render server exited during startup (code ${code}, signal ${signal})`));
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
child.once("message", onMessage);
|
|
152
|
+
child.once("error", onError);
|
|
153
|
+
child.once("exit", onStartupExit);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
const ports = await Promise.all(readies);
|
|
157
|
+
const recordRenderError = (err) => {
|
|
158
|
+
if (!renderTransportError && isWorkerTransportError(err)) renderTransportError = err instanceof Error ? err : new Error(String(err));
|
|
159
|
+
};
|
|
160
|
+
const assertHealthy = () => {
|
|
161
|
+
if (crash) throw new Error(`[vinext] A prerender render worker (port ${crash.port}) exited unexpectedly (code ${crash.code}, signal ${crash.signal}) during the build. Prerender output is incomplete; failing the build. This is often an out-of-memory kill — try a lower --prerender-concurrency.`);
|
|
162
|
+
if (renderTransportError) {
|
|
163
|
+
const causeCode = getErrorCauseCode(renderTransportError);
|
|
164
|
+
throw new Error(`[vinext] A prerender render worker request failed` + (causeCode ? ` (${causeCode})` : "") + " before the worker exit was observed. Prerender output is incomplete; failing the build. This is often an out-of-memory kill — try a lower --prerender-concurrency.");
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
ports,
|
|
169
|
+
assertHealthy,
|
|
170
|
+
recordRenderError,
|
|
171
|
+
close
|
|
172
|
+
};
|
|
173
|
+
} catch (err) {
|
|
174
|
+
await close();
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function closeChild(child) {
|
|
179
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
180
|
+
const exited = new Promise((resolve) => {
|
|
181
|
+
const timer = setTimeout(resolve, WORKER_CLOSE_TIMEOUT_MS);
|
|
182
|
+
timer.unref?.();
|
|
183
|
+
const finish = () => {
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
resolve();
|
|
186
|
+
};
|
|
187
|
+
child.once("exit", finish);
|
|
188
|
+
if (child.exitCode !== null || child.signalCode !== null) finish();
|
|
189
|
+
});
|
|
190
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
191
|
+
await exited;
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
export { prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool };
|
package/dist/build/prerender.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
|
|
2
|
-
import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
|
|
2
|
+
import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
|
|
3
|
+
import { VINEXT_PRERENDER_CACHE_LIFE_HEADER } from "../server/headers.js";
|
|
3
4
|
import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
|
|
4
5
|
import { NoOpCacheHandler, getCacheHandler, setCacheHandler } from "../shims/cache-handler.js";
|
|
5
6
|
import { headersContextFromRequest, runWithHeadersContext } from "../shims/headers.js";
|
|
@@ -14,6 +15,7 @@ import { readPrerenderSecret } from "./server-manifest.js";
|
|
|
14
15
|
import { createAppPprFallbackShells, markAppPprDynamicFallbackShellHtml } from "../server/app-ppr-fallback-shell.js";
|
|
15
16
|
import { getOutputPath, getRscOutputPath } from "../utils/prerender-output-paths.js";
|
|
16
17
|
import { startProdServer } from "../server/prod-server.js";
|
|
18
|
+
import { prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool } from "./prerender-server-pool.js";
|
|
17
19
|
import fs from "node:fs";
|
|
18
20
|
import path from "node:path";
|
|
19
21
|
import os from "node:os";
|
|
@@ -42,6 +44,14 @@ function isExperimentalPprFallbackShellGenerationEnabled(env = process.env) {
|
|
|
42
44
|
function getErrorMessageWithStack(err) {
|
|
43
45
|
return err.stack || err.message;
|
|
44
46
|
}
|
|
47
|
+
async function startOptionalPrerenderServerPool(outDir, poolSize) {
|
|
48
|
+
try {
|
|
49
|
+
return await startPrerenderServerPool(outDir, poolSize);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.warn(`[vinext] prerender render pool failed to start; falling back to single-process render: ${e instanceof Error ? e.message : String(e)}`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
45
55
|
var PrerenderUserFunctionError = class extends Error {};
|
|
46
56
|
function parsePrerenderEndpointError(text) {
|
|
47
57
|
try {
|
|
@@ -257,11 +267,12 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
|
|
|
257
267
|
const previousPrerenderFlag = process.env.VINEXT_PRERENDER;
|
|
258
268
|
process.env.VINEXT_PRERENDER = "1";
|
|
259
269
|
let ownedProdServerHandle = null;
|
|
270
|
+
let renderPool = null;
|
|
260
271
|
try {
|
|
261
272
|
let prerenderSecret = options._prerenderSecret;
|
|
262
273
|
if (!prerenderSecret && pagesBundlePath) prerenderSecret = readPrerenderSecret(path.dirname(pagesBundlePath));
|
|
263
274
|
if (!prerenderSecret) console.warn("[vinext] Warning: prerender secret not found. /__vinext/prerender/* endpoints will return 403 and dynamic routes will produce no paths. Run `vinext build` to regenerate the secret.");
|
|
264
|
-
const
|
|
275
|
+
const prodServer = options._prodServer ? options._prodServer : await (async () => {
|
|
265
276
|
const srv = await startProdServer({
|
|
266
277
|
port: 0,
|
|
267
278
|
host: "127.0.0.1",
|
|
@@ -271,12 +282,18 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
|
|
|
271
282
|
});
|
|
272
283
|
ownedProdServerHandle = srv;
|
|
273
284
|
return srv;
|
|
274
|
-
})()
|
|
285
|
+
})();
|
|
286
|
+
const baseUrl = `http://127.0.0.1:${prodServer.port}`;
|
|
275
287
|
const secretHeaders = prerenderSecret ? { [VINEXT_PRERENDER_SECRET_HEADER]: prerenderSecret } : {};
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
288
|
+
let renderPorts = [prodServer.port];
|
|
289
|
+
let renderRoundRobin = 0;
|
|
290
|
+
const renderPage = (urlPath) => {
|
|
291
|
+
const port = renderPorts[renderRoundRobin++ % renderPorts.length];
|
|
292
|
+
return fetch(`http://127.0.0.1:${port}${urlPath}`, {
|
|
293
|
+
headers: secretHeaders,
|
|
294
|
+
redirect: "manual"
|
|
295
|
+
});
|
|
296
|
+
};
|
|
280
297
|
const bundlePageRoutes = routes.map((r) => ({
|
|
281
298
|
pattern: r.pattern,
|
|
282
299
|
isDynamic: r.isDynamic ?? false,
|
|
@@ -397,6 +414,13 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
|
|
|
397
414
|
revalidate
|
|
398
415
|
});
|
|
399
416
|
}
|
|
417
|
+
if (!options._prodServer && pagesBundlePath && prerenderPoolAvailable()) {
|
|
418
|
+
const poolSize = resolvePrerenderPoolSize(pagesToRender.length, concurrency);
|
|
419
|
+
if (poolSize > 1) {
|
|
420
|
+
renderPool = await startOptionalPrerenderServerPool(path.dirname(path.dirname(pagesBundlePath)), poolSize);
|
|
421
|
+
if (renderPool) renderPorts = renderPool.ports;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
400
424
|
let completed = 0;
|
|
401
425
|
const pageResults = await runWithConcurrency(pagesToRender, concurrency, async ({ route, urlPath, revalidate }) => {
|
|
402
426
|
let result;
|
|
@@ -427,6 +451,7 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
|
|
|
427
451
|
...urlPath !== route.pattern ? { path: urlPath } : {}
|
|
428
452
|
};
|
|
429
453
|
} catch (e) {
|
|
454
|
+
renderPool?.recordRenderError(e);
|
|
430
455
|
const err = e;
|
|
431
456
|
result = {
|
|
432
457
|
route: route.pattern,
|
|
@@ -443,6 +468,7 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
|
|
|
443
468
|
return result;
|
|
444
469
|
});
|
|
445
470
|
results.push(...pageResults);
|
|
471
|
+
renderPool?.assertHealthy();
|
|
446
472
|
const hasCustom404 = findFileWithExtensions(path.join(pagesDir, "404"), fileMatcher);
|
|
447
473
|
const hasErrorPage = findFileWithExtensions(path.join(pagesDir, "_error"), fileMatcher);
|
|
448
474
|
if (hasCustom404 || hasErrorPage) try {
|
|
@@ -460,13 +486,17 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
|
|
|
460
486
|
router: "pages"
|
|
461
487
|
});
|
|
462
488
|
}
|
|
463
|
-
} catch {
|
|
489
|
+
} catch (e) {
|
|
490
|
+
renderPool?.recordRenderError(e);
|
|
491
|
+
}
|
|
492
|
+
renderPool?.assertHealthy();
|
|
464
493
|
if (!skipManifest) writePrerenderIndex(results, manifestDir, {
|
|
465
494
|
buildId: config.buildId,
|
|
466
495
|
trailingSlash: config.trailingSlash
|
|
467
496
|
});
|
|
468
497
|
return { routes: results };
|
|
469
498
|
} finally {
|
|
499
|
+
if (renderPool) await renderPool.close();
|
|
470
500
|
setCacheHandler(previousHandler);
|
|
471
501
|
if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
|
|
472
502
|
else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
|
|
@@ -505,10 +535,13 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
505
535
|
let rscHandler;
|
|
506
536
|
let staticParamsMap = {};
|
|
507
537
|
let ownedProdServerHandle = null;
|
|
538
|
+
let renderPool = null;
|
|
539
|
+
let renderPorts = [];
|
|
540
|
+
let renderRoundRobin = 0;
|
|
508
541
|
try {
|
|
509
542
|
const prerenderSecret = readPrerenderSecret(serverDir);
|
|
510
543
|
if (!prerenderSecret) console.warn("[vinext] Warning: prerender secret not found. /__vinext/prerender/* endpoints will return 403 and generateStaticParams will not be called. Run `vinext build` to regenerate the secret.");
|
|
511
|
-
const
|
|
544
|
+
const prodServer = options._prodServer ? options._prodServer : await (async () => {
|
|
512
545
|
const srv = await startProdServer({
|
|
513
546
|
port: 0,
|
|
514
547
|
host: "127.0.0.1",
|
|
@@ -518,11 +551,13 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
518
551
|
});
|
|
519
552
|
ownedProdServerHandle = srv;
|
|
520
553
|
return srv;
|
|
521
|
-
})()
|
|
554
|
+
})();
|
|
555
|
+
const baseUrl = `http://127.0.0.1:${prodServer.port}`;
|
|
556
|
+
renderPorts = [prodServer.port];
|
|
522
557
|
const secretHeaders = prerenderSecret ? { [VINEXT_PRERENDER_SECRET_HEADER]: prerenderSecret } : {};
|
|
523
558
|
rscHandler = (req) => {
|
|
524
559
|
const parsed = new URL(req.url);
|
|
525
|
-
const url =
|
|
560
|
+
const url = `http://127.0.0.1:${renderPorts[renderRoundRobin++ % renderPorts.length]}${parsed.pathname}${parsed.search}`;
|
|
526
561
|
return fetch(url, {
|
|
527
562
|
method: req.method,
|
|
528
563
|
headers: {
|
|
@@ -718,12 +753,14 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
718
753
|
const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParams);
|
|
719
754
|
const htmlHeaders = new Headers();
|
|
720
755
|
if (prerenderRouteParamsHeader !== null) htmlHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
|
|
756
|
+
if (isSpeculative) htmlHeaders.set(VINEXT_PRERENDER_SPECULATIVE_HEADER, "1");
|
|
721
757
|
const htmlRequest = new Request(`http://localhost${urlPath}`, { headers: htmlHeaders });
|
|
722
758
|
const htmlRender = await runWithHeadersContext(headersContextFromRequest(htmlRequest), async () => {
|
|
723
759
|
const response = await rscHandler(htmlRequest);
|
|
724
760
|
const cacheControl = response.headers.get("cache-control") ?? "";
|
|
725
761
|
const linkHeader = response.headers.get("link");
|
|
726
|
-
|
|
762
|
+
const responseCacheLife = readPrerenderCacheLifeHeader(response.headers);
|
|
763
|
+
if (!response.ok || cacheControl.includes("no-store")) {
|
|
727
764
|
await response.body?.cancel();
|
|
728
765
|
return {
|
|
729
766
|
cacheControl,
|
|
@@ -734,12 +771,14 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
734
771
|
status: response.status
|
|
735
772
|
};
|
|
736
773
|
}
|
|
774
|
+
const html = await response.text();
|
|
775
|
+
const processCacheLife = _consumeRequestScopedCacheLife();
|
|
737
776
|
return {
|
|
738
777
|
cacheControl,
|
|
739
778
|
linkHeader,
|
|
740
|
-
html
|
|
779
|
+
html,
|
|
741
780
|
ok: true,
|
|
742
|
-
requestCacheLife:
|
|
781
|
+
requestCacheLife: responseCacheLife ?? processCacheLife,
|
|
743
782
|
status: response.status
|
|
744
783
|
};
|
|
745
784
|
});
|
|
@@ -756,13 +795,11 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
756
795
|
error: `RSC handler returned ${htmlRender.status}`
|
|
757
796
|
};
|
|
758
797
|
}
|
|
759
|
-
if (
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
};
|
|
765
|
-
}
|
|
798
|
+
if (htmlCacheControl.includes("no-store")) return {
|
|
799
|
+
route: routePattern,
|
|
800
|
+
status: "skipped",
|
|
801
|
+
reason: "dynamic"
|
|
802
|
+
};
|
|
766
803
|
if (htmlRender.html === null) return {
|
|
767
804
|
route: routePattern,
|
|
768
805
|
status: "error",
|
|
@@ -776,6 +813,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
776
813
|
RSC: "1"
|
|
777
814
|
});
|
|
778
815
|
if (prerenderRouteParamsHeader !== null) rscHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
|
|
816
|
+
if (isSpeculative) rscHeaders.set(VINEXT_PRERENDER_SPECULATIVE_HEADER, "1");
|
|
779
817
|
const rscRequest = new Request(`http://localhost${urlPath}`, { headers: rscHeaders });
|
|
780
818
|
const rscRes = await runWithHeadersContext(headersContextFromRequest(rscRequest), () => rscHandler(rscRequest));
|
|
781
819
|
if (!rscRes.ok) {
|
|
@@ -809,6 +847,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
809
847
|
...isFallback ? { fallback: true } : {}
|
|
810
848
|
};
|
|
811
849
|
} catch (e) {
|
|
850
|
+
renderPool?.recordRenderError(e);
|
|
812
851
|
if (isSpeculative) return {
|
|
813
852
|
route: routePattern,
|
|
814
853
|
status: "skipped",
|
|
@@ -823,6 +862,13 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
823
862
|
};
|
|
824
863
|
}
|
|
825
864
|
}
|
|
865
|
+
if (!options._prodServer && prerenderPoolAvailable()) {
|
|
866
|
+
const poolSize = resolvePrerenderPoolSize(urlsToRender.length, concurrency);
|
|
867
|
+
if (poolSize > 1) {
|
|
868
|
+
renderPool = await startOptionalPrerenderServerPool(path.dirname(serverDir), poolSize);
|
|
869
|
+
if (renderPool) renderPorts = renderPool.ports;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
826
872
|
let completedApp = 0;
|
|
827
873
|
const appResults = await runWithConcurrency(urlsToRender, concurrency, async (urlToRender) => {
|
|
828
874
|
const result = await renderUrl(urlToRender);
|
|
@@ -835,6 +881,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
835
881
|
return result;
|
|
836
882
|
});
|
|
837
883
|
results.push(...appResults);
|
|
884
|
+
renderPool?.assertHealthy();
|
|
838
885
|
const outputFiles = mode === "export" && metadataRoutes.length > 0 ? emitStaticMetadataFiles(metadataRoutes, outDir) : [];
|
|
839
886
|
try {
|
|
840
887
|
const notFoundRequest = new Request(`http://localhost${NOT_FOUND_SENTINEL_PATH}`);
|
|
@@ -852,7 +899,10 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
852
899
|
router: "app"
|
|
853
900
|
});
|
|
854
901
|
}
|
|
855
|
-
} catch {
|
|
902
|
+
} catch (e) {
|
|
903
|
+
renderPool?.recordRenderError(e);
|
|
904
|
+
}
|
|
905
|
+
renderPool?.assertHealthy();
|
|
856
906
|
if (!skipManifest) writePrerenderIndex(results, manifestDir, {
|
|
857
907
|
buildId: config.buildId,
|
|
858
908
|
trailingSlash: config.trailingSlash
|
|
@@ -862,6 +912,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
|
|
|
862
912
|
...outputFiles.length > 0 ? { outputFiles } : {}
|
|
863
913
|
};
|
|
864
914
|
} finally {
|
|
915
|
+
if (renderPool) await renderPool.close();
|
|
865
916
|
setCacheHandler(previousHandler);
|
|
866
917
|
if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
|
|
867
918
|
else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
|
|
@@ -881,6 +932,19 @@ function resolveRenderedCacheControl(requestCacheLife, cacheControl, fallbackExp
|
|
|
881
932
|
...revalidate === void 0 ? {} : { revalidate }
|
|
882
933
|
};
|
|
883
934
|
}
|
|
935
|
+
function readPrerenderCacheLifeHeader(headers) {
|
|
936
|
+
const value = headers.get(VINEXT_PRERENDER_CACHE_LIFE_HEADER);
|
|
937
|
+
if (!value) return null;
|
|
938
|
+
try {
|
|
939
|
+
const parsed = JSON.parse(value);
|
|
940
|
+
const cacheLife = {};
|
|
941
|
+
if (typeof parsed.revalidate === "number" && Number.isFinite(parsed.revalidate)) cacheLife.revalidate = parsed.revalidate;
|
|
942
|
+
if (typeof parsed.expire === "number" && Number.isFinite(parsed.expire)) cacheLife.expire = parsed.expire;
|
|
943
|
+
return cacheLife.revalidate === void 0 && cacheLife.expire === void 0 ? null : cacheLife;
|
|
944
|
+
} catch {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
884
948
|
function resolveRenderedExpireSeconds(options) {
|
|
885
949
|
const { fallbackExpireSeconds, sMaxage, staleWhileRevalidate } = options;
|
|
886
950
|
if (sMaxage === void 0 || staleWhileRevalidate === void 0) return fallbackExpireSeconds;
|
|
@@ -4,7 +4,7 @@ import { appRouter } from "../routing/app-router.js";
|
|
|
4
4
|
import { scanMetadataFiles } from "../server/metadata-routes.js";
|
|
5
5
|
import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
|
|
6
6
|
import { readPrerenderSecret } from "./server-manifest.js";
|
|
7
|
-
import { startProdServer } from "../server/prod-server.js";
|
|
7
|
+
import { rememberCurrentServerEntryImportMtime, startProdServer } from "../server/prod-server.js";
|
|
8
8
|
import { prerenderApp, prerenderPages, writePrerenderIndex } from "./prerender.js";
|
|
9
9
|
import { injectPregeneratedConcretePaths } from "./inject-pregenerated-paths.js";
|
|
10
10
|
import fs from "node:fs";
|
|
@@ -224,6 +224,7 @@ async function runPrerender(options) {
|
|
|
224
224
|
throw new Error(`Static export failed: ${errors} route${errors !== 1 ? "s" : ""} cannot be statically exported.\n${errorRoutes}\n\nRemove server-side data fetching (getServerSideProps, force-dynamic, revalidate) from these routes, or remove \`output: "export"\` from next.config.js.`);
|
|
225
225
|
}
|
|
226
226
|
injectPregeneratedConcretePaths(root);
|
|
227
|
+
if (fs.existsSync(rscBundlePath)) rememberCurrentServerEntryImportMtime(rscBundlePath);
|
|
227
228
|
return {
|
|
228
229
|
routes: allRoutes,
|
|
229
230
|
...allOutputFiles.length > 0 ? { outputFiles: allOutputFiles } : {}
|