Best way to prefetch queries with tRPC and app/ dir

I am in the process of switching my pages/ T3 app to the new fancy app directory and know that React Query has a few ways to prefetch queries, and was wondering which one is the best for use with T3 app dir. 1. initialData The easiest, but also the most annoying in my eyes. You fetch the data on the server and then pass it down as initialData. 2. prefetchQuery & HydrationLayer Saw this in the docs but i’m not sure if it will work with the extended version of react-query that tRPC uses. 3. @tanstack/react-query-next-experimental This seems to be brand new, and I can’t really tell what’s different from the second option. Which method is best for prefetching on the server with T3 app dir?
Solution:
I think the main takeaway is: 1. prefetch the data using .prefetch() 2. dehydrate the state 3. pass the dehydrated state into react query 4. call .query(), which will pull from the cache instead of re-fetching...
Jump to solution
7 Replies
Endgame1013
Endgame101313mo ago
I’ve used a setup like this before and it’s worked pretty well:
// MySSRComponent.tsx
import { createSSRHelper } from '@/server/api/ssr';
import ReactQueryHydrate from '@/components/ReactQueryHydrate';
import { dehydrate } from '@tanstack/react-query';

export default async function MySSRComponent() {
// 1. create the server-side helper
const helpers = await createSSRHelper();
// 2. pre-fetch the tRPC procedure server-side
await helpers.example.greeting.prefetch();
// 3. get the dehydrated query client and pass it down to react-query's context provider
const dehydratedState = dehydrate(helpers.queryClient);

return (
<ReactQueryHydrate state={dehydratedState}>
<MyClientComponent />
</ReactQueryHydrate>
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MyClientComponent.tsx
'use client';

import { api } from '@/lib/api';

export default function MyClientComponent() {
const { data, status } = api.example.greeting.useQuery();

if (status !== 'success') {
// won't happen since the query has been prefetched
return <div>Loading...</div>;
}

return (
<pre>{JSON.stringify(data, null, 4)}</pre>
);
}
// MySSRComponent.tsx
import { createSSRHelper } from '@/server/api/ssr';
import ReactQueryHydrate from '@/components/ReactQueryHydrate';
import { dehydrate } from '@tanstack/react-query';

export default async function MySSRComponent() {
// 1. create the server-side helper
const helpers = await createSSRHelper();
// 2. pre-fetch the tRPC procedure server-side
await helpers.example.greeting.prefetch();
// 3. get the dehydrated query client and pass it down to react-query's context provider
const dehydratedState = dehydrate(helpers.queryClient);

return (
<ReactQueryHydrate state={dehydratedState}>
<MyClientComponent />
</ReactQueryHydrate>
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MyClientComponent.tsx
'use client';

import { api } from '@/lib/api';

export default function MyClientComponent() {
const { data, status } = api.example.greeting.useQuery();

if (status !== 'success') {
// won't happen since the query has been prefetched
return <div>Loading...</div>;
}

return (
<pre>{JSON.stringify(data, null, 4)}</pre>
);
}
ethan
ethan13mo ago
what does the ReactQueryHydrate component look like or is it just a client wrapper
Endgame1013
Endgame101313mo ago
'use client';

import { Hydrate as RQHydrate, HydrateProps } from '@tanstack/react-query';

export default function ReactQueryHydrate(props: HydrateProps) {
return <RQHydrate {...props} />;
}
'use client';

import { Hydrate as RQHydrate, HydrateProps } from '@tanstack/react-query';

export default function ReactQueryHydrate(props: HydrateProps) {
return <RQHydrate {...props} />;
}
Solution
Endgame1013
Endgame101313mo ago
I think the main takeaway is: 1. prefetch the data using .prefetch() 2. dehydrate the state 3. pass the dehydrated state into react query 4. call .query(), which will pull from the cache instead of re-fetching
ethan
ethan13mo ago
yeah, makes sense thank you!!
yaba101
yaba10112mo ago
@Endgame1013 can we have repo for this, because I don't get the import { createSSRHelper } from '@/server/api/ssr'; implementation
Endgame1013
Endgame101312mo ago
@yaba101, here is the code for that portion, sorry for the oversight:
import { appRouter } from './routes/_app';
import { createContext } from './context';
import { createServerSideHelpers } from '@trpc/react-query/server';
import superjson from 'superjson';

/**
* SSR/RSC caller
* @description use this function to create a TRPC server-side caller
* @see https://trpc.io/docs/server/server-side-calls#create-caller
*/
export const createSSRCaller = async () => {
return appRouter.createCaller(await createContext());
};

/**
* Server-Side Helper
* @description use this function to call tRPC procedures server-side and hydrate `react-query`'s cache
* @see https://trpc.io/docs/client/nextjs/server-side-helpers#1-internal-router
*/
export const createSSRHelper = async () =>
createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
transformer: superjson,
});
import { appRouter } from './routes/_app';
import { createContext } from './context';
import { createServerSideHelpers } from '@trpc/react-query/server';
import superjson from 'superjson';

/**
* SSR/RSC caller
* @description use this function to create a TRPC server-side caller
* @see https://trpc.io/docs/server/server-side-calls#create-caller
*/
export const createSSRCaller = async () => {
return appRouter.createCaller(await createContext());
};

/**
* Server-Side Helper
* @description use this function to call tRPC procedures server-side and hydrate `react-query`'s cache
* @see https://trpc.io/docs/client/nextjs/server-side-helpers#1-internal-router
*/
export const createSSRHelper = async () =>
createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
transformer: superjson,
});
Want results from more Discord servers?
Add your server