vinext 0.0.22 → 0.0.24

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 (61) hide show
  1. package/dist/build/static-export.d.ts.map +1 -1
  2. package/dist/build/static-export.js +9 -7
  3. package/dist/build/static-export.js.map +1 -1
  4. package/dist/config/config-matchers.d.ts.map +1 -1
  5. package/dist/config/config-matchers.js +13 -3
  6. package/dist/config/config-matchers.js.map +1 -1
  7. package/dist/config/next-config.d.ts +4 -1
  8. package/dist/config/next-config.d.ts.map +1 -1
  9. package/dist/config/next-config.js +10 -5
  10. package/dist/config/next-config.js.map +1 -1
  11. package/dist/deploy.d.ts.map +1 -1
  12. package/dist/deploy.js +83 -24
  13. package/dist/deploy.js.map +1 -1
  14. package/dist/index.d.ts +36 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +230 -40
  17. package/dist/index.js.map +1 -1
  18. package/dist/routing/app-router.d.ts +2 -1
  19. package/dist/routing/app-router.d.ts.map +1 -1
  20. package/dist/routing/app-router.js +73 -66
  21. package/dist/routing/app-router.js.map +1 -1
  22. package/dist/routing/file-matcher.d.ts +24 -0
  23. package/dist/routing/file-matcher.d.ts.map +1 -0
  24. package/dist/routing/file-matcher.js +75 -0
  25. package/dist/routing/file-matcher.js.map +1 -0
  26. package/dist/routing/pages-router.d.ts +3 -2
  27. package/dist/routing/pages-router.d.ts.map +1 -1
  28. package/dist/routing/pages-router.js +24 -17
  29. package/dist/routing/pages-router.js.map +1 -1
  30. package/dist/server/app-dev-server.d.ts.map +1 -1
  31. package/dist/server/app-dev-server.js +110 -64
  32. package/dist/server/app-dev-server.js.map +1 -1
  33. package/dist/server/dev-server.d.ts +2 -1
  34. package/dist/server/dev-server.d.ts.map +1 -1
  35. package/dist/server/dev-server.js +16 -14
  36. package/dist/server/dev-server.js.map +1 -1
  37. package/dist/server/prod-server.d.ts +8 -2
  38. package/dist/server/prod-server.d.ts.map +1 -1
  39. package/dist/server/prod-server.js +71 -16
  40. package/dist/server/prod-server.js.map +1 -1
  41. package/dist/server/worker-utils.d.ts +15 -0
  42. package/dist/server/worker-utils.d.ts.map +1 -0
  43. package/dist/server/worker-utils.js +41 -0
  44. package/dist/server/worker-utils.js.map +1 -0
  45. package/dist/shims/cache.d.ts +1 -1
  46. package/dist/shims/cache.d.ts.map +1 -1
  47. package/dist/shims/cache.js +8 -3
  48. package/dist/shims/cache.js.map +1 -1
  49. package/dist/shims/headers.d.ts +6 -0
  50. package/dist/shims/headers.d.ts.map +1 -1
  51. package/dist/shims/headers.js +8 -0
  52. package/dist/shims/headers.js.map +1 -1
  53. package/dist/shims/metadata.d.ts +1 -0
  54. package/dist/shims/metadata.d.ts.map +1 -1
  55. package/dist/shims/metadata.js +5 -1
  56. package/dist/shims/metadata.js.map +1 -1
  57. package/dist/utils/project.d.ts +13 -1
  58. package/dist/utils/project.d.ts.map +1 -1
  59. package/dist/utils/project.js +63 -13
  60. package/dist/utils/project.js.map +1 -1
  61. package/package.json +6 -1
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { loadEnv, parseAst } from "vite";
2
2
  import { pagesRouter, apiRouter, invalidateRouteCache, matchRoute, patternToNextFormat as pagesPatternToNextFormat } from "./routing/pages-router.js";
3
3
  import { appRouter, invalidateAppRouteCache } from "./routing/app-router.js";
4
+ import { createValidFileMatcher } from "./routing/file-matcher.js";
4
5
  import { createSSRHandler } from "./server/dev-server.js";
5
6
  import { handleApiRoute } from "./server/api-handler.js";
6
7
  import { generateRscEntry, generateSsrEntry, generateBrowserEntry, } from "./server/app-dev-server.js";
