Which project does this relate to?
Start
Describe the bug
tanstack-start:start-manifest-capture-client-build assumes every build of the Vite environment named client is the TanStack Start client build.
When TanStack Start is embedded under Astro using the Vite builder API, both frameworks use the environment name client. The composed build intentionally builds that environment twice:
- TanStack Start client build with the single named
index entry.
- Astro client build with its island and renderer entries.
The first build is captured correctly. The second build invokes the same stateful generateBundle hook and fails while normalizing the legitimate Astro multi-entry bundle:
multiple entries detected: _astro/Header.seLcSLqc.js _astro/client.B8ptpwhy.js
at normalizeViteClientBuild (.../@tanstack/start-plugin-core/dist/esm/vite/start-manifest-plugin/normalized-client-build.js:35:34)
This is related to #7032, but it is not the same case. That issue has multiple entries injected into one Start client bundle. Here there are two separate builds of a shared Vite environment, and the second build does not contain the Start client entry at all.
The failure appears in @tanstack/start-plugin-core@1.171.24. The older 1.132.51 capture timing happened not to normalize the later Astro bundle.
Reproduction outline
Configure Astro 7 and React Start in one Vite builder, preserving both buildApp hooks. Build the shared client environment before Astro so the Start manifest is available to the subsequent Start server build, then run the Astro build followed by the TanStack build:
async buildApp(builder: ViteBuilder) {
const clientEnvironment = builder.environments.client
if (clientEnvironment && !clientEnvironment.isBuilt) {
await builder.build(clientEnvironment)
}
await astroBuildApp(builder)
await tanstackBuildApp(builder)
}
Add any hydrated Astro React island. The later Astro client build then has at least the island entry and @astrojs/react/client.js, causing the Start capture hook to reject it.
Verified workaround
Wrapping tanstack-start:start-manifest-capture-client-build so its original generateBundle handler runs only for the bundle containing the Start named index entry fixes the build while preserving both Start and Astro assets:
const isTanStackClientBuild = Object.values(bundle).some(
(entry) => entry.type === "chunk" && entry.isEntry && entry.name === "index",
)
if (!isTanStackClientBuild) return
return originalGenerateBundle.call(this, options, bundle, isWrite)
With that guard, typechecking, tests, and the production Astro server build all succeed.
Expected behavior
The manifest capture plugin should identify the build containing the configured Start client entry, preferably using its resolved entry path and facadeModuleId, and ignore client-environment builds that do not contain that entry.
Alternatively, Start could expose configurable environment names or a supported composition hook so embedded frameworks do not need to wrap an internal plugin by name.
Platform
astro: 7.0.0
@astrojs/react: 6.0.0
@tanstack/react-start: 1.168.32
@tanstack/start-plugin-core: 1.171.24
@tanstack/react-router: 1.170.18
vite: 8.0.16
- Node.js:
24.13.1
- Package manager: pnpm
10.33.2
Additional context
This is a custom Astro/TanStack Start embedding rather than an officially documented integration. The underlying concern is generic Vite Environment API composability: environment names are not exclusive, and a stateful plugin can observe more than one build of the same environment.
Which project does this relate to?
Start
Describe the bug
tanstack-start:start-manifest-capture-client-buildassumes every build of the Vite environment namedclientis the TanStack Start client build.When TanStack Start is embedded under Astro using the Vite builder API, both frameworks use the environment name
client. The composed build intentionally builds that environment twice:indexentry.The first build is captured correctly. The second build invokes the same stateful
generateBundlehook and fails while normalizing the legitimate Astro multi-entry bundle:This is related to #7032, but it is not the same case. That issue has multiple entries injected into one Start client bundle. Here there are two separate builds of a shared Vite environment, and the second build does not contain the Start client entry at all.
The failure appears in
@tanstack/start-plugin-core@1.171.24. The older1.132.51capture timing happened not to normalize the later Astro bundle.Reproduction outline
Configure Astro 7 and React Start in one Vite builder, preserving both
buildApphooks. Build the shared client environment before Astro so the Start manifest is available to the subsequent Start server build, then run the Astro build followed by the TanStack build:Add any hydrated Astro React island. The later Astro client build then has at least the island entry and
@astrojs/react/client.js, causing the Start capture hook to reject it.Verified workaround
Wrapping
tanstack-start:start-manifest-capture-client-buildso its originalgenerateBundlehandler runs only for the bundle containing the Start namedindexentry fixes the build while preserving both Start and Astro assets:With that guard, typechecking, tests, and the production Astro server build all succeed.
Expected behavior
The manifest capture plugin should identify the build containing the configured Start client entry, preferably using its resolved entry path and
facadeModuleId, and ignore client-environment builds that do not contain that entry.Alternatively, Start could expose configurable environment names or a supported composition hook so embedded frameworks do not need to wrap an internal plugin by name.
Platform
astro:7.0.0@astrojs/react:6.0.0@tanstack/react-start:1.168.32@tanstack/start-plugin-core:1.171.24@tanstack/react-router:1.170.18vite:8.0.1624.13.110.33.2Additional context
This is a custom Astro/TanStack Start embedding rather than an officially documented integration. The underlying concern is generic Vite Environment API composability: environment names are not exclusive, and a stateful plugin can observe more than one build of the same environment.