# Server-Side Helpers

> Prefetch or fetch tRPC queries in getServerSideProps and getStaticProps with createTRPCOptionsProxy and a query client, then pass them to the page as trpcState

To run queries on the server in `getServerSideProps` or `getStaticProps`, make a query client and pass it to `createTRPCOptionsProxy` from `@trpc/tanstack-react-query`. The proxy gives you the same `queryOptions` as `useTRPC()` in a component. You prefetch them into the query client, then send its cache to the page, so the page doesn't have to fetch them again.

> [!WARNING] Deprecated
> `createServerSideHelpers` from `@trpc/react-query/server` belongs to the [classic React Query integration](https://trpc.io/docs/client/react.md), which only gets bug fixes and will be removed in the next major version of tRPC. Use `createTRPCOptionsProxy` instead, as below.


## Create the proxy

There are two ways to create it, depending on where your router runs.

### 1. Internal router

Use this when your router is in the same Next.js app. The proxy calls your procedures directly, with no HTTP request, as [server-side calls](https://trpc.io/docs/server/server-side-calls.md) do. So pass it a context: build it from `context.req` in `getServerSideProps`, as [SSR](https://trpc.io/docs/client/nextjs/pages-router/ssr.md#build-the-context-from-the-request) shows. In `getStaticProps` there's no request, so the context can't need one. The ["inner" and "outer" context](https://trpc.io/docs/server/context.md) pattern helps here.

```ts
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import { createContext } from './server/context';
import { appRouter } from './server/routers/_app';
import { makeQueryClient } from './utils/query-client';

export async function getStaticProps() {
  const queryClient = makeQueryClient();
  const trpc = createTRPCOptionsProxy({
    router: appRouter,
    ctx: await createContext(),
    queryClient,
  });
  // ...
}
```

### 2. External router

Use this when your API runs on another server. Pass a tRPC client instead of the router:

```ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server/router';
import { makeQueryClient } from './utils/query-client';

export async function getStaticProps() {
  const queryClient = makeQueryClient();
  const trpc = createTRPCOptionsProxy({
    client: createTRPCClient<AppRouter>({
      links: [
        httpBatchLink({
          url: 'http://localhost:3000/api/trpc',
        }),
      ],
    }),
    queryClient,
  });
  // ...
}
```

Make the query client and the proxy inside `getServerSideProps` or `getStaticProps`, so each call gets new ones. A query client at module scope would share one user's data with the next.

## Prefetch, fetch and dehydrate

Run the queries with the query client's methods:

- `queryClient.prefetchQuery(trpc.post.byId.queryOptions(input))` puts the result in the cache. It never throws and returns nothing.
- `queryClient.fetchQuery(trpc.post.byId.queryOptions(input))` also caches the result, but returns it, and throws if the query fails.
- `prefetchInfiniteQuery` and `fetchInfiniteQuery` do the same with `trpc.post.list.infiniteQueryOptions(input)`.

Use `prefetchQuery` for data the page shows, and `fetchQuery` when you need the result on the server, for instance to return `notFound`. Then send the cache to the page with `dehydrate`:

```ts
// In getServerSideProps / getStaticProps:
const props = {
  // `_app` reads this key
  trpcState: dehydrate(queryClient),
};
```

These are TanStack React Query's own functions. See [their docs](https://tanstack.com/query/v5/docs/framework/react/guides/prefetching) for more.

> [!TIP]
> `loadHandles` from `@trpc/tanstack-react-query/unstable-handles` can also start queries with this proxy and return **handles** to them: `const [handles] = await loadHandles(trpc, { post: ensureEntry(trpc.post.byId, input) })`. Spread the handles into `props` next to `trpcState`, and the page reads one with `useQuery(fromHandle(trpc, props.post))`, so it can't ask for a different input than the server used. A handle is plain JSON when its input is. If the input has a `Date` or another value your transformer handles, run the handles through the transformer too, as you do the dehydrated state. See [Prefetching with handles](https://trpc.io/docs/client/tanstack-react-query/handles.md).


Coming from `createServerSideHelpers`, `helpers.post.byId.prefetch(input)` is now `queryClient.prefetchQuery(trpc.post.byId.queryOptions(input))`, `fetch` is `fetchQuery`, and `helpers.dehydrate()` is `dehydrate(queryClient)`. If you passed a `transformer` to the helpers, run the dehydrated state through it yourself, as [Using a data transformer](https://trpc.io/docs/client/nextjs/pages-router/setup.md#using-a-data-transformer) shows.

## Next.js example

```tsx title="pages/posts/[id].tsx"
import { dehydrate, useQuery } from '@tanstack/react-query';
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import type {
  GetServerSidePropsContext,
  InferGetServerSidePropsType,
} from 'next';
import { appRouter } from '../../server/routers/_app';
import { makeQueryClient } from '../../utils/query-client';
import { useTRPC } from '../../utils/trpc';

export async function getServerSideProps(
  context: GetServerSidePropsContext<{ id: string }>,
) {
  const id = context.params?.id as string;
  const queryClient = makeQueryClient();
  const trpc = createTRPCOptionsProxy({
    router: appRouter,
    ctx: {},
    queryClient,
  });

  await queryClient.prefetchQuery(trpc.post.byId.queryOptions({ id }));

  return {
    props: {
      trpcState: dehydrate(queryClient),
      id,
    },
  };
}

export default function PostViewPage(
  props: InferGetServerSidePropsType<typeof getServerSideProps>,
) {
  const trpc = useTRPC();
  const postQuery = useQuery(trpc.post.byId.queryOptions({ id: props.id }));

  switch (postQuery.status) {
    case 'pending':
      return <p>Loading...</p>;
    case 'error':
      return <p>Error: {postQuery.error.message}</p>;
    case 'success':
      return (
        <article>
          <h1>{postQuery.data.title}</h1>
          <p>{postQuery.data.text}</p>
        </article>
      );
  }
}
```
