Primeros pasos con TanStack Start y Convex (Guía 2026)
Guía paso a paso para empezar con TanStack Start y Convex: crea la base de un SaaS full-stack en React y conecta el backend en tiempo real de Convex en 2026.
Actualizado Publicado 36 min de lectura

TanStack Start
Part 1 of 1
Este es el primer artículo de nuestra serie sobre cómo construir una aplicación SaaS moderna con TanStack Start y Convex. TanStack Start ya está en v1 Release Candidate, con las funciones completas y una API estable, y las herramientas han cambiado significativamente desde la versión original de 2025 de esta guía. Al terminar este artículo tendrás una app React full-stack funcionando con SSR, enrutamiento basado en archivos y un backend en tiempo real de Convex.
Qué ha cambiado desde 2025
El ecosistema de TanStack Start pasó por varios cambios incompatibles:
- Estructura del proyecto: el directorio
app/pasó asrc/ - Servidor de desarrollo: vinxi fue reemplazado por un plugin nativo de Vite (construido sobre Nitro)
- Cambio de nombre del paquete:
@tanstack/startahora es@tanstack/react-start - Gestión del head:
Metade@tanstack/startfue reemplazado porHeadContentde@tanstack/react-router
Esta guía refleja el estado actual (2026) del framework.
¿Qué es TanStack Start?
TanStack Start es un framework React full-stack construido sobre TanStack Router. Proviene del equipo de TanStack (los mismos detrás de TanStack Query, Router, Table y otras librerías muy usadas). Características clave:
- Enrutamiento basado en archivos: igual que en Next.js o Remix, las rutas corresponden a archivos en
src/routes/. - Renderizado del lado del servidor (SSR) con hidratación en el cliente.
- Obtención de datos integrada mediante hooks de TanStack Query.
- Tooling de Vite (o Rsbuild 2 como alternativa) para un desarrollo y compilación rápidos.
- Salida de servidor con Nitro: despliega en cualquier host Node.js, plataforma serverless o VPS.
TanStack Start alcanzó la v1.0 Release Candidate en septiembre de 2025 y publica versiones 1.x en npm. La API se considera estable. Si quieres ver cómo se compara TanStack Start con Next.js y Astro, tenemos una comparativa dedicada.
¿Qué es Convex?
Convex es una plataforma backend-as-a-service en tiempo real. Se encarga de tu base de datos, funciones serverless y sincronización del cliente para que puedas centrarte en el código del frontend. También puedes crear una app en tiempo real con Convex usando otros frameworks.
- Base de datos en tiempo real: sincroniza automáticamente los cambios de datos con los clientes conectados
- Funciones serverless: escribe consultas (queries, lectura) y mutaciones (mutations, escritura) en TypeScript, alojadas en Convex
- Seguridad de tipos de extremo a extremo: tipos compartidos entre frontend y backend
- Panel web: gestiona datos, consulta logs y depura desde docs.convex.dev/dashboard
- Almacenamiento de archivos, tareas programadas y búsqueda vectorial integrados
Convex se integra con TanStack Start mediante el paquete @convex-dev/react-query, que te permite consultar y mutar datos con los hooks estándar de TanStack Query.
Requisitos previos
Antes de empezar, asegúrate de tener:
- Node.js >= 20.19 (se recomienda Node 22 LTS). El paquete
@tanstack/react-routerlo exige como mínimo. Si estás en Node 18, verás errores de motor al instalar. - npm (viene con Node)
- Una terminal y un editor de código
- Una cuenta de Convex es opcional para desarrollo local — Convex funciona en local sin ella. Puedes vincular una cuenta en la nube más tarde con
npx convex login.
Sin Docker, sin VPS, sin DNS para este tutorial. Todo se ejecuta en local.
Instalación: genera el proyecto con Convex
La forma más rápida de empezar es la plantilla de Convex, que genera un proyecto con TanStack Start y Convex ya conectados:
Generación rápida (recomendado)
npm create convex@latest -- -t tanstack-start my-saas-app
cd my-saas-appEsto crea un proyecto con React 19, TypeScript, TanStack Start y la configuración del cliente de Convex ya lista. El directorio convex/ incluye un archivo de esquema y los tipos generados.
Configuración manual
npx create-start-app@latest
cd my-app
npm install convex @convex-dev/react-query @tanstack/react-router-with-query @tanstack/react-queryLa vía manual te da un proyecto TanStack Start básico. Tendrás que conectar Convex en src/router.tsx por tu cuenta (lo cubrimos en la sección del router más abajo). Consulta la documentación de TanStack Start para construir desde cero para más detalles.
Verifica que la generación funcionó:
cat package.json | grep -E "tanstack|convex"Deberías ver @tanstack/react-start, @tanstack/react-router, @tanstack/react-query, convex y @convex-dev/react-query en la salida.
Caso de fallo: si npm create convex falla, comprueba tu versión de Node (node -v) y la conectividad con el registro de npm (npm ping).
Arrancar el entorno de desarrollo
Ejecuta el servidor de desarrollo:
npm run devInternamente, esto ejecuta convex dev --start 'vite dev' — la CLI de Convex controla Vite directamente. En la primera ejecución, Convex te pedirá un nombre para el proyecto y levantará un backend local:
? Choose a name: my-saas-app
✔ Downloaded Convex backend binary
✔ Started running a deployment locally at http://127.0.0.1:3210
name as CONVEX_DEPLOYMENT to .env.local
URL as VITE_CONVEX_URL to .env.local
➜ Local: http://localhost:3000/
✔ Convex functions ready! (839.54ms)Verifica que funciona
- Abre
http://localhost:3000en tu navegador — deberías ver la página por defecto. - Confirma que aparece “Convex functions ready” en la terminal.
- Comprueba que
.env.localcontieneVITE_CONVEX_URL:
cat .env.local | grep VITE_CONVEX_URLQué está pasando:
- Inicialización de Convex:
convex devlevanta un backend local y guardaVITE_CONVEX_URLyCONVEX_DEPLOYMENTen.env.local. Estas variables conectan tu frontend con el backend de Convex. - Servidor de desarrollo de Vite: tu app corre en
http://localhost:3000. - Panel de Convex: ejecuta
npx convex dashboardpara abrir la interfaz web donde gestionar datos, ver logs y probar funciones.
Consejo: ejecuta npx convex login para vincular tu despliegue local a una cuenta de Convex y poder sincronizar y desplegar en la nube.
Casos de fallo:
- Falta
VITE_CONVEX_URL: si el frontend no puede conectar, reinicianpm run devdespués de que el primerconvex devhaya generado el archivo de entorno. - Puerto 3000 ocupado: Vite elige otro puerto automáticamente (mira la salida de la terminal), o define
server.portenvite.config.ts.
Estructura del proyecto
La plantilla crea esta estructura (ojo: src/, no app/):
.
├── src/
│ ├── routes/ # Page routes
│ │ ├── __root.tsx # Root layout (HTML structure, global providers)
│ │ └── index.tsx # Homepage component ('/')
│ ├── router.tsx # Router setup (integrates Convex + Query)
│ ├── routeTree.gen.ts # Auto-generated route tree
│ ├── start.ts # App entry point (replaces old client.tsx + ssr.tsx)
│ └── styles/
│ └── app.css # Global styles
├── convex/ # Backend logic
│ ├── schema.ts # Database schema (defines tables)
│ ├── _generated/ # Auto-generated types and API
│ └── ... # Your queries and mutations
├── public/ # Static assets
├── .env.local # Environment variables (VITE_CONVEX_URL)
├── vite.config.ts # Vite configuration
├── convex.config.ts # Convex config
├── package.json
└── tsconfig.jsonArchivos clave:
src/router.tsx— crea la instancia de TanStack Router y conecta Convex + el cliente de Query. Este es el punto de integración.src/routes/__root.tsx— define el layout raíz (<html>,<head>,<body>) y renderiza<Outlet />para las rutas hijas.src/routes/index.tsx— la página de inicio, usacreateFileRoute('/').src/start.ts— el punto de entrada único que reemplaza a los antiguosclient.tsx+ssr.tsx.convex/schema.ts— define las tablas de tu base de datos. Consulta la documentación de esquemas de Convex..env.local— guardaVITE_CONVEX_URL. Nunca subas este archivo a Git.
Si ves un directorio app/
Si tu proyecto tiene app/routes/, app/router.tsx, app/client.tsx y app/ssr.tsx, estás siguiendo documentación desactualizada. La raíz correcta ahora es src/. Vuelve a generar el proyecto con la plantilla más reciente.
Cómo se conectan el router y el cliente de Convex
La integración ocurre en src/router.tsx. Este es el código actual de la plantilla:
// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { QueryClient } from '@tanstack/react-query'
import { routerWithQueryClient } from '@tanstack/react-router-with-query'
import { ConvexQueryClient } from '@convex-dev/react-query'
import { ConvexProvider } from 'convex/react'
import { routeTree } from './routeTree.gen'
export function getRouter() {
const CONVEX_URL = (import.meta as any).env.VITE_CONVEX_URL!
if (!CONVEX_URL) console.error('missing envar VITE_CONVEX_URL')
const convexQueryClient = new ConvexQueryClient(CONVEX_URL)
const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
gcTime: 5000,
},
},
})
convexQueryClient.connect(queryClient)
const router = routerWithQueryClient(
createRouter({
routeTree,
defaultPreload: 'intent',
context: { queryClient },
scrollRestoration: true,
defaultPreloadStaleTime: 0,
Wrap: ({ children }) => (
<ConvexProvider client={convexQueryClient.convexClient}>
{children}
</ConvexProvider>
),
}),
queryClient,
)
return router
}Qué está pasando:
ConvexQueryClientse construye a partir deVITE_CONVEX_URL— es el puente entre las funciones de Convex y TanStack Query.QueryClientse configura con elhashFny elqueryFnde Convex para que las consultas pasen por Convex automáticamente.routerWithQueryClientde@tanstack/react-router-with-queryconecta el router y el cliente de Query para la obtención de datos compatible con SSR.ConvexProviderenvuelve la app mediante la propWrap, dejando el cliente de Convex disponible para todos los componentes.defaultPreloadStaleTime: 0deja la caché en manos de React Query — esto evita peticiones duplicadas y datos obsoletos al usarrouterWithQueryClient.
¿Por qué dos paquetes de integración?
Existen dos paquetes que coexisten para conectar TanStack Router con TanStack Query:
@tanstack/react-router-with-query— el que usa la plantilla oficial de Convex. ProporcionarouterWithQueryClient().@tanstack/react-router-ssr-query— el que aparece en la documentación de inicio rápido de Convex. ProporcionasetupRouterSsrQueryIntegration().
Ambos funcionan. Esta guía sigue el enfoque de la plantilla (router-with-query) porque es lo que obtienes al generar el proyecto. Si lees la documentación de Convex y ves el otro paquete, no te confundas — resuelven el mismo problema con APIs ligeramente distintas.
Enrutamiento básico con TanStack Router
TanStack Start usa enrutamiento basado en archivos a través de TanStack Router. Dos archivos definen la base del enrutamiento:
src/routes/index.tsx — la página de inicio:
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: Home,
})
function Home() {
return (
<div>
<h1>Welcome to My SaaS App</h1>
<p>Edit this page in <code>src/routes/index.tsx</code></p>
</div>
)
}src/routes/__root.tsx — el layout raíz:
// src/routes/__root.tsx
import { createRootRouteWithContext, HeadContent, Outlet, Scripts } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
interface MyRouterContext {
queryClient: QueryClient
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
component: RootComponent,
})
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}Puntos clave:
createFileRoute('/')mapea un archivo a una ruta URL.createRootRouteWithContextconfigura el layout raíz con contexto tipado (elqueryClientdel router).HeadContent(reemplaza al antiguoMeta) inyecta metadatos en<head>.Scriptsinyecta los bundles de JavaScript al final de<body>.Outletrenderiza el componente de la ruta actual.
Añadir cabecera y pie de página
Vamos a añadir una cabecera con navegación y un pie de página a src/routes/__root.tsx. Así todas las páginas mantienen una marca consistente:
Cambio incompatible desde 2025
El antiguo import de Meta desde @tanstack/start ya no existe. Usa HeadContent de @tanstack/react-router en su lugar. ScrollRestoration ahora se gestiona con scrollRestoration: true en la configuración del router — no hace falta ningún componente explícito.
// src/routes/__root.tsx
import {
createRootRouteWithContext,
HeadContent,
Link,
Outlet,
Scripts,
} from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
interface MyRouterContext {
queryClient: QueryClient
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
notFoundComponent: () => (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h1>404 - Page Not Found</h1>
<Link to="/">Go Home</Link>
</div>
),
component: RootComponent,
})
function RootComponent() {
return (
<RootDocument>
<header
style={{
padding: '1rem',
borderBottom: '1px solid #eee',
background: '#f8f9fa',
position: 'sticky',
top: 0,
zIndex: 10,
}}
role="banner"
>
<nav aria-label="Main navigation">
<Link
to="/"
style={{
marginRight: '1rem',
fontWeight: 'bold',
color: '#333',
textDecoration: 'none',
}}
aria-label="Home page"
>
My SaaS App
</Link>
</nav>
</header>
<main style={{ padding: '1rem', minHeight: '80vh' }} role="main">
<Outlet />
</main>
<footer
style={{
padding: '1rem',
borderTop: '1px solid #eee',
textAlign: 'center',
background: '#f8f9fa',
}}
role="contentinfo"
>
<p>© {new Date().getFullYear()} My SaaS App. All rights reserved.</p>
</footer>
</RootDocument>
)
}
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}Qué ha cambiado respecto a la versión de 2025:
Metafue reemplazado porHeadContent(importado de@tanstack/react-router, no de@tanstack/start).ScrollRestorationse eliminó — ahora se gestiona conscrollRestoration: trueen la configuración del router.- Se añadió
notFoundComponentpara corregir el aviso del 404. - La interfaz
MyRouterContextes más simple — el cliente de Convex se envuelve conConvexProvideren la propWrapdel router, así que no necesita estar en el tipo del contexto.
Caso de fallo: si el import de HeadContent falla, lo estás importando desde @tanstack/start en lugar de @tanstack/react-router. Actualiza el import.
Nota sobre estilos: este ejemplo usa estilos en línea por simplicidad. La plantilla de Convex ahora incluye Tailwind v4, que puedes usar en su lugar. Los próximos artículos de la serie introducirán componentes de Radix UI.
Verifica la instalación (build y prueba de humo)
Ejecuta una compilación de producción para detectar errores de tipos y confirmar que todo compila:
npm run buildEsto ejecuta npm run typecheck && vite build y genera la salida en .output/server/index.mjs (formato Nitro). Después prueba el servidor de producción:
npm run startAbre la URL que muestra la terminal. La app debería renderizarse igual que en modo desarrollo.
Verifica también el backend de Convex:
npx convex dashboardEsto abre el panel de Convex en tu navegador. Deberías ver tu proyecto con sus funciones.
¡Listo!
Ya tienes una app full-stack funcionando con:
- SSR mediante TanStack Start (con Nitro)
- Enrutamiento basado en archivos con TanStack Router
- Backend en tiempo real de Convex conectado a través de TanStack Query
- Un layout raíz con cabecera, pie de página y manejo de 404
- Una compilación de producción sin errores
Caso de fallo: si npm run build falla con errores de TypeScript, ejecuta primero npm run typecheck para aislarlos. Si tienes errores de memoria (OOM) en un VPS pequeño, aumenta la memoria de Node: NODE_OPTIONS=--max-old-space-size=4096 npm run build.
Precios de Convex y notas de despliegue
Convex tiene un plan gratuito generoso, suficiente para prototipos y las primeras fases de desarrollo:
Desglose de costes
Plan gratuito ($0):
- 1M de llamadas a funciones/mes
- 20 GB-horas de cómputo de actions
- 0,5 GB de almacenamiento de base de datos
- 1 GB de almacenamiento de archivos
- 40 despliegues
- 1.000 sesiones concurrentes
Professional ($25/desarrollador/mes):
- 25M de llamadas a funciones incluidas
- Copias de seguridad diarias
- Dominios personalizados
- Streaming de logs
Business y Enterprise: mínimo $2.500/mes.
Precios a agosto de 2026 — consulta convex.dev/pricing para las cifras actuales.
Arquitectura de despliegue: Convex es un backend gestionado en la nube — tus funciones y la base de datos de Convex corren en la nube de Convex. El frontend de TanStack Start se puede desplegar en cualquier host Node.js o plataforma compatible con Nitro. Si quieres desplegar tu app TanStack Start en tu propio VPS, Dokploy es una opción sólida sobre algo como Hetzner Cloud.
Los precios de los VPS subieron en general en 2026 — si estás reconsiderando un servidor alquilado, mira qué ha cambiado y cuándo gana un mini PC.
Para desplegar la parte de Convex en producción, consulta nuestra guía sobre cómo desplegar una app de Convex en producción. Si prefieres mantener todo en tu propia infraestructura, puedes mirar autoalojar Convex — también tenemos benchmarks que comparan el rendimiento de Convex autoalojado frente al plan gratuito en la nube.
Solución de problemas habituales
Falta VITE_CONVEX_URL
Síntoma: el frontend no puede conectar con Convex. La consola muestra missing envar VITE_CONVEX_URL.
Solución: reinicia npm run dev. La primera ejecución de convex dev genera .env.local con la URL. Si arrancaste el frontend antes de que Convex terminara de inicializarse, la variable aún no estará disponible.
Verifica: cat .env.local | grep VITE_CONVEX_URL debería mostrar una URL como http://127.0.0.1:3210.
Confusión entre app/ y src/
Síntoma: estás creando archivos en app/routes/ pero nada funciona.
Solución: la estructura del proyecto pasó de app/ a src/. Si estás siguiendo un tutorial antiguo o la versión de 2025 de este artículo, tus archivos deben ir en src/routes/, src/router.tsx, etc. Si tienes dudas, vuelve a generar el proyecto con la plantilla más reciente.
Errores de orden de los plugins de Vite
Síntoma: la compilación falla con errores crípticos relacionados con los plugins de Vite.
Solución: en vite.config.ts, viteReact() (o @vitejs/plugin-react) debe ir después de tanstackStart() en el array de plugins:
plugins: [
tsConfigPaths({ projects: ['./tsconfig.json'] }),
tanstackStart(), // first
viteReact(), // after tanstackStart
],Errores de verbatimModuleSyntax
Síntoma: bundles del servidor filtrándose en bundles del cliente, o errores de importación que no tienen sentido.
Solución: la documentación de TanStack Start advierte que verbatimModuleSyntax en tsconfig.json puede provocar que bundles del servidor se filtren en los bundles del cliente. Déjalo desactivado (ponlo en false o elimínalo).
El puerto 3000 ya está en uso
Síntoma: error EADDRINUSE al arrancar el servidor de desarrollo.
Solución: Vite elegirá otro puerto automáticamente — mira la salida de la terminal. Para forzar un puerto concreto, añade server: { port: 3001 } en vite.config.ts. O mata el proceso que usa el puerto 3000: lsof -ti:3000 | xargs kill.
El panel de Convex no carga
Síntoma: la URL del panel que aparece en la terminal no se abre o muestra un error.
Solución: ejecuta npx convex dashboard manualmente. Si estás en un servidor remoto, el panel se vincula a localhost por defecto — necesitarás un túnel SSH o una regla de firewall para acceder desde tu máquina local.
Aviso notFoundError en la terminal
Síntoma: Warning: A notFoundError was encountered on the route with ID "__root__", but a notFoundComponent option was not configured...
Solución: añade notFoundComponent a la definición de tu ruta raíz (ver la sección de cabecera/pie de página más arriba). Esto proporciona una página 404 personalizada en lugar del error por defecto.
Qué sigue en esta serie
Ahora que la base está lista, el siguiente artículo cubrirá cómo escribir tu primera consulta y mutación de Convex — usando el ejemplo de tasks del inicio rápido. Aprenderás a definir un esquema, escribir funciones del servidor y obtener datos con useSuspenseQuery y convexQuery de @convex-dev/react-query.
La serie también cubrirá autenticación con Clerk, componentes de Radix UI y pagos con Polar.sh.
Crea una app de tareas completa con TanStack Start

