Skip to content

Start SPA mode _shell.html includes / route content and loader data when using @cloudflare/vite-plugin #7740

Description

@bbertold

Which project does this relate to?

Start

Describe the bug

My goal with my app is to use TanStack Start hosted on Cloudflare with normal app routes served as static client-rendered pages, while API routes and server functions are still handled by Workers.

Cloudflare Static Assets SPA mode expects a real SPA fallback file, usually /index.html. Since TanStack Start SPA mode generates /_shell.html, I wanted to verify that the shell is route-neutral before copying/remapping it to /index.html.

From the SPA mode docs, my understanding is that spa.enabled: true should prerender only the root shell and should not include matched route content or route loader data. The Selective SSR docs also seem to say SPA mode disables server-side route loader execution and route-component SSR.

However, when using the official Cloudflare setup with @cloudflare/vite-plugin, the generated dist/client/_shell.html includes the / route component HTML and loader data.

This is already visible in the build output, so to me it does not appear to be a Cloudflare runtime/routing issue.

Complete minimal reproducer

Created using the start-basic-cloudflare example, removing the unused routes and enabling spa mode.

https://github.com/bbertold/tanstack-start-shell-issue

Steps to Reproduce the Bug

  1. Clone the reproducer
  2. Install dependencies with npm install
  3. Run the build command
  4. Open dist/client/_shell.html

The generated shell contains the / route content:

<div class="p-2">
  <h3>Welcome Home!!!</h3>
  <p>Running in Cloudflare-Workers</p>
  <p>Hello from Cloudflare</p>
</div>

It also serializes the / route match and loader data:

routes: {
  __root__: ...,
  "/": ...
},
matches: [
  { i: "__root__\0", s: "success", ssr: true },
  {
    i: "\0\0",
    s: "success",
    l: {
      message: "Running in Cloudflare-Workers",
      myVar: "Hello from Cloudflare"
    },
    ssr: true
  }
]

The relevant SPA config is:

tanstackStart({
  spa: {
    enabled: true,
  },
})

I also tried changing plugin order, but both produced the same shell output.

Expected behavior

I expected dist/client/_shell.html in SPA mode to contain only the root shell/layout, without rendering the matched / route component and without serializing / route loader data.

Root layout/nav output from shellComponent seems expected. The unexpected part is the index route content being rendered inside {children}.

For Cloudflare Static Assets SPA hosting, I expected this shell to be safe to use as a route-neutral fallback file, such as /index.html.

Screenshots or Videos

No response

Platform

The core package versions are same as in the start-basic-cloudflare example.

  • Router / Start Version: 1.170.17
  • OS: macOS
  • Browser: Chrome
  • Browser Version: 150.0.7871.46
  • Bundler: vite
  • Bundler Version: 8.0.14

Additional context

I also tested a similar TanStack Start app without the Cloudflare Vite plugin, using the Nitro setup. In that case, there were no issues, only the root match was serialized, with no / route content or loader data. That makes me think this may be specific to the Cloudflare Vite plugin integration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions