vinext 0.1.0 → 0.1.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 (119) hide show
  1. package/dist/build/assets-ignore.d.ts +32 -0
  2. package/dist/build/assets-ignore.js +48 -0
  3. package/dist/build/client-build-config.d.ts +27 -1
  4. package/dist/build/client-build-config.js +58 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/client/navigation-runtime.d.ts +8 -0
  7. package/dist/client/navigation-runtime.js +1 -1
  8. package/dist/client/vinext-next-data.d.ts +2 -1
  9. package/dist/config/config-matchers.d.ts +20 -1
  10. package/dist/config/config-matchers.js +35 -1
  11. package/dist/config/next-config.d.ts +16 -3
  12. package/dist/config/next-config.js +30 -2
  13. package/dist/deploy.js +40 -304
  14. package/dist/entries/app-rsc-entry.d.ts +8 -2
  15. package/dist/entries/app-rsc-entry.js +54 -4
  16. package/dist/entries/app-rsc-manifest.js +20 -2
  17. package/dist/entries/pages-server-entry.js +9 -1
  18. package/dist/index.js +162 -217
  19. package/dist/plugins/postcss.js +18 -14
  20. package/dist/plugins/require-context.d.ts +6 -0
  21. package/dist/plugins/require-context.js +184 -0
  22. package/dist/routing/app-route-graph.d.ts +12 -1
  23. package/dist/routing/app-route-graph.js +137 -5
  24. package/dist/routing/route-pattern.d.ts +2 -1
  25. package/dist/routing/route-pattern.js +16 -1
  26. package/dist/server/api-handler.js +4 -0
  27. package/dist/server/app-browser-entry.js +84 -39
  28. package/dist/server/app-browser-interception-context.d.ts +2 -1
  29. package/dist/server/app-browser-interception-context.js +15 -2
  30. package/dist/server/app-browser-navigation-controller.d.ts +11 -1
  31. package/dist/server/app-browser-navigation-controller.js +77 -1
  32. package/dist/server/app-browser-popstate.d.ts +12 -3
  33. package/dist/server/app-browser-popstate.js +19 -4
  34. package/dist/server/app-browser-state.d.ts +3 -0
  35. package/dist/server/app-browser-state.js +6 -3
  36. package/dist/server/app-browser-visible-commit.js +9 -7
  37. package/dist/server/app-history-state.d.ts +45 -1
  38. package/dist/server/app-history-state.js +109 -1
  39. package/dist/server/app-page-boundary-render.js +41 -19
  40. package/dist/server/app-page-dispatch.d.ts +6 -0
  41. package/dist/server/app-page-dispatch.js +3 -1
  42. package/dist/server/app-page-element-builder.d.ts +1 -0
  43. package/dist/server/app-page-element-builder.js +22 -10
  44. package/dist/server/app-page-render.d.ts +6 -0
  45. package/dist/server/app-page-render.js +5 -3
  46. package/dist/server/app-page-request.d.ts +8 -6
  47. package/dist/server/app-page-request.js +12 -9
  48. package/dist/server/app-page-response.d.ts +2 -2
  49. package/dist/server/app-page-response.js +1 -1
  50. package/dist/server/app-page-route-wiring.js +2 -1
  51. package/dist/server/app-page-stream.d.ts +37 -2
  52. package/dist/server/app-page-stream.js +36 -3
  53. package/dist/server/app-pages-bridge.d.ts +16 -0
  54. package/dist/server/app-pages-bridge.js +23 -3
  55. package/dist/server/app-route-handler-cache.d.ts +1 -0
  56. package/dist/server/app-route-handler-cache.js +1 -0
  57. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  58. package/dist/server/app-route-handler-dispatch.js +2 -0
  59. package/dist/server/app-route-handler-execution.d.ts +1 -0
  60. package/dist/server/app-route-handler-execution.js +1 -0
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  62. package/dist/server/app-route-handler-runtime.js +3 -2
  63. package/dist/server/app-rsc-handler.d.ts +1 -0
  64. package/dist/server/app-rsc-handler.js +4 -3
  65. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  66. package/dist/server/app-rsc-route-matching.js +29 -4
  67. package/dist/server/app-server-action-execution.d.ts +11 -1
  68. package/dist/server/app-server-action-execution.js +68 -10
  69. package/dist/server/app-ssr-entry.d.ts +6 -0
  70. package/dist/server/app-ssr-entry.js +17 -1
  71. package/dist/server/dev-server.d.ts +1 -1
  72. package/dist/server/dev-server.js +54 -31
  73. package/dist/server/isr-cache.d.ts +37 -1
  74. package/dist/server/isr-cache.js +85 -1
  75. package/dist/server/navigation-planner.js +5 -3
  76. package/dist/server/navigation-trace.d.ts +1 -1
  77. package/dist/server/pages-node-compat.d.ts +2 -0
  78. package/dist/server/pages-node-compat.js +4 -0
  79. package/dist/server/pages-page-data.d.ts +10 -7
  80. package/dist/server/pages-page-data.js +4 -2
  81. package/dist/server/pages-page-handler.d.ts +9 -2
  82. package/dist/server/pages-page-handler.js +29 -16
  83. package/dist/server/pages-page-response.d.ts +11 -2
  84. package/dist/server/pages-page-response.js +8 -1
  85. package/dist/server/pages-readiness.d.ts +36 -0
  86. package/dist/server/pages-readiness.js +21 -0
  87. package/dist/server/pages-request-pipeline.d.ts +99 -0
  88. package/dist/server/pages-request-pipeline.js +209 -0
  89. package/dist/server/pages-revalidate.d.ts +15 -0
  90. package/dist/server/pages-revalidate.js +19 -0
  91. package/dist/server/prod-server.d.ts +6 -2
  92. package/dist/server/prod-server.js +101 -217
  93. package/dist/server/socket-error-backstop.d.ts +19 -1
  94. package/dist/server/socket-error-backstop.js +77 -4
  95. package/dist/shims/app-router-scroll.js +22 -4
  96. package/dist/shims/cache-runtime.js +31 -1
  97. package/dist/shims/error-boundary.d.ts +21 -11
  98. package/dist/shims/error-boundary.js +8 -1
  99. package/dist/shims/fetch-cache.d.ts +14 -1
  100. package/dist/shims/fetch-cache.js +18 -1
  101. package/dist/shims/hash-scroll.d.ts +1 -0
  102. package/dist/shims/hash-scroll.js +3 -1
  103. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  104. package/dist/shims/internal/link-status-registry.js +42 -0
  105. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  106. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  107. package/dist/shims/internal/utils.d.ts +1 -0
  108. package/dist/shims/link.js +20 -6
  109. package/dist/shims/navigation.d.ts +2 -2
  110. package/dist/shims/navigation.js +63 -7
  111. package/dist/shims/router-state.d.ts +1 -0
  112. package/dist/shims/router-state.js +2 -0
  113. package/dist/shims/router.d.ts +6 -3
  114. package/dist/shims/router.js +128 -21
  115. package/dist/utils/client-build-manifest.d.ts +8 -1
  116. package/dist/utils/client-build-manifest.js +30 -5
  117. package/dist/utils/client-entry-manifest.d.ts +11 -0
  118. package/dist/utils/client-entry-manifest.js +29 -0
  119. package/package.json +5 -1
