vinext 0.0.21 → 0.0.22

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 (51) hide show
  1. package/dist/deploy.d.ts.map +1 -1
  2. package/dist/deploy.js +6 -3
  3. package/dist/deploy.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +179 -10
  6. package/dist/index.js.map +1 -1
  7. package/dist/routing/app-router.d.ts.map +1 -1
  8. package/dist/routing/app-router.js +1 -41
  9. package/dist/routing/app-router.js.map +1 -1
  10. package/dist/routing/pages-router.d.ts.map +1 -1
  11. package/dist/routing/pages-router.js +1 -27
  12. package/dist/routing/pages-router.js.map +1 -1
  13. package/dist/routing/utils.d.ts +25 -0
  14. package/dist/routing/utils.d.ts.map +1 -0
  15. package/dist/routing/utils.js +70 -0
  16. package/dist/routing/utils.js.map +1 -0
  17. package/dist/server/app-dev-server.d.ts.map +1 -1
  18. package/dist/server/app-dev-server.js +70 -3
  19. package/dist/server/app-dev-server.js.map +1 -1
  20. package/dist/server/dev-server.d.ts.map +1 -1
  21. package/dist/server/dev-server.js +77 -4
  22. package/dist/server/dev-server.js.map +1 -1
  23. package/dist/server/prod-server.d.ts.map +1 -1
  24. package/dist/server/prod-server.js +7 -0
  25. package/dist/server/prod-server.js.map +1 -1
  26. package/dist/server/request-log.d.ts +34 -0
  27. package/dist/server/request-log.d.ts.map +1 -0
  28. package/dist/server/request-log.js +65 -0
  29. package/dist/server/request-log.js.map +1 -0
  30. package/dist/shims/cache-runtime.d.ts.map +1 -1
  31. package/dist/shims/cache-runtime.js +5 -1
  32. package/dist/shims/cache-runtime.js.map +1 -1
  33. package/dist/shims/cache.d.ts +6 -0
  34. package/dist/shims/cache.d.ts.map +1 -1
  35. package/dist/shims/cache.js +22 -2
  36. package/dist/shims/cache.js.map +1 -1
  37. package/dist/shims/head.d.ts +11 -0
  38. package/dist/shims/head.d.ts.map +1 -1
  39. package/dist/shims/head.js +21 -0
  40. package/dist/shims/head.js.map +1 -1
  41. package/dist/shims/headers.d.ts +8 -0
  42. package/dist/shims/headers.d.ts.map +1 -1
  43. package/dist/shims/headers.js +41 -0
  44. package/dist/shims/headers.js.map +1 -1
  45. package/dist/shims/script.d.ts.map +1 -1
  46. package/dist/shims/script.js +7 -1
  47. package/dist/shims/script.js.map +1 -1
  48. package/dist/shims/server.d.ts.map +1 -1
  49. package/dist/shims/server.js +2 -1
  50. package/dist/shims/server.js.map +1 -1
  51. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { handleApiRoute } from "./server/api-handler.js";
6
6
  import { generateRscEntry, generateSsrEntry, generateBrowserEntry, } from "./server/app-dev-server.js";
7
7
  import { loadNextConfig, resolveNextConfig, } from "./config/next-config.js";
8
8
  import { findMiddlewareFile, isProxyFile, runMiddleware } from "./server/middleware.js";
9
+ import { logRequest, now } from "./server/request-log.js";
9
10
  import { generateSafeRegExpCode, generateMiddlewareMatcherCode, generateNormalizePathCode } from "./server/middleware-codegen.js";
10
11
  import { normalizePath } from "./server/normalize-path.js";
11
12
  import { findInstrumentationFile, runInstrumentation } from "./server/instrumentation.js";
@@ -1026,6 +1027,12 @@ function createReqRes(request, url, query, body) {
1026
1027
  else { res.writeHead(statusOrUrl, { Location: url2 }); }
1027
1028
  res.end();
1028
1029
  },
1030
+ getHeaders: function() {
1031
+ var h = Object.assign({}, resHeaders);
1032
+ if (setCookieHeaders.length > 0) h["set-cookie"] = setCookieHeaders;
1033
+ return h;
1034
+ },
1035
+ get headersSent() { return ended; },
1029
1036
  };
1030
1037
 
1031
1038
  return { req, res, responsePromise };
@@ -1140,8 +1147,9 @@ export async function renderPage(request, url, manifest) {
1140
1147
  }
1141
1148
 
1142
1149
  let pageProps = {};
