vinext 0.0.24 → 0.0.25

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 (92) hide show
  1. package/README.md +24 -0
  2. package/dist/check.d.ts.map +1 -1
  3. package/dist/check.js +3 -2
  4. package/dist/check.js.map +1 -1
  5. package/dist/client/entry.js +1 -1
  6. package/dist/client/entry.js.map +1 -1
  7. package/dist/config/config-matchers.d.ts +21 -0
  8. package/dist/config/config-matchers.d.ts.map +1 -1
  9. package/dist/config/config-matchers.js +46 -6
  10. package/dist/config/config-matchers.js.map +1 -1
  11. package/dist/config/next-config.d.ts +8 -2
  12. package/dist/config/next-config.d.ts.map +1 -1
  13. package/dist/config/next-config.js +90 -35
  14. package/dist/config/next-config.js.map +1 -1
  15. package/dist/deploy.d.ts +10 -0
  16. package/dist/deploy.d.ts.map +1 -1
  17. package/dist/deploy.js +70 -35
  18. package/dist/deploy.js.map +1 -1
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +172 -18
  22. package/dist/index.js.map +1 -1
  23. package/dist/plugins/async-hooks-stub.d.ts +16 -0
  24. package/dist/plugins/async-hooks-stub.d.ts.map +1 -0
  25. package/dist/plugins/async-hooks-stub.js +45 -0
  26. package/dist/plugins/async-hooks-stub.js.map +1 -0
  27. package/dist/routing/app-router.d.ts +12 -6
  28. package/dist/routing/app-router.d.ts.map +1 -1
  29. package/dist/routing/app-router.js +19 -40
  30. package/dist/routing/app-router.js.map +1 -1
  31. package/dist/routing/pages-router.d.ts.map +1 -1
  32. package/dist/routing/pages-router.js +3 -9
  33. package/dist/routing/pages-router.js.map +1 -1
  34. package/dist/routing/utils.d.ts +9 -0
  35. package/dist/routing/utils.d.ts.map +1 -1
  36. package/dist/routing/utils.js +10 -0
  37. package/dist/routing/utils.js.map +1 -1
  38. package/dist/server/api-handler.d.ts.map +1 -1
  39. package/dist/server/api-handler.js +6 -0
  40. package/dist/server/api-handler.js.map +1 -1
  41. package/dist/server/app-dev-server.d.ts +2 -2
  42. package/dist/server/app-dev-server.d.ts.map +1 -1
  43. package/dist/server/app-dev-server.js +238 -114
  44. package/dist/server/app-dev-server.js.map +1 -1
  45. package/dist/server/dev-module-runner.d.ts +84 -0
  46. package/dist/server/dev-module-runner.d.ts.map +1 -0
  47. package/dist/server/dev-module-runner.js +105 -0
  48. package/dist/server/dev-module-runner.js.map +1 -0
  49. package/dist/server/dev-server.js.map +1 -1
  50. package/dist/server/instrumentation.d.ts +52 -9
  51. package/dist/server/instrumentation.d.ts.map +1 -1
  52. package/dist/server/instrumentation.js +52 -15
  53. package/dist/server/instrumentation.js.map +1 -1
  54. package/dist/server/middleware.d.ts +7 -3
  55. package/dist/server/middleware.d.ts.map +1 -1
  56. package/dist/server/middleware.js +16 -6
  57. package/dist/server/middleware.js.map +1 -1
  58. package/dist/server/prod-server.d.ts.map +1 -1
  59. package/dist/server/prod-server.js +15 -25
  60. package/dist/server/prod-server.js.map +1 -1
  61. package/dist/shims/cache.d.ts.map +1 -1
  62. package/dist/shims/cache.js +14 -2
  63. package/dist/shims/cache.js.map +1 -1
  64. package/dist/shims/fetch-cache.d.ts.map +1 -1
  65. package/dist/shims/fetch-cache.js +139 -29
  66. package/dist/shims/fetch-cache.js.map +1 -1
  67. package/dist/shims/form.d.ts.map +1 -1
  68. package/dist/shims/form.js +2 -3
  69. package/dist/shims/form.js.map +1 -1
  70. package/dist/shims/layout-segment-context.d.ts +5 -4
  71. package/dist/shims/layout-segment-context.d.ts.map +1 -1
  72. package/dist/shims/layout-segment-context.js +6 -5
  73. package/dist/shims/layout-segment-context.js.map +1 -1
  74. package/dist/shims/link.d.ts.map +1 -1
  75. package/dist/shims/link.js +32 -17
  76. package/dist/shims/link.js.map +1 -1
  77. package/dist/shims/navigation.d.ts +14 -11
  78. package/dist/shims/navigation.d.ts.map +1 -1
  79. package/dist/shims/navigation.js +122 -102
  80. package/dist/shims/navigation.js.map +1 -1
  81. package/dist/shims/router.d.ts.map +1 -1
  82. package/dist/shims/router.js +37 -21
  83. package/dist/shims/router.js.map +1 -1
  84. package/dist/shims/server.d.ts +2 -0
  85. package/dist/shims/server.d.ts.map +1 -1
  86. package/dist/shims/server.js +4 -0
  87. package/dist/shims/server.js.map +1 -1
  88. package/dist/shims/url-utils.d.ts +13 -0
  89. package/dist/shims/url-utils.d.ts.map +1 -0
  90. package/dist/shims/url-utils.js +28 -0
  91. package/dist/shims/url-utils.js.map +1 -0
  92. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { appRouter, invalidateAppRouteCache } from "./routing/app-router.js";
