Deploying TanStack Start to Firebase App Hosting: What Nobody Tells You
TanStack Start is one of the most exciting full-stack React frameworks to emerge in 2025. Type-safe routing, server functions that feel like local calls, and a Vite-native build pipeline. Firebase App Hosting is Google's answer to Vercel — push to GitHub, get a deployed SSR app. In theory, connecting the two should be straightforward. In practice, there's a gap in the docs that will cost you hours if you don't know about it.
Here's how to actually get it working.
The Problem Nobody Mentions
TanStack Start v1.132+ made a significant architectural change: it dropped Nitro as a default dependency. Previous versions bundled Nitro automatically, which meant your vite build produced a .output/server/index.mjs file — a real HTTP server that listened on a port. Hosting platforms knew what to do with that.
The new builds produce something different. You get dist/server/server.js, which exports a WinterCG-compatible fetch handler. It's a function, not a server. There's no listen() call. No port binding. Nothing that Cloud Run (which powers App Hosting) can health-check against.
If you deploy this to Firebase App Hosting, you'll see:
The user-provided container failed to start and listen on the port
defined by the PORT=8080 environment variable within the allocated timeout.
This isn't a misconfiguration. Your app genuinely isn't starting an HTTP server.
The Fix: Bring Nitro Back
TanStack provides an official escape hatch: @tanstack/nitro-v2-vite-plugin. This is a compatibility plugin that restores Nitro as your server runtime, complete with deployment presets for every major platform.
Install it:
npm install @tanstack/nitro-v2-vite-plugin
Then add it to your vite.config.ts, with the firebase_app_hosting preset:
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { nitroV2Plugin } from '@tanstack/nitro-v2-vite-plugin'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
tanstackStart(),
nitroV2Plugin({
preset: 'firebase_app_hosting',
}),
viteReact(),
],
})
Now vite build produces two things that matter:
.output/server/index.mjs— a real Node.js HTTP server that readsPORTfrom the environment.apphosting/bundle.yaml— a manifest that tells Firebase App Hosting exactly how to run your app
The generated bundle.yaml looks like this:
version: v1
runConfig:
runCommand: node .output/server/index.mjs
metadata:
framework: nitro
frameworkVersion: 2.x
Firebase reads this and knows what to do. No custom start script needed in package.json, though adding one doesn't hurt:
{
"scripts": {
"start": "node .output/server/index.mjs"
}
}
Setting Up App Hosting
With the build sorted, the Firebase side is standard:
firebase apphosting:backends:create
This walks you through connecting your GitHub repo and choosing a region. Every push to your chosen branch triggers a build and deploy.
The one thing that catches people out is secrets. App Hosting doesn't read .env files. Every environment variable referenced in your apphosting.yaml must exist in Firebase Secrets Manager:
firebase apphosting:secrets set MY_SECRET_NAME
firebase apphosting:secrets:grantaccess MY_SECRET_NAME --backend my-backend
If any secret is missing, the build fails immediately with a clear error pointing to the missing name. Set them all before your first deploy.
What About vite preview?
You might be tempted to use vite preview as your production server. Don't. It's a development tool for locally previewing builds. It doesn't respect PORT reliably, it doesn't bind to 0.0.0.0 by default, and it's not designed for production traffic. The Nitro server is purpose-built for this.
The Full Sequence
For anyone starting fresh:
- Scaffold your TanStack Start app
- Install
@tanstack/nitro-v2-vite-plugin - Add
nitroV2Plugin({ preset: 'firebase_app_hosting' })to your Vite config - Create your Firebase project and App Hosting backend
- Set all secrets via
firebase apphosting:secrets set - Push to GitHub
Your app will be live at https://your-backend--your-project.region.hosted.app within a few minutes.
Looking Forward
TanStack Start is moving fast. The team has been clear that the Nitro plugin is a "temporary compatibility" layer while the ecosystem catches up to the new fetch-handler architecture. Eventually, platforms like Firebase will likely support WinterCG handlers natively. But today, in February 2026, the Nitro plugin is the production-ready path. It works, it's maintained, and it takes five lines of config to set up.
The gap between "builds locally" and "runs in production" is always where frameworks earn or lose trust. TanStack Start has the right escape hatch — it just needs better signposting.