Deploy TanStack Start to a VPS in Five Minutes
Networking update. The server setup described here uses Tako’s legacy VPN integration. For new installations, see native WireGuard networks. For an existing Tailscale or NetBird host, follow the migration guide.
TanStack Start is a full-stack React framework with file-based routing, server functions, and a real SSR build. Most tutorials for it end with “now deploy to Netlify / Vercel / Cloudflare Workers.” Those work, but they’re not your only option. The SSR output is a Node-compatible fetch handler — which means a single Linux box with Tako on it is enough.
Before starting, complete the CLI and remote server setup. You need a supported Linux VPS, admin SSH access, and your workstation and server connected to the same Tailscale tailnet. Point your app’s domain at the VPS and allow ports 80 and 443 for this direct HTTPS setup. Setup and build time depend on your environment.
Step 1 — Scaffold the app
npx @tanstack/cli@latest create my-app
cd my-app
Pick Bun as the package manager when the wizard asks (Node works too — Tako supports both). Add Tailwind or whatever else you want. The default starter is a working SSR app right out of the box.
Install the SDK with bun add tako.sh, then add the Tako Vite plugin so the build emits a server entry Tako can launch:
// vite.config.ts
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import { tako } from "tako.sh/vite";
export default defineConfig({
plugins: [tanstackStart(), tako()],
});
That’s the only framework-side change. The plugin doesn’t replace TanStack Start’s build — it adds a thin wrapper at dist/server/tako-entry.mjs that re-exports the SSR fetch handler in the shape Tako expects.
Step 2 — tako init
tako init
Init is interactive. It detects Bun from your lockfile, sees @tanstack/react-start in package.json, and offers tanstack-start as the preset. The relevant configuration is shown below; keep any runtime version pin generated by init in the runtime field:
name = "my-app"
runtime = "bun"
preset = "tanstack-start"
[envs.production]
route = "my-app.example.com"
servers = ["prod"]
The tanstack-start preset bakes in main = "dist/server/tako-entry.mjs" and assets = ["dist/client"], so you don’t write either. Init also runs bun add tako.sh so the SDK is in your dependencies, and updates .gitignore so .tako/* is ignored while .tako/secrets.json stays tracked.
Change route to a domain you actually own. Tako will issue a Let’s Encrypt cert for it on first deploy.
Step 3 — tako deploy
If you don’t have a server registered yet, do that once:
tako servers add ubuntu@prod
Replace ubuntu with your admin SSH user and prod with the server’s Tailscale MagicDNS name. The admin-user form can install or repair the server. Use the saved server name in servers = ["prod"]; see the remote setup guide.
Then:
tako deploy
Follow any deployment prompts, then open the URL for your configured domain. Check a server-rendered route and a static asset. Use the troubleshooting guide if startup or routing fails.
What just happened
The deploy ran vite build locally, packaged everything (excluding .git, .tako, .env*, and node_modules) into a .tar.zst artifact, and uploaded it over signed private HTTP through Tailscale. tako-server unpacked the versioned release, ran a production install, and started the SSR bundle directly under Bun. Pingora terminates TLS on :443, the per-app load balancer routes to your process, and the SDK answers Tako’s internal health checks so unhealthy instances drop out automatically.
There is no container, no Node-on-Bun shim, no Lambda cold start. It’s a Linux process started by a service manager on a VPS — but you got there with two commands.
Why this matters
TanStack Start runs anywhere a fetch handler runs: Vercel, Netlify, Cloudflare Workers, Bun on a box. The hosted platforms are the loudest option in the room, and they’re great at what they do — but they aren’t the only option, and the lock-in tradeoff is real. With Tako you keep the same SSR app and the same fetch-handler interface, deployed to hardware you control, with zero-downtime rolling updates and HTTPS in local dev thrown in.
Continue with the deployment guide for secrets, multi-server routing, and rollbacks, and configure backups for persistent app data.
