TanStack Start — полный разбор нового React фреймворка
12 августа 2026 г.
5 мин
6

TanStack Start — полный разбор нового React фреймворка

Если ты слышал про TanStack - скорее всего думаешь про TanStack Query или TanStack Router. Оба инструмента заработали сильную репутацию. Но TanStack Start - это другое. Это полноценный full-stack React фреймворк который в 2026 году стал серьёзным конкурентом Next.js.

Я сам на Next.js 15 - поэтому изучил TanStack Start внимательно. Вот что нужно знать.

React, Next.js, TanStack Start - кто есть кто

Прежде чем делать выбор - важно понять базовое различие которое часто упускают.

React - это библиотека, а не фреймворк. Это движок для рендеринга компонентов и управления состоянием. Сам по себе React не даёт тебе роутинг, data fetching, сборку или стратегию рендеринга. Ты собираешь это сам.

Next.js и TanStack Start - это фреймворки поверх React. Они принимают за тебя решения которые иначе пришлось бы принимать самому.

Самая частая ошибка - выбирают Next.js «потому что популярный», а потом никогда не используют серверный рендеринг потому что всё приложение за авторизацией. В итоге не используешь все преимущества, потому что они в общем не нужны.

Что такое TanStack Start

TanStack Start - это full-stack React мета-фреймворк построенный поверх TanStack Router, использующий Vite как инструмент сборки и Vinxi как абстракцию бандлера.

Главная философия - «просто React». Никаких React Server Components. Никакой магии компилятора. Никаких директив 'use client'. Ты пишешь React компоненты так как всегда - а фреймворк даёт тебе full-stack возможности поверх этого.

Фреймворк создал Таннер Линслей - разработчик TanStack Query, TanStack Router и TanStack Table. Люди которые ими пользуются знают его подход: строгая типизация, предсказуемость, никакой магии.

Чем отличается от Next.js

Next.js - RSC-first. Компоненты рендерятся на сервере по умолчанию. Ты opt-in в клиентское поведение через 'use client'.

TanStack Start - клиент-first. Сервер - это возможность к которой ты явно обращаешься, а не дефолтный контекст рендеринга. Это фундаментальное архитектурное различие.

// Next.js - сервер по умолчанию, opt-in в клиент
'use client'
import { useState } from 'react'
export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

// TanStack Start - клиент по умолчанию,
// серверные функции вызываются явно
import { createServerFn } from '@tanstack/start'

const getUser = createServerFn({ method: 'GET' })
  .handler(async () => {
    return db.user.findFirst() // выполняется только на сервере
  })

Server Functions - замена API роутам

Одна из самых интересных концепций TanStack Start. Server Functions - это функции которые гарантированно выполняются на сервере. Вместо того чтобы писать отдельный API endpoint - ты просто создаёшь серверную функцию и вызываешь её напрямую из компонента.

// Раньше нужно было два файла:
// 1. /api/notes/route.ts - API endpoint
// 2. компонент - fetch('/api/notes')

// TanStack Start - один файл, одна функция
import { createServerFn } from '@tanstack/start'
import { db } from '~/db'

// Эта функция выполняется на сервере
// но вызывается как обычная JS функция
export const getNotes = createServerFn({ method: 'GET' })
  .handler(async () => {
    return db.note.findMany({ orderBy: { createdAt: 'desc' } })
  })

// В компоненте - просто вызываем как обычную функцию
function NotesPage() {
  const notes = Route.useLoaderData()
  return <ul>{notes.map(n => <li key={n.id}>{n.title}</li>)}</ul>
}

Никаких fetch('/api/...'). Никаких отдельных файлов для API. Типы автоматически прокидываются с сервера на клиент.

Роутинг и загрузка данных

Роутинг в TanStack Start - файловый, как в Next.js. Но данные загружаются через loader функции которые запускаются до рендеринга компонента - параллельно для всех совпавших роутов.

// src/routes/notes/$noteId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { getNote } from '~/server/notes'

export const Route = createFileRoute('/notes/$noteId')({
  // validateSearch типизирует query параметры
  validateSearch: z.object({
    tab: z.enum(['preview', 'edit']).optional(),
  }),

  // loader запускается на сервере при SSR
  // и на клиенте при навигации
  loader: async ({ params }) => {
    return getNote({ data: params.noteId })
  },

  component: NoteComponent,
})

function NoteComponent() {
  // Всё полностью типизировано - TypeScript знает
  // тип данных из loader
  const note = Route.useLoaderData()
  const { noteId } = Route.useParams()
  const { tab } = Route.useSearch()

  return <article>{note.content}</article>
}

Параллельная загрузка данных - killer фича. Когда ты переходишь на /notes/123 - loader для /notes и loader для /notes/123 запускаются одновременно. Не последовательно как в useEffect.

Сквозная типизация - главное преимущество

Это то где TanStack Start реально выделяется. Типы проходят от базы данных через серверные функции до компонентов без потерь - без единого any и без ручных аннотаций.

// db/schema.ts - определяем схему один раз
export const notes = pgTable('notes', {
  id: text('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content').notNull(),
  createdAt: timestamp('created_at').defaultNow(),
})

// server/notes.ts - серверная функция с типом из схемы
export const getNote = createServerFn({ method: 'GET' })
  .validator(z.string()) // валидация входных данных
  .handler(async ({ data: noteId }) => {
    const note = await db.query.notes.findFirst({
      where: eq(notes.id, noteId),
    })
    if (!note) throw notFound()
    return note // тип автоматически выведен из Drizzle
  })

