TanStack Start

Learn how to use SmoothUI animated React components in TanStack Start projects with type-safe routing, streaming SSR, Tailwind CSS 4, and Motion.

Last updated: March 7, 2026

Overview

TanStack Start is a modern full-stack React framework built on Vinxi (a Vite-based server toolkit) and TanStack Router. It provides type-safe routing, streaming SSR, and server functions out of the box. SmoothUI components integrate smoothly with TanStack Start's architecture.

How It Works

TanStack Start uses Vite under the hood via Vinxi, so SmoothUI components work the same way as in any Vite-based project. Components are server-rendered and hydrated on the client. The "use client" directive ensures animations activate after hydration.

Newer Framework

TanStack Start is a newer framework that is evolving quickly. The setup steps below reflect the current stable release. Check the official docs for the latest changes.

Prerequisites

Before getting started, make sure you have the following:

RequirementMinimum VersionPurpose
Node.js18+Runtime
TanStack Start1.0+Framework
React19.0+UI library
Tailwind CSS4.0+Styling
Motion12.0+Animations (Framer Motion)

Project Setup

Create a New TanStack Start Project

If you're starting from scratch, create a new TanStack Start project:

pnpm create @tanstack/start@latest my-smoothui-app

Then install dependencies:

cd my-smoothui-app && pnpm install

Set Up Tailwind CSS 4

TanStack Start uses Vinxi, which is built on Vite. Install Tailwind CSS and the Vite plugin:

pnpm add tailwindcss @tailwindcss/vite

Add the Tailwind CSS Vite plugin to your app config:

app.config.ts
import { defineConfig } from "@tanstack/react-start/config";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  vite: {
    plugins: () => [tailwindcss()],
  },
});

Create or update your main CSS file with the Tailwind import:

app/styles/app.css
@import "tailwindcss";

Import the stylesheet in your root route (typically app/routes/__root.tsx):

app/routes/__root.tsx
import { createRootRoute, Outlet } from "@tanstack/react-router";
import appCss from "@/styles/app.css?url";

export const Route = createRootRoute({
  head: () => ({
    links: [{ rel: "stylesheet", href: appCss }],
  }),
  component: RootComponent,
});

function RootComponent() {
  return (
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <Outlet />
      </body>
    </html>
  );
}

Install Motion

SmoothUI animations are powered by Motion (Framer Motion). Install it as a dependency:

pnpm add motion

Configure Path Aliases

SmoothUI components use the @/ path alias. Configure it in your tsconfig.json:

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./app/*"]
    }
  }
}

TanStack Start resolves TypeScript paths automatically via Vinxi's Vite integration. If path aliases don't resolve at runtime, add vite-tsconfig-paths to your app config:

app.config.ts
import { defineConfig } from "@tanstack/react-start/config";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  vite: {
    plugins: () => [tailwindcss(), tsconfigPaths()],
  },
});

Installing SmoothUI Components

Using the SmoothUI CLI

pnpm dlx smoothui-cli@latest add siri-orb

Using the shadcn CLI

pnpm dlx shadcn@latest add @smoothui/siri-orb

shadcn Configuration

If this is your first time using the shadcn CLI in a TanStack Start project, it will prompt you to create a components.json file. Configure the aliases.components path to match your project structure (typically @/components).

SSR & Streaming

TanStack Start supports streaming SSR by default, which means components render progressively on the server. Here's what you need to know when using SmoothUI components:

ConcernHow SmoothUI Handles It
"use client" directiveSmoothUI components include this directive. TanStack Start respects it for client-side hydration.
Motion SSRMotion handles SSR gracefully — animations start after hydration. No special config needed.
Streaming compatibilitySmoothUI components work with streaming SSR. Animations start as each component hydrates.
Browser APIsComponents using window or ResizeObserver during render need a client-only guard.

Client-Only Components

For the rare component that needs browser APIs during initial render, use a mounted check:

app/components/client-only.tsx
"use client";

import { type ReactNode, useEffect, useState } from "react";

export type ClientOnlyProps = {
  children: ReactNode;
  fallback?: ReactNode;
};

const ClientOnly = ({ children, fallback = null }: ClientOnlyProps) => {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  return mounted ? children : fallback;
};

export default ClientOnly;

Using Components in Routes

Here is a complete example of using SmoothUI components in a TanStack Start route:

app/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { SiriOrb } from "@/components/smoothui/ui/SiriOrb";
import { MagneticButton } from "@/components/smoothui/ui/MagneticButton";

export const Route = createFileRoute("/")({
  component: Home,
});

function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center gap-8 p-8">
      <h1 className="text-4xl font-bold">SmoothUI + TanStack Start</h1>
      <SiriOrb size="200px" />
      <MagneticButton>Get Started</MagneticButton>
    </main>
  );
}

Custom Animated Components

If you create custom animated components alongside SmoothUI, follow the same pattern:

app/components/FadeIn.tsx
"use client";

import { motion, useReducedMotion } from "motion/react";
import type { ReactNode } from "react";

export type FadeInProps = {
  children: ReactNode;
};

const FadeIn = ({ children }: FadeInProps) => {
  const shouldReduceMotion = useReducedMotion();

  return (
    <motion.div
      initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={
        shouldReduceMotion
          ? { duration: 0 }
          : { type: "spring", duration: 0.25, bounce: 0.1 }
      }
    >
      {children}
    </motion.div>
  );
};

export default FadeIn;

Troubleshooting

Next Steps