Grundlagen lernen

Diese Anleitung hilft Ihnen, die Grundlagen der Funktionsweise von TanStack Start zu verstehen, unabhängig davon, wie Sie Ihr Projekt eingerichtet haben.

Abhängigkeiten

TanStack Start wird von Vite und TanStack Router angetrieben.

  • TanStack Router: Ein Router zum Erstellen von Webanwendungen.
  • Vite: Ein Build-Tool zum Bündeln Ihrer Anwendung.

Alles "beginnt" mit dem Router

Dies ist die Datei, die das Verhalten des in Start verwendeten TanStack Routers bestimmt. Hier können Sie alles konfigurieren, von der standardmäßigen Preloading-Funktionalität bis hin zur Aktualität des Caching.

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

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

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
// src/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

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

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
  • Beachten Sie die Eigenschaft scrollRestoration. Diese wird verwendet, um die Scroll-Position der Seite beim Navigieren zwischen Routen wiederherzustellen.

Routengenerierung

Die Datei routeTree.gen.ts wird generiert, wenn Sie TanStack Start (über npm run dev oder npm run start) zum ersten Mal ausführen. Diese Datei enthält den generierten Routenbaum und eine Handvoll TS-Dienstprogramme, die TanStack Start vollständig typsicher machen.

Der Server-Einstiegspunkt (optional)

Hinweis

Der Server-Einstiegspunkt ist standardmäßig optional. Wenn keiner bereitgestellt wird, kümmert sich TanStack Start automatisch um den Server-Einstiegspunkt für Sie, indem es die folgende Standardkonfiguration verwendet.

Dies geschieht über die Datei src/server.ts

tsx
// src/server.ts
import {
  createStartHandler,
  defaultStreamHandler,
} from '@tanstack/react-start/server'
import { createRouter } from './router'

export default createStartHandler({
  createRouter,
})(defaultStreamHandler)
// src/server.ts
import {
  createStartHandler,
  defaultStreamHandler,
} from '@tanstack/react-start/server'
import { createRouter } from './router'

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

Unabhängig davon, ob wir unsere App statisch generieren oder dynamisch bereitstellen, ist die Datei server.ts der Einstiegspunkt für alle SSR-bezogenen Arbeiten.

  • Es ist wichtig, dass für jede Anfrage ein neuer Router erstellt wird. Dies stellt sicher, dass alle vom Router verarbeiteten Daten eindeutig für die Anfrage sind.
  • Die Funktion defaultStreamHandler wird verwendet, um unsere Anwendung in einen Stream zu rendern, sodass wir HTML an den Client streamen können. (Dies ist der Standardhandler, aber Sie können auch andere Handler wie defaultRenderHandler verwenden oder sogar Ihre eigenen erstellen)

Der Client-Einstiegspunkt (optional)

Hinweis

Der Client-Einstiegspunkt ist standardmäßig optional. Wenn keiner bereitgestellt wird, kümmert sich TanStack Start automatisch um den Client-Einstiegspunkt für Sie, indem es die folgende Standardkonfiguration verwendet.

Das Übertragen unseres HTMLs an den Client ist nur die halbe Miete. Sobald es dort ist, müssen wir unser clientseitiges JavaScript hydrieren, sobald die Route auf dem Client aufgelöst ist. Dies geschieht durch die Hydrierung der Wurzel unserer Anwendung mit der Komponente StartClient

tsx
// src/client.tsx
import { StartClient } from '@tanstack/react-start'
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { createRouter } from './router'

const router = createRouter()

hydrateRoot(
  document,
  <StrictMode>
    <StartClient router={router} />
  </StrictMode>,
)
// src/client.tsx
import { StartClient } from '@tanstack/react-start'
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { createRouter } from './router'

const router = createRouter()

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

Dies ermöglicht es uns, das clientseitige Routing zu starten, sobald die anfängliche Serveranfrage des Benutzers erfüllt wurde.

Die Wurzel Ihrer Anwendung

Abgesehen vom Client-Einstiegspunkt (der standardmäßig optional ist), ist die Route __root Ihrer Anwendung der Einstiegspunkt. Der Code in dieser Datei umschließt alle anderen Routen in der App, einschließlich Ihrer Startseite. Sie verhält sich wie eine pfadlose Layout-Route für Ihre gesamte Anwendung.

Da sie immer gerendert wird, ist sie der perfekte Ort, um Ihre Anwendungs-Shell zu konstruieren und sich um beliebige globale Logik zu kümmern.

tsx
// src/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-router'
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>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}
// src/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-router'
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>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}
  • Dieses Layout kann sich in Zukunft ändern, wenn wir den SPA-Modus einführen, der es der Stammroute ermöglicht, die SPA-Shell ohne seitenbezogenen Inhalt zu rendern.
  • Beachten Sie die Komponente Scripts. Diese wird verwendet, um das gesamte clientseitige JavaScript für die Anwendung zu laden.