package/dist/deploy.js CHANGED
@@ -334,30 +334,14 @@ function generatePagesRouterWorkerEntry() {
334
334
  * Cloudflare Worker entry point -- auto-generated by vinext deploy.
335
335
  * Edit freely or delete to regenerate on next deploy.
336
336
  */
337
+ import { runPagesRequest } from "vinext/server/pages-request-pipeline";
338
+ import type { PagesPipelineDeps } from "vinext/server/pages-request-pipeline";
337
339
  import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath } from "vinext/server/image-optimization";
338
340
  import type { ImageConfig } from "vinext/server/image-optimization";
339
- import {
340
- matchRedirect,
341
- matchRewrite,
342
- requestContextFromRequest,
343
- applyMiddlewareRequestHeaders,
344
- isExternalUrl,
345
- proxyExternalRequest,
346
- sanitizeDestination,
347
- } from "vinext/config/config-matchers";
348
- import {
349
- applyConfigHeadersToHeaderRecord,
350
- cloneRequestWithHeaders,
351
- filterInternalHeaders,
352
- isOpenRedirectShaped,
353
- normalizeTrailingSlash,
354
- } from "vinext/server/request-pipeline";
355
- import { mergeHeaders } from "vinext/server/worker-utils";
341
+ import { cloneRequestWithHeaders, filterInternalHeaders, isOpenRedirectShaped } from "vinext/server/request-pipeline";
356
342
  import { notFoundStaticAssetResponse } from "vinext/server/http-error-responses";
357
343
  import { assetPrefixPathname, isNextStaticPath } from "vinext/utils/asset-prefix";
358
344
  import { hasBasePath, stripBasePath } from "vinext/utils/base-path";
359
- import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "vinext/server/pages-i18n";
360
- import { mergeRewriteQuery } from "vinext/utils/query";
361
345
 
362
346
  // @ts-expect-error -- virtual module resolved by vinext at build time
363
347
  import { renderPage, handleApiRoute, runMiddleware, vinextConfig, matchPageRoute } from "virtual:vinext-server-entry";