// routes/notes/$noteId.tsx - тип пришёл с сервера
function NoteComponent() {
  const note = Route.useLoaderData()
  // note.title, note.content, note.createdAt
  // - всё типизировано, TypeScript знает каждое поле
}

Никаких as Note, никаких ! операторов. Вся цепочка от PostgreSQL до JSX типизирована.

Middleware - защита серверных функций

Middleware в TanStack Start - это переиспользуемые цепочки логики которые применяются к серверным функциям. Авторизация, логирование, CSP - всё через middleware.

// middleware.ts
import { createMiddleware } from '@tanstack/start'
import { auth } from './my-auth'

// Создаём middleware для авторизации
export const authMiddleware = createMiddleware().server(
  async ({ next, request }) => {
    const session = await auth.getSession({
      headers: request.headers
    })

    if (!session) {
      throw new Error('Unauthorized')
    }

    // Прокидываем сессию в контекст - типизировано
    return next({ context: { session } })
  }
)

// Middleware для проверки прав
export const adminMiddleware = createMiddleware()
  .middleware([authMiddleware]) // композиция middleware
  .server(async ({ next, context }) => {
    if (context.session.user.role !== 'admin') {
      throw new Error('Forbidden')
    }
    return next()
  })
// server/notes.ts - применяем middleware к функции
export const deleteNote = createServerFn({ method: 'POST' })
  .middleware([authMiddleware]) // только авторизованные
  .validator(z.string())
  .handler(async ({ data: noteId, context }) => {
    // context.session типизирован из middleware
    const note = await db.query.notes.findFirst({
      where: and(
        eq(notes.id, noteId),
        eq(notes.userId, context.session.user.id)
      ),
    })
    if (!note) throw notFound()
    await db.delete(notes).where(eq(notes.id, noteId))
  })

Важный момент который часто упускают: route guard и middleware - это разные вещи. Route guard (beforeLoad) защищает UX - перенаправляет неавторизованного пользователя до рендеринга страницы. Middleware в серверной функции защищает данные - потому что серверная функция это HTTP endpoint который можно вызвать напрямую минуя роут.

Структура проекта

src/
  routes/
    __root.tsx          # корневой layout
    index.tsx           # главная страница
    notes/
      index.tsx         # /notes
      $noteId.tsx       # /notes/:id
    _protected/         # группа защищённых роутов
      dashboard.tsx     # /dashboard
      settings.tsx      # /settings
  server/
    notes.ts            # серверные функции для заметок
    auth.ts             # серверные функции для авторизации
  middleware.ts         # глобальные middleware
  start.ts              # конфигурация старта
  db/
    index.ts            # подключение к базе
    schema.ts           # схема Drizzle

Файловый роутинг понятен - но структура серверных функций в отдельной папке это паттерн который команда TanStack рекомендует. Логика сервера отдельно от UI - принцип чистой архитектуры.

Деплой без привязки к платформе

Ты деплоишь на AWS Lambda, Cloudflare Workers, обычный Node.js сервер или Bun - и фреймворк формирует вывод соответственно через адаптеры.

// vite.config.ts
import { tanstackStart } from '@tanstack/react-start/plugin/vite'

export default defineConfig({
  plugins: [
    tanstackStart({
      server: {
        preset: 'cloudflare-worker', // или 'node', 'aws-lambda', 'bun'
      }
    })
  ]
})

Это принципиальное отличие от Next.js который оптимизирован под Vercel. Ничего плохого в Vercel - но если твоя инфраструктура на других платформах, ты это чувствуешь.

Один разработчик который год держит TanStack Start в продакшне: два приложения на Cloudflare Workers, полностью stateless, общаются с бэкенд API через tRPC. Весь стек портируемый потому что ничего не зависит от проприетарных фич платформы.

Когда выбирать каждый инструмент

TanStack Start:

  • Динамические приложения - дашборды, SaaS, admin panels, internal tools
  • Типизация критически важна на всём стеке
  • Команда уже использует TanStack Router или Query
  • Нужна гибкость в деплое без vendor lock-in
  • Важна прозрачность - никакой магии под капотом

Next.js:

  • Публичные сайты где важен SEO и первая загрузка
  • Контентные сайты, блоги, документация, e-commerce
  • Команда уже инвестировала в RSC модель
  • Нужна глубина экосистемы - 1.5M еженедельных загрузок vs ~50k у TanStack Start

Plain React + Vite:

  • Всё приложение за авторизацией - никакой поисковик не видит
  • Нужна минимальная абстракция и полный контроль
  • Важна предсказуемость - Vite стабилен и редко меняется под тобой

Текущий статус

TanStack Start - около 50 000 еженедельных загрузок npm на середину 2026 года. Разрыв с Next.js примерно 30 к 1. Community-ответы на edge cases малочисленнее. Документации меньше.

Но траектория важна. Рост TanStack Start в 2025-2026 повторяет то как выглядело принятие Vite в 2021-2022. Быстрый рост среди опытных разработчиков - с более широким принятием следом.

Стоит ли смотреть

Я на Next.js 15 - и пока менять не планирую. Экосистема, документация, поддержка React 19 - всё работает. Но TanStack Start слежу внимательно.

Если бы начинал новый проект сегодня - особенно dashboard или SaaS где важна сквозная типизация и гибкость деплоя - серьёзно рассмотрел бы TanStack Start.

Это уже не экспериментальный инструмент. Люди разрабатывают реальные приложения - auth, billing, RBAC, i18n, e2e тесты. И разрыв с Next.js по возможностям продолжает сокращаться.

Выбирай инструмент под задачу а не под тренд. Правильный фреймворк тот который соответствует твоей модели данных. 🚀