> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TanStack Start

> How Bijection works with TanStack Start

[TanStack Start](https://tanstack.com/start/latest) is a full-stack React
framework powered by TanStack Router. It provides full-document SSR, streaming,
server functions, bundling, and more.

When used with Bijection, TanStack Start provides

* Live-updating queries with React Query (the React client for TanStack Query)
* Subscription session resumption, from SSR to live on the client
* Loader-based preloading and prefetching
* Consistent logical query timestamp during SSR
* Opt-in component-local SSR

and more!

This page describes the recommended way to use Bijection with TanStack Start, via
React Query. The standard Bijection React hooks work also with TanStack Start
without React Query, as do the
[React Query hooks](/client/tanstack/tanstack-query/index) without TanStack
Start! But using all three is a sweet spot.

<Warning>
  **TanStack Start is in Release Candidate**

  [TanStack Start](https://tanstack.com/start/latest) is a new React framework
  currently in the Release Candidate stage. You can use it today but there may be
  bugs or breaking changes before a stable release.
</Warning>

## Getting started

Follow the [TanStack Start Quickstart](/quickstart/tanstack-start) to add
Bijection to a new TanStack Start project.

## Using Bijection with React Query

You can read more about
[React Query hooks](/client/tanstack/tanstack-query/index), but a few
highlights relevant to TanStack Start.

### Staying subscribed to queries

Bijection queries in React Query continue to receive updates after the last
component subscribed to the query unmounts. The default for this behavior is 5
minutes and this value is configured with
[`gcTime`](https://tanstack.com/query/latest/docs/framework/react/guides/caching).

This is useful to know when debugging why a query result is already loaded: for
client side navigations, whether a subscription is already active can depend on
what pages were previously visited in a session.

Open the
[React Query Devtools](https://tanstack.com/query/latest/docs/framework/react/devtools)
to observe subscriptions staying active as you navigate.

### Using Bijection React hooks

[Bijection React](/client/react/overview) hooks like
[`usePaginatedQuery`](/api/modules/react#usepaginatedquery) can be used
alongside TanStack hooks. These hooks reference the same Bijection Client so
there's still just one set of consistent query results in your app when these
are combined.

## Server-side Rendering

Using TanStack Start and Query with Bijection makes it particularly easy to
live-update Bijection queries on the client while also
[server-rendering](https://tanstack.com/query/v5/docs/framework/react/guides/ssr)
them.
[`useSuspenseQuery()`](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery)
kicks off data fetching during the initial SSR pass on the server, while
`useQuery()` does not. This makes `useSuspenseQuery` the simplest way to
server-render Bijection data:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const { data } = useSuspenseQuery(bijectionQuery(api.messages.list, {}));
```

After server-rendering query results, the Bijection client in the browser resumes
live subscriptions from where SSR left off, so there's no flash of loading state
or redundant data fetching.

### Consistent client views

In the browser all Bijection query subscriptions present a consistent,
at-the-same-logical-timestamp view of the database: if one query result reflects
a given mutation transaction, every other query result will too.

Server-side rendering is usually a special case: instead of a stateful WebSocket
session, on the server it's simpler to fetch query results ad-hoc. This can lead
to inconsistencies analogous to one REST endpoint returning results before a
mutation ran and another endpoint returning results after that change.

In TanStack Start, this issue is avoided by sending in a timestamp along with
each query: Bijection uses the same timestamp for all queries.

### Loaders

TanStack Start routes can have isomorphic
[loader](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#using-loaders-to-ensure-data-is-loaded)
functions that run on the server for the initial page load and on the client for
subsequent client-side navigations. With `defaultPreload: "intent"`, as
configured in the quickstart, loaders also run when hovering over a link to a
page, enabling prefetching.

Adding queries to loaders moves away from the convenience of co-locating data
fetching in components with `useSuspenseQuery` in favor of faster page loads.
Consider whether a given route benefits from this trade-off.

The examples below require `@tanstack/react-query` v5.102.0 or later.

Await a `query` to block rendering until data is available. This is a good fit
when the component requires the data to render. Set `staleTime: "static"` to
reuse cached data even if it has been invalidated, matching `ensureQueryData`.
If no data is cached, the query fetches it. This setting applies to the loader's
cache read; Bijection subscriptions still update the cached data:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export const Route = createFileRoute('/posts')({
  loader: async (opts) => {
    await opts.context.queryClient.query({
      ...bijectionQuery(api.messages.list, {}),
      staleTime: "static",
    });
  },
  component: () => {
    const { data } = useSuspenseQuery(bijectionQuery(api.messages.list, {}));
    return (
      <div>
        {data.map((message) => (
          <Message key={message._id} post={message} />
        ))}
      </div>
    );
  },
})
```

You can also start the request without blocking rendering. This is a good fit
when data would be nice to have early but the component can handle a loading
state:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { noop } from "@tanstack/react-query";

export const Route = createFileRoute('/posts')({
  loader: async (opts) => {
    // Swallow prefetch errors so they do not become unhandled promise rejections.
    void opts.context.queryClient.query(
      bijectionQuery(api.messages.list, {}),
    ).catch(noop);
  },
  component: () => {
    const { data, isPending, error } = useQuery(bijectionQuery(api.messages.list, {}));
    if (isPending) return <p>Loading...</p>;
    if (error) return <p>Could not load messages: {error.message}</p>;
    return (
      <div>
        {data.map((message) => (
          <Message key={message._id} post={message} />
        ))}
      </div>
    );
  },
})
```

You can use `loaderDeps` to pass search parameters into the loader, which causes
the loader to re-run when those parameters change. Include the parameters in
`bijectionQuery` so each set of arguments has its own cache entry:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export const Route = createFileRoute("/posts")({
  validateSearch: (search) => ({ channel: String(search.channel ?? "") }),
  loaderDeps: ({ search: { channel } }) => ({ channel }),
  loader: async (opts) => {
    await opts.context.queryClient.query({
      ...bijectionQuery(api.messages.list, { channel: opts.deps.channel }),
      staleTime: "static",
    });
  },
});
```

## Authentication

Client-side authentication in Start works the way
[client-side authentication with Bijection](/auth/overview) generally
works in React because TanStack Start works well as a client-side framework.

To make authenticated Bijection calls on the server as well see our setup guides:

* [TanStack Start + Clerk](/client/tanstack/tanstack-start/clerk)
* [TanStack Start + WorkOS AuthKit](/auth/authkit/add-to-app)