Routen

Routen sind ein umfassendes Feature von TanStack Router und werden im Routing-Leitfaden ausführlich behandelt. Zusammenfassend lässt sich sagen:

  • Routen werden mit der Funktion createFileRoute definiert.
  • Routen werden automatisch code-split und lazy-geladen.
  • Kritisches Datenabrufen wird von einem Loader einer Route koordiniert
  • Und vieles mehr!
tsx
// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const filePath = 'count.txt'

async function readCount() {
  return parseInt(
    await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
  )
}

const getCount = createServerFn({
  method: 'GET',
}).handler(() => {
  return readCount()
})

const updateCount = createServerFn({ method: 'POST' })
  .validator((d: number) => d)
  .handler(async ({ data }) => {
    const count = await readCount()
    await fs.promises.writeFile(filePath, `${count + data}`)
  })

export const Route = createFileRoute('/')({
  component: Home,
  loader: async () => await getCount(),
})

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

  return (
    <button
      type="button"
      onClick={() => {
        updateCount({ data: 1 }).then(() => {
          router.invalidate()
        })
      }}
    >
      Add 1 to {state}?
    </button>
  )
}
// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const filePath = 'count.txt'

async function readCount() {
  return parseInt(
    await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
  )
}

const getCount = createServerFn({
  method: 'GET',
}).handler(() => {
  return readCount()
})

const updateCount = createServerFn({ method: 'POST' })
  .validator((d: number) => d)
  .handler(async ({ data }) => {
    const count = await readCount()
    await fs.promises.writeFile(filePath, `${count + data}`)
  })

export const Route = createFileRoute('/')({
  component: Home,
  loader: async () => await getCount(),
})

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

  return (
    <button
      type="button"
      onClick={() => {
        updateCount({ data: 1 }).then(() => {
          router.invalidate()
        })
      }}
    >
      Add 1 to {state}?
    </button>
  )
}

TanStack Start baut zu 100% auf TanStack Router auf, sodass Ihnen alle Navigationsfunktionen von TanStack Router zur Verfügung stehen. Zusammenfassend:

  • Verwenden Sie die Komponente Link, um zu einer neuen Route zu navigieren.
  • Verwenden Sie den Hook useNavigate, um imperativ zu navigieren.
  • Verwenden Sie den Hook useRouter irgendwo in Ihrer Anwendung, um auf die Router-Instanz zuzugreifen und Invalidierungen durchzuführen.
  • Jeder Router-Hook, der einen Zustand zurückgibt, ist reaktiv, was bedeutet, dass er automatisch neu ausgeführt wird, wenn sich der entsprechende Zustand ändert.

Hier ist ein kurzes Beispiel, wie Sie die Komponente Link verwenden können, um zu einer neuen Route zu navigieren

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

function Home() {
  return <Link to="/about">About</Link>
}
import { Link } from '@tanstack/react-router'

function Home() {
  return <Link to="/about">About</Link>
}

Weitere detaillierte Informationen zur Navigation finden Sie im Navigationsleitfaden.

Serverfunktionen (RPCs)

Möglicherweise haben Sie die oben mit createServerFn erstellte Serverfunktion bemerkt. Dies ist eines der mächtigsten Features von TanStack, das es Ihnen ermöglicht, serverseitige Funktionen zu erstellen, die sowohl auf dem Server während SSR als auch vom Client aufgerufen werden können!

Hier ist ein kurzer Überblick darüber, wie Serverfunktionen funktionieren

  • Serverfunktionen werden mit der Funktion createServerFn erstellt.
  • Sie können sowohl vom Server während SSR als auch vom Client aufgerufen werden.
  • Sie können verwendet werden, um Daten vom Server abzurufen oder andere serverseitige Aktionen durchzuführen.

Hier ist ein kurzes Beispiel, wie Sie Serverfunktionen verwenden können, um Daten vom Server abzurufen und zurückzugeben

tsx
import { createServerFn } from '@tanstack/react-start'
import * as fs from 'node:fs'
import { z } from 'zod'

const getUserById = createServerFn({ method: 'GET' })
  // Always validate data sent to the function, here we use Zod
  .validator(z.string())
  // The handler function is where you perform the server-side logic
  .handler(async ({ data }) => {
    return db.query.users.findFirst({ where: eq(users.id, data) })
  })

// Somewhere else in your application
const user = await getUserById({ data: '1' })
import { createServerFn } from '@tanstack/react-start'
import * as fs from 'node:fs'
import { z } from 'zod'

