4 min read

Exploring tanstack start, next best full-stack framework

A few days ago I saw a video on youtube with this title: "A new React framework just dropped... I'm impressed"
I was triggered: do we really need ANOTHER React framework? I almost didn't click the video because of how triggered I was, but my curiosity won, and I'm glad it did.

The video was about Tanstack Start and since I've watched the video, I've spent a good few hours in the docs just being amazed by what it offers. Naturally, I also immediately started a new project just to try it out, but I also decided that I would share my journey on this blog.

Just to explain why I am so excited about this framework:

  • It's client first; they're not pushing RSC's or server first approach
  • They have actual middleware functions, not like that Nextjs BS. You can chain middleware functions (for client, for server, for RPC's ...)
  • You have a typesafe file-based router that is flexible (try passing context to routes in nextjs).
  • They have async loader functions
  • They have server function that don't feel like magic
  • The framework actually thought about how to do authentication and protect routes (CRAZY right 😏)
  • ... and so much more

I will decide what the project will be about while we setup the basic starting point, and then we'll continue building from there.

Here is a link to the official docs: https://tanstack.com/start/latest
This is still in early development, so they do not yet have a CLI that you can use to scaffold a project. But there is a guide that you can follow in their docs, or from this blog.

One thing to add about this project is that we will be using pocketbase as our "database." I don't like any of the js ORM's and I want to make my life simple with the authentication and file uploads as well. We will use our tanstack server functions and api handlers to talk to database / pocketbase server which means we don't have to expose the pocketbase server to the public, which means it will work pretty much like a normal database but with a nice sdk 😄

If you would like to follow along as I build a SaaS with Tanstack Start you can subscribe (it's free) and you will get email notifications whenever I post a new part.

Let's start building.

First, here is a link where you can find the official tanstack start guide on how to setup a project:
https://tanstack.com/router/latest/docs/framework/react/start/getting-started

First create your project folder, then inside it initialize an npm repository:

npm init -y

Create a tsconfig.json with the following settings:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true,
    "paths": {
      "@/*": ["./app/*"]
    }
  },
}

Install dependencies

npm i @tanstack/start @tanstack/react-router vinxi
npm i react react-dom; npm i -D @vitejs/plugin-react
npm i -D typescript @types/react @types/react-dom

Update package.json

{
  // ...
  "type": "module",
  "scripts": {
    "dev": "vinxi dev",
    "build": "vinxi build",
    "start": "vinxi start"
  }
}

Create app.config.ts file

// app.config.ts
import { defineConfig } from '@tanstack/start/config'

export default defineConfig({})

Add the Basic Templating

There are four required files for TanStack Start usage:

  • The router configuration
  • The server entry point
  • The client entry point
  • The root of your application
  • Once configuration is done, we'll have a file tree that looks like the following:

├── app/

│ ├── routes/

│ │ └── `__root.tsx`

│ ├── `client.tsx`

│ ├── `router.tsx`

│ ├── `routeTree.gen.ts`

│ └── `ssr.tsx`

├── `.gitignore`

├── `app.config.ts`

├── `package.json`

└── `tsconfig.json`

Router configuration

// app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  const router = createTanStackRouter({
    routeTree,
  })

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}

Server entrypoint

// app/ssr.tsx
/// <reference types="vinxi/types/server" />
import {
  createStartHandler,
  defaultStreamHandler,
} from '@tanstack/start/server'
import { getRouterManifest } from '@tanstack/start/router-manifest'

import { createRouter } from './router'

export default createStartHandler({
  createRouter,
  getRouterManifest,
})(defaultStreamHandler)

Client entrypoint

// app/client.tsx
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from 'react-dom/client'
import { StartClient } from '@tanstack/start'
import { createRouter } from './router'

const router = createRouter()

hydrateRoot(document, <StartClient router={router} />)

The root of the application

// app/routes/__root.tsx
import {
  Outlet,
  ScrollRestoration,
  createRootRoute,
} from '@tanstack/react-router'
import { Meta, Scripts } from '@tanstack/start'
import type { ReactNode } from 'react'

export const Route = createRootRoute({
  head: () => ({
    meta: [
      {
        charSet: 'utf-8',
      },
      {
        name: 'viewport',
        content: 'width=device-width, initial-scale=1',
      },
      {
        title: 'TanStack Start Starter',
      },
    ],
  }),
  component: RootComponent,
})

function RootComponent() {
  return (
    <RootDocument>
      <Outlet />
    </RootDocument>
  )
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
  return (
    <html>
      <head>
        <Meta />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  )
}

Now we can start adding our routes

// app/routes/index.tsx
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { Button } from '@mantine/core' // We will setup mantine in the next one

export const Route = createFileRoute('/')({
  component: Home,
  loader: async () => await Promise.resolve(1),
})

function Home() {
  const router = useRouter()
  const state = Route.useLoaderData()

  return (
    <Button
      type="button"
      onClick={() => {
        console.log('Clicked')
      }}
    >
      Add 1 to {state}?
    </Button>
  )
}

And that's it for now. You can run npm run dev to start the development server.

In the next part, we will install mantine ui library and Tailwind CSS, and we will add pocketbase to our repo. We will talk about the features of the framework once we start building.