Skip to content

[Bug]: splitRouteModules "enforce" falsely rejects JSX exports during Vite dev #15332

Description

@comp615

Reproduction

Failing integration test on the current main branch:

https://github.com/comp615/react-router/blob/5574f7ef87af726d1aedba2b2ed7520d71c40f3b/integration/bug-report-test.ts

The fixture configures Framework Mode with:

export default {
  splitRouteModules: "enforce",
} satisfies Config;

and uses the standard React Router and React Vite plugins:

export default defineConfig({
  plugins: [reactRouter(), react()],
});

The route has two otherwise independent JSX exports:

export function HydrateFallback() {
  return <p>Loading...</p>;
}

export default function Index() {
  return <h1>Ready</h1>;
}

Run:

pnpm install
pnpm build
pnpm test:integration:run bug-report --project chromium

The test first confirms that the production fixture build succeeds. Loading the same fixture through the Vite dev server then renders the error overlay:

Error splitting route module: routes/_index.tsx

- HydrateFallback

This export could not be split into its own chunk because it shares code with other exports.

System Info

System:
  OS: macOS 26.5.2
  CPU: (16) arm64 Apple M3 Max
  Memory: 2.36 GB / 64.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 24.16.0
  npm: 11.13.0
  pnpm: 11.7.0
Browsers:
  Chrome: 150.0.7871.125
  Firefox: 152.0.5
  Safari: 26.5.2
npmPackages:
  @react-router/dev: workspace:* => 8.2.0
  @react-router/fs-routes: workspace:* => 8.2.0
  @react-router/serve: workspace:* => 8.2.0
  @vitejs/plugin-react: ^6.0.1 => 6.0.1
  react-router: workspace:* => 8.2.0
  vite: ^8.0.0 => 8.0.2

Used Package Manager

pnpm

Expected Behavior

splitRouteModules: "enforce" should accept this route in Vite dev because the two exports do not share user-authored module-local code and the production build can split them successfully.

Actual Behavior

The React JSX development transform emits a shared module-level _jsxFileName binding used by the jsxDEV source metadata for both JSX exports. Dev manifest generation runs route-chunk dependency analysis on that transformed output, treats _jsxFileName as shared application code, and rejects HydrateFallback.

Production output does not contain the same development-only dependency, so the production build succeeds. This makes dev enforcement stricter than—and inconsistent with—the build output it is intended to enforce.

Potential directions include skipping build-only split enforcement during dev manifest generation, analyzing pre-development-JSX source, or excluding compiler-generated JSX source metadata from shared-binding analysis.

🤖 Report and reproduction prepared with AI assistance.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions