← Back to list

TanStack AI Just Dropped — And It Makes Building AI Apps Surprisingly Simple

TanStack just released a brand-new AI library, and unlike a lot of AI tooling out there, this one doesn’t ask you to rebuild your app or…

Shehzad Ahmed · 2026-01-07 15:52 · 4 claps · 3.9 min read
#javascript #react #ai #ai-agent #frontend-development
Open on Medium ↗
Wiki topics: AGT · AI Agents AI · AI · General 🌐 · Web Development 📚 · Books & Reading

TanStack AI Just Dropped — And It Makes Building AI Apps Surprisingly Simple

TanStack just released a brand-new AI library, and unlike a lot of AI tooling out there, this one doesn’t ask you to rebuild your app or adopt a new framework.

TanStack AI is just a library. You install it into an existing app — in this case, a Next.js project — and wire it into your server routes and client components.

No vendor lock-in. No special runtime. No magic wrappers.

In this article, we’ll walk through a real working example that shows how to integrate TanStack AI into a Next.js app, using Gemini as the model, and how to build AI tools that run on both the server and the client.

Installing TanStack AI in a Next.js App

You start with a normal Next.js project. Then you install TanStack AI and the adapters you need.

npm install @tanstack/ai
npm install @tanstack/ai-react
npm install @tanstack/ai-gemini
npm install @tanstack/react-ai-devtools zod

TanStack AI is provider-agnostic. If you want OpenAI instead of Gemini, you just install a different adapter.

Project Structure (Standard Next.js)

This is a normal Next.js app with everything inside src/:

src/
  routes/
    index.tsx
    __root.tsx
    api/
      chat.ts
  components/
    chat.tsx
    counter.tsx

TanStack AI doesn’t care about your framework structure — it just needs:

  • a server endpoint
  • a client hook

Rendering the Page

src/routes/index.tsx

import { ClientOnly, createFileRoute } from '@tanstack/react-router'
import { Chat } from '@/components/chat'
import { Counter } from '@/components/counter'

export const Route = createFileRoute('/')({ component: App })
function App() {
  return (
    <>
      <Chat />
      <ClientOnly>
        <Counter />
      </ClientOnly>
    </>
  )
}

The ClientOnly wrapper is important because the counter uses localStorage, which only exists in the browser.

The Chat UI (Client Side)

src/components/chat.tsx

'use client'

import { useState } from 'react'
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
import { clientTools } from '@tanstack/ai-client'
import { updateCounterToolDef } from '@/routes/api/chat'

Client-Side Tool Implementation

The tool definition lives on the server, but the implementation runs on the client.

const updateCounterTool = updateCounterToolDef.client(({ count }) => {
  localStorage.setItem('counter', count.toString())
  return { success: true }
})

This lets the AI safely update browser-only state without touching your server.

Connecting Everything with useChat

const { messages, sendMessage, isLoading } = useChat({
  connection: fetchServerSentEvents('/api/chat'),
  tools: clientTools(updateCounterTool),
})

That single hook gives you:

  • Streaming AI responses
  • Full conversation state
  • Loading state
  • Automatic tool execution

Rendering Messages

{messages.map((message) => (
  <div key={message.id}>
    <strong>
      {message.role === 'assistant' ? 'Assistant' : 'You'}
    </strong>

    {message.parts.map((part, idx) => {
      if (part.type === 'thinking') {
        return (
          <div key={idx} className="italic text-sm">
            💭 {part.content}
          </div>
        )
      }
      if (part.type === 'text') {
        return <div key={idx}>{part.content}</div>
      }
      return null
    })}
  </div>
))}

TanStack AI exposes structured message parts, making it easy to render reasoning, text, and tool calls differently.

The AI Server Route

src/routes/api/chat.ts

This is a normal server endpoint inside your Next.js app.

import { chat, toStreamResponse, toolDefinition } from '@tanstack/ai'
import { gemini } from '@tanstack/ai-gemini'
import z from 'zod'

