TanStack Start - Framework React full-stack

2024.12.28

Qué es TanStack Start

TanStack Start es un framework React full-stack basado en TanStack Router. Integra enrutamiento type-safe, funciones de servidor y SSR/SSG, siendo considerado una alternativa a Next.js.

Características

✓ Type-safety completo
✓ Enrutamiento basado en archivos
✓ Funciones de servidor
✓ SSR con streaming
✓ Múltiples targets de despliegue
✓ Integración con TanStack Query

Configuración del proyecto

npm create tanstack-start@latest my-app
cd my-app
npm install
npm run dev

Enrutamiento basado en archivos

app/
├── routes/
│   ├── __root.tsx      # Layout raíz
│   ├── index.tsx       # /
│   ├── about.tsx       # /about
│   ├── posts/
│   │   ├── index.tsx   # /posts
│   │   ├── $postId.tsx # /posts/:postId
│   │   └── new.tsx     # /posts/new
│   └── _auth/          # Grupo de layout
│       ├── login.tsx
│       └── register.tsx
└── routes.gen.ts       # Generado automáticamente

Definición de rutas

Ruta básica

// routes/index.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/')({
  component: HomePage
});

function HomePage() {
  return <h1>Welcome to TanStack Start!</h1>;
}

Ruta dinámica

// routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    // params.postId es type-safe
    const post = await fetchPost(params.postId);
    return { post };
  },
  component: PostPage
});

function PostPage() {
  const { post } = Route.useLoaderData();
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Funciones de servidor

Definición y uso

// app/serverFunctions/posts.ts
import { createServerFn } from '@tanstack/start';
import { z } from 'zod';

export const getPosts = createServerFn('GET', async () => {
  return db.posts.findMany();
});

export const createPost = createServerFn('POST', async (data) => {
  const validated = z.object({
    title: z.string(),
    content: z.string()
  }).parse(data);

  return db.posts.create({ data: validated });
});

Llamada desde el cliente

// routes/posts/index.tsx
import { getPosts, createPost } from '../serverFunctions/posts';

export const Route = createFileRoute('/posts/')({
  loader: () => getPosts(),
  component: PostsPage
});

function PostsPage() {
  const posts = Route.useLoaderData();

  const handleCreate = async () => {
    await createPost({ title: 'New Post', content: 'Content' });
  };

  return (
    <div>
      {posts.map(post => <PostCard key={post.id} post={post} />)}
      <button onClick={handleCreate}>Create Post</button>
    </div>
  );
}

Fetching de datos

Patrón Loader

export const Route = createFileRoute('/dashboard')({
  // Obtener datos en el servidor
  loader: async ({ context }) => {
    const [user, stats] = await Promise.all([
      getUser(context.userId),
      getStats(context.userId)
    ]);
    return { user, stats };
  },
  // Mostrar estado pendiente
  pendingComponent: () => <div>Loading...</div>,
  // Mostrar estado de error
  errorComponent: ({ error }) => <div>Error: {error.message}</div>,
  component: DashboardPage
});

Integración con TanStack Query

import { createFileRoute } from '@tanstack/react-router';
import { useSuspenseQuery } from '@tanstack/react-query';

export const Route = createFileRoute('/posts/')({
  component: PostsPage
});

function PostsPage() {
  const { data: posts } = useSuspenseQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json())
  });

  return <PostList posts={posts} />;
}

Layouts

// routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router';

export const Route = createRootRoute({
  component: RootLayout
});

function RootLayout() {
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <title>My App</title>
      </head>
      <body>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/posts">Posts</Link>
        </nav>
        <main>
          <Outlet />
        </main>
      </body>
    </html>
  );
}

Enlaces type-safe

import { Link } from '@tanstack/react-router';

// Error de tipo: faltan parámetros
<Link to="/posts/$postId">Post</Link>

// Uso correcto
<Link to="/posts/$postId" params={{ postId: '123' }}>
  Post 123
</Link>

// Los parámetros de búsqueda también son type-safe
<Link to="/search" search={{ q: 'react', page: 1 }}>
  Search
</Link>

Middleware

// app/middleware.ts
import { createMiddleware } from '@tanstack/start';

export const authMiddleware = createMiddleware({
  beforeLoad: async ({ context }) => {
    const session = await getSession();
    if (!session) {
      throw redirect({ to: '/login' });
    }
    return { ...context, session };
  }
});

// Uso en ruta
export const Route = createFileRoute('/dashboard')({
  middleware: [authMiddleware],
  component: Dashboard
});

Despliegue

# Node.js
npm run build
npm start

# Cloudflare Workers
npm run build --preset cloudflare-pages

# Vercel
npm run build --preset vercel

# Netlify
npm run build --preset netlify

Resumen

TanStack Start es un framework React full-stack con type-safety completo y funciones modernas de servidor. El potente sistema de enrutamiento de TanStack Router y las funciones de servidor simples permiten un desarrollo full-stack eficiente.

← Volver a la lista