1150
+ var gsspRes = null;
1143
1151
  if (typeof pageModule.getServerSideProps === "function") {
1144
- const { req, res } = createReqRes(request, routeUrl, parseQuery(routeUrl), undefined);
1152
+ const { req, res, responsePromise } = createReqRes(request, routeUrl, parseQuery(routeUrl), undefined);
1145
1153
  const ctx = {
1146
1154
  params, req, res,
1147
1155
  query: parseQuery(routeUrl),
@@ -1151,6 +1159,10 @@ export async function renderPage(request, url, manifest) {
1151
1159
  defaultLocale: i18nConfig ? i18nConfig.defaultLocale : undefined,
1152
1160
  };
1153
1161
  const result = await pageModule.getServerSideProps(ctx);
1162
+ // If gSSP called res.end() directly (short-circuit), return that response.
1163
+ if (res.headersSent) {
1164
+ return await responsePromise;
1165
+ }
1154
1166
  if (result && result.props) pageProps = result.props;
1155
1167
  if (result && result.redirect) {
1156
1168
  var gsspStatus = result.redirect.statusCode != null ? result.redirect.statusCode : (result.redirect.permanent ? 308 : 307);
@@ -1159,6 +1171,9 @@ export async function renderPage(request, url, manifest) {
1159
1171
  if (result && result.notFound) {
1160
1172
  return new Response("404", { status: 404 });
1161
1173
  }
1174
+ // Preserve the res object so headers/status/cookies set by gSSP
1175
+ // can be merged into the final HTML response.
1176
+ gsspRes = res;
1162
1177
  }
1163
1178
  // Build font Link header early so it's available for ISR cached responses too.
1164
1179
  // Font preloads are module-level state populated at import time and persist across requests.
@@ -1335,16 +1350,33 @@ export async function renderPage(request, url, manifest) {
1335
1350
  await isrSet(isrCacheKey, { kind: "PAGES", html: fullHtml, pageData: pageProps, headers: undefined, status: undefined }, isrRevalidateSeconds);
1336
1351
  }
1337
1352
 
1338
- const responseHeaders = { "Content-Type": "text/html" };
1353
+ // Merge headers/status/cookies set by getServerSideProps on the res object.
1354
+ // gSSP commonly uses res.setHeader("Set-Cookie", ...) or res.status(304).
1355
+ var finalStatus = 200;
1356
+ const responseHeaders = new Headers({ "Content-Type": "text/html" });
1357
+ if (gsspRes) {
1358
+ finalStatus = gsspRes.statusCode;
1359
+ var gsspHeaders = gsspRes.getHeaders();
1360
+ for (var hk of Object.keys(gsspHeaders)) {
1361
+ var hv = gsspHeaders[hk];
1362
+ if (hk === "set-cookie" && Array.isArray(hv)) {
1363
+ for (var sc of hv) responseHeaders.append("set-cookie", sc);
1364
+ } else if (hv != null) {
1365
+ responseHeaders.set(hk, String(hv));
1366
+ }
1367
+ }
1368
+ // Ensure Content-Type stays text/html (gSSP shouldn't override it for page renders)
1369
+ responseHeaders.set("Content-Type", "text/html");
1370
+ }
1339
1371
  if (isrRevalidateSeconds) {
1340
- responseHeaders["Cache-Control"] = "s-maxage=" + isrRevalidateSeconds + ", stale-while-revalidate";
1341
- responseHeaders["X-Vinext-Cache"] = "MISS";
1372
+ responseHeaders.set("Cache-Control", "s-maxage=" + isrRevalidateSeconds + ", stale-while-revalidate");
1373
+ responseHeaders.set("X-Vinext-Cache", "MISS");
1342
1374
  }
1343
1375
  // Set HTTP Link header for font preloading
1344
1376
  if (_fontLinkHeader) {
1345
- responseHeaders["Link"] = _fontLinkHeader;
1377
+ responseHeaders.set("Link", _fontLinkHeader);
1346
1378
  }
1347
- return new Response(compositeStream, { status: 200, headers: responseHeaders });
1379
+ return new Response(compositeStream, { status: finalStatus, headers: responseHeaders });
1348
1380
  } catch (e) {
1349
1381
  console.error("[vinext] SSR error:", e);
1350
1382
  return new Response("Internal Server Error", { status: 500 });
@@ -2236,8 +2268,135 @@ hydrate();
2236
2268
  console.error("[vinext] Instrumentation error:", err);
2237
2269
  });
2238
2270
  }