4
4
  import { createValidFileMatcher } from "./routing/file-matcher.js";
5
5
  import { createSSRHandler } from "./server/dev-server.js";
6
6
  import { handleApiRoute } from "./server/api-handler.js";
7
+ import { createDirectRunner } from "./server/dev-module-runner.js";
7
8
  import { generateRscEntry, generateSsrEntry, generateBrowserEntry, } from "./server/app-dev-server.js";
8
9
  import { loadNextConfig, resolveNextConfig, } from "./config/next-config.js";
9
10
  import { findMiddlewareFile, isProxyFile, runMiddleware } from "./server/middleware.js";
@@ -16,6 +17,7 @@ import { validateDevRequest } from "./server/dev-origin-check.js";
16
17
  import { safeRegExp, isExternalUrl, proxyExternalRequest, parseCookies, matchHeaders, matchRedirect, matchRewrite, } from "./config/config-matchers.js";
17
18
  import { scanMetadataFiles } from "./server/metadata-routes.js";
18
19
  import { detectPackageManager } from "./utils/project.js";
20
+ import { asyncHooksStubPlugin } from "./plugins/async-hooks-stub.js";
19
21
  import tsconfigPaths from "vite-tsconfig-paths";
20
22
  import react from "@vitejs/plugin-react";
21
23
  import MagicString from "magic-string";
@@ -567,10 +569,35 @@ export default function vinext(options = {}) {
567
569
  contentSecurityPolicy: nextConfig?.images?.contentSecurityPolicy,
568
570
  },
569
571
  });
572
+ // Generate instrumentation code if instrumentation.ts exists.
573
+ // For production (Cloudflare Workers), instrumentation.ts is bundled into the
574
+ // Worker and register() is called as a top-level await at module evaluation time —
575
+ // before any request is handled. This mirrors App Router behavior (generateRscEntry)
576
+ // and matches Next.js semantics: register() runs once on startup in the process
577
+ // that handles requests.
578
+ //
579
+ // The onRequestError handler is stored on globalThis so it is visible across
580
+ // all code within the Worker (same global scope).
581
+ const instrumentationImportCode = instrumentationPath
582
+ ? `import * as _instrumentation from ${JSON.stringify(instrumentationPath.replace(/\\/g, "/"))};`
583
+ : "";
584
+ const instrumentationInitCode = instrumentationPath
585
+ ? `// Run instrumentation register() once at module evaluation time — before any
586
+ // requests are handled. Matches Next.js semantics: register() is called once
587
+ // on startup in the process that handles requests.
588
+ if (typeof _instrumentation.register === "function") {
589
+ await _instrumentation.register();
590
+ }
591
+ // Store the onRequestError handler on globalThis so it is visible to all
592
+ // code within the Worker (same global scope).
593
+ if (typeof _instrumentation.onRequestError === "function") {
594
+ globalThis.__VINEXT_onRequestErrorHandler__ = _instrumentation.onRequestError;
595
+ }`
596
+ : "";
570
597
  // Generate middleware code if middleware.ts exists
571
598
  const middlewareImportCode = middlewarePath
572
599
  ? `import * as middlewareModule from ${JSON.stringify(middlewarePath.replace(/\\/g, "/"))};
573
- import { NextRequest } from "next/server";`
600
+ import { NextRequest, NextFetchEvent } from "next/server";`
574
601
  : "";
575
602
  // The matcher config is read from the middleware module at import time.
576
603
  // We inline the matching + execution logic so the prod server can call it.
@@ -581,7 +608,7 @@ ${generateNormalizePathCode("es5")}
581
608
  ${generateSafeRegExpCode("es5")}
582
609
  ${generateMiddlewareMatcherCode("es5")}
583
610
 
584
- export async function runMiddleware(request) {
611
+ export async function runMiddleware(request, ctx) {
585
612
  var isProxy = ${middlewarePath ? JSON.stringify(isProxyFile(middlewarePath)) : "false"};
586
613
  var middlewareFn = isProxy
587
614
  ? (middlewareModule.proxy ?? middlewareModule.default)
@@ -615,12 +642,14 @@ export async function runMiddleware(request) {
615
642
  mwRequest = new Request(mwUrl, request);
616
643
  }
617
644
  var nextRequest = mwRequest instanceof NextRequest ? mwRequest : new NextRequest(mwRequest);
645
+ var fetchEvent = new NextFetchEvent({ page: normalizedPathname });
618
646
  var response;
619
- try { response = await middlewareFn(nextRequest); }
647
+ try { response = await middlewareFn(nextRequest, fetchEvent); }
620
648
  catch (e) {
621
649
  console.error("[vinext] Middleware error:", e);
622
650
  return { continue: false, response: new Response("Internal Server Error", { status: 500 }) };
623
651
  }
652
+ if (ctx && typeof ctx.waitUntil === "function") { ctx.waitUntil(fetchEvent.drainWaitUntil()); } else { fetchEvent.drainWaitUntil(); }
624
653
 
625
654
  if (!response) return { continue: true };
626
655
 
@@ -678,8 +707,11 @@ import { runWithHeadState } from "vinext/head-state";
678
707
  import { safeJsonStringify } from "vinext/html";
679
708
  import { getSSRFontLinks as _getSSRFontLinks, getSSRFontStyles as _getSSRFontStylesGoogle, getSSRFontPreloads as _getSSRFontPreloadsGoogle } from "next/font/google";
680
709
  import { getSSRFontStyles as _getSSRFontStylesLocal, getSSRFontPreloads as _getSSRFontPreloadsLocal } from "next/font/local";
710
+ ${instrumentationImportCode}
681
711
  ${middlewareImportCode}
682
712
 
713
+ ${instrumentationInitCode}
714
+
683
715
  // i18n config (embedded at build time)
684
716
  const i18nConfig = ${i18nConfigJson};
685
717
 
@@ -1677,7 +1709,7 @@ hydrate();
1677
1709
  // Load next.config.js if present (always from project root, not src/)
1678
1710
  const phase = env?.command === "build" ? PHASE_PRODUCTION_BUILD : PHASE_DEVELOPMENT_SERVER;
1679
1711
  const rawConfig = await loadNextConfig(root, phase);
1680
- nextConfig = await resolveNextConfig(rawConfig);
1712
+ nextConfig = await resolveNextConfig(rawConfig, root);
1681
1713
  fileMatcher = createValidFileMatcher(nextConfig.pageExtensions);
1682
1714
  // Merge env from next.config.js with NEXT_PUBLIC_* env vars
1683
1715
  const defines = getNextPublicEnvDefines();
@@ -1717,6 +1749,7 @@ hydrate();
1717
1749
  // Build the shim alias map — used by both resolve.alias and resolveId
1718
1750
  // (resolveId handles .js extension variants for libraries like nuqs)
1719
1751
  nextShimMap = {
1752
+ ...nextConfig.aliases,
1720
1753
  "next/link": path.join(shimsDir, "link"),
1721
1754
  "next/head": path.join(shimsDir, "head"),
1722
1755
  "next/router": path.join(shimsDir, "router"),
@@ -1894,12 +1927,21 @@ hydrate();
1894
1927
  origin: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
1895
1928
  },
1896
1929
  },
1897
- // Externalize React packages from SSR transform they are CJS and
1898
- // must be loaded natively by Node, not through Vite's ESM evaluator.
1930
+ // Configure SSR transform behaviour for Node targets.
1931
+ // - `external`: React packages are loaded natively by Node (CJS)
1932
+ // rather than through Vite's ESM evaluator.
1933
+ // - `noExternal: true`: force everything else through Vite's
1934
+ // transform pipeline so non-JS imports (CSS, images) from
1935
+ // node_modules don't hit Node's native ESM loader.
1936
+ // Any user-provided `ssr.noExternal` is intentionally superseded
1937
+ // by this setting; only `ssr.external` entries escape Vite's transform.
1899
1938
  // Skip when targeting bundled runtimes (Cloudflare/Nitro bundle everything).
1939
+ // This also resolves extensionless-import issues in packages like
1940
+ // `validator` (see #189) by routing them through Vite's resolver.
1900
1941
  ...(hasCloudflarePlugin || hasNitroPlugin ? {} : {
1901
1942
  ssr: {
1902
1943
  external: ["react", "react-dom", "react-dom/server"],
1944
+ noExternal: true,
1903
1945
  },
1904
1946
  }),
1905
1947
  resolve: {
@@ -1934,6 +1976,17 @@ hydrate();
1934
1976
  // Inject resolved PostCSS plugins if string names were found
1935
1977
  ...(postcssOverride ? { css: { postcss: postcssOverride } } : {}),
1936
1978
  };
1979
+ // Collect user-provided ssr.external so we can propagate it into
1980
+ // both the RSC and SSR environment configs. Vite's `ssr.*` config
1981
+ // only applies to the default `ssr` environment, not custom ones
1982
+ // like `rsc`. Native addon packages (e.g. better-sqlite3) listed
1983
+ // in ssr.external must be externalized from ALL server environments.
1984
+ // Vite's SSROptions.external is `string[] | true`; handle both forms.
1985
+ const userSsrExternal = Array.isArray(config.ssr?.external)
1986
+ ? config.ssr.external
1987
+ : config.ssr?.external === true
1988
+ ? true
1989
+ : [];
1937
1990
  // If app/ directory exists, configure RSC environments
1938
1991
  if (hasAppDir) {
1939
1992
  // Compute optimizeDeps.entries so Vite discovers server-side
@@ -1956,11 +2009,19 @@ hydrate();
1956
2009
  // Note: Do NOT externalize react/react-dom here — they must
1957
2010
  // be bundled with the "react-server" condition for RSC.
1958
2011
  // Skip when targeting bundled runtimes (Cloudflare/Nitro).
1959
- external: [
2012
+ external: userSsrExternal === true ? true : [
1960
2013
  "satori",
1961
2014
  "@resvg/resvg-js",
1962
2015
  "yoga-wasm-web",
2016
+ ...userSsrExternal,
1963
2017
  ],
2018
+ // Force all node_modules through Vite's transform pipeline
2019
+ // so non-JS imports (CSS, images) don't hit Node's native
2020
+ // ESM loader. Matches Next.js behavior of bundling everything.
2021
+ // Packages in `external` above take precedence per Vite rules.
2022
+ // When user sets `ssr.external: true`, skip noExternal since
2023
+ // everything is already externalized.
2024
+ ...(userSsrExternal === true ? {} : { noExternal: true }),
1964
2025
  },
1965
2026
  }),
1966
2027
  optimizeDeps: {
@@ -1975,6 +2036,17 @@ hydrate();
1975
2036
  },
1976
2037
  },
1977
2038
  ssr: {
2039
+ ...(hasCloudflarePlugin || hasNitroPlugin ? {} : {
2040
+ resolve: {
2041
+ external: userSsrExternal === true ? true : [...userSsrExternal],
2042
+ // Force all node_modules through Vite's transform pipeline
2043
+ // so non-JS imports (CSS, images) don't hit Node's native
2044
+ // ESM loader. Matches Next.js behavior of bundling everything.
2045
+ // When user sets `ssr.external: true`, skip noExternal since
2046
+ // everything is already externalized.
2047
+ ...(userSsrExternal === true ? {} : { noExternal: true }),
2048
+ },
2049
+ }),
1978
2050
  optimizeDeps: {
1979
2051
  exclude: ["vinext"],
1980
2052
  entries: appEntries,
@@ -2138,8 +2210,8 @@ hydrate();
2138
2210
  rewrites: nextConfig?.rewrites,
2139
2211
  headers: nextConfig?.headers,
2140
2212
  allowedOrigins: nextConfig?.serverActionsAllowedOrigins,
2141
- allowedDevOrigins: nextConfig?.serverActionsAllowedOrigins,
2142
- });
2213
+ allowedDevOrigins: nextConfig?.allowedDevOrigins,
2214
+ }, instrumentationPath);
2143
2215
  }
2144
2216
  if (id === RESOLVED_APP_SSR_ENTRY && hasAppDir) {
2145
2217
  return generateSsrEntry();
@@ -2149,6 +2221,8 @@ hydrate();
2149
2221
  }
2150
2222
  },
2151
2223
  },
2224
+ // Stub node:async_hooks in client builds — see src/plugins/async-hooks-stub.ts
2225
+ asyncHooksStubPlugin,
2152
2226
  // Proxy plugin for @mdx-js/rollup. The real MDX plugin is created lazily
2153
2227
  // during vinext:config's config() (when MDX files are detected), but
2154
2228
  // plugins returned from config() hooks run too late in the pipeline —
@@ -2242,6 +2316,32 @@ hydrate();
2242
2316
  configureServer(server) {
2243
2317
  // Watch pages directory for file additions/removals to invalidate route cache.
2244
2318
  const pageExtensions = fileMatcher.extensionRegex;
2319
+ // Build a long-lived ModuleRunner for loading all Pages Router modules
2320
+ // (middleware, API routes, SSR page rendering) on every request.
2321
+ //
2322
+ // We must NOT use server.ssrLoadModule() here: when @cloudflare/vite-plugin
2323
+ // is present its environments replace the SSR transport, causing
2324
+ // SSRCompatModuleRunner to crash with:
2325
+ // TypeError: Cannot read properties of undefined (reading 'outsideEmitter')
2326
+ // on the very first request.
2327
+ //
2328
+ // createDirectRunner() builds a runner on environment.fetchModule() which
2329
+ // is a plain async method — safe with all plugin combinations, including
2330
+ // @cloudflare/vite-plugin.
2331
+ //
2332
+ // The runner is created lazily on first use so that all environments are
2333
+ // fully registered before we inspect them. We prefer "ssr", then any
2334
+ // non-"rsc" environment, then whatever is available.
2335
+ let pagesRunner = null;
2336
+ function getPagesRunner() {
2337
+ if (!pagesRunner) {
2338
+ const env = server.environments["ssr"] ??
2339
+ Object.values(server.environments).find((e) => e !== server.environments["rsc"]) ??
2340
+ Object.values(server.environments)[0];
2341
+ pagesRunner = createDirectRunner(env);
2342
+ }
2343
+ return pagesRunner;
2344
+ }
2245
2345
  /**
2246
2346
  * Invalidate the virtual RSC entry module in Vite's module graph.
2247
2347
  *
@@ -2291,7 +2391,7 @@ hydrate();
2291
2391
  "x-forwarded-host": req.headers["x-forwarded-host"],
2292
2392
  "sec-fetch-site": req.headers["sec-fetch-site"],
2293
2393
  "sec-fetch-mode": req.headers["sec-fetch-mode"],
2294
- }, nextConfig?.serverActionsAllowedOrigins);
2394
+ }, nextConfig?.allowedDevOrigins);
2295
2395
  if (blockReason) {
2296
2396
  console.warn(`[vinext] Blocked dev request: ${blockReason} (${req.url})`);
2297
2397
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -2303,11 +2403,28 @@ hydrate();
2303
2403
  // Return a function to register middleware AFTER Vite's built-in middleware
2304
2404
  return () => {
2305
2405
  // Run instrumentation.ts register() if present (once at server startup).
2306
- // Must be inside the returned function ssrLoadModule() requires the
2307
- // SSR environment's transport channel, which is not initialized until
2308
- // after configureServer() returns. (See issue #167)
2309
- if (instrumentationPath) {
2310
- runInstrumentation(server, instrumentationPath).catch((err) => {
2406
+ // Must be inside the returned function so that all environments are
2407
+ // fully registered before getPagesRunner() inspects them.
2408
+ //
2409
+ // App Router: register() is baked into the generated RSC entry as a
2410
+ // top-level await, so it runs inside the Worker process (or RSC Vite
2411
+ // environment) — the same process as request handling. Calling
2412
+ // runInstrumentation() here too would run it a second time in the host
2413
+ // process, which is wrong when @cloudflare/vite-plugin is present.
2414
+ //
2415
+ // Pages Router prod: register() is baked into generateServerEntry() as
2416
+ // a top-level await, so it runs inside the Worker bundle — the same
2417
+ // process as request handling. configureServer() is never called during
2418
+ // a prod build, so there is no double-invocation risk there either.
2419
+ //
2420
+ // We pass getPagesRunner() (createDirectRunner) rather than server so
2421
+ // that this is safe when @cloudflare/vite-plugin is present. That
2422
+ // plugin replaces the SSR environment's hot channel, causing
2423
+ // server.ssrLoadModule() to crash with outsideEmitter. The runner
2424
+ // calls environment.fetchModule() directly and never touches the hot
2425
+ // channel, making it safe with all Vite plugin combinations.
2426
+ if (instrumentationPath && !hasAppDir) {
2427
+ runInstrumentation(getPagesRunner(), instrumentationPath).catch((err) => {
2311
2428
  console.error("[vinext] Instrumentation error:", err);
2312
2429
  });
2313
2430
  }
@@ -2446,7 +2563,7 @@ hydrate();
2446
2563
  "x-forwarded-host": req.headers["x-forwarded-host"],
2447
2564
  "sec-fetch-site": req.headers["sec-fetch-site"],
2448
2565
  "sec-fetch-mode": req.headers["sec-fetch-mode"],
2449
- }, nextConfig?.serverActionsAllowedOrigins);
2566
+ }, nextConfig?.allowedDevOrigins);
2450
2567
  if (blockReason) {
2451
2568
  console.warn(`[vinext] Blocked dev request: ${blockReason} (${url})`);
2452
2569
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -2575,7 +2692,7 @@ hydrate();
2575
2692
  .filter(([, v]) => v !== undefined)
2576
2693
  .map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v)])),
2577
2694
  });
2578
- const result = await runMiddleware(server, middlewarePath, middlewareRequest);
2695
+ const result = await runMiddleware(getPagesRunner(), middlewarePath, middlewareRequest);
2579
2696
  if (!result.continue) {
2580
2697
  if (result.redirectUrl) {
2581
2698
  const redirectHeaders = { Location: result.redirectUrl };
@@ -2626,11 +2743,41 @@ hydrate();
2626
2743
  // Apply middleware rewrite (URL and optional status code)
2627
2744
  if (result.rewriteUrl) {
2628
2745
  url = result.rewriteUrl;
2746
+ // Write the rewritten URL back onto req.url so every subsequent
2747
+ // handler in the connect chain sees the correct path. The local
2748
+ // `url` variable is only visible within this handler — anything
2749
+ // further down the chain (Vite's built-in middleware, the
2750
+ // Cloudflare plugin's handler, or any other connect middleware)
2751
+ // reads req.url directly. Without this, a middleware rewrite
2752
+ // would be invisible to those handlers and the original URL
2753
+ // would be dispatched instead.
2754
+ req.url = url;
2629
2755
  }
2630
2756
  if (result.rewriteStatus) {
2631
2757
  req.__vinextRewriteStatus = result.rewriteStatus;
2632
2758
  }
2633
2759
  }
2760
+ // ── Cloudflare Workers dev mode ────────────────────────────
2761
+ // When @cloudflare/vite-plugin is present, ALL rendering runs
2762
+ // inside the miniflare Worker subprocess — both App Router (via
2763
+ // virtual:vinext-rsc-entry) and Pages Router (via
2764
+ // virtual:vinext-server-entry → renderPage/handleApiRoute).
2765
+ //
2766
+ // The Worker entry already handles config redirects, rewrites,
2767
+ // headers, and all routing internally. Running them here too
2768
+ // would duplicate that logic and produce incorrect behaviour
2769
+ // (double redirects, headers set on the wrong object, etc.).
2770
+ //
2771
+ // Middleware.ts is the only thing that belongs in the host connect
2772
+ // handler — it has already run above. Any terminal middleware
2773
+ // result (redirect, block response) has already been sent.
2774
+ // Any rewrite has been written back to req.url above so the
2775
+ // Cloudflare plugin's handler sees the correct path.
2776
+ //
2777
+ // Call next() to hand off to the Cloudflare plugin's connect
2778
+ // handler, which dispatches the request to miniflare.
2779
+ if (hasCloudflarePlugin)
2780
+ return next();
2634
2781
  // Build request context once for has/missing condition checks
2635
2782
  // across headers, redirects, and rewrites.
2636
2783
  const reqUrl = new URL(url, `http://${req.headers.host || "localhost"}`);