const getUserById = createServerFn({ method: 'GET' })
  // Always validate data sent to the function, here we use Zod
  .validator(z.string())
  // The handler function is where you perform the server-side logic
  .handler(async ({ data }) => {
    return db.query.users.findFirst({ where: eq(users.id, data) })
  })

// Somewhere else in your application
const user = await getUserById({ data: '1' })

Um mehr über Serverfunktionen zu erfahren, lesen Sie den Serverfunktionen-Leitfaden.

Mutationen

Serverfunktionen können auch verwendet werden, um Mutationen auf dem Server durchzuführen. Dies geschieht ebenfalls mit derselben Funktion createServerFn, erfordert jedoch zusätzlich die Invalidierung aller Daten auf dem Client, die von der Mutation betroffen waren.

  • Wenn Sie nur TanStack Router verwenden, können Sie die Methode router.invalidate() verwenden, um alle Router-Daten zu invalidieren und neu abzurufen.
  • Wenn Sie TanStack Query verwenden, können Sie die Methode queryClient.invalidateQueries() verwenden, um Daten zu invalidieren, sowie weitere spezifische Methoden, um bestimmte Abfragen gezielt anzusprechen.

Hier ist ein kurzes Beispiel, wie Sie Serverfunktionen verwenden können, um eine Mutation auf dem Server durchzuführen und die Daten auf dem Client zu invalidieren

tsx
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { dbUpdateUser } from '...'

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
})
export type User = z.infer<typeof UserSchema>

export const updateUser = createServerFn({ method: 'POST' })
  .validator(UserSchema)
  .handler(({ data }) => dbUpdateUser(data))

// Somewhere else in your application
import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from '@tanstack/react-router'
import { useServerFn } from '@tanstack/react-start'
import { updateUser, type User } from '...'

export function useUpdateUser() {
  const router = useRouter()
  const queryClient = useQueryClient()
  const _updateUser = useServerFn(updateUser)

  return useCallback(
    async (user: User) => {
      const result = await _updateUser({ data: user })

      router.invalidate()
      queryClient.invalidateQueries({
        queryKey: ['users', 'updateUser', user.id],
      })

      return result
    },
    [router, queryClient, _updateUser],
  )
}

// Somewhere else in your application
import { useUpdateUser } from '...'

function MyComponent() {
  const updateUser = useUpdateUser()
  const onClick = useCallback(async () => {
    await updateUser({ id: '1', name: 'John' })
    console.log('Updated user')
  }, [updateUser])

  return <button onClick={onClick}>Click Me</button>
}
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { dbUpdateUser } from '...'

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
})
export type User = z.infer<typeof UserSchema>

export const updateUser = createServerFn({ method: 'POST' })
  .validator(UserSchema)
  .handler(({ data }) => dbUpdateUser(data))

// Somewhere else in your application
import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from '@tanstack/react-router'
import { useServerFn } from '@tanstack/react-start'
import { updateUser, type User } from '...'

export function useUpdateUser() {
  const router = useRouter()
  const queryClient = useQueryClient()
  const _updateUser = useServerFn(updateUser)

  return useCallback(
    async (user: User) => {
      const result = await _updateUser({ data: user })

      router.invalidate()
      queryClient.invalidateQueries({
        queryKey: ['users', 'updateUser', user.id],
      })

      return result
    },
    [router, queryClient, _updateUser],
  )
}

// Somewhere else in your application
import { useUpdateUser } from '...'

function MyComponent() {
  const updateUser = useUpdateUser()
  const onClick = useCallback(async () => {
    await updateUser({ id: '1', name: 'John' })
    console.log('Updated user')
  }, [updateUser])

  return <button onClick={onClick}>Click Me</button>
}

Weitere Informationen zu Mutationen finden Sie im Mutationsleitfaden.

Daten laden

Ein weiteres mächtiges Feature von TanStack Router ist das Datenladen. Dies ermöglicht es Ihnen, Daten für SSR abzurufen und Routendaten vor dem Rendern vorzuladen. Dies geschieht mit der Funktion loader einer Route.

Hier ist ein kurzer Überblick darüber, wie das Datenladen funktioniert

  • Das Datenladen erfolgt über die Funktion loader einer Route.
  • Datenloader sind isomorph, was bedeutet, dass sie sowohl auf dem Server als auch auf dem Client ausgeführt werden.
  • Für die serverseitige Logik rufen Sie eine Serverfunktion innerhalb des Loaders auf.
  • Ähnlich wie bei TanStack Query werden Datenloader auf dem Client gecached und wiederverwendet und sogar im Hintergrund neu abgerufen, wenn die Daten veraltet sind.

Weitere Informationen zum Datenladen finden Sie im Datenladen-Leitfaden.

Unsere Partner
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
Prisma
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.