Handling the Request

export async function POST({ request }: { request: Request }) {
  const { messages, conversationId } = await request.json()
  const stream = chat({
    adapter: gemini(),
    model: 'gemini-2.5-flash',
    messages,
    conversationId,
    tools: [getTodosTool, updateCounterToolDef],
  })
  return toStreamResponse(stream)
}

TanStack AI handles:

  • Streaming
  • Tool calls
  • Message validation
  • Error handling

You just return the stream.

Server-Side Tool: Fetching Todos

Tool Definition

const getTodosToolDef = toolDefinition({
  name: 'get_todos',
  description: 'Fetch a list of todos from the database',
  inputSchema: z.object({
    query: z.string().optional(),
  }),
  outputSchema: z.array(
    z.object({
      id: z.number(),
      title: z.string(),
      completed: z.boolean(),
      userId: z.number(),
    }),
  ),
})

Server Implementation

const getTodosTool = getTodosToolDef.server(async ({ query }) => {
  const url = new URL('https://jsonplaceholder.typicode.com/todos')
  if (query) url.searchParams.set('q', query)
  const response = await fetch(url.toString())
  return await response.json()
})

Now the AI can answer things like:

  • “How many todos do I have?”
  • “Search todos with a specific title”

Client-Side Tool: Updating Local State

Shared Tool Definition

export const updateCounterToolDef = toolDefinition({
  name: 'set_count',
  description: 'Set the counter value stored in the browser',
  inputSchema: z.object({
    count: z.number(),
  }),
  outputSchema: z.object({ success: z.boolean() }),
})

The AI sees this tool on the server, but it executes entirely on the client.

Counter Component

src/components/counter.tsx

export function Counter() {
  const [count, setCount] = useLocalStorage('counter', 0)
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

Now when the user says:

“Update my local count to the number of todos in my database”

The AI:

  1. Fetches todos (server)
  2. Calculates the count
  3. Calls set_count (client)
  4. Updates localStorage
  5. UI updates automatically

AI DevTools (Highly Recommended)

Inside src/routes/__root.tsx:

<TanStackDevtools
  plugins={[aiDevtoolsPlugin()]}
  eventBusConfig={{ connectToServerBus: true }}
/>

These devtools let you inspect:

  • Messages sent to the AI
  • Responses
  • Tool calls
  • Tool inputs and outputs

Debugging AI behavior becomes much easier.

Why TanStack AI Is Interesting

With a simple npm install, this Next.js app now has:

  • Streaming AI chat
  • Server-side AI tools
  • Client-side AI tools
  • Strong type safety
  • No provider lock-in

And this is still version 0.

TanStack has already mentioned plans for:

  • Headless UI helpers
  • Less boilerplate
  • Deeper integrations

Given TanStack’s track record, this library has serious potential.

Final Thoughts

TanStack AI doesn’t try to reinvent AI.

It just makes AI practical, composable, and boring — which is exactly what you want when you’re shipping real products.

If you already use Next.js, adding TanStack AI feels like dropping in another TanStack library. Nothing more.

Reference Links

Find me on your favorite platform

  • Github — Follow me on GitHub for further useful code snippets and open source repos.
  • Instagram — Follow me on instagram to connect.
  • LinkedIn Profile — Connect with me on LinkedIn for further discussions and updates.
  • Twitter (X) — Connect with me on Twitter (X) for useless tech tweets.

메타데이터
post_id
67eccd64c345
slug
tanstack-ai-just-dropped-and-it-makes-building-ai-apps-surprisingly-simple-67eccd64c345
url
https://medium.com/@shaxadd/tanstack-ai-just-dropped-and-it-makes-building-ai-apps-surprisingly-simple-67eccd64c345
canonical_url
https://medium.com/@shaxadd/tanstack-ai-just-dropped-and-it-makes-building-ai-apps-surprisingly-simple-67eccd64c345
author_url
https://medium.com/@shaxadd
status
ok
fetched_at
2026-06-15 22:55:51