vinext 0.0.23 → 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 (105) 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 +59 -9
  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 +140 -56
  18. package/dist/deploy.js.map +1 -1
  19. package/dist/index.d.ts +14 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +195 -25
  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 +294 -133
  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 +8 -2
  59. package/dist/server/prod-server.d.ts.map +1 -1
  60. package/dist/server/prod-server.js +56 -35
  61. package/dist/server/prod-server.js.map +1 -1
  62. package/dist/server/worker-utils.d.ts +15 -0
  63. package/dist/server/worker-utils.d.ts.map +1 -0
  64. package/dist/server/worker-utils.js +41 -0
  65. package/dist/server/worker-utils.js.map +1 -0
  66. package/dist/shims/cache.d.ts.map +1 -1
  67. package/dist/shims/cache.js +14 -2
  68. package/dist/shims/cache.js.map +1 -1
  69. package/dist/shims/fetch-cache.d.ts.map +1 -1
  70. package/dist/shims/fetch-cache.js +139 -29
  71. package/dist/shims/fetch-cache.js.map +1 -1
  72. package/dist/shims/form.d.ts.map +1 -1
  73. package/dist/shims/form.js +2 -3
  74. package/dist/shims/form.js.map +1 -1
  75. package/dist/shims/headers.d.ts +6 -0
  76. package/dist/shims/headers.d.ts.map +1 -1
  77. package/dist/shims/headers.js +8 -0
  78. package/dist/shims/headers.js.map +1 -1
  79. package/dist/shims/layout-segment-context.d.ts +5 -4
  80. package/dist/shims/layout-segment-context.d.ts.map +1 -1
  81. package/dist/shims/layout-segment-context.js +6 -5
  82. package/dist/shims/layout-segment-context.js.map +1 -1
  83. package/dist/shims/link.d.ts.map +1 -1
  84. package/dist/shims/link.js +32 -17
  85. package/dist/shims/link.js.map +1 -1
  86. package/dist/shims/navigation.d.ts +14 -11
  87. package/dist/shims/navigation.d.ts.map +1 -1
  88. package/dist/shims/navigation.js +122 -102
  89. package/dist/shims/navigation.js.map +1 -1
  90. package/dist/shims/router.d.ts.map +1 -1
  91. package/dist/shims/router.js +37 -21
  92. package/dist/shims/router.js.map +1 -1
  93. package/dist/shims/server.d.ts +2 -0
  94. package/dist/shims/server.d.ts.map +1 -1
  95. package/dist/shims/server.js +4 -0
  96. package/dist/shims/server.js.map +1 -1
  97. package/dist/shims/url-utils.d.ts +13 -0
  98. package/dist/shims/url-utils.d.ts.map +1 -0
  99. package/dist/shims/url-utils.js +28 -0
  100. package/dist/shims/url-utils.js.map +1 -0
  101. package/dist/utils/project.d.ts +13 -1
  102. package/dist/utils/project.d.ts.map +1 -1
  103. package/dist/utils/project.js +63 -13
  104. package/dist/utils/project.js.map +1 -1
  105. package/package.json +6 -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,7 +17,9 @@ 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";
22
+ import react from "@vitejs/plugin-react";
20
23
  import MagicString from "magic-string";
21
24
  import path from "node:path";
22
25
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -566,10 +569,35 @@ export default function vinext(options = {}) {
566
569
  contentSecurityPolicy: nextConfig?.images?.contentSecurityPolicy,
567
570
  },
568
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
+ : "";
569
597
  // Generate middleware code if middleware.ts exists
570
598
  const middlewareImportCode = middlewarePath
571
599
  ? `import * as middlewareModule from ${JSON.stringify(middlewarePath.replace(/\\/g, "/"))};
572
- import { NextRequest } from "next/server";`
600
+ import { NextRequest, NextFetchEvent } from "next/server";`
573
601
  : "";
574
602
  // The matcher config is read from the middleware module at import time.
575
603
  // We inline the matching + execution logic so the prod server can call it.
@@ -580,7 +608,7 @@ ${generateNormalizePathCode("es5")}
580
608
  ${generateSafeRegExpCode("es5")}
581
609
  ${generateMiddlewareMatcherCode("es5")}
582
610
 
583
- export async function runMiddleware(request) {
611
+ export async function runMiddleware(request, ctx) {
584
612
  var isProxy = ${middlewarePath ? JSON.stringify(isProxyFile(middlewarePath)) : "false"};
585
613
  var middlewareFn = isProxy
586
614
  ? (middlewareModule.proxy ?? middlewareModule.default)
@@ -614,12 +642,14 @@ export async function runMiddleware(request) {
614
642
  mwRequest = new Request(mwUrl, request);
615
643
  }
616
644
  var nextRequest = mwRequest instanceof NextRequest ? mwRequest : new NextRequest(mwRequest);
645
+ var fetchEvent = new NextFetchEvent({ page: normalizedPathname });
617
646
  var response;
618
- try { response = await middlewareFn(nextRequest); }
647
+ try { response = await middlewareFn(nextRequest, fetchEvent); }
619
648
  catch (e) {
620
649
  console.error("[vinext] Middleware error:", e);
621
650
  return { continue: false, response: new Response("Internal Server Error", { status: 500 }) };
622
651
  }
652
+ if (ctx && typeof ctx.waitUntil === "function") { ctx.waitUntil(fetchEvent.drainWaitUntil()); } else { fetchEvent.drainWaitUntil(); }
623
653
 
624
654
  if (!response) return { continue: true };
625
655
 
@@ -677,8 +707,11 @@ import { runWithHeadState } from "vinext/head-state";
677
707
  import { safeJsonStringify } from "vinext/html";
678
708
  import { getSSRFontLinks as _getSSRFontLinks, getSSRFontStyles as _getSSRFontStylesGoogle, getSSRFontPreloads as _getSSRFontPreloadsGoogle } from "next/font/google";
679
709
  import { getSSRFontStyles as _getSSRFontStylesLocal, getSSRFontPreloads as _getSSRFontPreloadsLocal } from "next/font/local";
710
+ ${instrumentationImportCode}
680
711
  ${middlewareImportCode}
681
712
 
713
+ ${instrumentationInitCode}
714
+
682
715
  // i18n config (embedded at build time)
683
716
  const i18nConfig = ${i18nConfigJson};
684
717
 
@@ -1597,10 +1630,15 @@ hydrate();
1597
1630
  // Shared state for the MDX proxy plugin. Populated during config() if MDX
1598
1631
  // files are detected and @mdx-js/rollup is installed.
1599
1632
  let mdxDelegate = null;
1633
+ const reactPlugin = options.react === false
1634
+ ? false
1635
+ : react(options.react === true ? undefined : options.react);
1600
1636
  const plugins = [
1601
1637
  // Resolve tsconfig paths/baseUrl aliases so real-world Next.js repos
1602
1638
  // that use @/*, #/*, or baseUrl imports work out of the box.
1603
1639
  tsconfigPaths(),
1640
+ // React Fast Refresh + JSX transform for client components.
1641
+ reactPlugin,
1604
1642
  // Transform CJS require()/module.exports to ESM before other plugins
1605
1643
  // analyze imports (RSC directive scanning, shim resolution, etc.)
1606
1644
  commonjs(),
@@ -1671,7 +1709,7 @@ hydrate();
1671
1709
  // Load next.config.js if present (always from project root, not src/)
1672
1710
  const phase = env?.command === "build" ? PHASE_PRODUCTION_BUILD : PHASE_DEVELOPMENT_SERVER;
1673
1711
  const rawConfig = await loadNextConfig(root, phase);
1674
- nextConfig = await resolveNextConfig(rawConfig);
1712
+ nextConfig = await resolveNextConfig(rawConfig, root);
1675
1713
  fileMatcher = createValidFileMatcher(nextConfig.pageExtensions);
1676
1714
  // Merge env from next.config.js with NEXT_PUBLIC_* env vars
1677
1715
  const defines = getNextPublicEnvDefines();
@@ -1711,6 +1749,7 @@ hydrate();
1711
1749
  // Build the shim alias map — used by both resolve.alias and resolveId
1712
1750
  // (resolveId handles .js extension variants for libraries like nuqs)
1713
1751
  nextShimMap = {
1752
+ ...nextConfig.aliases,
1714
1753
  "next/link": path.join(shimsDir, "link"),
1715
1754
  "next/head": path.join(shimsDir, "head"),
1716
1755
  "next/router": path.join(shimsDir, "router"),
@@ -1888,12 +1927,21 @@ hydrate();
1888
1927
  origin: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
1889
1928
  },
1890
1929
  },
1891
- // Externalize React packages from SSR transform they are CJS and
1892
- // 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.
1893
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.
1894
1941
  ...(hasCloudflarePlugin || hasNitroPlugin ? {} : {
1895
1942
  ssr: {
1896
1943
  external: ["react", "react-dom", "react-dom/server"],
1944
+ noExternal: true,
1897
1945
  },
1898
1946
  }),
1899
1947
  resolve: {
@@ -1928,6 +1976,17 @@ hydrate();
1928
1976
  // Inject resolved PostCSS plugins if string names were found
1929
1977
  ...(postcssOverride ? { css: { postcss: postcssOverride } } : {}),
1930
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
+ : [];
1931
1990
  // If app/ directory exists, configure RSC environments
1932
1991
  if (hasAppDir) {
1933
1992
  // Compute optimizeDeps.entries so Vite discovers server-side
@@ -1950,11 +2009,19 @@ hydrate();
1950
2009
  // Note: Do NOT externalize react/react-dom here — they must
1951
2010
  // be bundled with the "react-server" condition for RSC.
1952
2011
  // Skip when targeting bundled runtimes (Cloudflare/Nitro).
1953
- external: [
2012
+ external: userSsrExternal === true ? true : [
1954
2013
  "satori",
1955
2014
  "@resvg/resvg-js",
1956
2015
  "yoga-wasm-web",
2016
+ ...userSsrExternal,
1957
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 }),
1958
2025
  },
1959
2026
  }),
1960
2027
  optimizeDeps: {
@@ -1969,6 +2036,17 @@ hydrate();
1969
2036
  },
1970
2037
  },
1971
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
+ }),
1972
2050
  optimizeDeps: {
1973
2051
  exclude: ["vinext"],
1974
2052
  entries: appEntries,
@@ -1981,6 +2059,13 @@ hydrate();
1981
2059
  },
1982
2060
  },
1983
2061
  client: {
2062
+ // Explicitly mark as client consumer so other plugins (e.g. Nitro)
2063
+ // can detect this during configEnvironment hooks — before Vite
2064
+ // applies the default consumer based on environment name.
2065
+ // Without this, Nitro's configEnvironment creates a server-side
2066
+ // service for the client environment, causing virtual module
2067
+ // imports to leak to Node's native ESM loader (ERR_UNSUPPORTED_ESM_URL_SCHEME).
2068
+ consumer: "client",
1984
2069
  optimizeDeps: {
1985
2070
  exclude: ["vinext"],
1986
2071
  // React packages aren't crawled from app/ source files,
@@ -2018,6 +2103,7 @@ hydrate();
2018
2103
  // and there's no client-side hydration.
2019
2104
  viteConfig.environments = {
2020
2105
  client: {
2106
+ consumer: "client",
2021
2107
  build: {
2022
2108
  manifest: true,
2023
2109
  ssrManifest: true,
@@ -2124,8 +2210,8 @@ hydrate();
2124
2210
  rewrites: nextConfig?.rewrites,
2125
2211
  headers: nextConfig?.headers,
2126
2212
  allowedOrigins: nextConfig?.serverActionsAllowedOrigins,
2127
- allowedDevOrigins: nextConfig?.serverActionsAllowedOrigins,
2128
- });
2213
+ allowedDevOrigins: nextConfig?.allowedDevOrigins,
2214
+ }, instrumentationPath);
2129
2215
  }
2130
2216
  if (id === RESOLVED_APP_SSR_ENTRY && hasAppDir) {
2131
2217
  return generateSsrEntry();
@@ -2135,6 +2221,8 @@ hydrate();
2135
2221
  }
2136
2222
  },
2137
2223
  },
2224
+ // Stub node:async_hooks in client builds — see src/plugins/async-hooks-stub.ts
2225
+ asyncHooksStubPlugin,
2138
2226
  // Proxy plugin for @mdx-js/rollup. The real MDX plugin is created lazily
2139
2227
  // during vinext:config's config() (when MDX files are detected), but
2140
2228
  // plugins returned from config() hooks run too late in the pipeline —
@@ -2212,12 +2300,11 @@ hydrate();
2212
2300
  {
2213
2301
  name: "vinext:pages-router",
2214
2302
  // HMR: trigger full-reload for Pages Router page changes.
2215
- // Without @vitejs/plugin-react (React Fast Refresh), component edits
2216
- // can't be hot-updated. In theory Vite's default propagation should
2217
- // reach the root and trigger a full-reload, but the Pages Router
2218
- // injects hydration via inline <script type="module"> which may not
2219
- // be tracked in the module graph. Explicitly sending full-reload
2220
- // ensures changes are always reflected in the browser.
2303
+ // Even with @vitejs/plugin-react providing React Fast Refresh,
2304
+ // the Pages Router injects hydration via inline <script type="module">
2305
+ // which may not be tracked in Vite's module graph. Explicitly
2306
+ // sending full-reload ensures changes are always reflected in
2307
+ // the browser.
2221
2308
  hotUpdate(options) {
2222
2309
  if (!hasPagesDir || hasAppDir)
2223
2310
  return;
@@ -2229,6 +2316,32 @@ hydrate();
2229
2316
  configureServer(server) {
2230
2317
  // Watch pages directory for file additions/removals to invalidate route cache.
2231
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
+ }
2232
2345
  /**
2233
2346
  * Invalidate the virtual RSC entry module in Vite's module graph.
2234
2347
  *
@@ -2266,12 +2379,6 @@ hydrate();
2266
2379
  invalidateRscEntryModule();
2267
2380
  }
2268
2381
  });
2269
- // Run instrumentation.ts register() if present (once at server startup)
2270
- if (instrumentationPath) {
2271
- runInstrumentation(server, instrumentationPath).catch((err) => {
2272
- console.error("[vinext] Instrumentation error:", err);
2273
- });
2274
- }
2275
2382
  // ── Dev request origin check ─────────────────────────────────────
2276
2383
  // Registered directly (not in the returned function) so it runs
2277
2384
  // BEFORE Vite's built-in middleware. This ensures all requests
@@ -2284,7 +2391,7 @@ hydrate();
2284
2391
  "x-forwarded-host": req.headers["x-forwarded-host"],
2285
2392
  "sec-fetch-site": req.headers["sec-fetch-site"],
2286
2393
  "sec-fetch-mode": req.headers["sec-fetch-mode"],
2287
- }, nextConfig?.serverActionsAllowedOrigins);
2394
+ }, nextConfig?.allowedDevOrigins);
2288
2395
  if (blockReason) {
2289
2396
  console.warn(`[vinext] Blocked dev request: ${blockReason} (${req.url})`);
2290
2397
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -2295,6 +2402,32 @@ hydrate();
2295
2402
  });
2296
2403
  // Return a function to register middleware AFTER Vite's built-in middleware
2297
2404
  return () => {
2405
+ // Run instrumentation.ts register() if present (once at server startup).
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) => {
2428
+ console.error("[vinext] Instrumentation error:", err);
2429
+ });
2430
+ }
2298
2431
  // App Router request logging in dev server
2299
2432
  //
2300
2433
  // For App Router, the RSC plugin handles requests internally.
@@ -2430,7 +2563,7 @@ hydrate();
2430
2563
  "x-forwarded-host": req.headers["x-forwarded-host"],
2431
2564
  "sec-fetch-site": req.headers["sec-fetch-site"],
2432
2565
  "sec-fetch-mode": req.headers["sec-fetch-mode"],
2433
- }, nextConfig?.serverActionsAllowedOrigins);
2566
+ }, nextConfig?.allowedDevOrigins);
2434
2567
  if (blockReason) {
2435
2568
  console.warn(`[vinext] Blocked dev request: ${blockReason} (${url})`);
2436
2569
  res.writeHead(403, { "Content-Type": "text/plain" });
@@ -2559,7 +2692,7 @@ hydrate();
2559
2692
  .filter(([, v]) => v !== undefined)
2560
2693
  .map(([k, v]) => [k, Array.isArray(v) ? v.join(", ") : String(v)])),
2561
2694
  });
2562
- const result = await runMiddleware(server, middlewarePath, middlewareRequest);
2695
+ const result = await runMiddleware(getPagesRunner(), middlewarePath, middlewareRequest);
2563
2696
  if (!result.continue) {
2564
2697
  if (result.redirectUrl) {
2565
2698
  const redirectHeaders = { Location: result.redirectUrl };
@@ -2610,11 +2743,41 @@ hydrate();
2610
2743
  // Apply middleware rewrite (URL and optional status code)
2611
2744
  if (result.rewriteUrl) {
2612
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;
2613
2755
  }
2614
2756
  if (result.rewriteStatus) {
2615
2757
  req.__vinextRewriteStatus = result.rewriteStatus;
2616
2758
  }
2617
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();
2618
2781
  // Build request context once for has/missing condition checks
2619
2782
  // across headers, redirects, and rewrites.
2620
2783
  const reqUrl = new URL(url, `http://${req.headers.host || "localhost"}`);
@@ -3751,6 +3914,8 @@ function applyRewrites(pathname, rewrites, ctx) {
3751
3914
  }
3752
3915
  /**
3753
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.
3754
3919
  */
3755
3920
  function applyHeaders(pathname, res, headers, ctx) {
3756
3921
  const matched = matchHeaders(pathname, headers, ctx);
@@ -3783,7 +3948,11 @@ function applyHeaders(pathname, res, headers, ctx) {
3783
3948
  }
3784
3949
  }
3785
3950
  else {
3786
- 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
+ }
3787
3956
  }
3788
3957
  }
3789
3958
  }
@@ -3838,4 +4007,5 @@ export { clientManualChunks, clientOutputConfig, clientTreeshakeConfig, computeL
3838
4007
  export { resolvePostcssStringPlugins as _resolvePostcssStringPlugins };
3839
4008
  export { parseStaticObjectLiteral as _parseStaticObjectLiteral };
3840
4009
  export { stripServerExports as _stripServerExports };
4010
+ export { asyncHooksStubPlugin as _asyncHooksStubPlugin };
3841
4011
  //# sourceMappingURL=index.js.map