@@ -10,11 +11,13 @@ import { logRequest, now } from "./server/request-log.js";
10
11
  import { generateSafeRegExpCode, generateMiddlewareMatcherCode, generateNormalizePathCode } from "./server/middleware-codegen.js";
11
12
  import { normalizePath } from "./server/normalize-path.js";
12
13
  import { findInstrumentationFile, runInstrumentation } from "./server/instrumentation.js";
14
+ import { PHASE_PRODUCTION_BUILD, PHASE_DEVELOPMENT_SERVER } from "./shims/constants.js";
13
15
  import { validateDevRequest } from "./server/dev-origin-check.js";
14
16
  import { safeRegExp, isExternalUrl, proxyExternalRequest, parseCookies, matchHeaders, matchRedirect, matchRewrite, } from "./config/config-matchers.js";
15
17
  import { scanMetadataFiles } from "./server/metadata-routes.js";
16
18
  import { detectPackageManager } from "./utils/project.js";
17
19
  import tsconfigPaths from "vite-tsconfig-paths";
20
+ import react from "@vitejs/plugin-react";
18
21
  import MagicString from "magic-string";
19
22
  import path from "node:path";
20
23
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -493,6 +496,7 @@ export default function vinext(options = {}) {
493
496
  let hasAppDir = false;
494
497
  let hasPagesDir = false;
495
498
  let nextConfig;
499
+ let fileMatcher;
496
500
  let middlewarePath = null;
497
501
  let instrumentationPath = null;
498
502
  let hasCloudflarePlugin = false;
@@ -506,8 +510,8 @@ export default function vinext(options = {}) {
506
510
  * This is the entry point for `vite build --ssr`.
507
511
  */
508
512
  async function generateServerEntry() {
509
- const pageRoutes = await pagesRouter(pagesDir);
510
- const apiRoutes = await apiRouter(pagesDir);
513
+ const pageRoutes = await pagesRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher);
514
+ const apiRoutes = await apiRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher);
511
515
  // Generate import statements using absolute paths since virtual
512
516
  // modules don't have a real file location for relative resolution.
513
517
  const pageImports = pageRoutes.map((r, i) => {
@@ -527,16 +531,15 @@ export default function vinext(options = {}) {
527
531
  return ` { pattern: ${JSON.stringify(r.pattern)}, isDynamic: ${r.isDynamic}, params: ${JSON.stringify(r.params)}, module: api_${i} }`;
528
532
  });
529
533
  // Check for _app and _document
530
- const hasApp = fs.existsSync(path.join(pagesDir, "_app.tsx")) || fs.existsSync(path.join(pagesDir, "_app.jsx")) || fs.existsSync(path.join(pagesDir, "_app.ts")) || fs.existsSync(path.join(pagesDir, "_app.js"));
531
- const hasDoc = fs.existsSync(path.join(pagesDir, "_document.tsx")) || fs.existsSync(path.join(pagesDir, "_document.jsx")) || fs.existsSync(path.join(pagesDir, "_document.ts")) || fs.existsSync(path.join(pagesDir, "_document.js"));
532
- // Use absolute paths for _app and _document too
533
- const appFileBase = path.join(pagesDir, "_app").replace(/\\/g, "/");
534
- const docFileBase = path.join(pagesDir, "_document").replace(/\\/g, "/");
534
+ const appFilePath = findFileWithExts(pagesDir, "_app", fileMatcher);
535
+ const docFilePath = findFileWithExts(pagesDir, "_document", fileMatcher);
536
+ const hasApp = appFilePath !== null;
537
+ const hasDoc = docFilePath !== null;
535
538
  const appImportCode = hasApp
536
- ? `import { default as AppComponent } from ${JSON.stringify(appFileBase)};`
539
+ ? `import { default as AppComponent } from ${JSON.stringify(appFilePath.replace(/\\/g, "/"))};`
537
540
  : `const AppComponent = null;`;
538
541
  const docImportCode = hasDoc
539
- ? `import { default as DocumentComponent } from ${JSON.stringify(docFileBase)};`
542
+ ? `import { default as DocumentComponent } from ${JSON.stringify(docFilePath.replace(/\\/g, "/"))};`
540
543
  : `const DocumentComponent = null;`;
541
544
  // Serialize i18n config for embedding in the server entry
542
545
  const i18nConfigJson = nextConfig?.i18n
@@ -1460,8 +1463,9 @@ ${middlewareExportCode}
1460
1463
  * __NEXT_DATA__ to determine which page to hydrate.
1461
1464
  */
1462
1465
  async function generateClientEntry() {
1463
- const pageRoutes = await pagesRouter(pagesDir);
1464
- const hasApp = fs.existsSync(path.join(pagesDir, "_app.tsx")) || fs.existsSync(path.join(pagesDir, "_app.jsx")) || fs.existsSync(path.join(pagesDir, "_app.ts")) || fs.existsSync(path.join(pagesDir, "_app.js"));
1466
+ const pageRoutes = await pagesRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher);
1467
+ const appFilePath = findFileWithExts(pagesDir, "_app", fileMatcher);
1468
+ const hasApp = appFilePath !== null;
1465
1469
  // Build a map of route pattern -> dynamic import.
1466
1470
  // Keys must use Next.js bracket format (e.g. "/user/[id]") to match
1467
1471
  // __NEXT_DATA__.page which is set via patternToNextFormat() during SSR.
@@ -1473,7 +1477,7 @@ ${middlewareExportCode}
1473
1477
  // lgtm[js/bad-code-sanitization]
1474
1478
  return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;
1475
1479
  });
