Here’s what I found via some LLM assisted sluething;
### Why `server.js` exists in SPA mode
With this config:
```ts
tanstackStart({
spa: {
enabled: true,
prerender: {
outputPath: '/',
crawlLinks: true,
retryCount: 3,
},
},
})
Start is doing static prerendering of your routes. To prerender, it spins up a Node-ish server build, hits the routes, writes out HTML, then leaves that server bundle in the build output. That server.js is build-time only – it is not needed (or used) at runtime on Cloudflare if you’re serving a pure SPA with static assets.
So from Cloudflare’s point of view it’s just “another file in dist”, and static-assets happily uploads it.
You have two sane options.
Option 1: Only point Wrangler at the client output
If your build output looks something like:
dist/
client/
index.html
assets/...
server/
server.js
(or .output/public + .output/server), then set assets.directory to the client/public dir only:
name = "my-tanstack-spa"
compatibility_date = "2025-01-01"
[assets]
directory = "./dist/client"
not_found_handling = "single-page-application"
Now server.js simply isn’t in the assets tree, so it’s never uploaded.
Option 2: Use .assetsignore to exclude server.js
If you can’t or don’t want to change the directory (e.g. everything is flattened into dist/), add an .assetsignore file inside that directory:
# dist/.assetsignore
server.js
server.mjs
server/**
Wrangler uses .assetsignore just like .gitignore and will skip matching files when uploading static assets.
Does this check out?