@@ -402,7 +386,6 @@ export default {
402
386
  try {
403
387
  const url = new URL(request.url);
404
388
  let pathname = url.pathname;
405
- let urlWithQuery = pathname + url.search;
406
389
 
407
390
  // Block protocol-relative URL open redirects in all shapes:
408
391
  // literal //evil.com, /\\\\evil.com
@@ -445,11 +428,12 @@ export default {
445
428
  {
446
429
  const stripped = stripBasePath(pathname, basePath);
447
430
  if (stripped !== pathname) {
448
- urlWithQuery = stripped + url.search;
431
+ const strippedUrl = new URL(request.url);
432
+ strippedUrl.pathname = stripped;
433
+ request = new Request(strippedUrl, request);
449
434
  pathname = stripped;
450
435
  }
451
436
  }
452
- const basePathState = { basePath, hadBasePath };
453
437
 
454
438
  // ── Image optimization via Cloudflare Images binding ──────────
455
439
  // Checked after basePath stripping so /<basePath>/_next/image works.
@@ -464,291 +448,43 @@ export default {
464
448
  }, allowedWidths, imageConfig);
465
449
  }
466
450
 
467
- // ── 2. Trailing slash normalization ────────────────────────────
468
- {
469
- const trailingSlashRedirect = normalizeTrailingSlash(
470
- pathname,
471
- basePath,
472
- trailingSlash,
473
- url.search,
474
- );
475
- if (trailingSlashRedirect) {
476
- return trailingSlashRedirect;
477
- }
478
- }
479
-
480
- // Build a request with the basePath-stripped URL for middleware and
481
- // downstream handlers. In Workers the incoming request URL still
482
- // contains basePath; prod-server constructs its webRequest from
483
- // the already-stripped URL, so we replicate that here.
484
- if (basePath) {
485
- const strippedUrl = new URL(request.url);
486
- strippedUrl.pathname = pathname;
487
- request = new Request(strippedUrl, request);
488
- }
489
-
490
- // Build request context for pre-middleware config matching. Redirects
491
- // run before middleware in Next.js. Header match conditions also use the
492
- // original request snapshot even though header merging happens later so
493
- // middleware response headers can still take precedence.
494
- // beforeFiles, afterFiles, and fallback rewrites run after middleware
495
- // (App Router order), so they use postMwReqCtx created after
496
- // x-middleware-request-* headers are unpacked into request.
497
- const reqCtx = requestContextFromRequest(request);
498
-
499
- // Default-locale path normalisation (issue #1336, item 4). Mirrors
500
- // Next.js's resolve-routes.ts: every request without a locale prefix
501
- // gets the (domain-aware) default locale prepended before config rule
502
- // matching so that locale-aware rules with :locale placeholders or
503
- // locale: false overrides still match default-locale URLs.
504
- const matchPathname = i18nConfig
505
- ? normalizeDefaultLocalePathname(pathname, i18nConfig, {
506
- hostname: url.hostname,
507
- })
508
- : pathname;
509
-
510
- // ── 3. Apply redirects from next.config.js ────────────────────
511
- if (configRedirects.length) {
512
- const redirect = matchRedirect(matchPathname, configRedirects, reqCtx, basePathState);
513
- if (redirect) {
514
- // Only prepend basePath when the request was actually under basePath.
515
- // Opt-out rules running on out-of-basepath requests must not receive
516
- // a basePath prefix.
517
- const dest = sanitizeDestination(
518
- basePath &&
519
- hadBasePath &&
520
- !isExternalUrl(redirect.destination) &&
521
- !hasBasePath(redirect.destination, basePath)
522
- ? basePath + redirect.destination
523
- : redirect.destination,
524
- );
525
- return new Response(null, {
526
- status: redirect.permanent ? 308 : 307,
527
- headers: { Location: dest },
528
- });
529
- }
530
- }
531
-
532
- // ── 4. Run middleware ──────────────────────────────────────────
533
- let resolvedUrl = urlWithQuery;
534
- const middlewareHeaders: Record<string, string | string[]> = {};
535
- let middlewareRewriteStatus: number | undefined;
536
- if (typeof runMiddleware === "function") {
537
- const result = await runMiddleware(request, ctx, { isDataRequest });
538
-
539
- // Bubble up waitUntil promises (e.g. Clerk telemetry/session sync)
540
- if (result.waitUntilPromises?.length) {
541
- for (const p of result.waitUntilPromises) {
542
- ctx.waitUntil(p);
543
- }
544
- }
545
-
546
- if (!result.continue) {
547
- if (result.redirectUrl) {
548
- const redirectHeaders = new Headers({ Location: result.redirectUrl });
549
- if (result.responseHeaders) {
550
- for (const [key, value] of result.responseHeaders) {
551
- redirectHeaders.append(key, value);
552
- }
553
- }
554
- return new Response(null, {
555
- status: result.redirectStatus ?? 307,
556
- headers: redirectHeaders,
557
- });
558
- }
559
- if (result.response) {
560
- return result.response;
561
- }
562
- }
563
-
564
- // Collect middleware response headers to merge into final response.
565
- // Use an array for Set-Cookie to preserve multiple values.
566
- if (result.responseHeaders) {
567
- for (const [key, value] of result.responseHeaders) {
568
- if (key === "set-cookie") {
569
- const existing = middlewareHeaders[key];
570
- if (Array.isArray(existing)) {
571
- existing.push(value);
572
- } else if (existing) {
573
- middlewareHeaders[key] = [existing as string, value];
574
- } else {
575
- middlewareHeaders[key] = [value];
576
- }
577
- } else {
578
- middlewareHeaders[key] = value;
579
- }
580
- }
581
- }
582
-
583
- // Apply middleware rewrite
584
- if (result.rewriteUrl) {
585
- resolvedUrl = result.rewriteUrl;
586
- }
587
-
588
- // Apply custom status code from middleware rewrite
589
- middlewareRewriteStatus = result.rewriteStatus;
590
- }
591
-
592
- // Unpack x-middleware-request-* headers into the actual request and strip
593
- // all x-middleware-* internal signals. Rebuilds postMwReqCtx for use by
594
- // beforeFiles, afterFiles, and fallback config rules (which run after
595
- // middleware per the Next.js execution order).
596
- const { postMwReqCtx, request: postMwReq } = applyMiddlewareRequestHeaders(middlewareHeaders, request);
597
- request = postMwReq;
598
-
599
- // Config header matching must keep using the original normalized pathname
600
- // even if middleware rewrites the downstream route/render target.
601
- let resolvedPathname = resolvedUrl.split("?")[0];
602
-
603
- // ── 5. Apply custom headers from next.config.js ───────────────
604
- // Config headers are additive for multi-value headers (Vary,
605
- // Set-Cookie) and override for everything else. Vary values are
606
- // comma-joined per HTTP spec. Set-Cookie values are accumulated
607
- // as arrays (RFC 6265 forbids comma-joining cookies).
608
- // Middleware headers take precedence: skip config keys already set
609
- // by middleware so middleware always wins for the same key.
610
- if (configHeaders.length) {
611
- applyConfigHeadersToHeaderRecord(middlewareHeaders, {
612
- configHeaders,
613
- pathname: matchPathname,
614
- requestContext: reqCtx,
615
- basePathState,
616
- });
617
- }
618
-
619
- if (isExternalUrl(resolvedUrl)) {
620
- const proxyResponse = await proxyExternalRequest(request, resolvedUrl);
621
- return mergeHeaders(proxyResponse, middlewareHeaders, undefined);
622
- }
623
-
624
- // Default-locale-normalised form of resolvedPathname for matching
625
- // against next.config.js rewrites (beforeFiles, afterFiles, fallback).
626
- const matchResolvedPathname = (p: string): string =>
627
- i18nConfig
628
- ? normalizeDefaultLocalePathname(p, i18nConfig, { hostname: url.hostname })
629
- : p;
630
-
631
- // ── 6. Apply beforeFiles rewrites from next.config.js ─────────
632
- let configRewriteFired = false;
633
- if (configRewrites.beforeFiles?.length) {
634
- const rewritten = matchRewrite(
635
- matchResolvedPathname(resolvedPathname),
636
- configRewrites.beforeFiles,
637
- postMwReqCtx,
638
- basePathState,
639
- );
640
- if (rewritten) {
641
- if (isExternalUrl(rewritten)) {
642
- return proxyExternalRequest(request, rewritten);
643
- }
644
- // Preserve original query params across rewrites (Next.js parity).
645
- resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
646
- resolvedPathname = resolvedUrl.split("?")[0];
647
- configRewriteFired = true;
648
- }
649
- }
650
-
651
- // Reject out-of-basePath requests that no rule rewrote. See the
652
- // matching comment in prod-server.ts step 7b.
653
- if (basePath && !hadBasePath && !configRewriteFired) {
654
- return new Response("This page could not be found", {
655
- status: 404,
656
- headers: { "Content-Type": "text/html; charset=utf-8" },
657
- });
658
- }
659
-
660
- // ── 7. API routes ─────────────────────────────────────────────
661
- // Forward ctx so handlePagesApiRoute can wrap the user handler in
662
- // runWithExecutionContext, making ctx.waitUntil() reachable from
663
- // after() and other shims that schedule deferred work.
664
- //
665
- // Strip the i18n locale prefix before the /api/ check so
666
- // /fr/api/ok resolves to the pages/api/ok handler (Next.js
667
- // parity -- see base-server.ts's normalizeLocalePath call).
668
- const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, vinextConfig?.i18n ?? null);
669
- const apiLookupPathname = apiLookupUrl.split("?")[0];
670
- if (apiLookupPathname.startsWith("/api/") || apiLookupPathname === "/api") {
671
- const response = typeof handleApiRoute === "function"
672
- ? await handleApiRoute(request, apiLookupUrl, ctx)
673
- : new Response("404 - API route not found", { status: 404 });
674
- return mergeHeaders(response, middlewareHeaders, middlewareRewriteStatus);
675
- }
676
-
677
- const pageMatch =
678
- typeof matchPageRoute === "function" ? matchPageRoute(resolvedPathname, request) : null;
679
-
680
- // ── 8. Apply afterFiles rewrites from next.config.js ──────────
681
- // These run after non-dynamic page routes but before dynamic routes.
682
- if ((!pageMatch || pageMatch.route.isDynamic) && configRewrites.afterFiles?.length) {
683
- const rewritten = matchRewrite(
684
- matchResolvedPathname(resolvedPathname),
685
- configRewrites.afterFiles,
686
- postMwReqCtx,
687
- basePathState,
688
- );
689
- if (rewritten) {
690
- if (isExternalUrl(rewritten)) {
691
- return proxyExternalRequest(request, rewritten);
692
- }
693
- resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
694
- resolvedPathname = resolvedUrl.split("?")[0];
695
- }
696
- }
697
-
698
- // ── 9. Page routes ────────────────────────────────────────────
699
- let response: Response | undefined;
700
- if (typeof renderPage === "function") {
701
- const renderPageMatch =
702
- typeof matchPageRoute === "function" ? matchPageRoute(resolvedPathname, request) : null;
703
- const shouldDeferErrorPageOnMiss =
704
- !isDataRequest && typeof matchPageRoute === "function" && !renderPageMatch;
705
- const initialRenderOptions = shouldDeferErrorPageOnMiss
706
- ? { renderErrorPageOnMiss: false }
707
- : undefined;
708
- response = await renderPage(request, resolvedUrl, null, ctx, undefined, initialRenderOptions);
709
-
710
- // ── 10. Fallback rewrites (if SSR returned 404) ─────────────
711
- let matchedFallbackRewrite = false;
712
- if (
713
- response &&
714
- response.status === 404 &&
715
- shouldDeferErrorPageOnMiss &&
716
- configRewrites.fallback?.length
717
- ) {
718
- const fallbackRewrite = matchRewrite(
719
- matchResolvedPathname(resolvedPathname),
720
- configRewrites.fallback,
721
- postMwReqCtx,
722
- basePathState,
723
- );
724
- if (fallbackRewrite) {
725
- if (isExternalUrl(fallbackRewrite)) {
726
- return proxyExternalRequest(request, fallbackRewrite);
727
- }
728
- matchedFallbackRewrite = true;
729
- response = await renderPage(
730
- request,
731
- mergeRewriteQuery(resolvedUrl, fallbackRewrite),
732
- null,
733
- ctx,
734
- );
735
- }
736
- }
737
- if (
738
- response &&
739
- response.status === 404 &&
740
- shouldDeferErrorPageOnMiss &&
741
- !matchedFallbackRewrite
742
- ) {
743
- response = await renderPage(request, resolvedUrl, null, ctx);
744
- }
745
- }
451
+ // Delegate the canonical 9-step Next.js pipeline to the shared owner.
452
+ // The worker adapter is responsible for: open-redirect guard, _next/static
453
+ // 404 short-circuit, header filtering, basePath stripping, and image
454
+ // optimization. runPagesRequest receives a clean, basePath-stripped request.
455
+ const deps: PagesPipelineDeps = {
456
+ basePath,
457
+ trailingSlash,
458
+ i18nConfig,
459
+ configRedirects,
460
+ configRewrites,
461
+ configHeaders,
462
+ hadBasePath,
463
+ // The worker adapter does not do _next/data URL normalization (no
464
+ // buildId available at request time). isDataReq is used by the pipeline
465
+ // only for renderPage options and shouldDeferErrorPageOnMiss -- false
466
+ // is correct here.
467
+ isDataReq: false,
468
+ isDataRequest,
469
+ ctx,
470
+ matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
471
+ runMiddleware: typeof runMiddleware === "function" ? runMiddleware : null,
472
+ renderPage: typeof renderPage === "function"
473
+ ? (req, resolvedUrl, options, stagedHeaders) =>
474
+ renderPage(req, resolvedUrl, null, ctx, stagedHeaders, options)
475
+ : null,
476
+ handleApi: typeof handleApiRoute === "function"
477
+ ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx)
478
+ : null,
479
+ };
746
480
 
