Deploy TanStack Start to the edge
A TanStack Start app deploys in one of two ways:
- Rendered on the server, when it's built with Cloudflare's Vite plugin: pages, server functions and server routes run in a Worker on Cloudflare's network, next to your static files. See Server rendering.
- In SPA mode, served as static files. Routes you prerender also get their own HTML, rendered at build time. See SPA mode.
React and Solid apps deploy the same way.
A Vite app that only uses TanStack Router isn't concerned: it deploys as a Vite app.
Detection
A project is treated as TanStack Start when @tanstack/react-start or @tanstack/solid-start is in dependencies or devDependencies. It's checked before Vite, which TanStack Start runs on.
Defaults
| Setting | Default |
|---|---|
| Install command | From your lockfile, see Frameworks |
| Build command | npx vite build |
| Output directory | dist/client |
Server rendering
TanStack Start renders on the server with @cloudflare/vite-plugin. Start from Cloudflare's template:
Or add the plugin to an existing app, before tanstackStart(), with the ssr environment, and point main in wrangler.jsonc at TanStack Start's server entry:
The build command doesn't change: vite build writes the Worker and your static files, and Hiraiship publishes both. The output directory setting is ignored, since the build says where everything is. With the plugin, a build in SPA mode is published as a Worker too, as wrangler deploy would.
Static files are served without running your Worker. Every other path runs it, unknown ones included: your not-found component renders the 404.
A server build without the plugin fails with an error naming the plugin to add.
Reading variables
Your runtime variables reach the Worker as bindings. Read them from cloudflare:workers, in a server function or a server route:
On a project whose latest deployment rendered on the server, a new variable in the dashboard is both a build and a runtime variable by default. A secret is runtime only, so it never ends up in your bundle.
vars in wrangler.jsonc are ignored, and the build log says so: set those values as the project's variables instead. .dev.vars isn't read either.
What isn't supported
Hiraiship provides your Worker with its variables and its static files, nothing else. A build whose wrangler.jsonc declares other bindings fails, naming each one: KV namespaces, D1 databases, R2 buckets, Durable Objects, queues, service bindings, Workers AI, Hyperdrive, Analytics Engine, cron triggers. A Worker that read env.DB and got nothing would fail on every request.
WebAssembly, text and data modules aren't supported either: the Worker must be JavaScript only.
CPU time
Each server-rendered request, and each server function call, runs your Worker within your plan's CPU time per request:
| Free | Standard | Pro | Platform | Enterprise | |
|---|---|---|---|---|---|
| CPU per request | 15 ms | 30 ms | 50 ms | 50 ms | 50 ms |
That's enough for a typical page. A heavy render that goes past it fails with Cloudflare's exceededResources error. See Limits.
SPA mode
To deploy without a server, enable SPA mode in your Vite config:
The build then writes an HTML shell, _shell.html, and Hiraiship serves it for every path with no file of its own, so the router takes over in the browser: opening or reloading /settings works. Keep the shell's default path: a build whose shell is elsewhere is taken for a server-rendered one.
Without SPA mode, and without the Cloudflare plugin, the build fails with an error rather than publishing pages that would never render. Server functions (createServerFn) and server routes need a server too: in SPA mode, fetch your data from an API instead.
Your root route renders the HTML document through shellComponent, as in TanStack Start's templates:
Prerendering
With prerender enabled, the routes TanStack Start crawls get their own HTML at build time, served as is to visitors and search engines:
Environment variables
These are the build variables every TanStack Start app has. A server-rendered app also reads runtime variables, see Reading variables.
TanStack Start builds with Vite: import.meta.env.VITE_* values are inlined into your bundle at build time. Add them as build variables: in the dashboard, a new variable on a TanStack Start project in SPA mode is a build variable by default.
Only VITE_-prefixed variables reach your code. Whatever ends up in the bundle is downloaded by every visitor, which is why a secret can't be a build variable.
For one deployment only, pass them to the CLI:
Paths with no file
In SPA mode, unknown paths get the shell, and the router renders the matching route, or its own not-found component, in the browser. If your build also writes an index.html of its own (a prerendered /), the shell is served as 404.html instead: such paths still open your app, with a 404 status. A 404.html of your own is never replaced.