1476
- const appFileBase = path.join(pagesDir, "_app").replace(/\\/g, "/");
1480
+ const appFileBase = appFilePath?.replace(/\\/g, "/");
1477
1481
  return `
1478
1482
  import React from "react";
1479
1483
  import { hydrateRoot } from "react-dom/client";
@@ -1594,10 +1598,15 @@ hydrate();
1594
1598
  // Shared state for the MDX proxy plugin. Populated during config() if MDX
1595
1599
  // files are detected and @mdx-js/rollup is installed.
1596
1600
  let mdxDelegate = null;
1601
+ const reactPlugin = options.react === false
1602
+ ? false
1603
+ : react(options.react === true ? undefined : options.react);
1597
1604
  const plugins = [
1598
1605
  // Resolve tsconfig paths/baseUrl aliases so real-world Next.js repos
1599
1606
  // that use @/*, #/*, or baseUrl imports work out of the box.
1600
1607
  tsconfigPaths(),
1608
+ // React Fast Refresh + JSX transform for client components.
1609
+ reactPlugin,
1601
1610
  // Transform CJS require()/module.exports to ESM before other plugins
1602
1611
  // analyze imports (RSC directive scanning, shim resolution, etc.)
1603
1612
  commonjs(),
@@ -1666,8 +1675,10 @@ hydrate();
1666
1675
  middlewarePath = findMiddlewareFile(root);
1667
1676
  instrumentationPath = findInstrumentationFile(root);
1668
1677
  // Load next.config.js if present (always from project root, not src/)
1669
- const rawConfig = await loadNextConfig(root);
1678
+ const phase = env?.command === "build" ? PHASE_PRODUCTION_BUILD : PHASE_DEVELOPMENT_SERVER;
1679
+ const rawConfig = await loadNextConfig(root, phase);
1670
1680
  nextConfig = await resolveNextConfig(rawConfig);
1681
+ fileMatcher = createValidFileMatcher(nextConfig.pageExtensions);
1671
1682
  // Merge env from next.config.js with NEXT_PUBLIC_* env vars
1672
1683
  const defines = getNextPublicEnvDefines();
1673
1684
  if (!config.define ||
@@ -1976,6 +1987,13 @@ hydrate();
1976
1987
  },
1977
1988
  },
1978
1989
  client: {
1990
+ // Explicitly mark as client consumer so other plugins (e.g. Nitro)
1991
+ // can detect this during configEnvironment hooks — before Vite
1992
+ // applies the default consumer based on environment name.
1993
+ // Without this, Nitro's configEnvironment creates a server-side
1994
+ // service for the client environment, causing virtual module
1995
+ // imports to leak to Node's native ESM loader (ERR_UNSUPPORTED_ESM_URL_SCHEME).
1996
+ consumer: "client",
1979
1997
  optimizeDeps: {
1980
1998
  exclude: ["vinext"],
1981
1999
  // React packages aren't crawled from app/ source files,
@@ -2013,6 +2031,7 @@ hydrate();
2013
2031
  // and there's no client-side hydration.
2014
2032
  viteConfig.environments = {
2015
2033
  client: {
2034
+ consumer: "client",
2016
2035
  build: {
2017
2036
  manifest: true,
2018
2037
  ssrManifest: true,
@@ -2110,10 +2129,10 @@ hydrate();
2110
2129
  }
2111
2130
  // App Router virtual modules
2112
2131
  if (id === RESOLVED_RSC_ENTRY && hasAppDir) {
2113
- const routes = await appRouter(appDir);
2132
+ const routes = await appRouter(appDir, nextConfig?.pageExtensions, fileMatcher);
2114
2133
  const metaRoutes = scanMetadataFiles(appDir);
2115
2134
  // Check for global-error.tsx at app root
2116
- const globalErrorPath = findFileWithExts(appDir, "global-error");
2135
+ const globalErrorPath = findFileWithExts(appDir, "global-error", fileMatcher);
2117
2136
  return generateRscEntry(appDir, routes, middlewarePath, metaRoutes, globalErrorPath, nextConfig?.basePath, nextConfig?.trailingSlash, {
2118
2137
  redirects: nextConfig?.redirects,
2119
2138
  rewrites: nextConfig?.rewrites,
@@ -2207,24 +2226,22 @@ hydrate();
2207
2226
  {
2208
2227
  name: "vinext:pages-router",
2209
2228
  // HMR: trigger full-reload for Pages Router page changes.
2210
- // Without @vitejs/plugin-react (React Fast Refresh), component edits
2211
- // can't be hot-updated. In theory Vite's default propagation should
2212
- // reach the root and trigger a full-reload, but the Pages Router
2213
- // injects hydration via inline <script type="module"> which may not
2214
- // be tracked in the module graph. Explicitly sending full-reload
2215
- // ensures changes are always reflected in the browser.
2229
+ // Even with @vitejs/plugin-react providing React Fast Refresh,
2230
+ // the Pages Router injects hydration via inline <script type="module">
2231
+ // which may not be tracked in Vite's module graph. Explicitly
2232
+ // sending full-reload ensures changes are always reflected in
2233
+ // the browser.
2216
2234
  hotUpdate(options) {
2217
2235
  if (!hasPagesDir || hasAppDir)
2218
2236
  return;
2219
- const ext = /\.(tsx?|jsx?|mdx)$/;
2220
- if (options.file.startsWith(pagesDir) && ext.test(options.file)) {
2237
+ if (options.file.startsWith(pagesDir) && fileMatcher.extensionRegex.test(options.file)) {
2221
2238
  options.server.environments.client.hot.send({ type: "full-reload" });
2222
2239
  return [];
2223
2240
  }
2224
2241
  },
2225
2242
  configureServer(server) {
2226
2243
  // Watch pages directory for file additions/removals to invalidate route cache.
2227
- const pageExtensions = /\.(tsx?|jsx?|mdx)$/;
2244
+ const pageExtensions = fileMatcher.extensionRegex;
2228
2245
  /**
2229
2246
  * Invalidate the virtual RSC entry module in Vite's module graph.
2230
2247
  *
@@ -2262,12 +2279,6 @@ hydrate();
2262
2279
  invalidateRscEntryModule();
2263
2280
  }
2264
2281
  });
2265
- // Run instrumentation.ts register() if present (once at server startup)
2266
- if (instrumentationPath) {
2267
- runInstrumentation(server, instrumentationPath).catch((err) => {
2268
- console.error("[vinext] Instrumentation error:", err);
2269
- });
2270
- }
2271
2282
  // ── Dev request origin check ─────────────────────────────────────
2272
2283
  // Registered directly (not in the returned function) so it runs
2273
2284
  // BEFORE Vite's built-in middleware. This ensures all requests
@@ -2291,6 +2302,15 @@ hydrate();
2291
2302
  });
2292
2303
  // Return a function to register middleware AFTER Vite's built-in middleware
2293
2304
  return () => {
2305
+ // 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) => {
2311
+ console.error("[vinext] Instrumentation error:", err);
2312
+ });
2313
+ }
2294
2314
  // App Router request logging in dev server
2295
2315
  //
2296
2316
  // For App Router, the RSC plugin handles requests internally.
@@ -2587,10 +2607,20 @@ hydrate();
2587
2607
  return;
2588
2608
  }
2589
2609
  }
2590
- // Apply middleware response headers
2610
+ // Apply middleware response headers. Unpack
2611
+ // x-middleware-request-* headers into req.headers so
2612
+ // config has/missing conditions and downstream handlers
2613
+ // see middleware-modified cookies and headers.
2591
2614
  if (result.responseHeaders) {
2615
+ const mwReqPrefix = "x-middleware-request-";
2592
2616
  for (const [key, value] of result.responseHeaders) {
2593
- res.appendHeader(key, value);
2617
+ if (key.startsWith(mwReqPrefix)) {
2618
+ const realName = key.slice(mwReqPrefix.length);
2619
+ req.headers[realName] = value;
2620
+ }
2621
+ else if (!key.startsWith("x-middleware-")) {
2622
+ res.appendHeader(key, value);
2623
+ }
2594
2624
  }
2595
2625
  }
2596
2626
  // Apply middleware rewrite (URL and optional status code)
@@ -2639,7 +2669,7 @@ hydrate();
2639
2669
  const resolvedPathname = resolvedUrl.split("?")[0];
2640
2670
  if (resolvedPathname.startsWith("/api/") ||
2641
2671
  resolvedPathname === "/api") {
2642
- const apiRoutes = await apiRouter(pagesDir);
2672
+ const apiRoutes = await apiRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher);
2643
2673
  const handled = await handleApiRoute(server, req, res, resolvedUrl, apiRoutes);
2644
2674
  if (handled)
2645
2675
  return;
@@ -2651,7 +2681,7 @@ hydrate();
2651
2681
  res.end("404 - API route not found");
2652
2682
  return;
2653
2683
  }
2654
- const routes = await pagesRouter(pagesDir);
2684
+ const routes = await pagesRouter(pagesDir, nextConfig?.pageExtensions, fileMatcher);
2655
2685
  // Apply afterFiles rewrites — these run after initial route matching
2656
2686
  // If beforeFiles already rewrote the URL, afterFiles still run on the
2657
2687
  // *resolved* pathname. Next.js applies these when route matching succeeds
@@ -2666,7 +2696,7 @@ hydrate();
2666
2696
  await proxyExternalRewriteNode(req, res, resolvedUrl);
2667
2697
  return;
2668
2698
  }
2669
- const handler = createSSRHandler(server, routes, pagesDir, nextConfig?.i18n);
2699
+ const handler = createSSRHandler(server, routes, pagesDir, nextConfig?.i18n, fileMatcher);
2670
2700
  const mwStatus = req.__vinextRewriteStatus;
2671
2701
  // Try rendering the resolved URL
2672
2702
  const match = matchRoute(resolvedUrl.split("?")[0], routes);
@@ -2700,6 +2730,41 @@ hydrate();
2700
2730
  };
2701
2731
  },
2702
2732
  },
2733
+ // Strip server-only data-fetching exports (getServerSideProps, getStaticProps,
2734
+ // getStaticPaths) from page modules in the client bundle. These functions
2735
+ // often import server-only modules (database drivers, fs, etc.) that would
2736
+ // break or bloat the client bundle. Next.js does this via an SWC transform
2737
+ // (next-ssg-transform); we use Vite's parseAst + MagicString.
2738
+ //
2739
+ // Only applies to client builds (not SSR) and only to files under the
2740
+ // pages/ directory.
2741
+ {
2742
+ name: "vinext:strip-server-exports",
2743
+ transform: {
2744
+ // Only match page source files, not node_modules
2745
+ filter: { id: /\.(tsx?|jsx?|mjs)$/ },
2746
+ handler(code, id) {
2747
+ const ssr = this.environment?.name !== "client";
2748
+ if (ssr)
2749
+ return null;
2750
+ if (!hasPagesDir)
2751
+ return null;
2752
+ // Only transform files under the pages/ directory
2753
+ if (!id.startsWith(pagesDir))
2754
+ return null;
2755
+ // Skip API routes, _app, _document, _error
2756
+ const relativePath = id.slice(pagesDir.length);
2757
+ if (relativePath.startsWith("/api/") || relativePath === "/api")
2758
+ return null;
2759
+ if (/\/_(?:app|document|error)\b/.test(relativePath))
2760
+ return null;
2761
+ const result = stripServerExports(code);
2762
+ if (!result)
2763
+ return null;
2764
+ return { code: result, map: null };
2765
+ },
2766
+ },
2767
+ },
2703
2768
  // Local image import transform:
2704
2769
  // When a source file imports a local image (e.g., `import hero from './hero.jpg'`),
2705
2770
  // this plugin transforms the default import to a StaticImageData object with
@@ -3342,6 +3407,7 @@ hydrate();
3342
3407
  " Cache-Control: public, max-age=31536000, immutable",
3343
3408
  "",
3344
3409
  ].join("\n");
3410
+ fs.mkdirSync(clientDir, { recursive: true });
3345
3411
  fs.writeFileSync(headersPath, headersContent);
3346
3412
  }
3347
3413
  },
@@ -3400,6 +3466,101 @@ function extractConstraint(str, re) {
3400
3466
  * :param+ — matches one or more segments
3401
3467
  * (regex) — inline regex patterns in the source
3402
3468
  */
3469
+ /**
3470
+ * Strip server-only data-fetching exports from a page module's source code.
3471
+ * Returns the transformed code, or null if no changes were made.
3472
+ *
3473
+ * Handles:
3474
+ * - export (async) function getServerSideProps(...) { ... }
3475
+ * - export const getStaticProps = async (...) => { ... }
3476
+ * - export const getServerSideProps = someHelper;
3477
+ */
3478
+ /**
3479
+ * Skip past balanced brackets/parens/braces starting at `pos` (which should
3480
+ * point to the opening bracket). Returns the position AFTER the closing bracket.
3481
+ * Handles nested brackets, string literals, and comments.
3482
+ */
3483
+ /**
3484
+ * Strip server-only data-fetching exports (getServerSideProps,
3485
+ * getStaticProps, getStaticPaths) from page modules for the client
3486
+ * bundle. Uses Vite's parseAst (Rollup/acorn) for correct handling
3487
+ * of all export patterns including function expressions, arrow
3488
+ * functions with TS return types, and re-exports.
3489
+ *
3490
+ * Modeled after Next.js's SWC `next-ssg-transform`.
3491
+ */
3492
+ function stripServerExports(code) {
3493
+ const SERVER_EXPORTS = new Set(["getServerSideProps", "getStaticProps", "getStaticPaths"]);
3494
+ if (![...SERVER_EXPORTS].some(name => code.includes(name)))
3495
+ return null;
3496
+ let ast;
3497
+ try {
3498
+ ast = parseAst(code);
3499
+ }
3500
+ catch {
3501
+ // If parsing fails (shouldn't happen post-JSX/TS transform), bail out
3502
+ return null;
3503
+ }
3504
+ const s = new MagicString(code);
3505
+ let changed = false;
3506
+ for (const node of ast.body) {
3507
+ if (node.type !== "ExportNamedDeclaration")
3508
+ continue;
3509
+ // Case 1: export function name() {} / export async function name() {}
3510
+ // Case 2: export const/let/var name = ...
3511
+ if (node.declaration) {
3512
+ const decl = node.declaration;
3513
+ if (decl.type === "FunctionDeclaration" && SERVER_EXPORTS.has(decl.id?.name)) {
3514
+ s.overwrite(node.start, node.end, `export function ${decl.id.name}() { return { props: {} }; }`);
3515
+ changed = true;
3516
+ }
3517
+ else if (decl.type === "VariableDeclaration") {
3518
+ for (const declarator of decl.declarations) {
3519
+ if (declarator.id?.type === "Identifier" && SERVER_EXPORTS.has(declarator.id.name)) {
3520
+ s.overwrite(node.start, node.end, `export const ${declarator.id.name} = undefined;`);
3521
+ changed = true;
3522
+ }
3523
+ }
3524
+ }
3525
+ continue;
3526
+ }
3527
+ // Case 3: export { getServerSideProps } or export { getServerSideProps as gSSP }
3528
+ if (node.specifiers && node.specifiers.length > 0 && !node.source) {
3529
+ const kept = [];
3530
+ const stripped = [];
3531
+ for (const spec of node.specifiers) {
3532
+ // spec.local.name is the binding name, spec.exported.name is the export name
3533
+ const exportedName = spec.exported?.name ?? spec.exported?.value;
3534
+ if (SERVER_EXPORTS.has(exportedName)) {
3535
+ stripped.push(exportedName);
3536
+ }
3537
+ else {
3538
+ kept.push(spec);
3539
+ }
3540
+ }
3541
+ if (stripped.length > 0) {
3542
+ // Build replacement: keep non-server specifiers, add stubs for stripped ones
3543
+ const parts = [];
3544
+ if (kept.length > 0) {
3545
+ const keptStr = kept.map((sp) => {
3546
+ const local = sp.local.name;
3547
+ const exported = sp.exported?.name ?? sp.exported?.value;
3548
+ return local === exported ? local : `${local} as ${exported}`;
3549
+ }).join(", ");
3550
+ parts.push(`export { ${keptStr} };`);
3551
+ }
3552
+ for (const name of stripped) {
3553
+ parts.push(`export const ${name} = undefined;`);
3554
+ }
3555
+ s.overwrite(node.start, node.end, parts.join("\n"));
3556
+ changed = true;
3557
+ }
3558
+ }
3559
+ }
3560
+ if (!changed)
3561
+ return null;
3562
+ return s.toString();
3563
+ }
3403
3564
  export function matchConfigPattern(pathname, pattern) {
3404
3565
  // If the pattern contains regex groups like (\\d+) or (.*), use regex matching.
3405
3566
  // Also enter this branch when a catch-all parameter (:param* or :param+) is
@@ -3610,16 +3771,44 @@ function applyRewrites(pathname, rewrites, ctx) {
3610
3771
  function applyHeaders(pathname, res, headers, ctx) {
3611
3772
  const matched = matchHeaders(pathname, headers, ctx);
3612
3773
  for (const header of matched) {
3613
- res.setHeader(header.key, header.value);
3774
+ // Use append semantics for headers where multiple values must coexist
3775
+ // (Vary, Set-Cookie). Using setHeader() on these would destroy
3776
+ // existing values like "Vary: RSC, Accept".
3777
+ const lk = header.key.toLowerCase();
3778
+ if (lk === "set-cookie") {
3779
+ // Node.js res.getHeader("set-cookie") returns string[] when
3780
+ // multiple Set-Cookie headers have been set. Preserve the array.
3781
+ const existing = res.getHeader(lk);
3782
+ if (Array.isArray(existing)) {
3783
+ res.setHeader(header.key, [...existing, header.value]);
3784
+ }
3785
+ else if (existing) {
3786
+ res.setHeader(header.key, [String(existing), header.value]);
3787
+ }
3788
+ else {
3789
+ res.setHeader(header.key, header.value);
3790
+ }
3791
+ }
3792
+ else if (lk === "vary") {
3793
+ const existing = res.getHeader(lk);
3794
+ if (existing) {
3795
+ res.setHeader(header.key, existing + ", " + header.value);
3796
+ }
3797
+ else {
3798
+ res.setHeader(header.key, header.value);
3799
+ }
3800
+ }
3801
+ else {
3802
+ res.setHeader(header.key, header.value);
3803
+ }
3614
3804
  }
3615
3805
  }
3616
3806
  /**
3617
3807
  * Find a file by name (without extension) in a directory.
3618
- * Checks .tsx, .ts, .jsx, .js extensions.
3808
+ * Checks the configured page extensions.
3619
3809
  */
3620
- function findFileWithExts(dir, name) {
3621
- const extensions = [".tsx", ".ts", ".jsx", ".js"];
3622
- for (const ext of extensions) {
3810
+ function findFileWithExts(dir, name, matcher) {
3811
+ for (const ext of matcher.dottedExtensions) {
3623
3812
  const filePath = path.join(dir, name + ext);
3624
3813
  if (fs.existsSync(filePath))
3625
3814
  return filePath;
@@ -3664,4 +3853,5 @@ export { staticExportPages, staticExportApp } from "./build/static-export.js";
3664
3853
  export { clientManualChunks, clientOutputConfig, clientTreeshakeConfig, computeLazyChunks };
3665
3854
  export { resolvePostcssStringPlugins as _resolvePostcssStringPlugins };
3666
3855
  export { parseStaticObjectLiteral as _parseStaticObjectLiteral };
3856
+ export { stripServerExports as _stripServerExports };
3667
3857
  //# sourceMappingURL=index.js.map