tRPC, ssr specific route

Hello. I'm migrating to tRPC and I ran into this specific issue. In this scenario I prefetch data on server using getServerSideProps and then pass them to initialData inside useQuery. I use it for SEO purposes. What can I do when I'm using tRPC, I'm unable to do the fetch on server using useQuery. I know there's option ssr: true, but I don't want to all routes to run on SSR just because of this one specific case. Thanks.
const { data: track } = useQuery(
['track', router.query.id],
({ signal }) =>
axios
.get(`/api/spotify/track/${String(router.query.id)}`, { signal })
.then((res) => res.data),
{ enabled: !!router.query.id, initialData }
);

export const getServerSideProps: GetServerSideProps = async (context) => {
context.res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=86400'
);

return axios
.get(`${getBaseUrl()}/api/spotify/track/${context.query.id}`)
.then(({ data }) => ({ props: { initialData: data } }));
};
const { data: track } = useQuery(
['track', router.query.id],
({ signal }) =>
axios
.get(`/api/spotify/track/${String(router.query.id)}`, { signal })
.then((res) => res.data),
{ enabled: !!router.query.id, initialData }
);

export const getServerSideProps: GetServerSideProps = async (context) => {
context.res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=86400'
);

return axios
.get(`${getBaseUrl()}/api/spotify/track/${context.query.id}`)
.then(({ data }) => ({ props: { initialData: data } }));
};
23 Replies
venus
venus•2y ago
I found SSG helper: https://trpc.io/docs/v10/ssg-helpers I had to edit CreateContextOptions, so I was able to pass req and res from SSR context
type CreateContextOptions = {
req: NextApiRequest | GetServerSidePropsContext['req'];
res: NextApiResponse | GetServerSidePropsContext['res'];
};

export const ssgHelper = async (ctx: GetServerSidePropsContext) => {
return createProxySSGHelpers({
router: appRouter,
ctx: await createContext({ req: ctx.req, res: ctx.res }),
transformer: superjson
});
};
type CreateContextOptions = {
req: NextApiRequest | GetServerSidePropsContext['req'];
res: NextApiResponse | GetServerSidePropsContext['res'];
};

export const ssgHelper = async (ctx: GetServerSidePropsContext) => {
return createProxySSGHelpers({
router: appRouter,
ctx: await createContext({ req: ctx.req, res: ctx.res }),
transformer: superjson
});
};
bennettdev
bennettdev•2y ago
Why do you pass the request and response to your createContext ? The docs for SSG helpers does not show that it's needed. Maybe because the docs uses getStaticProps, where request and response does not exist?
venus
venus•2y ago
It is because I pass them to the context ctx so I can work with them in queries and such
bennettdev
bennettdev•2y ago
I do that as well, but not when/for prefetching. I'm just interested, what is your use case?
venus
venus•2y ago
I'm off PC atm, but this is the case
const TrackPage = (
props: InferGetServerSidePropsType<typeof getServerSideProps>
) => {
const { id } = props;

const { data: track } = trpc.spotify.getTrack.useQuery({ trackId: id });

return <></>;
};

export const getServerSideProps = async (
context: GetServerSidePropsContext<{ id: string }>
) => {
const ssg = createProxySSGHelpers({
router: appRouter,
ctx: await createContext({ req: context.req, res: context.res }),
transformer: superjson
});
const id = context.params?.id as string;

await ssg.spotify.getTrack.fetch({ trackId: id });

return {
props: {
trpcState: ssg.dehydrate(),
id
}
};
};
const TrackPage = (
props: InferGetServerSidePropsType<typeof getServerSideProps>
) => {
const { id } = props;

const { data: track } = trpc.spotify.getTrack.useQuery({ trackId: id });

return <></>;
};

export const getServerSideProps = async (
context: GetServerSidePropsContext<{ id: string }>
) => {
const ssg = createProxySSGHelpers({
router: appRouter,
ctx: await createContext({ req: context.req, res: context.res }),
transformer: superjson
});
const id = context.params?.id as string;

await ssg.spotify.getTrack.fetch({ trackId: id });

return {
props: {
trpcState: ssg.dehydrate(),
id
}
};
};
it threw me an error if I didn't pass the context with req and res props
venus
venus•2y ago
Property 'ctx' is missing in type...
venus
venus•2y ago
or
bennettdev
bennettdev•2y ago
Sorry if my question was not clear enough. I was not asking leaving out the ctx field when creating the helpers, but rather why you pass the request and response to the createContext: Why this: ctx: await createContext({ req: context.req, res: context.res }), and not this ctx: await createContext(), Or, in other words, what are you planning to do with the request and response when prefetching? Imagine using createProxySSGHelpers in getStaticProps - there's no request and response there that's why the docs for the SSG helpers also do createContext() without passing req and res ohhhhh you createContext function's opts argument is not optional that's why you pass it through 😄
bennettdev
bennettdev•2y ago
I followed the docs where the argument is optional: https://trpc.io/docs/v10/context#example-code
Request Context | tRPC
The createContext() function is called for each request and the result is propagated to all resolvers. You can use this to pass contextual data down to the resolvers.
venus
venus•2y ago
Yeah, because of TS error but what I've seen from others, they didn't have it optional tho
bennettdev
bennettdev•2y ago
Yea, for Create T3 App, it is not optional. For the official docs, it is optional.
venus
venus•2y ago
I had this:
type CreateContextOptions = {
req: NextApiRequest | GetServerSidePropsContext['req'];
res: NextApiResponse | GetServerSidePropsContext['res'];
};

export const createContext = async (opts: CreateContextOptions) => {
const { req, res } = opts;

return await createContextInner({ req, res });
};
type CreateContextOptions = {
req: NextApiRequest | GetServerSidePropsContext['req'];
res: NextApiResponse | GetServerSidePropsContext['res'];
};

export const createContext = async (opts: CreateContextOptions) => {
const { req, res } = opts;

return await createContextInner({ req, res });
};
bennettdev
bennettdev•2y ago
GitHub
create-t3-app/base-context.ts at main · t3-oss/create-t3-app
Quickest way to start a new web app with full stack typesafety - create-t3-app/base-context.ts at main · t3-oss/create-t3-app
bennettdev
bennettdev•2y ago
For T3, there's a helper that one can use. It already says in the comment:
venus
venus•2y ago
But making it optional I'm running into this issue:
// /server/trpc/router/example.ts
testRoute: t.procedure.query(({ ctx }) => {
// Work with it as optional?
ctx?.res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=43200'
);
})
// /server/trpc/router/example.ts
testRoute: t.procedure.query(({ ctx }) => {
// Work with it as optional?
ctx?.res.setHeader(
'Cache-Control',
'public, s-maxage=86400, stale-while-revalidate=43200'
);
})
Want results from more Discord servers?
Add your server