Deploy TanStack Start on Ploy with server routes and a database
A step-by-step guide to deploy TanStack Start on Ploy: the ploy() Vite plugin, server routes on the worker runtime, build-time prerendering, and a SQLite database bound through ploy.yaml.

TanStack Start gives you file-based routing, server functions, and streaming SSR without a framework-specific host. The catch is the last step: where do the server routes run, and where does the data live?
Ploy runs TanStack Start on its serverless worker runtime through the same ploy() Vite plugin it uses for other Vite apps. Pages render on the worker, server routes are API endpoints on the same deployment, prerendered routes ship as static files, and a db line in ploy.yaml gives the app a SQLite database. Here is how to deploy TanStack Start on Ploy from an empty directory.
Project setup
TanStack Start needs the current Vite major (8+). Install the TanStack packages alongside the Ploy CLI and Vite plugin:
{"scripts": {"dev": "ploy dev","build": "ploy vite build && tsc --noEmit","types": "ploy types -o env.d.ts"},"dependencies": {"@tanstack/react-router": "^1.168.0","@tanstack/react-start": "^1.167.0","react": "^19","react-dom": "^19"},"devDependencies": {"@meetploy/cli": "latest","@meetploy/vite": "latest","@vitejs/plugin-react": "^6","vite": "^8"}}
{"scripts": {"dev": "ploy dev","build": "ploy vite build && tsc --noEmit","types": "ploy types -o env.d.ts"},"dependencies": {"@tanstack/react-router": "^1.168.0","@tanstack/react-start": "^1.167.0","react": "^19","react-dom": "^19"},"devDependencies": {"@meetploy/cli": "latest","@meetploy/vite": "latest","@vitejs/plugin-react": "^6","vite": "^8"}}
Combine ploy() with the TanStack Start plugin in vite.config.ts:
import { ploy } from "@meetploy/vite";import { tanstackStart } from "@tanstack/react-start/plugin/vite";import viteReact from "@vitejs/plugin-react";import { defineConfig } from "vite";export default defineConfig({plugins: [...ploy({ viteEnvironment: { name: "ssr" } }),tanstackStart({prerender: {enabled: true,autoSubfolderIndex: true,crawlLinks: false,failOnError: true,},}),viteReact(),],});
import { ploy } from "@meetploy/vite";import { tanstackStart } from "@tanstack/react-start/plugin/vite";import viteReact from "@vitejs/plugin-react";import { defineConfig } from "vite";export default defineConfig({plugins: [...ploy({ viteEnvironment: { name: "ssr" } }),tanstackStart({prerender: {enabled: true,autoSubfolderIndex: true,crawlLinks: false,failOnError: true,},}),viteReact(),],});
And the ploy.yaml:
kind: dynamicbuild: pnpm buildout: distcompatibility_date: "2026-03-17"compatibility_flags:- nodejs_compatdb:DB: default
kind: dynamicbuild: pnpm buildout: distcompatibility_date: "2026-03-17"compatibility_flags:- nodejs_compatdb:DB: default
When @tanstack/react-start is installed, Ploy maps the worker entry to @tanstack/react-start/server-entry automatically. There is no worker/index.ts to maintain.
Pages and server routes
Pages live under src/routes and render with React:
import { createFileRoute } from "@tanstack/react-router";export const Route = createFileRoute("/")({component: HomePage,});function HomePage() {return <h1>Rendered at build time, served from the worker</h1>;}
import { createFileRoute } from "@tanstack/react-router";export const Route = createFileRoute("/")({component: HomePage,});function HomePage() {return <h1>Rendered at build time, served from the worker</h1>;}
A route with a server block runs on the worker. This is where API endpoints go:
import { createFileRoute } from "@tanstack/react-router";export const Route = createFileRoute("/api/hello")({server: {handlers: {GET: ({ pathname }) =>Response.json({ message: "hello from worker", pathname }),},},});
import { createFileRoute } from "@tanstack/react-router";export const Route = createFileRoute("/api/hello")({server: {handlers: {GET: ({ pathname }) =>Response.json({ message: "hello from worker", pathname }),},},});
The bindings declared in ploy.yaml are injected into that worker, so the db line above gives your server routes a SQLite database, backed by the local emulator during ploy dev and by Ploy in production. Run pnpm types after changing bindings so env.d.ts stays current. Tables come from .sql files in migrations/, which Ploy applies on production deploys and ploy dev applies locally. See the Vite bindings docs for the binding maps.
Prerendering
The prerender option in vite.config.ts emits static HTML for routes at build time. Those routes are served as assets; everything else, including server handlers and dynamic pages, keeps running on the worker. You get static speed for marketing and docs pages and server rendering for the rest, in one deployment.
Deploy
Push the repository to GitHub, connect it in the Ploy dashboard, and every push builds with ploy vite build and deploys. The production branch gets your production URL; every other branch gets a preview URL. Locally, ploy dev detects the TanStack Start project and starts the Vite dev server next to the Ploy dashboard, where the database is browsable.
Frequently Asked Questions
Can I add queues, cron, or auth to a TanStack Start app?
Yes. Add queue, cron, workflow, or auth to ploy.yaml the same way as db; the bindings are injected into the worker. See the Vite bindings docs.
Do I need a separate API host?
No. Server routes run on the same deployment as the pages. Calls from the browser go to the same origin, so there is no CORS setup.
Does ploy dev use the same bindings as production?
Yes. The local emulator provides the database, queues, workflows, and cron declared in ploy.yaml, and the dashboard at http://localhost:4000 shows their state.
Is there a complete example?
The examples/tanstack-start project in the Ploy repository has prerendered routes, a server route, and worker APIs.