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
venusOP•3y 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•3y 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
venusOP•3y ago
It is because I pass them to the context ctx so I can work with them in queries and such
bennettdev
bennettdev•3y ago
I do that as well, but not when/for prefetching. I'm just interested, what is your use case?
venus
venusOP•3y 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
venusOP•3y ago
Property 'ctx' is missing in type...
venus
venusOP•3y ago
or
bennettdev
bennettdev•3y 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•3y 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
venusOP•3y ago
Yeah, because of TS error but what I've seen from others, they didn't have it optional tho
bennettdev
bennettdev•3y ago
Yea, for Create T3 App, it is not optional. For the official docs, it is optional.
venus
venusOP•3y 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•3y 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•3y ago
For T3, there's a helper that one can use. It already says in the comment:
venus
venusOP•3y 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'
);
})
bennettdev
bennettdev•3y ago
Not saying it should be optional, was just wondering about your use case. As you can see, in Create T3 App, there's a helper function for that. In the official tRPC docs, it is optional, because in getStaticProps, you don't have a request and response, so you cannot pass it to the context.
venus
venusOP•3y ago
In my use case I'm not using getStaticProps, but you made me thinking how to do it if I wanted to use it
venus
venusOP•3y ago
so basically like: ctx?.res?.
venus
venusOP•3y ago
How to solve the TypeScript here?
export const createContextInner = async (opts?: CreateContextOptions) => {
return {
...opts,
prisma
};
};

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

return await createContextInner({ req, res });
};
export const createContextInner = async (opts?: CreateContextOptions) => {
return {
...opts,
prisma
};
};

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

return await createContextInner({ req, res });
};
of descructing possible undefined type const { req, res } = opts || {}; Does this make sense? return await createContextInner(!req || !res ? undefined : { req, res });
bennettdev
bennettdev•3y ago
I just looked at an official tRPC example, where the opts are also mandatory: https://github.com/trpc/examples-next-prisma-starter/blob/next/src/server/context.ts
GitHub
examples-next-prisma-starter/context.ts at next · trpc/examples-nex...
mirror of https://github.com/trpc/trpc/tree/main/examples/next-prisma-starter - examples-next-prisma-starter/context.ts at next · trpc/examples-next-prisma-starter
bennettdev
bennettdev•3y ago
My opts were always optional, as I do authentication on a per-request basis instead of globally for all requests, so I'm not the right person to answer your question.
bennettdev
bennettdev•3y ago
There is another official example that is dedicated to the SSG helpers where opts is optional though. I think it really depends on how you use the request in your queries, are they really always necessary? https://github.com/trpc/examples-next-prisma-todomvc/blob/next/src/server/context.ts
GitHub
examples-next-prisma-todomvc/context.ts at next · trpc/examples-nex...
mirror of https://github.com/trpc/trpc/tree/main/examples/next-prisma-todomvc - examples-next-prisma-todomvc/context.ts at next · trpc/examples-next-prisma-todomvc
venus
venusOP•3y ago
Mmm, as I said I'm not planning to use SSG, so in my particular case, I don't need them optional, but it's good thing to keep eyes on future projects
Want results from more Discord servers?
Add your server