747
- if (!response) {
748
- return new Response("This page could not be found", { status: 404 });
481
+ const result = await runPagesRequest(request, deps);
482
+ if (result.type === "response") {
483
+ return result.response;
749
484
  }
485
+ // Should not reach here for prod/worker (all callbacks supplied).
486
+ return new Response("This page could not be found", { status: 404 });
750
487
 
751
- return mergeHeaders(response, middlewareHeaders, middlewareRewriteStatus);
752
488
  } catch (error) {
753
489
  console.error("[vinext] Worker error:", error);
754
490
  return new Response("Internal Server Error", { status: 500 });
@@ -17,7 +17,8 @@ type AppRouterConfig = {
17
17
  headers?: NextHeader[]; /** Extra origins allowed for server action CSRF checks (from experimental.serverActions.allowedOrigins). */
18
18
  allowedOrigins?: string[]; /** Extra origins allowed for dev server access (from allowedDevOrigins). */
19
19
  allowedDevOrigins?: string[]; /** Body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). */
20
- bodySizeLimit?: number; /** Serialized next.config htmlLimitedBots regexp source. */
20
+ bodySizeLimit?: number; /** Verbatim body size limit config value (e.g. "2mb") for the "Body exceeded {limit} limit" error. */
21
+ bodySizeLimitLabel?: string; /** Serialized next.config htmlLimitedBots regexp source. */
21
22
  htmlLimitedBots?: string;
22
23
  /**
23
24
  * Allow-list of keys (from `experimental.clientTraceMetadata`) to surface
@@ -34,7 +35,12 @@ type AppRouterConfig = {
34
35
  * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/assetPrefix
35
36
  */
36
37
  assetPrefix?: string; /** Route-level expire fallback in seconds for ISR entries with numeric revalidate. */
37
- expireTime?: number; /** Maximum in-memory cache size in bytes. 0 disables the default memory cache. */
38
+ expireTime?: number;
39
+ /**
40
+ * Maximum total length (in characters) of the preload `Link` header emitted
41
+ * during App Router SSR. `0` disables emission. Defaults to 6000.
42
+ */
43
+ reactMaxHeadersLength?: number; /** Maximum in-memory cache size in bytes. 0 disables the default memory cache. */
38
44
  cacheMaxMemorySize?: number; /** Inline app CSS into production HTML (from experimental.inlineCss). */
39
45
  inlineCss?: boolean; /** Enables Next.js Cache Components semantics for App Router document HTML. */
40
46
  cacheComponents?: boolean; /** Internationalization routing config for middleware matcher locale handling. */
@@ -15,6 +15,7 @@ import { randomUUID } from "node:crypto";
15
15
  * Previously housed in server/app-dev-server.ts.
16
16
  */
17
17
  const DEFAULT_EXPIRE_TIME = 31536e3;
18
+ const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6e3;
18
19
  const middlewareRequestHeadersPath = resolveEntryPath("../server/middleware-request-headers.js", import.meta.url);
19
20
  const normalizePathModulePath = resolveEntryPath("../server/normalize-path.js", import.meta.url);
20
21
  const appRscHandlerPath = resolveEntryPath("../server/app-rsc-handler.js", import.meta.url);
@@ -63,10 +64,12 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
63
64
  const headers = config?.headers ?? [];
64
65
  const allowedOrigins = config?.allowedOrigins ?? [];
65
66
  const bodySizeLimit = config?.bodySizeLimit ?? 1 * 1024 * 1024;
67
+ const bodySizeLimitLabel = config?.bodySizeLimitLabel ?? "1 MB";
66
68
  const htmlLimitedBots = config?.htmlLimitedBots;
67
69
  const clientTraceMetadata = config?.clientTraceMetadata;
68
70
  const assetPrefix = config?.assetPrefix ?? "";
69
71
  const expireTime = config?.expireTime ?? DEFAULT_EXPIRE_TIME;
72
+ const reactMaxHeadersLength = config?.reactMaxHeadersLength ?? DEFAULT_REACT_MAX_HEADERS_LENGTH;
70
73
  const cacheMaxMemorySize = config?.cacheMaxMemorySize;
71
74
  const inlineCss = config?.inlineCss === true;
72
75
  const i18nConfig = config?.i18n ?? null;
@@ -119,6 +122,7 @@ import {
119
122
  import {
120
123
  handleProgressiveServerActionRequest as __handleProgressiveServerActionRequest,
121
124
  handleServerActionRscRequest as __handleServerActionRscRequest,
125
+ isProgressiveServerActionRequest as __isProgressiveServerActionRequest,
122
126
  readActionBodyWithLimit as __readBodyWithLimit,
123
127
  readActionFormDataWithLimit as __readFormDataWithLimit,
124
128
  } from ${JSON.stringify(appServerActionExecutionPath)};
@@ -337,13 +341,14 @@ function findIntercept(pathname, sourcePathname = null) {
337
341
  return __routeMatcher.findIntercept(pathname, sourcePathname);
338
342
  }
339
343
 
340
- async function buildPageElements(route, params, routePath, pageRequest, layoutParamAccess) {
344
+ async function buildPageElements(route, params, routePath, pageRequest, layoutParamAccess, displayPathname = routePath) {
341
345
  // Hydrate lazy page/route-handler modules before any synchronous read.
342
346
  await __ensureRouteLoaded(route);
343
347
  return __buildPageElements({
344
348
  route,
345
349
  params,
346
350
  routePath,
351
+ displayPathname,
347
352
  pageRequest,
348
353
  globalErrorModule: ${globalErrorVar ? globalErrorVar : "null"},
349
354
  rootNotFoundModule: ${rootNotFoundVar ? rootNotFoundVar : "null"},
@@ -366,11 +371,13 @@ const __allowedOrigins = ${JSON.stringify(allowedOrigins)};
366
371
  const __expireTime = ${JSON.stringify(expireTime)};
367
372
  const __htmlLimitedBots = ${JSON.stringify(htmlLimitedBots)};
368
373
  const __clientTraceMetadata = ${JSON.stringify(clientTraceMetadata)};
374
+ const __reactMaxHeadersLength = ${JSON.stringify(reactMaxHeadersLength)};
369
375
  // Re-exported for the App Router prod-server to consume at startup —
370
376
  // mirrors the embedded \`__basePath\` pattern (and Pages Router's
371
377
  // \`vinextConfig\` export). Empty string when unset.
372
378
  export const __assetPrefix = ${JSON.stringify(assetPrefix)};
373
379
  export const __inlineCss = ${JSON.stringify(inlineCss)};
380
+ export const __hasPagesDir = ${JSON.stringify(hasPagesDir)};
374
381
 
375
382
  export function seedMemoryCacheFromPrerender(serverDir) {
376
383
  return __seedMemoryCacheFromPrerender(serverDir, {
@@ -397,6 +404,13 @@ ${generateDevOriginCheckCode(config?.allowedDevOrigins)}
397
404
  */
398
405
  var __MAX_ACTION_BODY_SIZE = ${JSON.stringify(bodySizeLimit)};
399
406
 
407
+ /**
408
+ * Verbatim serverActions.bodySizeLimit config value (e.g. "2mb"), used in the
409
+ * "Body exceeded {limit} limit" error so the message matches Next.js byte-for-byte.
410
+ * Defaults to "1 MB" (Next.js' defaultBodySizeLimit literal).
411
+ */
412
+ var __MAX_ACTION_BODY_SIZE_LABEL = ${JSON.stringify(bodySizeLimitLabel)};
413
+
400
414
  // Map from route pattern to generateStaticParams function.
401
415
  // Used by the prerender phase to enumerate dynamic route URLs without
402
416
  // loading route modules via the dev server.
@@ -421,6 +435,7 @@ export default __createAppRscHandler({
421
435
  dispatchMatchedPage({
422
436
  clientReuseManifest,
423
437
  cleanPathname,
438
+ displayPathname,
424
439
  formState,
425
440
  actionError,
426
441
  actionFailed,
@@ -456,6 +471,7 @@ export default __createAppRscHandler({
456
471
  basePath: __basePath,
457
472
  ensureRouteLoaded: __ensureRouteLoaded,
458
473
  clientTraceMetadata: __clientTraceMetadata,
474
+ reactMaxHeadersLength: __reactMaxHeadersLength,
459
475
  buildPageElement(targetRoute, targetParams, targetOpts, targetSearchParams, layoutParamAccess) {
460
476
  return buildPageElements(targetRoute, targetParams, cleanPathname, {
461
477
  opts: targetOpts,
@@ -464,7 +480,7 @@ export default __createAppRscHandler({
464
480
  request,
465
481
  mountedSlotsHeader,
466
482
  renderMode,
467
- }, layoutParamAccess);
483
+ }, layoutParamAccess, displayPathname);
468
484
  },
469
485
  clientReuseManifest,
470
486
  cleanPathname,
@@ -527,13 +543,23 @@ export default __createAppRscHandler({
527
543
  route,
528
544
  });
529
545
  },
530
- probePage() {
546
+ async probePage() {
547
+ const __probeIntercept = findIntercept(cleanPathname, interceptionContext);
548
+ // The intercepting-route page module is lazy (page: null + __pageLoader).
549
+ // Resolve it before probing so buildAppPageProbes inspects the real page
550
+ // component for dynamic bailout — matching the render path, which also
551
+ // awaits __pageLoader (resolveAppPageInterceptState). Without this the
552
+ // intercept probe branch silently inspects an undefined component and
553
+ // never observes the page's searchParams/headers access.
554
+ if (__probeIntercept && __probeIntercept.__pageLoader && __probeIntercept.page == null) {
555
+ __probeIntercept.page = await __probeIntercept.__pageLoader();
556
+ }
531
557
  return Promise.all(__buildAppPageProbes({
532
558
  route,
533
559
  pageComponent: PageComponent,
534
560
  asyncRouteParams: _asyncRouteParams,
535
561
  searchParams,
536
- intercept: findIntercept(cleanPathname, interceptionContext),
562
+ intercept: __probeIntercept,
537
563
  isRscRequest,
538
564
  matchedParams: params,
539
565
  makeThenableParams,
@@ -583,6 +609,7 @@ export default __createAppRscHandler({
583
609
  },
584
610
  draftModeSecret: __draftModeSecret,
585
611
  i18n: __i18nConfig,
612
+ trailingSlash: __trailingSlash,
586
613
  isrDebug: __isrDebug,
587
614
  isrGet: __isrGet,
588
615
  isrRouteKey: __isrRouteKey,
@@ -610,6 +637,26 @@ export default __createAppRscHandler({
610
637
  middlewareContext,
611
638
  request,
612
639
  }) {
640
+ // A multipart form POST to a page is always a server-action attempt, so a
641
+ // body that decodes to no action must surface as 404 action-not-found
642
+ // (#1340). Route handlers run after this dispatch and accept raw multipart
643
+ // POSTs, so only flag actual page routes. The __loadPage / __loadRouteHandler
644
+ // markers are static and available before lazy module hydration.
645
+ //
646
+ // Only the progressive (multipart, no actionId) POST path consults
647
+ // hasPageRoute, so skip the route match entirely for every other request
648
+ // rather than re-matching on each App Router request.
649
+ const __isProgressiveAction = __isProgressiveServerActionRequest(
650
+ request,
651
+ contentType,
652
+ actionId,
653
+ );
654
+ const __progressiveActionMatch = __isProgressiveAction ? matchRoute(cleanPathname) : null;
655
+ const __hasPageRoute = Boolean(
656
+ __progressiveActionMatch &&
657
+ __progressiveActionMatch.route.__loadPage &&
658
+ !__progressiveActionMatch.route.__loadRouteHandler,
659
+ );
613
660
  return __handleProgressiveServerActionRequest({
614
661
  actionId,
615
662
  allowedOrigins: __allowedOrigins,
@@ -623,6 +670,7 @@ export default __createAppRscHandler({
623
670
  decodeFormState,
624
671
  getAndClearPendingCookies,
625
672
  getDraftModeCookieHeader,
673
+ hasPageRoute: __hasPageRoute,
626
674
  maxActionBodySize: __MAX_ACTION_BODY_SIZE,
627
675
  middlewareHeaders: middlewareContext.headers,
628
676
  readFormDataWithLimit: __readFormDataWithLimit,
@@ -713,6 +761,7 @@ export default __createAppRscHandler({
713
761
  return matchRoute(pathnameToMatch);
714
762
  },
715
763
  maxActionBodySize: __MAX_ACTION_BODY_SIZE,
764
+ maxActionBodySizeLabel: __MAX_ACTION_BODY_SIZE_LABEL,
716
765
  middlewareHeaders: middlewareContext.headers,
717
766
  middlewareStatus: middlewareContext.status,
718
767
  mountedSlotsHeader,
@@ -763,6 +812,7 @@ export default __createAppRscHandler({
763
812
  buildRequestHeaders: __buildRequestHeadersFromMiddlewareResponse,
764
813
  decodePathParams: __decodePathParams,
765
814
  applyRouteHandlerMiddlewareContext: __applyRouteHandlerMiddlewareContext,
815
+ getDraftModeCookieHeader,
766
816
  }
767
817
  );
768
818
  },` : ""}
@@ -77,10 +77,14 @@ function registerRouteModules(routes, imports) {
77
77
  if (slot.loadingPath) imports.getImportVar(slot.loadingPath);
78
78
  if (slot.errorPath) imports.getImportVar(slot.errorPath);
79
79
  for (const ir of slot.interceptingRoutes) {
80
- imports.getImportVar(ir.pagePath);
80
+ imports.getLazyLoaderVar(ir.pagePath);
81
81
  for (const layoutPath of ir.layoutPaths) imports.getImportVar(layoutPath);
82
82
  }
83
83
  }
84
+ for (const ir of route.siblingIntercepts ?? []) {
85
+ imports.getLazyLoaderVar(ir.pagePath);
86
+ for (const layoutPath of ir.layoutPaths) imports.getImportVar(layoutPath);
87
+ }
84
88
  }
85
89
  }
86
90
  function buildRouteEntries(routes, imports) {
@@ -91,13 +95,24 @@ function buildRouteEntries(routes, imports) {
91
95
  const notFoundVars = (route.notFoundPaths ?? []).map((nf) => nf ? imports.getImportVar(nf) : "null");
92
96
  const forbiddenVars = (route.forbiddenPaths ?? []).map((fp) => fp ? imports.getImportVar(fp) : "null");
93
97
  const unauthorizedVars = (route.unauthorizedPaths ?? []).map((up) => up ? imports.getImportVar(up) : "null");
98
+ const siblingInterceptEntries = (route.siblingIntercepts ?? []).map((ir) => ` {
99
+ convention: ${JSON.stringify(ir.convention)},
100
+ targetPattern: ${JSON.stringify(ir.targetPattern)},
101
+ sourceMatchPattern: ${JSON.stringify(ir.sourceMatchPattern)},
102
+ slotId: ${JSON.stringify(ir.slotId ?? null)},
103
+ interceptLayouts: [${ir.layoutPaths.map((l) => imports.getImportVar(l)).join(", ")}],
104
+ page: null,
105
+ __pageLoader: ${imports.getLazyLoaderVar(ir.pagePath)},
106
+ params: ${JSON.stringify(ir.params)},
107
+ }`);
94
108
  const slotEntries = route.parallelSlots.map((slot) => {
95
109
  const interceptEntries = slot.interceptingRoutes.map((ir) => ` {
96
110
  convention: ${JSON.stringify(ir.convention)},
97
111
  targetPattern: ${JSON.stringify(ir.targetPattern)},
98
112
  sourceMatchPattern: ${JSON.stringify(ir.sourceMatchPattern)},
99
113
  interceptLayouts: [${ir.layoutPaths.map((layoutPath) => imports.getImportVar(layoutPath)).join(", ")}],
100
- page: ${imports.getImportVar(ir.pagePath)},
114
+ page: null,
115
+ __pageLoader: ${imports.getLazyLoaderVar(ir.pagePath)},
101
116
  params: ${JSON.stringify(ir.params)},
102
117
  }`);
103
118
  return ` ${JSON.stringify(slot.key)}: {
@@ -146,6 +161,9 @@ ${interceptEntries.join(",\n")}
146
161
  slots: {
147
162
  ${slotEntries.join(",\n")}
148
163
  },
164
+ siblingIntercepts: [
165
+ ${siblingInterceptEntries.join(",\n")}
166
+ ],
149
167
  loading: ${route.loadingPath ? imports.getImportVar(route.loadingPath) : "null"},
150
168
  error: ${route.errorPath ? imports.getImportVar(route.errorPath) : "null"},
151
169
  notFound: ${route.notFoundPath ? imports.getImportVar(route.notFoundPath) : "null"},
@@ -143,7 +143,7 @@ import React from "react";
143
143
  import { renderToReadableStream } from "react-dom/server.edge";
144
144
  import { resetSSRHead, getSSRHeadHTML, setDocumentInitialHead } from "next/head";
145
145
  import { flushPreloads } from "next/dynamic";
146
- import { setSSRContext, wrapWithRouterContext } from "next/router";
146
+ import { setSSRContext, wrapWithRouterContext, getPagesNavigationIsReadyFromSerializedState } from "next/router";
147
147
  import { _runWithCacheState, configureMemoryCacheHandler as __configureMemoryCacheHandler } from "next/cache";
148
148
  import { registerConfiguredCacheAdapters as __registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
149
149
  import { runWithPrivateCache } from "vinext/cache-runtime";
@@ -287,9 +287,17 @@ const _renderPage = __createPagesPageHandler({
287
287
  buildId,
288
288
  hasMiddleware: __hasMiddleware,
289
289
  appAssetPath: _appAssetPath,
290
+ hasRewrites:
291
+ vinextConfig.rewrites.beforeFiles.length > 0 ||
292
+ vinextConfig.rewrites.afterFiles.length > 0 ||
293
+ vinextConfig.rewrites.fallback.length > 0,
290
294
 
291
295
  // next/*-derived closures
292
296
  setSSRContext: typeof setSSRContext === "function" ? setSSRContext : null,
297
+ getPagesNavigationIsReadyFromSerializedState:
298
+ typeof getPagesNavigationIsReadyFromSerializedState === "function"
299
+ ? getPagesNavigationIsReadyFromSerializedState
300
+ : null,
293
301
  setI18nContext: typeof setI18nContext === "function" ? setI18nContext : null,
294
302
  wrapWithRouterContext: typeof wrapWithRouterContext === "function" ? wrapWithRouterContext : null,
295
303
  resetSSRHead: typeof resetSSRHead === "function" ? resetSSRHead : undefined,