TanStack / TanStack/router

[Start] virtual:tanstack-start-client-entry 404 in pnpm monorepo (regression on react-start@1.168 + vite@8)

Open
#7,418 5 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

information needed
Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Which project does this relate to?

Start

Bug

On a fresh install with @tanstack/react-start@1.168.0 + vite@8.0.13 in a pnpm monorepo (enableGlobalVirtualStore: true), every dev request to:

GET /@id/virtual:tanstack-start-client-entry

returns 404. Hydration fails (Uncaught (in promise) TypeError: Failed to fetch dynamically imported module) and the routed <Outlet /> renders empty — header and footer from the pathless layout show, the page body is blank.

This is the exact symptom of #6588 (closed, "fixed in v1.159.6" via #7178), so I'm filing this as a regression rather than commenting on the closed thread.

Repro

  • pnpm 11.x, Node 24.x, macOS
  • vite@8.0.13
  • @tanstack/react-start@1.168.0 → resolves start-plugin-core@1.170.0, start-server-core@1.168.0, start-client-core@1.169.0
  • pnpm-workspace.yaml: enableGlobalVirtualStore: true
  • Start the Vite dev server, open / in any browser

SSR HTML emits:

import("/@id/virtual:tanstack-start-client-entry")

Diagnosis

In Vite 8 the /@id/ middleware requires virtual-module URLs to carry the null-byte prefix encoded as __x00__ (or %00):

URL Status
/@id/virtual:tanstack-start-client-entry 404
/@id/__x00__virtual:tanstack-start-client-entry 200
/@id/%00virtual:tanstack-start-client-entry 200

start-plugin-core@1.170.0's createVirtualModule correctly returns the \0-prefixed resolvedId from its resolveId hook, but the SSR-emitted client bootstrap (from start-server-core@1.168.0) writes the bare-prefix URL into the HTML, so Vite 8's URL-to-id middleware never reaches the plugin's hook.

Workaround

Inline Vite plugin that rewrites the URL before resolution (works locally):

import { defineConfig, type Plugin } from 'vite'

const tanstackVirtualUrlShim = (): Plugin => ({
  name: 'tanstack-start:virtual-url-shim',
  enforce: 'pre',
  configureServer(server) {
    server.middlewares.use((req, _res, next) => {
      if (req.url?.startsWith('/@id/virtual:tanstack-')) {
        req.url = req.url.replace('/@id/virtual:', '/@id/__x00__virtual:')
      }
      next()
    })
  },
})

export default defineConfig({
  plugins: [tanstackVirtualUrlShim(), /* ...rest */],
})

Related

  • Closed: #6588 (original report)
  • Closed: #7155 (reverse-proxy variant — same \0-prefix discussion)
  • Merged: #7178 (the previous fix this regressed against)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure in a fresh pnpm monorepo with Vite 8, then start in the start-server-core SSR client bootstrap and trace the virtual module URL it emits. Compare that URL with createVirtualModule and its resolveId hook in start-plugin-core. Done means the client entry resolves under Vite 8, hydration succeeds, and the routed page renders without the 404.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript, vite
Domain
build-system, full-stack
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.