vinext 0.0.27 → 0.0.29
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/report.d.ts +117 -0
- package/dist/build/report.d.ts.map +1 -0
- package/dist/build/report.js +303 -0
- package/dist/build/report.js.map +1 -0
- package/dist/build/static-export.d.ts +1 -1
- package/dist/build/static-export.d.ts.map +1 -1
- package/dist/build/static-export.js +2 -1
- package/dist/build/static-export.js.map +1 -1
- package/dist/cli.js +106 -9
- package/dist/cli.js.map +1 -1
- package/dist/cloudflare/kv-cache-handler.d.ts +28 -17
- package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
- package/dist/cloudflare/kv-cache-handler.js +109 -42
- package/dist/cloudflare/kv-cache-handler.js.map +1 -1
- package/dist/cloudflare/tpr.d.ts +10 -0
- package/dist/cloudflare/tpr.d.ts.map +1 -1
- package/dist/cloudflare/tpr.js +36 -41
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.d.ts +1 -0
- package/dist/config/config-matchers.d.ts.map +1 -1
- package/dist/config/config-matchers.js +51 -23
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts.map +1 -1
- package/dist/config/next-config.js +16 -0
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.d.ts +1 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +48 -32
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +3 -1
- package/dist/entries/app-rsc-entry.d.ts.map +1 -1
- package/dist/entries/app-rsc-entry.js +514 -99
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.d.ts.map +1 -1
- package/dist/entries/pages-server-entry.js +154 -58
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.d.ts +40 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +239 -79
- package/dist/index.js.map +1 -1
- package/dist/plugins/client-reference-dedup.d.ts +19 -0
- package/dist/plugins/client-reference-dedup.d.ts.map +1 -0
- package/dist/plugins/client-reference-dedup.js +96 -0
- package/dist/plugins/client-reference-dedup.js.map +1 -0
- package/dist/routing/app-router.d.ts +2 -0
- package/dist/routing/app-router.d.ts.map +1 -1
- package/dist/routing/app-router.js +145 -161
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/pages-router.d.ts +1 -1
- package/dist/routing/pages-router.d.ts.map +1 -1
- package/dist/routing/pages-router.js +37 -65
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/routing/route-trie.d.ts +57 -0
- package/dist/routing/route-trie.d.ts.map +1 -0
- package/dist/routing/route-trie.js +160 -0
- package/dist/routing/route-trie.js.map +1 -0
- package/dist/routing/route-validation.d.ts +8 -0
- package/dist/routing/route-validation.d.ts.map +1 -0
- package/dist/routing/route-validation.js +136 -0
- package/dist/routing/route-validation.js.map +1 -0
- package/dist/routing/utils.d.ts +19 -0
- package/dist/routing/utils.d.ts.map +1 -1
- package/dist/routing/utils.js +47 -0
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.d.ts.map +1 -1
- package/dist/server/api-handler.js +52 -20
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/dev-server.d.ts.map +1 -1
- package/dist/server/dev-server.js +67 -9
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/image-optimization.d.ts.map +1 -1
- package/dist/server/image-optimization.js +1 -1
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/instrumentation.js +17 -8
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/isr-cache.d.ts +5 -13
- package/dist/server/isr-cache.d.ts.map +1 -1
- package/dist/server/isr-cache.js +13 -12
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-routes.d.ts +8 -2
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/metadata-routes.js +73 -28
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware-codegen.d.ts +11 -1
- package/dist/server/middleware-codegen.d.ts.map +1 -1
- package/dist/server/middleware-codegen.js +204 -12
- package/dist/server/middleware-codegen.js.map +1 -1
- package/dist/server/middleware.d.ts +9 -8
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +76 -14
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/prod-server.d.ts +8 -2
- package/dist/server/prod-server.d.ts.map +1 -1
- package/dist/server/prod-server.js +144 -74
- package/dist/server/prod-server.js.map +1 -1
- package/dist/shims/cache.d.ts +2 -0
- package/dist/shims/cache.d.ts.map +1 -1
- package/dist/shims/cache.js +20 -8
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts.map +1 -1
- package/dist/shims/fetch-cache.js +5 -2
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/form.d.ts.map +1 -1
- package/dist/shims/form.js +103 -8
- package/dist/shims/form.js.map +1 -1
- package/dist/shims/headers.d.ts +11 -3
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/headers.js +182 -30
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/internal/parse-cookie-header.d.ts +12 -0
- package/dist/shims/internal/parse-cookie-header.d.ts.map +1 -0
- package/dist/shims/internal/parse-cookie-header.js +32 -0
- package/dist/shims/internal/parse-cookie-header.js.map +1 -0
- package/dist/shims/link.d.ts +2 -1
- package/dist/shims/link.d.ts.map +1 -1
- package/dist/shims/link.js +19 -45
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +56 -0
- package/dist/shims/metadata.d.ts.map +1 -1
- package/dist/shims/metadata.js +66 -0
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +5 -7
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/shims/navigation.js +61 -39
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/readonly-url-search-params.d.ts +11 -0
- package/dist/shims/readonly-url-search-params.d.ts.map +1 -0
- package/dist/shims/readonly-url-search-params.js +24 -0
- package/dist/shims/readonly-url-search-params.js.map +1 -0
- package/dist/shims/router.d.ts +4 -3
- package/dist/shims/router.d.ts.map +1 -1
- package/dist/shims/router.js +55 -48
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/server.d.ts +1 -1
- package/dist/shims/server.d.ts.map +1 -1
- package/dist/shims/server.js +7 -13
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/url-utils.d.ts +20 -6
- package/dist/shims/url-utils.d.ts.map +1 -1
- package/dist/shims/url-utils.js +79 -0
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/utils/manifest-paths.d.ts +4 -0
- package/dist/utils/manifest-paths.d.ts.map +1 -0
- package/dist/utils/manifest-paths.js +20 -0
- package/dist/utils/manifest-paths.js.map +1 -0
- package/dist/utils/query.d.ts +9 -0
- package/dist/utils/query.d.ts.map +1 -1
- package/dist/utils/query.js +59 -9
- package/dist/utils/query.js.map +1 -1
- package/package.json +2 -2
|
@@ -28,6 +28,8 @@ import { IMAGE_OPTIMIZATION_PATH, IMAGE_CONTENT_SECURITY_POLICY, parseImageParam
|
|
|
28
28
|
import { normalizePath } from "./normalize-path.js";
|
|
29
29
|
import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
30
30
|
import { computeLazyChunks } from "../index.js";
|
|
31
|
+
import { manifestFileWithBase } from "../utils/manifest-paths.js";
|
|
32
|
+
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
31
33
|
/** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
|
|
32
34
|
function readNodeStream(req) {
|
|
33
35
|
return new ReadableStream({
|
|
@@ -118,10 +120,18 @@ function mergeResponseHeaders(middlewareHeaders, response) {
|
|
|
118
120
|
* Send a compressed response if the content type is compressible and the
|
|
119
121
|
* client supports compression. Otherwise send uncompressed.
|
|
120
122
|
*/
|
|
121
|
-
function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true) {
|
|
123
|
+
function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true, statusText = undefined) {
|
|
122
124
|
const buf = typeof body === "string" ? Buffer.from(body) : body;
|
|
123
125
|
const baseType = contentType.split(";")[0].trim();
|
|
124
126
|
const encoding = compress ? negotiateEncoding(req) : null;
|
|
127
|
+
const writeHead = (headers) => {
|
|
128
|
+
if (statusText) {
|
|
129
|
+
res.writeHead(statusCode, statusText, headers);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
res.writeHead(statusCode, headers);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
125
135
|
if (encoding && COMPRESSIBLE_TYPES.has(baseType) && buf.length >= COMPRESS_THRESHOLD) {
|
|
126
136
|
const compressor = createCompressor(encoding);
|
|
127
137
|
// Merge Accept-Encoding into existing Vary header from extraHeaders instead
|
|
@@ -139,7 +149,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
139
149
|
else {
|
|
140
150
|
varyValue = "Accept-Encoding";
|
|
141
151
|
}
|
|
142
|
-
|
|
152
|
+
writeHead({
|
|
143
153
|
...extraHeaders,
|
|
144
154
|
"Content-Type": contentType,
|
|
145
155
|
"Content-Encoding": encoding,
|
|
@@ -151,8 +161,11 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
151
161
|
});
|
|
152
162
|
}
|
|
153
163
|
else {
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
// Strip any pre-existing content-length (from the Web Response constructor)
|
|
165
|
+
// before setting our own — avoids duplicate Content-Length headers.
|
|
166
|
+
const { "content-length": _cl, "Content-Length": _CL, ...headersWithoutLength } = extraHeaders;
|
|
167
|
+
writeHead({
|
|
168
|
+
...headersWithoutLength,
|
|
156
169
|
"Content-Type": contentType,
|
|
157
170
|
"Content-Length": String(buf.length),
|
|
158
171
|
});
|
|
@@ -276,15 +289,21 @@ const trustedHosts = new Set((process.env.VINEXT_TRUSTED_HOSTS ?? "")
|
|
|
276
289
|
const trustProxy = process.env.VINEXT_TRUST_PROXY === "1" || trustedHosts.size > 0;
|
|
277
290
|
/**
|
|
278
291
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
292
|
+
*
|
|
293
|
+
* When `urlOverride` is provided, it is used as the path + query string
|
|
294
|
+
* instead of `req.url`. This avoids redundant path normalization when the
|
|
295
|
+
* caller has already decoded and normalized the pathname (e.g. the App
|
|
296
|
+
* Router prod server normalizes before static-asset lookup, and can pass
|
|
297
|
+
* the result here so the downstream RSC handler doesn't re-normalize).
|
|
279
298
|
*/
|
|
280
|
-
function nodeToWebRequest(req) {
|
|
299
|
+
function nodeToWebRequest(req, urlOverride) {
|
|
281
300
|
const rawProto = trustProxy
|
|
282
301
|
? req.headers["x-forwarded-proto"]?.split(",")[0]?.trim()
|
|
283
302
|
: undefined;
|
|
284
303
|
const proto = rawProto === "https" || rawProto === "http" ? rawProto : "http";
|
|
285
304
|
const host = resolveHost(req, "localhost");
|
|
286
305
|
const origin = `${proto}://${host}`;
|
|
287
|
-
const url = new URL(req.url ?? "/", origin);
|
|
306
|
+
const url = new URL(urlOverride ?? req.url ?? "/", origin);
|
|
288
307
|
const headers = new Headers();
|
|
289
308
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
290
309
|
if (value === undefined)
|
|
@@ -317,6 +336,15 @@ function nodeToWebRequest(req) {
|
|
|
317
336
|
*/
|
|
318
337
|
async function sendWebResponse(webResponse, req, res, compress) {
|
|
319
338
|
const status = webResponse.status;
|
|
339
|
+
const statusText = webResponse.statusText || undefined;
|
|
340
|
+
const writeHead = (headers) => {
|
|
341
|
+
if (statusText) {
|
|
342
|
+
res.writeHead(status, statusText, headers);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
res.writeHead(status, headers);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
320
348
|
// Collect headers, handling multi-value headers (e.g. Set-Cookie)
|
|
321
349
|
const nodeHeaders = {};
|
|
322
350
|
webResponse.headers.forEach((value, key) => {
|
|
@@ -329,7 +357,7 @@ async function sendWebResponse(webResponse, req, res, compress) {
|
|
|
329
357
|
}
|
|
330
358
|
});
|
|
331
359
|
if (!webResponse.body) {
|
|
332
|
-
|
|
360
|
+
writeHead(nodeHeaders);
|
|
333
361
|
res.end();
|
|
334
362
|
return;
|
|
335
363
|
}
|
|
@@ -358,7 +386,7 @@ async function sendWebResponse(webResponse, req, res, compress) {
|
|
|
358
386
|
nodeHeaders["Vary"] = "Accept-Encoding";
|
|
359
387
|
}
|
|
360
388
|
}
|
|
361
|
-
|
|
389
|
+
writeHead(nodeHeaders);
|
|
362
390
|
// HEAD requests: send headers only, skip the body
|
|
363
391
|
if (req.method === "HEAD") {
|
|
364
392
|
res.end();
|
|
@@ -405,11 +433,36 @@ export async function startProdServer(options = {}) {
|
|
|
405
433
|
}
|
|
406
434
|
return startPagesRouterServer({ port, host, clientDir, serverEntryPath, compress });
|
|
407
435
|
}
|
|
436
|
+
function createNodeExecutionContext() {
|
|
437
|
+
return {
|
|
438
|
+
waitUntil(promise) {
|
|
439
|
+
// Node doesn't provide a Workers lifecycle, but we still attach a
|
|
440
|
+
// rejection handler so background waitUntil work doesn't surface as an
|
|
441
|
+
// unhandled rejection when a Worker-style entry is used with vinext start.
|
|
442
|
+
void Promise.resolve(promise).catch(() => { });
|
|
443
|
+
},
|
|
444
|
+
passThroughOnException() { },
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function resolveAppRouterHandler(entry) {
|
|
448
|
+
if (typeof entry === "function") {
|
|
449
|
+
return (request) => Promise.resolve(entry(request));
|
|
450
|
+
}
|
|
451
|
+
if (entry && typeof entry === "object" && "fetch" in entry) {
|
|
452
|
+
const workerEntry = entry;
|
|
453
|
+
if (typeof workerEntry.fetch === "function") {
|
|
454
|
+
return (request) => Promise.resolve(workerEntry.fetch(request, undefined, createNodeExecutionContext()));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
console.error("[vinext] App Router entry must export either a default handler function or a Worker-style default export with fetch()");
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
408
460
|
/**
|
|
409
461
|
* Start the App Router production server.
|
|
410
462
|
*
|
|
411
|
-
* The
|
|
412
|
-
* handler(request: Request) → Promise<Response>
|
|
463
|
+
* The App Router entry (dist/server/index.js) can export either:
|
|
464
|
+
* - a default handler function: handler(request: Request) → Promise<Response>
|
|
465
|
+
* - a Worker-style object: { fetch(request, env, ctx) → Promise<Response> }
|
|
413
466
|
*
|
|
414
467
|
* This handler already does everything: route matching, RSC rendering,
|
|
415
468
|
* SSR HTML generation (via import("./ssr/index.js")), route handlers,
|
|
@@ -437,18 +490,14 @@ async function startAppRouterServer(options) {
|
|
|
437
490
|
}
|
|
438
491
|
// Import the RSC handler (use file:// URL for reliable dynamic import)
|
|
439
492
|
const rscModule = await import(pathToFileURL(rscEntryPath).href);
|
|
440
|
-
const rscHandler = rscModule.default;
|
|
441
|
-
if (typeof rscHandler !== "function") {
|
|
442
|
-
console.error("[vinext] RSC entry does not export a default handler function");
|
|
443
|
-
process.exit(1);
|
|
444
|
-
}
|
|
493
|
+
const rscHandler = resolveAppRouterHandler(rscModule.default);
|
|
445
494
|
const server = createServer(async (req, res) => {
|
|
446
|
-
const
|
|
495
|
+
const rawUrl = req.url ?? "/";
|
|
447
496
|
// Normalize backslashes (browsers treat /\ as //), then decode and normalize path.
|
|
448
|
-
const rawPathname =
|
|
497
|
+
const rawPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
|
|
449
498
|
let pathname;
|
|
450
499
|
try {
|
|
451
|
-
pathname = normalizePath(
|
|
500
|
+
pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPathname));
|
|
452
501
|
}
|
|
453
502
|
catch {
|
|
454
503
|
// Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of crashing.
|
|
@@ -470,7 +519,7 @@ async function startAppRouterServer(options) {
|
|
|
470
519
|
// Image optimization passthrough (Node.js prod server has no Images binding;
|
|
471
520
|
// serves the original file with cache headers and security headers)
|
|
472
521
|
if (pathname === IMAGE_OPTIMIZATION_PATH) {
|
|
473
|
-
const parsedUrl = new URL(
|
|
522
|
+
const parsedUrl = new URL(rawUrl, "http://localhost");
|
|
474
523
|
const defaultAllowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
|
|
475
524
|
const params = parseImageParams(parsedUrl, defaultAllowedWidths);
|
|
476
525
|
if (!params) {
|
|
@@ -491,7 +540,7 @@ async function startAppRouterServer(options) {
|
|
|
491
540
|
const imageSecurityHeaders = {
|
|
492
541
|
"Content-Security-Policy": imageConfig?.contentSecurityPolicy ?? IMAGE_CONTENT_SECURITY_POLICY,
|
|
493
542
|
"X-Content-Type-Options": "nosniff",
|
|
494
|
-
"Content-Disposition": imageConfig?.contentDispositionType
|
|
543
|
+
"Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline",
|
|
495
544
|
};
|
|
496
545
|
if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) {
|
|
497
546
|
return;
|
|
@@ -501,8 +550,13 @@ async function startAppRouterServer(options) {
|
|
|
501
550
|
return;
|
|
502
551
|
}
|
|
503
552
|
try {
|
|
553
|
+
// Build the normalized URL (pathname + original query string) so the
|
|
554
|
+
// RSC handler receives an already-canonical path and doesn't need to
|
|
555
|
+
// re-normalize. This deduplicates the normalizePath work done above.
|
|
556
|
+
const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
557
|
+
const normalizedUrl = pathname + qs;
|
|
504
558
|
// Convert Node.js request to Web Request and call the RSC handler
|
|
505
|
-
const request = nodeToWebRequest(req);
|
|
559
|
+
const request = nodeToWebRequest(req, normalizedUrl);
|
|
506
560
|
const response = await rscHandler(request);
|
|
507
561
|
// Stream the Web Response back to the Node.js response
|
|
508
562
|
await sendWebResponse(response, req, res, compress);
|
|
@@ -536,33 +590,12 @@ async function startAppRouterServer(options) {
|
|
|
536
590
|
*/
|
|
537
591
|
async function startPagesRouterServer(options) {
|
|
538
592
|
const { port, host, clientDir, serverEntryPath, compress } = options;
|
|
539
|
-
// Load the SSR manifest (maps module URLs to client asset URLs)
|
|
540
|
-
let ssrManifest = {};
|
|
541
|
-
const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
|
|
542
|
-
if (fs.existsSync(manifestPath)) {
|
|
543
|
-
ssrManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
544
|
-
}
|
|
545
|
-
// Load the build manifest to compute lazy chunks — chunks only reachable via
|
|
546
|
-
// dynamic imports (React.lazy, next/dynamic). These should not be
|
|
547
|
-
// modulepreloaded since they are fetched on demand.
|
|
548
|
-
const buildManifestPath = path.join(clientDir, ".vite", "manifest.json");
|
|
549
|
-
if (fs.existsSync(buildManifestPath)) {
|
|
550
|
-
try {
|
|
551
|
-
const buildManifest = JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"));
|
|
552
|
-
const lazyChunks = computeLazyChunks(buildManifest);
|
|
553
|
-
if (lazyChunks.length > 0) {
|
|
554
|
-
globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
catch {
|
|
558
|
-
/* ignore parse errors */
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
593
|
// Import the server entry module (use file:// URL for reliable dynamic import)
|
|
562
594
|
const serverEntry = await import(pathToFileURL(serverEntryPath).href);
|
|
563
595
|
const { renderPage, handleApiRoute: handleApi, runMiddleware, vinextConfig } = serverEntry;
|
|
564
596
|
// Extract config values (embedded at build time in the server entry)
|
|
565
597
|
const basePath = vinextConfig?.basePath ?? "";
|
|
598
|
+
const assetBase = basePath ? `${basePath}/` : "/";
|
|
566
599
|
const trailingSlash = vinextConfig?.trailingSlash ?? false;
|
|
567
600
|
const configRedirects = vinextConfig?.redirects ?? [];
|
|
568
601
|
const configRewrites = vinextConfig?.rewrites ?? {
|
|
@@ -584,6 +617,28 @@ async function startPagesRouterServer(options) {
|
|
|
584
617
|
contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy,
|
|
585
618
|
}
|
|
586
619
|
: undefined;
|
|
620
|
+
// Load the SSR manifest (maps module URLs to client asset URLs)
|
|
621
|
+
let ssrManifest = {};
|
|
622
|
+
const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
|
|
623
|
+
if (fs.existsSync(manifestPath)) {
|
|
624
|
+
ssrManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
625
|
+
}
|
|
626
|
+
// Load the build manifest to compute lazy chunks — chunks only reachable via
|
|
627
|
+
// dynamic imports (React.lazy, next/dynamic). These should not be
|
|
628
|
+
// modulepreloaded since they are fetched on demand.
|
|
629
|
+
const buildManifestPath = path.join(clientDir, ".vite", "manifest.json");
|
|
630
|
+
if (fs.existsSync(buildManifestPath)) {
|
|
631
|
+
try {
|
|
632
|
+
const buildManifest = JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"));
|
|
633
|
+
const lazyChunks = computeLazyChunks(buildManifest).map((file) => manifestFileWithBase(file, assetBase));
|
|
634
|
+
if (lazyChunks.length > 0) {
|
|
635
|
+
globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
/* ignore parse errors */
|
|
640
|
+
}
|
|
641
|
+
}
|
|
587
642
|
const server = createServer(async (req, res) => {
|
|
588
643
|
const rawUrl = req.url ?? "/";
|
|
589
644
|
// Normalize backslashes (browsers treat /\ as //), then decode and normalize path.
|
|
@@ -594,7 +649,7 @@ async function startPagesRouterServer(options) {
|
|
|
594
649
|
const rawQs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
595
650
|
let pathname;
|
|
596
651
|
try {
|
|
597
|
-
pathname = normalizePath(
|
|
652
|
+
pathname = normalizePath(normalizePathnameForRouteMatchStrict(rawPagesPathname));
|
|
598
653
|
}
|
|
599
654
|
catch {
|
|
600
655
|
// Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of crashing.
|
|
@@ -641,7 +696,7 @@ async function startPagesRouterServer(options) {
|
|
|
641
696
|
const imageSecurityHeaders = {
|
|
642
697
|
"Content-Security-Policy": pagesImageConfig?.contentSecurityPolicy ?? IMAGE_CONTENT_SECURITY_POLICY,
|
|
643
698
|
"X-Content-Type-Options": "nosniff",
|
|
644
|
-
"Content-Disposition": pagesImageConfig?.contentDispositionType
|
|
699
|
+
"Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline",
|
|
645
700
|
};
|
|
646
701
|
if (tryServeStatic(req, res, clientDir, params.imageUrl, false, imageSecurityHeaders)) {
|
|
647
702
|
return;
|
|
@@ -696,12 +751,31 @@ async function startPagesRouterServer(options) {
|
|
|
696
751
|
// @ts-expect-error — duplex needed for streaming request bodies
|
|
697
752
|
duplex: hasBody ? "half" : undefined,
|
|
698
753
|
});
|
|
699
|
-
// Build request context for
|
|
700
|
-
//
|
|
701
|
-
// snapshot
|
|
702
|
-
//
|
|
754
|
+
// Build request context for pre-middleware config matching. Redirects
|
|
755
|
+
// run before middleware in Next.js. Header match conditions also use the
|
|
756
|
+
// original request snapshot even though header merging happens later so
|
|
757
|
+
// middleware response headers can still take precedence.
|
|
758
|
+
// beforeFiles, afterFiles, and fallback all run after middleware per the
|
|
759
|
+
// Next.js execution order, so they use postMwReqCtx below.
|
|
703
760
|
const reqCtx = requestContextFromRequest(webRequest);
|
|
704
|
-
// ── 4.
|
|
761
|
+
// ── 4. Apply redirects from next.config.js ────────────────────
|
|
762
|
+
if (configRedirects.length) {
|
|
763
|
+
const redirect = matchRedirect(pathname, configRedirects, reqCtx);
|
|
764
|
+
if (redirect) {
|
|
765
|
+
// Guard against double-prefixing: only add basePath if destination
|
|
766
|
+
// doesn't already start with it.
|
|
767
|
+
// Sanitize the final destination to prevent protocol-relative URL open redirects.
|
|
768
|
+
const dest = sanitizeDestination(basePath &&
|
|
769
|
+
!isExternalUrl(redirect.destination) &&
|
|
770
|
+
!hasBasePath(redirect.destination, basePath)
|
|
771
|
+
? basePath + redirect.destination
|
|
772
|
+
: redirect.destination);
|
|
773
|
+
res.writeHead(redirect.permanent ? 308 : 307, { Location: dest });
|
|
774
|
+
res.end();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// ── 5. Run middleware ─────────────────────────────────────────
|
|
705
779
|
let resolvedUrl = url;
|
|
706
780
|
const middlewareHeaders = {};
|
|
707
781
|
let middlewareRewriteStatus;
|
|
@@ -744,7 +818,12 @@ async function startPagesRouterServer(options) {
|
|
|
744
818
|
const setCookies = result.response.headers.getSetCookie?.() ?? [];
|
|
745
819
|
if (setCookies.length > 0)
|
|
746
820
|
respHeaders["set-cookie"] = setCookies;
|
|
747
|
-
|
|
821
|
+
if (result.response.statusText) {
|
|
822
|
+
res.writeHead(result.response.status, result.response.statusText, respHeaders);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
res.writeHead(result.response.status, respHeaders);
|
|
826
|
+
}
|
|
748
827
|
res.end(body);
|
|
749
828
|
return;
|
|
750
829
|
}
|
|
@@ -784,15 +863,17 @@ async function startPagesRouterServer(options) {
|
|
|
784
863
|
// middleware per the Next.js execution order).
|
|
785
864
|
const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, webRequest);
|
|
786
865
|
webRequest = postMwReq;
|
|
866
|
+
// Config header matching must keep using the original normalized pathname
|
|
867
|
+
// even if middleware rewrites the downstream route/render target.
|
|
787
868
|
let resolvedPathname = resolvedUrl.split("?")[0];
|
|
788
|
-
// ──
|
|
869
|
+
// ── 6. Apply custom headers from next.config.js ───────────────
|
|
789
870
|
// Config headers are additive for multi-value headers (Vary,
|
|
790
871
|
// Set-Cookie) and override for everything else. Set-Cookie values
|
|
791
872
|
// are stored as arrays (RFC 6265 forbids comma-joining cookies).
|
|
792
873
|
// Middleware headers take precedence: skip config keys already set
|
|
793
874
|
// by middleware so middleware always wins for the same key.
|
|
794
875
|
if (configHeaders.length) {
|
|
795
|
-
const matched = matchHeaders(
|
|
876
|
+
const matched = matchHeaders(pathname, configHeaders, reqCtx);
|
|
796
877
|
for (const h of matched) {
|
|
797
878
|
const lk = h.key.toLowerCase();
|
|
798
879
|
if (lk === "set-cookie") {
|
|
@@ -817,23 +898,6 @@ async function startPagesRouterServer(options) {
|
|
|
817
898
|
}
|
|
818
899
|
}
|
|
819
900
|
}
|
|
820
|
-
// ── 6. Apply redirects from next.config.js ────────────────────
|
|
821
|
-
if (configRedirects.length) {
|
|
822
|
-
const redirect = matchRedirect(resolvedPathname, configRedirects, reqCtx);
|
|
823
|
-
if (redirect) {
|
|
824
|
-
// Guard against double-prefixing: only add basePath if destination
|
|
825
|
-
// doesn't already start with it.
|
|
826
|
-
// Sanitize the final destination to prevent protocol-relative URL open redirects.
|
|
827
|
-
const dest = sanitizeDestination(basePath &&
|
|
828
|
-
!isExternalUrl(redirect.destination) &&
|
|
829
|
-
!hasBasePath(redirect.destination, basePath)
|
|
830
|
-
? basePath + redirect.destination
|
|
831
|
-
: redirect.destination);
|
|
832
|
-
res.writeHead(redirect.permanent ? 308 : 307, { Location: dest });
|
|
833
|
-
res.end();
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
901
|
// ── 7. Apply beforeFiles rewrites from next.config.js ─────────
|
|
838
902
|
if (configRewrites.beforeFiles?.length) {
|
|
839
903
|
const rewritten = matchRewrite(resolvedPathname, configRewrites.beforeFiles, postMwReqCtx);
|
|
@@ -863,7 +927,9 @@ async function startPagesRouterServer(options) {
|
|
|
863
927
|
// the handler doesn't set an explicit Content-Type.
|
|
864
928
|
const ct = response.headers.get("content-type") ?? "application/octet-stream";
|
|
865
929
|
const responseHeaders = mergeResponseHeaders(middlewareHeaders, response);
|
|
866
|
-
|
|
930
|
+
const finalStatus = middlewareRewriteStatus ?? response.status;
|
|
931
|
+
const finalStatusText = finalStatus === response.status ? response.statusText || undefined : undefined;
|
|
932
|
+
sendCompressed(req, res, responseBody, ct, finalStatus, responseHeaders, compress, finalStatusText);
|
|
867
933
|
return;
|
|
868
934
|
}
|
|
869
935
|
// ── 9. Apply afterFiles rewrites from next.config.js ──────────
|
|
@@ -905,12 +971,16 @@ async function startPagesRouterServer(options) {
|
|
|
905
971
|
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
906
972
|
const ct = response.headers.get("content-type") ?? "text/html";
|
|
907
973
|
const responseHeaders = mergeResponseHeaders(middlewareHeaders, response);
|
|
908
|
-
|
|
974
|
+
const finalStatus = middlewareRewriteStatus ?? response.status;
|
|
975
|
+
const finalStatusText = finalStatus === response.status ? response.statusText || undefined : undefined;
|
|
976
|
+
sendCompressed(req, res, responseBody, ct, finalStatus, responseHeaders, compress, finalStatusText);
|
|
909
977
|
}
|
|
910
978
|
catch (e) {
|
|
911
979
|
console.error("[vinext] Server error:", e);
|
|
912
|
-
res.
|
|
913
|
-
|
|
980
|
+
if (!res.headersSent) {
|
|
981
|
+
res.writeHead(500);
|
|
982
|
+
res.end("Internal Server Error");
|
|
983
|
+
}
|
|
914
984
|
}
|
|
915
985
|
});
|
|
916
986
|
await new Promise((resolve) => {
|