[GH-ISSUE #409] Module duplication: client references resolve to raw ESM paths instead of pre-bundled paths #90

Closed
opened 2026-05-06 12:37:08 +02:00 by BreizhHardware · 0 comments

Originally created by @Jbithell on GitHub (Mar 10, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/409

Summary

When a React Server Component imports a 'use client' module (e.g. from @mantine/core), vinext creates client references that resolve to the raw ESM path (/node_modules/@mantine/core/esm/...). Meanwhile, client components that directly import the same package load from Vite's pre-bundled path (/node_modules/.vite/deps/@mantine_core.js).

This creates two separate module instances in the browser, which means createContext() is called twice, producing different React context objects. Any context provider using the pre-bundled copy is invisible to consumers using the raw ESM copy, and vice versa.

Reproduction

  1. Set up a vinext project with @mantine/core (or any library that uses React context)
  2. In the root layout, render a MantineProvider via a 'use client' wrapper component
  3. In a page (server component), import and render a Mantine component like <TextInput />
  4. Open the page in the browser

Expected: TextInput renders correctly, finding MantineProvider in the component tree.

Actual: Error: @mantine/core: MantineProvider was not found in component tree

Root cause

In Vite's dev mode, vinext runs three environments (RSC, SSR, client) with separate module graphs. When the RSC environment encounters a 'use client' import, it serialises a client reference containing the module's raw file path. On the client, this reference is loaded as a raw ESM module from the Vite dev server.

However, the MantineProvider (rendered by a client component in the layout) loads @mantine/core through Vite's dependency pre-bundling, which bundles the package into a single .vite/deps/@mantine_core.js file.

The browser ends up loading both:

  • /node_modules/.vite/deps/@mantine_core.js (pre-bundled, used by client components)
  • /node_modules/@mantine/core/esm/core/MantineProvider/MantineThemeProvider/MantineThemeProvider.mjs (raw ESM, used by client references from server components)

These are separate JavaScript modules with separate createContext() calls, so the React context set by one is not visible to the other.

Workaround

Exclude the affected packages from Vite's dependency pre-bundling so all imports use raw ESM paths consistently:

// vite.config.ts
const mantinePackages = [
  "@mantine/core",
  "@mantine/hooks",
  "@mantine/modals",
  "@mantine/notifications",
  "@mantine/dates",
  "@mantine/charts",
  "@mantine/nprogress",
  "@mantine/form",
];

// CJS-only transitive deps that browsers can't load as raw ESM
const mantineCjsDeps = [
  "prop-types",
  "loose-envify",
  "object-assign",
  "react-is",
  "js-tokens",
  "fast-deep-equal",
  "klona",
];

function environmentOptimizeDepsFix() {
  return {
    name: "environment-optimize-deps-fix",
    config(config) {
      if (!config.environments) return;
      for (const [envName, env] of Object.entries(config.environments)) {
        if (env?.optimizeDeps) {
          env.optimizeDeps.exclude = [
            ...(env.optimizeDeps.exclude || []),
            ...mantinePackages,
          ];
          // CJS deps must stay pre-bundled in the client (browser) environment
          if (envName === "client") {
            env.optimizeDeps.include = [
              ...(env.optimizeDeps.include || []),
              ...mantineCjsDeps,
            ];
          }
        }
      }
    },
  };
}

This forces all @mantine imports to use raw ESM, eliminating the duplication. The downside is significantly more HTTP requests in dev mode (500+ individual module files instead of one pre-bundled file).

Suggested fix

Client references created by the RSC environment should resolve to the client environment's pre-bundled module paths (when available), rather than raw ESM file paths. This would ensure that both direct client imports and RSC client references load from the same module instance, preserving React context identity.

Environment

Originally created by @Jbithell on GitHub (Mar 10, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/409 ## Summary When a React Server Component imports a `'use client'` module (e.g. from `@mantine/core`), vinext creates client references that resolve to the raw ESM path (`/node_modules/@mantine/core/esm/...`). Meanwhile, client components that directly import the same package load from Vite's pre-bundled path (`/node_modules/.vite/deps/@mantine_core.js`). This creates **two separate module instances** in the browser, which means `createContext()` is called twice, producing different React context objects. Any context provider using the pre-bundled copy is invisible to consumers using the raw ESM copy, and vice versa. ## Reproduction 1. Set up a vinext project with `@mantine/core` (or any library that uses React context) 2. In the root layout, render a `MantineProvider` via a `'use client'` wrapper component 3. In a page (server component), import and render a Mantine component like `<TextInput />` 4. Open the page in the browser **Expected:** TextInput renders correctly, finding MantineProvider in the component tree. **Actual:** Error: `@mantine/core: MantineProvider was not found in component tree` ## Root cause In Vite's dev mode, vinext runs three environments (RSC, SSR, client) with separate module graphs. When the RSC environment encounters a `'use client'` import, it serialises a client reference containing the module's raw file path. On the client, this reference is loaded as a raw ESM module from the Vite dev server. However, the `MantineProvider` (rendered by a client component in the layout) loads `@mantine/core` through Vite's dependency pre-bundling, which bundles the package into a single `.vite/deps/@mantine_core.js` file. The browser ends up loading both: - `/node_modules/.vite/deps/@mantine_core.js` (pre-bundled, used by client components) - `/node_modules/@mantine/core/esm/core/MantineProvider/MantineThemeProvider/MantineThemeProvider.mjs` (raw ESM, used by client references from server components) These are separate JavaScript modules with separate `createContext()` calls, so the React context set by one is not visible to the other. ## Workaround Exclude the affected packages from Vite's dependency pre-bundling so all imports use raw ESM paths consistently: ```ts // vite.config.ts const mantinePackages = [ "@mantine/core", "@mantine/hooks", "@mantine/modals", "@mantine/notifications", "@mantine/dates", "@mantine/charts", "@mantine/nprogress", "@mantine/form", ]; // CJS-only transitive deps that browsers can't load as raw ESM const mantineCjsDeps = [ "prop-types", "loose-envify", "object-assign", "react-is", "js-tokens", "fast-deep-equal", "klona", ]; function environmentOptimizeDepsFix() { return { name: "environment-optimize-deps-fix", config(config) { if (!config.environments) return; for (const [envName, env] of Object.entries(config.environments)) { if (env?.optimizeDeps) { env.optimizeDeps.exclude = [ ...(env.optimizeDeps.exclude || []), ...mantinePackages, ]; // CJS deps must stay pre-bundled in the client (browser) environment if (envName === "client") { env.optimizeDeps.include = [ ...(env.optimizeDeps.include || []), ...mantineCjsDeps, ]; } } } }, }; } ``` This forces all `@mantine` imports to use raw ESM, eliminating the duplication. The downside is significantly more HTTP requests in dev mode (500+ individual module files instead of one pre-bundled file). ## Suggested fix Client references created by the RSC environment should resolve to the client environment's pre-bundled module paths (when available), rather than raw ESM file paths. This would ensure that both direct client imports and RSC client references load from the same module instance, preserving React context identity. ## Environment - Vite: 7.3.1 - [@mantine/core](https://github.com/mantinedev/mantine): 8.x - React: 19.x
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#90
No description provided.