2271
+ // ── Dev request origin check ─────────────────────────────────────
2272
+ // Registered directly (not in the returned function) so it runs
2273
+ // BEFORE Vite's built-in middleware. This ensures all requests
2274
+ // (including /@*, /__vite*, /node_modules* paths) are validated
2275
+ // before Vite serves any content.
2276
+ server.middlewares.use((req, res, next) => {
2277
+ const blockReason = validateDevRequest({
2278
+ origin: req.headers.origin,
2279
+ host: req.headers.host,
2280
+ "x-forwarded-host": req.headers["x-forwarded-host"],
2281
+ "sec-fetch-site": req.headers["sec-fetch-site"],
2282
+ "sec-fetch-mode": req.headers["sec-fetch-mode"],
2283
+ }, nextConfig?.serverActionsAllowedOrigins);
2284
+ if (blockReason) {
2285
+ console.warn(`[vinext] Blocked dev request: ${blockReason} (${req.url})`);
2286
+ res.writeHead(403, { "Content-Type": "text/plain" });
2287
+ res.end("Forbidden");
2288
+ return;
2289
+ }
2290
+ next();
2291
+ });
2239
2292
  // Return a function to register middleware AFTER Vite's built-in middleware
2240
2293
  return () => {
2294
+ // App Router request logging in dev server
2295
+ //
2296
+ // For App Router, the RSC plugin handles requests internally.
2297
+ // We install a timing middleware here that:
2298
+ // 1. Intercepts writeHead() to pluck the X-Vinext-Timing header
2299
+ // (compileMs,renderMs) that the RSC entry attaches before
2300
+ // it is flushed to the client.
2301
+ // 2. Logs the full request after res finishes, using those timings.
2302
+ if (hasAppDir) {
2303
+ server.middlewares.use((req, res, next) => {
2304
+ const url = req.url ?? "/";
2305
+ // Skip Vite internals, HMR, and static assets.
2306
+ // Do NOT skip .rsc-suffixed URLs or RSC wire requests (Accept: text/x-component)
2307
+ // — those are soft navigations and should be logged like any other page request.
2308
+ const [pathname] = url.split("?");
2309
+ if (url.startsWith("/@") ||
2310
+ url.startsWith("/__vite") ||
2311
+ url.startsWith("/node_modules") ||
2312
+ (url.includes(".") && !pathname.endsWith(".html") && !pathname.endsWith(".rsc"))) {
2313
+ return next();
2314
+ }
2315
+ const _reqStart = now();
2316
+ let _compileMs;
2317
+ let _renderMs;
2318
+ // Intercept setHeader and writeHead so we can strip X-Vinext-Timing
2319
+ // before it reaches the client and capture the compile/render split.
2320
+ // The RSC plugin may set headers either way depending on its version.
2321
+ // Parse the three-part X-Vinext-Timing header:
2322
+ // "handlerStart,inHandlerCompileMs,renderMs"
2323
+ //
2324
+ // True compile time = time the RSC plugin spent loading/transforming
2325
+ // modules before our handler code ran, plus any in-handler work before
2326
+ // renderToReadableStream. Concretely:
2327
+ // compileMs = (handlerStart - _reqStart) + inHandlerCompileMs
2328
+ // renderMs = renderMs from header, or -1 for RSC-only (soft-nav)
2329
+ // responses where rendering is not measured in the handler.
2330
+ // In that case the middleware computes render time as
2331
+ // totalMs - compileMs.
2332
+ //
2333
+ // handlerStart is performance.now() recorded at the very top of
2334
+ // _handleRequest in the generated RSC entry. _reqStart is recorded
2335
+ // here in the Node middleware, one stack frame before the RSC plugin
2336
+ // loads the module. The gap between them is exactly the Vite
2337
+ // compile/transform cost.
2338
+ function _parseTiming(raw) {
2339
+ const [handlerStart, inHandlerCompileMs, renderMs] = String(raw).split(",").map((v) => Number(v));
2340
+ if (!Number.isNaN(handlerStart) && !Number.isNaN(inHandlerCompileMs) && inHandlerCompileMs !== -1) {
2341
+ _compileMs = Math.max(0, Math.round(handlerStart - _reqStart)) + inHandlerCompileMs;
2342
+ }
2343
+ if (!Number.isNaN(renderMs) && renderMs !== -1) {
2344
+ _renderMs = renderMs;
2345
+ }
2346
+ }
2347
+ const _origSetHeader = res.setHeader.bind(res);
2348
+ res.setHeader = function (name, value) {
2349
+ if (name.toLowerCase() === "x-vinext-timing") {
2350
+ _parseTiming(value);
2351
+ return res; // drop the header — don't forward to client
2352
+ }
2353
+ return _origSetHeader(name, value);
2354
+ };
2355
+ const _origWriteHead = res.writeHead.bind(res);
2356
+ res.writeHead = function (statusCode, ...args) {
2357
+ // Normalise the optional headers argument (may be reason, headers object, or both).
2358
+ let headers;
2359
+ const [reasonOrHeaders, maybeHeaders] = args;
2360
+ if (typeof reasonOrHeaders === "string") {
2361
+ headers = maybeHeaders;
2362
+ }
2363
+ else {
2364
+ headers = reasonOrHeaders;
2365
+ }
2366
+ // Pull timing out of the headers object when present.
2367
+ if (headers && typeof headers === "object" && !Array.isArray(headers)) {
2368
+ const timingKey = Object.keys(headers).find((k) => k.toLowerCase() === "x-vinext-timing");
2369
+ if (timingKey) {
2370
+ _parseTiming(headers[timingKey]);
2371
+ delete headers[timingKey];
2372
+ }
2373
+ }
2374
+ return _origWriteHead(statusCode, ...args);
2375
+ };
2376
+ res.on("finish", () => {
2377
+ // Strip .rsc suffix — it's an internal RSC protocol detail,
2378
+ // not part of the actual page path the user navigated to.
2379
+ const logUrl = url.replace(/\.rsc(\?|$)/, "$1");
2380
+ const totalMs = now() - _reqStart;
2381
+ // For RSC-only responses (soft nav), renderMs is -1 (sentinel meaning
2382
+ // "not measured in the handler"). Compute it as totalMs - compileMs,
2383
+ // which is how long the RSC stream took to fully flush to the client —
2384
+ // matching what Next.js shows for soft navigations.
2385
+ const resolvedRenderMs = _renderMs !== undefined
2386
+ ? _renderMs
2387
+ : (_compileMs !== undefined ? Math.max(0, Math.round(totalMs - _compileMs)) : undefined);
2388
+ logRequest({
2389
+ method: req.method ?? "GET",
2390
+ url: logUrl,
2391
+ status: res.statusCode,
2392
+ totalMs,
2393
+ compileMs: _compileMs,
2394
+ renderMs: resolvedRenderMs,
2395
+ });
2396
+ });
2397
+ next();
2398
+ });
2399
+ }
2241
2400
  server.middlewares.use(async (req, res, next) => {
2242
2401
  try {
2243
2402
  let url = req.url ?? "/";
@@ -2255,9 +2414,12 @@ hydrate();
2255
2414
  if (url.split("?")[0].endsWith(".rsc")) {
2256
2415
  return next();
2257
2416
  }
2258
- // ── Cross-origin request protection ─────────────────────────
2259
- // Block requests from non-localhost origins to prevent
2260
- // cross-origin data exfiltration from the dev server.
2417
+ // ── Cross-origin request protection (defense-in-depth) ──────
2418
+ // The pre-Vite middleware above already blocks cross-origin
2419
+ // requests before Vite serves any content. This second check
2420
+ // guards the Pages Router handler specifically, in case the
2421
+ // middleware ordering changes or new middleware is added between
2422
+ // the two. Both calls use the same validateDevRequest() function.
2261
2423
  const blockReason = validateDevRequest({
2262
2424
  origin: req.headers.origin,
2263
2425
  host: req.headers.host,
@@ -2281,7 +2443,14 @@ hydrate();
2281
2443
  const imgUrl = rawImgUrl?.replaceAll("\\", "/") ?? null;
2282
2444
  // Allowlist: must start with "/" but not "//" — blocks absolute
2283
2445
  // URLs, protocol-relative, backslash variants, and exotic schemes.
2284
- if (!imgUrl || !imgUrl.startsWith("/") || imgUrl.startsWith("//")) {
2446
+ // Also block internal Vite paths (/@*, /__vite*, /node_modules*)
2447
+ // to prevent redirecting to dev server endpoints.
2448
+ if (!imgUrl ||
2449
+ !imgUrl.startsWith("/") ||
2450
+ imgUrl.startsWith("//") ||
2451
+ imgUrl.startsWith("/@") ||
2452
+ imgUrl.startsWith("/__vite") ||
2453
+ imgUrl.startsWith("/node_modules")) {
2285
2454
  res.writeHead(400);
2286
2455
  res.end(!rawImgUrl ? "Missing url parameter" : "Only relative URLs allowed");
2287
2456
  return;