@@ -3767,6 +3914,8 @@ function applyRewrites(pathname, rewrites, ctx) {
3767
3914
  }
3768
3915
  /**
3769
3916
  * Apply custom header rules from next.config.js.
3917
+ * Middleware headers take precedence: if a header key was already set on the
3918
+ * response (by middleware), the config value is skipped for that key.
3770
3919
  */
3771
3920
  function applyHeaders(pathname, res, headers, ctx) {
3772
3921
  const matched = matchHeaders(pathname, headers, ctx);
@@ -3799,7 +3948,11 @@ function applyHeaders(pathname, res, headers, ctx) {
3799
3948
  }
3800
3949
  }
3801
3950
  else {
3802
- res.setHeader(header.key, header.value);
3951
+ // Middleware headers take precedence: skip config keys already set by
3952
+ // middleware so middleware always wins over next.config.js headers.
3953
+ if (!res.getHeader(lk)) {
3954
+ res.setHeader(header.key, header.value);
3955
+ }
3803
3956
  }
3804
3957
  }
3805
3958
  }
@@ -3854,4 +4007,5 @@ export { clientManualChunks, clientOutputConfig, clientTreeshakeConfig, computeL
3854
4007
  export { resolvePostcssStringPlugins as _resolvePostcssStringPlugins };
3855
4008
  export { parseStaticObjectLiteral as _parseStaticObjectLiteral };
3856
4009
  export { stripServerExports as _stripServerExports };
4010
+ export { asyncHooksStubPlugin as _asyncHooksStubPlugin };
3857
4011
  //# sourceMappingURL=index.js.map